using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using AsbCloudApp.Data.ProcessMap; using AsbCloudApp.Repositories; using AsbCloudApp.Services; using ClosedXML.Excel; using System; using AsbCloudApp.Data; namespace AsbCloudInfrastructure.Services.ProcessMap; /* * password for ProcessMapImportTemplate.xlsx is ASB2020! */ public class ProcessMapPlanImportService : IProcessMapPlanImportService { private readonly IProcessMapPlanRepository processMapPlanRepository; private readonly ICrudRepository wellSectionTypeRepository; 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 WellSectionTypeDto[] sections = null!; public ProcessMapPlanImportService(IProcessMapPlanRepository processMapPlanRepository, ICrudRepository wellSectionTypeRepository) { this.processMapPlanRepository = processMapPlanRepository; this.wellSectionTypeRepository = wellSectionTypeRepository; } 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 processPlanMaps = ParseWorkBook(workBook); if (deleteProcessMapPlansBeforeImport) await processMapPlanRepository.RemoveByWellAsync(idWell, cancellationToken); foreach (var processPlanMap in processPlanMaps) { processPlanMap.IdWell = idWell; processPlanMap.IdUser = idUser; } await processMapPlanRepository.InsertRangeAsync(processPlanMaps, cancellationToken); } public async Task ExportAsync(int idWell, CancellationToken cancellationToken) { sections = (await wellSectionTypeRepository.GetAllAsync(cancellationToken)).ToArray(); var processMapPlans = (await processMapPlanRepository.GetByIdWellAsync(idWell, cancellationToken)).ToArray(); return await GenerateExcelFileStreamAsync(processMapPlans, cancellationToken); } public async Task GetExcelTemplateStreamAsync(CancellationToken cancellationToken) { var resourceName = Assembly.GetExecutingAssembly() .GetManifestResourceNames() .FirstOrDefault(n => n.EndsWith("ProcessMapPlanTemplate.xlsx"))!; using var stream = Assembly.GetExecutingAssembly() .GetManifestResourceStream(resourceName)!; var memoryStream = new MemoryStream(); await stream.CopyToAsync(memoryStream, cancellationToken); memoryStream.Position = 0; return memoryStream; } private void AddToWorkbook(XLWorkbook workbook, ProcessMapPlanDto[] processMapPlans) { if (!processMapPlans.Any()) return; var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan) ?? throw new FileFormatException($"Книга excel не содержит листа {sheetNamePlan}."); AddToSheet(sheet, processMapPlans); } private void AddToSheet(IXLWorksheet sheet, ProcessMapPlanDto[] processMapPlans) { for (int i = 0; i < processMapPlans.Length; i++) { var row = sheet.Row(1 + i + headerRowsCount); AddToRow(row, processMapPlans[i]); } } private void AddToRow(IXLRow row, ProcessMapPlanDto processMap) { row.Cell(columnWellSectionType).Value = sections.First(x => x.Id == processMap.IdWellSectionType).Caption; row.Cell(columnMode).Value = GetModeCaption(processMap.IdMode); row.Cell(columnDepthStart).Value = processMap.DepthStart; row.Cell(columnDepthEnd).Value = processMap.DepthEnd; row.Cell(columnPressurePlan).Value = processMap.Pressure.Plan; row.Cell(columnPressureLimitMax).Value = processMap.Pressure.LimitMax; row.Cell(columnAxialLoadPlan).Value = processMap.AxialLoad.Plan; row.Cell(columnAxialLoadLimitMax).Value = processMap.AxialLoad.LimitMax; row.Cell(columnTopDriveTorquePlan).Value = processMap.TopDriveTorque.Plan; row.Cell(columnTopDriveTorqueLimitMax).Value = processMap.TopDriveTorque.LimitMax; row.Cell(columnTopDriveSpeedPlan).Value = processMap.TopDriveSpeed.Plan; row.Cell(columnTopDriveSpeedLimitMax).Value = processMap.TopDriveSpeed.LimitMax; row.Cell(columnFlowPlan).Value = processMap.Flow.Plan; row.Cell(columnFlowLimitMax).Value = processMap.Flow.LimitMax; row.Cell(columnRopPlan).Value = processMap.RopPlan; row.Cell(columnUsageSaub).Value = processMap.UsageSaub; row.Cell(columnUsageSpin).Value = processMap.UsageSpin; } private ProcessMapPlanDto[] ParseWorkBook(IXLWorkbook workbook) { var sheet = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan) ?? throw new FileFormatException($"Книга excel не содержит листа {sheetNamePlan}."); return ParseSheet(sheet); } private ProcessMapPlanDto[] 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 ProcessMapPlanDto[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 ProcessMapPlanDto ParseRow(IXLRow row) { var wellSectionTypeCaption = GetCellValue(row, columnWellSectionType).Trim().ToLower(); var modeName = GetCellValue(row, columnMode).Trim().ToLower(); var depthStart = GetCellValue(row, columnDepthStart); var depthEnd = GetCellValue(row, columnDepthEnd); var pressurePlan = GetCellValue(row, columnPressurePlan); var pressureLimitMax = GetCellValue(row, columnPressureLimitMax); var axialLoadPlan = GetCellValue(row, columnAxialLoadPlan); var axialLoadLimitMax = GetCellValue(row, columnAxialLoadLimitMax); var topDriveTorquePlan = GetCellValue(row, columnTopDriveTorquePlan); var topDriveTorqueLimitMax = GetCellValue(row, columnTopDriveTorqueLimitMax); var topDriveSpeedPlan = GetCellValue(row, columnTopDriveSpeedPlan); var topDriveSpeedLimitMax = GetCellValue(row, columnTopDriveSpeedLimitMax); var flowPlan = GetCellValue(row, columnFlowPlan); var flowLimitMax = GetCellValue(row, columnFlowLimitMax); var ropPlan = GetCellValue(row, columnRopPlan); var usageSaub = GetCellValue(row, columnUsageSaub); var usageSpin = GetCellValue(row, columnUsageSpin); 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 > 50000) throw new FileFormatException( $"Лист {row.Worksheet.Name}. В строке {row.RowNumber()} указана некорректная стартовая глубина"); if (depthEnd is < 0 or > 50000) 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 > 50000) 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 }; } private async Task GenerateExcelFileStreamAsync(ProcessMapPlanDto[] processMapPlans, CancellationToken cancellationToken) { using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken); using var workbook = new XLWorkbook(excelTemplateStream, XLEventTracking.Disabled); AddToWorkbook(workbook, processMapPlans); MemoryStream memoryStream = new MemoryStream(); 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 => "Слайд", _ => "Ручной", }; private static T GetCellValue(IXLRow row, int columnNumber) { try { var cell = row.Cell(columnNumber); return (T)Convert.ChangeType(cell.Value, typeof(T)); } catch { throw new FileFormatException( $"Лист {row.Worksheet.Name}. Ячейка: ({row.RowNumber()},{columnNumber}) содержит некорректное значение"); } } }