diff --git a/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanBaseDto.cs b/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanBaseDto.cs index a6d4fd56..e8404c7f 100644 --- a/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanBaseDto.cs +++ b/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanBaseDto.cs @@ -19,6 +19,11 @@ public abstract class ProcessMapPlanBaseDto : ChangeLogAbstract, IId, IWellRelat [Range(1, int.MaxValue, ErrorMessage = "Id секции скважины не может быть меньше 1")] public int IdWellSectionType { get; set; } + /// + /// Название секции + /// + public string? Section { get; set; } + /// /// Глубина по стволу от, м /// @@ -41,6 +46,6 @@ public abstract class ProcessMapPlanBaseDto : ChangeLogAbstract, IId, IWellRelat public virtual IEnumerable Validate(ValidationContext validationContext) { if(DepthEnd <= DepthStart) - yield return new ("глубина окончания должна быть больше глубины начала", new string[] {nameof(DepthEnd), nameof(DepthStart) }); + yield return new ("Глубина окончания должна быть больше глубины начала", new string[] {nameof(DepthEnd), nameof(DepthStart) }); } } \ No newline at end of file diff --git a/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanDrillingDto.cs b/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanDrillingDto.cs index 15f30300..7ab7db5d 100644 --- a/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanDrillingDto.cs +++ b/AsbCloudApp/Data/ProcessMapPlan/ProcessMapPlanDrillingDto.cs @@ -12,6 +12,11 @@ public class ProcessMapPlanDrillingDto : ProcessMapPlanBaseDto /// [Range(1, 2, ErrorMessage = "Id режима должен быть либо 1-ротор либо 2-слайд")] public int IdMode { get; set; } + + /// + /// Название режима бурения + /// + public string? Mode { get; set; } /// /// Осевая нагрузка, т план diff --git a/AsbCloudApp/Services/ProcessMaps/IProcessMapPlanImportService.cs b/AsbCloudApp/Services/ProcessMaps/IProcessMapPlanImportService.cs index 7b30b80d..a0c3d4ec 100644 --- a/AsbCloudApp/Services/ProcessMaps/IProcessMapPlanImportService.cs +++ b/AsbCloudApp/Services/ProcessMaps/IProcessMapPlanImportService.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Threading.Tasks; using System.Threading; @@ -7,6 +8,7 @@ namespace AsbCloudApp.Services.ProcessMaps; /// /// Сервис импорта РТК /// +[Obsolete] public interface IProcessMapPlanImportService { /// diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index cdb57ae5..ba012eb5 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -36,6 +36,7 @@ + diff --git a/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanDrillingParser.cs b/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanDrillingParser.cs new file mode 100644 index 00000000..cab930b4 --- /dev/null +++ b/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanDrillingParser.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AsbCloudApp.Data; +using AsbCloudApp.Data.ProcessMapPlan; +using AsbCloudApp.Repositories; +using AsbCloudApp.Services; +using ClosedXML.Excel; +using Microsoft.Extensions.DependencyInjection; + +namespace AsbCloudInfrastructure.Services.ProcessMapPlan.Parser; + +public class ProcessMapPlanDrillingParser : ProcessMapPlanParser +{ + #region Columns + + private const int columnSection = 1; + private const int columnMode = 2; + private const int columnDepthStart = 3; + private const int columnDepthEnd = 4; + private const int columnPressurePlan = 5; + private const int columnPressureLimitMax = 6; + private const int columnAxialLoadPlan = 7; + private const int columnAxialLoadLimitMax = 8; + private const int columnTopDriveTorquePlan = 9; + private const int columnTopDriveTorqueLimitMax = 10; + private const int columnTopDriveSpeedPlan = 11; + private const int columnTopDriveSpeedLimitMax = 12; + private const int columnFlowPlan = 13; + private const int columnFlowLimitMax = 14; + private const int columnRopPlan = 15; + private const int columnUsageSaub = 16; + private const int columnUsageSpin = 17; + private const int columnComment = 18; + + #endregion + + private readonly IEnumerable sections; + + public ProcessMapPlanDrillingParser(IServiceProvider serviceProvider) + : base(serviceProvider) + { + var wellOperationRepository = serviceProvider.GetRequiredService(); + + sections = wellOperationRepository.GetSectionTypes(); + } + + protected override string SheetName => "План"; + + protected override string TemplateFileName => "ProcessMapPlanDrillingTemplate.xlsx"; + + protected override ValidationResultDto ParseRow(IXLRow row) + { + var sectionCaption = row.Cell(columnSection).GetCellValue()?.Trim().ToLower(); + var modeName = row.Cell(columnMode).GetCellValue()?.Trim().ToLower(); + var depthStart = row.Cell(columnDepthStart).GetCellValue(); + var depthEnd = row.Cell(columnDepthEnd).GetCellValue(); + var deltaPressurePlan = row.Cell(columnPressurePlan).GetCellValue(); + var deltaPressureLimitMax = row.Cell(columnPressureLimitMax).GetCellValue(); + var axialLoadPlan = row.Cell(columnAxialLoadPlan).GetCellValue(); + var axialLoadLimitMax = row.Cell(columnAxialLoadLimitMax).GetCellValue(); + var topDriveTorquePlan = row.Cell(columnTopDriveTorquePlan).GetCellValue(); + var topDriveTorqueLimitMax = row.Cell(columnTopDriveTorqueLimitMax).GetCellValue(); + var topDriveSpeedPlan = row.Cell(columnTopDriveSpeedPlan).GetCellValue(); + var topDriveSpeedLimitMax = row.Cell(columnTopDriveSpeedLimitMax).GetCellValue(); + var flowPlan = row.Cell(columnFlowPlan).GetCellValue(); + var flowLimitMax = row.Cell(columnFlowLimitMax).GetCellValue(); + var ropPlan = row.Cell(columnRopPlan).GetCellValue(); + var usageSaub = row.Cell(columnUsageSaub).GetCellValue(); + var usageSpin = row.Cell(columnUsageSpin).GetCellValue(); + var comment = row.Cell(columnComment).GetCellValue() ?? string.Empty; + + var section = sections.FirstOrDefault(s => + string.Equals(s.Caption.Trim(), sectionCaption?.Trim(), StringComparison.CurrentCultureIgnoreCase)); + + if (section is null) + { + var message = string.Format(IParserService.MessageTemplate, SheetName, row.RowNumber(), columnSection, + "Указана некорректная секция"); + throw new FileFormatException(message); + } + + var idMode = GetIdMode(modeName); + + if (idMode is null) + { + var message = string.Format(IParserService.MessageTemplate, SheetName, row.RowNumber(), columnSection, + "Указан некорректный режим бурения"); + throw new FileFormatException(message); + } + + var dto = new ProcessMapPlanDrillingDto + { + IdWellSectionType = section.Id, + Section = section.Caption, + IdMode = idMode.Value, + Mode = modeName, + DepthStart = depthStart, + DepthEnd = depthEnd, + AxialLoadPlan = axialLoadPlan, + AxialLoadLimitMax = axialLoadLimitMax, + DeltaPressurePlan = deltaPressurePlan, + DeltaPressureLimitMax = deltaPressureLimitMax, + TopDriveTorquePlan = topDriveTorquePlan, + TopDriveTorqueLimitMax = topDriveTorqueLimitMax, + TopDriveSpeedPlan = topDriveSpeedPlan, + TopDriveSpeedLimitMax = topDriveSpeedLimitMax, + FlowPlan = flowPlan, + FlowLimitMax = flowLimitMax, + RopPlan = ropPlan, + UsageSaub = usageSaub, + UsageSpin = usageSpin, + Comment = comment + }; + + var columnNumbers = new Dictionary + { + { nameof(ProcessMapPlanDrillingDto.DepthStart), columnDepthStart }, + { nameof(ProcessMapPlanDrillingDto.DepthEnd), columnDepthEnd }, + { nameof(ProcessMapPlanDrillingDto.DeltaPressurePlan), columnPressurePlan }, + { nameof(ProcessMapPlanDrillingDto.DeltaPressureLimitMax), columnPressureLimitMax }, + { nameof(ProcessMapPlanDrillingDto.AxialLoadPlan), columnAxialLoadPlan }, + { nameof(ProcessMapPlanDrillingDto.AxialLoadLimitMax), columnAxialLoadLimitMax }, + { nameof(ProcessMapPlanDrillingDto.TopDriveTorquePlan), columnTopDriveTorquePlan }, + { nameof(ProcessMapPlanDrillingDto.TopDriveTorqueLimitMax), columnTopDriveTorqueLimitMax }, + { nameof(ProcessMapPlanDrillingDto.TopDriveSpeedPlan), columnTopDriveSpeedPlan }, + { nameof(ProcessMapPlanDrillingDto.TopDriveSpeedLimitMax), columnTopDriveSpeedLimitMax }, + { nameof(ProcessMapPlanDrillingDto.FlowPlan), columnFlowPlan }, + { nameof(ProcessMapPlanDrillingDto.FlowLimitMax), columnFlowLimitMax }, + { nameof(ProcessMapPlanDrillingDto.RopPlan), columnRopPlan }, + { nameof(ProcessMapPlanDrillingDto.UsageSaub), columnUsageSaub }, + { nameof(ProcessMapPlanDrillingDto.UsageSpin), columnUsageSpin }, + { nameof(ProcessMapPlanDrillingDto.Comment), columnComment } + }; + + return ValidateRow(row.RowNumber(), columnNumbers, dto); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanParser.cs b/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanParser.cs new file mode 100644 index 00000000..5b7aab2d --- /dev/null +++ b/AsbCloudInfrastructure/Services/ProcessMapPlan/Parser/ProcessMapPlanParser.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Reflection; +using AsbCloudApp.Data; +using AsbCloudApp.Data.ProcessMapPlan; +using AsbCloudApp.Requests.ParserOptions; +using ClosedXML.Excel; + +namespace AsbCloudInfrastructure.Services.ProcessMapPlan.Parser; + +public abstract class ProcessMapPlanParser : ParserServiceBase + where TDto : ProcessMapPlanBaseDto +{ + protected ProcessMapPlanParser(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + private const int HeaderRowsCount = 2; + private const int ColumnCount = 18; + + protected abstract string TemplateFileName { get; } + + protected abstract ValidationResultDto ParseRow(IXLRow row); + + public override ParserResultDto Parse(Stream file, IParserOptionsRequest options) + { + using var workbook = new XLWorkbook(file); + + var sheet = workbook.GetWorksheet(SheetName); + + var processMaps = ParseExcelSheet(sheet, ParseRow, ColumnCount, HeaderRowsCount); + return processMaps; + } + + public override Stream GetTemplateFile() => + Assembly.GetExecutingAssembly().GetTemplateCopyStream(TemplateFileName) + ?? throw new ArgumentNullException($"Файл '{TemplateFileName}' не найден"); + + protected static int? GetIdMode(string? modeName) => + modeName?.Trim().ToLower() switch + { + "ручной" => 0, + "ротор" => 1, + "слайд" => 2, + _ => null + }; +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/ProcessMapPlan/Templates/ProcessMapPlanDrillingTemplate.xlsx b/AsbCloudInfrastructure/Services/ProcessMapPlan/Templates/ProcessMapPlanDrillingTemplate.xlsx new file mode 100644 index 00000000..bdf13143 Binary files /dev/null and b/AsbCloudInfrastructure/Services/ProcessMapPlan/Templates/ProcessMapPlanDrillingTemplate.xlsx differ diff --git a/AsbCloudInfrastructure/Services/ProcessMaps/WellDrilling/ProcessMapPlanImportWellDrillingService.cs b/AsbCloudInfrastructure/Services/ProcessMaps/WellDrilling/ProcessMapPlanImportWellDrillingService.cs index f322c798..454daa7a 100644 --- a/AsbCloudInfrastructure/Services/ProcessMaps/WellDrilling/ProcessMapPlanImportWellDrillingService.cs +++ b/AsbCloudInfrastructure/Services/ProcessMaps/WellDrilling/ProcessMapPlanImportWellDrillingService.cs @@ -18,6 +18,8 @@ namespace AsbCloudInfrastructure.Services.ProcessMaps.WellDrilling; /* * password for ProcessMapImportTemplate.xlsx is ASB2020! */ + +[Obsolete] public class ProcessMapPlanImportWellDrillingService : IProcessMapPlanImportService { private readonly IProcessMapPlanRepository processMapPlanWellDrillingRepository; diff --git a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs index c3fd0c49..fb55bcf7 100644 --- a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs +++ b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanBaseController.cs @@ -9,8 +9,13 @@ using Microsoft.AspNetCore.Http; using AsbCloudApp.Exceptions; using AsbCloudApp.Requests; using System; +using System.IO; using AsbCloudApp.Services; using System.Linq; +using AsbCloudApp.Data; +using AsbCloudApp.Requests.ParserOptions; +using AsbCloudInfrastructure.Services; +using AsbCloudWebApi.Controllers.Interfaces; namespace AsbCloudWebApi.Controllers.ProcessMapPlan; @@ -20,17 +25,38 @@ namespace AsbCloudWebApi.Controllers.ProcessMapPlan; [ApiController] [Route("api/well/{idWell}/[controller]")] [Authorize] -public abstract class ProcessMapPlanBaseController : ControllerBase - where TDto : ProcessMapPlanBaseDto +public abstract class ProcessMapPlanBaseController : ControllerBase, + IControllerWithParser + where TDto : ProcessMapPlanBaseDto { - private readonly IChangeLogRepository repository; - private readonly IWellService wellService; + private readonly IChangeLogRepository repository; + private readonly IWellService wellService; + private readonly IParserService parserService; - public ProcessMapPlanBaseController(IChangeLogRepository repository, IWellService wellService) - { - this.repository = repository; - this.wellService = wellService; - } + protected ProcessMapPlanBaseController(IChangeLogRepository repository, + IWellService wellService, + ParserServiceFactory parserFactory, + int idParserService) + { + this.repository = repository; + this.wellService = wellService; + parserService = parserFactory.Create(idParserService); + } + + protected abstract string TemplateFileName { get; } + + ActionResult> IControllerWithParser.Parse(Stream file, IParserOptionsRequest options) + { + try + { + var parserResult = parserService.Parse(file, options); + return Ok(parserResult); + } + catch (FileFormatException ex) + { + return this.ValidationBadRequest("files", ex.Message); + } + } /// /// Добавление @@ -190,6 +216,39 @@ public abstract class ProcessMapPlanBaseController : ControllerBase var result = await repository.UpdateOrInsertRange(idUser, dtos, token); return Ok(result); } + + /// + /// Импорт РТК из excel (xlsx) файла + /// + /// + /// + /// + /// + [HttpPost("parse")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task>> Parse(int idWell, + [FromForm] IFormFileCollection files, + CancellationToken token) + { + await AssertUserHasAccessToWell(idWell, token); + + return this.ParseExcelFile(files, IParserOptionsRequest.Empty()); + } + + /// + /// Получение шаблона для заполнения РТК + /// + /// + [HttpGet("template")] + [AllowAnonymous] + [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public IActionResult GetTemplate() + { + var stream = parserService.GetTemplateFile(); + return File(stream, "application/octet-stream", TemplateFileName); + } /// /// returns user id, if he has access to well @@ -221,4 +280,4 @@ public abstract class ProcessMapPlanBaseController : ControllerBase var idUser = User.GetUserId() ?? throw new ForbidException("Неизвестный пользователь"); return idUser; } -} +} \ No newline at end of file diff --git a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs index b0a20f1e..8855c54f 100644 --- a/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs +++ b/AsbCloudWebApi/Controllers/ProcessMapPlan/ProcessMapPlanDrillingController.cs @@ -2,15 +2,18 @@ using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; +using AsbCloudInfrastructure.Services; namespace AsbCloudWebApi.Controllers.ProcessMapPlan; public class ProcessMapPlanDrillingController : ProcessMapPlanBaseController { - public ProcessMapPlanDrillingController( - IChangeLogRepository repository, - IWellService wellService) - : base(repository, wellService) - { - } -} + public ProcessMapPlanDrillingController(IChangeLogRepository repository, + IWellService wellService, + ParserServiceFactory parserFactory) + : base(repository, wellService, parserFactory, ParserServiceFactory.IdProcessMapPlanDrillingParser) + { + } + + protected override string TemplateFileName => "ЕЦП_шаблон_файла_РТК_план_бурение.xlsx"; +} \ No newline at end of file