using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Data; using AsbCloudApp.Data.ProcessMaps; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Services; using AsbCloudApp.Services.ProcessMaps; using ClosedXML.Excel; namespace AsbCloudInfrastructure.Services.ProcessMaps.WellDrillingProcessMap; /* * password for ProcessMapImportTemplate.xlsx is ASB2020! */ public class WellDrillingProcessMapImportService : IProcessMapImportService { private readonly IProcessMapPlanRepository processMapPlanWellDrillingRepository; private readonly ICrudRepository wellSectionTypeRepository; private readonly IWellService wellService; private const string sheetNamePlan = "План"; private const int headerRowsCount = 2; private const int columnWellSectionType = 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; private WellSectionTypeDto[] sections = null!; public WellDrillingProcessMapImportService(IProcessMapPlanRepository processMapPlanWellDrillingRepository, ICrudRepository wellSectionTypeRepository, IWellService wellService) { this.processMapPlanWellDrillingRepository = processMapPlanWellDrillingRepository; this.wellSectionTypeRepository = wellSectionTypeRepository; this.wellService = wellService; } public async Task ImportAsync(int idWell, int idUser, bool deleteProcessMapPlansBeforeImport, Stream stream, CancellationToken cancellationToken) { sections = (await wellSectionTypeRepository.GetAllAsync(cancellationToken)).ToArray(); using var workBook = new XLWorkbook(stream); var wellDrillingProcessMaps = ParseWorkBook(workBook); if (deleteProcessMapPlansBeforeImport) await processMapPlanWellDrillingRepository.RemoveByWellAsync(idWell, cancellationToken); foreach (var wellDrillingProcessMap in wellDrillingProcessMaps) { wellDrillingProcessMap.IdWell = idWell; wellDrillingProcessMap.IdUser = idUser; } await processMapPlanWellDrillingRepository.InsertRangeAsync(wellDrillingProcessMaps, cancellationToken); } public async Task<(string Name, Stream File)> ExportAsync(int idWell, CancellationToken cancellationToken) { var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken) ?? throw new ArgumentInvalidException(nameof(idWell), $"Скважины с {idWell} не существует"); sections = (await wellSectionTypeRepository.GetAllAsync(cancellationToken)).ToArray(); var processMapPlans = (await processMapPlanWellDrillingRepository.GetByIdWellAsync(idWell, cancellationToken)).ToArray(); var file = await GenerateExcelFileStreamAsync(processMapPlans, cancellationToken); var fileName = $"РТК-план бурение по скважине {well.Caption} куст {well.Cluster}.xlsx"; return (fileName, file); } public async Task<(string Name, Stream File)> GetExcelTemplateStreamAsync(CancellationToken cancellationToken) { var resourceName = Assembly.GetExecutingAssembly() .GetManifestResourceNames() .FirstOrDefault(n => n.EndsWith("DrillingProcessMapTemplate.xlsx"))!; using var stream = Assembly.GetExecutingAssembly() .GetManifestResourceStream(resourceName)!; var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream, cancellationToken); memoryStream.Position = 0; var name = "ЕЦП_шаблон_файла_РТК_бурение.xlsx"; return (name, memoryStream); } private void AddToWorkbook(XLWorkbook workbook, IEnumerable processMapPlanWellDrillings) { var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan) ?? throw new FileFormatException($"Книга excel не содержит листа {sheetNamePlan}."); AddToSheet(sheet, processMapPlanWellDrillings.ToArray()); } private void AddToSheet(IXLWorksheet sheet, IList processMapPlanWellDrillings) { if (!processMapPlanWellDrillings.Any()) return; for (int i = 0; i < processMapPlanWellDrillings.Count; i++) { var row = sheet.Row(1 + i + headerRowsCount); AddToRow(row, processMapPlanWellDrillings[i]); } } private void AddToRow(IXLRow row, ProcessMapPlanWellDrillingDto processMapPlanWellDrillings) { row.Cell(columnWellSectionType).Value = sections.First(x => x.Id == processMapPlanWellDrillings.IdWellSectionType).Caption; row.Cell(columnMode).Value = GetModeCaption(processMapPlanWellDrillings.IdMode); row.Cell(columnDepthStart).Value = processMapPlanWellDrillings.DepthStart; row.Cell(columnDepthEnd).Value = processMapPlanWellDrillings.DepthEnd; row.Cell(columnPressurePlan).Value = processMapPlanWellDrillings.Pressure.Plan; row.Cell(columnPressureLimitMax).Value = processMapPlanWellDrillings.Pressure.LimitMax; row.Cell(columnAxialLoadPlan).Value = processMapPlanWellDrillings.AxialLoad.Plan; row.Cell(columnAxialLoadLimitMax).Value = processMapPlanWellDrillings.AxialLoad.LimitMax; row.Cell(columnTopDriveTorquePlan).Value = processMapPlanWellDrillings.TopDriveTorque.Plan; row.Cell(columnTopDriveTorqueLimitMax).Value = processMapPlanWellDrillings.TopDriveTorque.LimitMax; row.Cell(columnTopDriveSpeedPlan).Value = processMapPlanWellDrillings.TopDriveSpeed.Plan; row.Cell(columnTopDriveSpeedLimitMax).Value = processMapPlanWellDrillings.TopDriveSpeed.LimitMax; row.Cell(columnFlowPlan).Value = processMapPlanWellDrillings.Flow.Plan; row.Cell(columnFlowLimitMax).Value = processMapPlanWellDrillings.Flow.LimitMax; row.Cell(columnRopPlan).Value = processMapPlanWellDrillings.RopPlan; row.Cell(columnUsageSaub).Value = processMapPlanWellDrillings.UsageSaub; row.Cell(columnUsageSpin).Value = processMapPlanWellDrillings.UsageSpin; row.Cell(columnComment).Value = processMapPlanWellDrillings.Comment; } private IEnumerable ParseWorkBook(IXLWorkbook workbook) { var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan) ?? throw new FileFormatException($"Книга excel не содержит листа {sheetNamePlan}."); return ParseSheet(sheet); } private IEnumerable ParseSheet(IXLWorksheet sheet) { const int columnsCount = 17; if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < columnsCount) throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцов."); var rowsCount = sheet.RowsUsed().Count() - headerRowsCount; if (rowsCount <= 0) return Array.Empty(); var processMapPlans = new ProcessMapPlanWellDrillingDto[rowsCount]; var parseErrors = new List(); for (int i = 0; i < processMapPlans.Length; i++) { var row = sheet.Row(1 + i + headerRowsCount); try { processMapPlans[i] = ParseRow(row); } catch (FileFormatException ex) { parseErrors.Add(ex.Message); } } if (parseErrors.Any()) throw new FileFormatException(string.Join("\r\n", parseErrors)); return processMapPlans; } private ProcessMapPlanWellDrillingDto ParseRow(IXLRow row) { var wellSectionTypeCaption = row.Cell(columnWellSectionType).GetCellValue()?.Trim().ToLower(); var modeName = row.Cell(columnMode).GetCellValue()?.Trim().ToLower(); var depthStart = row.Cell(columnDepthStart).GetCellValue(); var depthEnd = row.Cell(columnDepthEnd).GetCellValue(); var pressurePlan = row.Cell(columnPressurePlan).GetCellValue(); var pressureLimitMax = 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(); var wellSection = sections.FirstOrDefault(s => s.Caption.Trim().ToLower() == wellSectionTypeCaption) ?? throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указана некорректная секция"); var idMode = GetIdMode(modeName) ?? throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указан некорректный режим"); if (depthStart is < 0 or > 99999.9) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указана некорректная стартовая глубина"); if (depthEnd is < 0 or > 99999.9) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указана некорректная конечная глубина"); if (pressurePlan is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение перепада давления"); if (pressureLimitMax is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничение перепада давления"); if (axialLoadPlan is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение нагрузки"); if (axialLoadLimitMax is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничение нагрузки"); if (topDriveTorquePlan is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение момента на ВСП"); if (topDriveTorqueLimitMax is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничение момента на ВСП"); if (topDriveSpeedPlan is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение оборотов на ВСП"); if (topDriveSpeedLimitMax is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничения оборота на ВСП"); if (flowPlan is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение расхода"); if (flowLimitMax is < 0 or > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное ограничение расхода"); if (ropPlan is < 0 or > 99999.9) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указано некорректное плановое значение механической скорости"); if (usageSaub is < 0 or > 100) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указан некорректный плановый процент использования АКБ"); if (usageSpin is < 0 or > 100) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указан некорректные плановый процент использования spin master"); return new() { IdWellSectionType = wellSection.Id, IdMode = idMode, DepthStart = depthStart, LastUpdate = DateTime.UtcNow, DepthEnd = depthEnd, Pressure = new() { Plan = pressurePlan, LimitMax = pressureLimitMax }, AxialLoad = new() { Plan = axialLoadPlan, LimitMax = axialLoadLimitMax }, TopDriveTorque = new() { Plan = topDriveTorquePlan, LimitMax = topDriveTorqueLimitMax }, TopDriveSpeed = new() { Plan = topDriveSpeedPlan, LimitMax = topDriveSpeedLimitMax }, Flow = new() { Plan = flowPlan, LimitMax = flowLimitMax }, RopPlan = ropPlan, UsageSaub = usageSaub, UsageSpin = usageSpin, Comment = comment }; } private async Task GenerateExcelFileStreamAsync(ProcessMapPlanWellDrillingDto[] processMapPlanWellDrillings, CancellationToken cancellationToken) { using var excelTemplateStream = (await GetExcelTemplateStreamAsync(cancellationToken)).File; using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled); AddToWorkbook(workbook, processMapPlanWellDrillings); MemoryStream memoryStream = new(); workbook.SaveAs(memoryStream, new SaveOptions { }); memoryStream.Seek(0, SeekOrigin.Begin); return memoryStream; } private static int? GetIdMode(string modeName) => modeName switch { "ручной" => 0, "ротор" => 1, "слайд" => 2, _ => null }; private static string GetModeCaption(int idMode) => idMode switch { 1 => "Ротор", 2 => "Слайд", _ => "Ручной", }; }