using System; using System.IO; using System.Collections.Generic; using System.Linq; using ClosedXML.Excel; using AsbCloudApp.Data; using AsbCloudDb.Model; namespace AsbCloudInfrastructure.Services { public class WellOperationImportService { private const string sheetNamePlan = "План"; private const string sheetNameFact = "Факт"; private static readonly DateTime dateLimitMin = new DateTime(2001,1,1,0,0,0); private static readonly DateTime dateLimitMax = new DateTime(2099,1,1,0,0,0); private static readonly TimeSpan drillingDurationLimitMax = TimeSpan.FromDays(366); private readonly IAsbCloudDbContext db; private List categories = null; public List Categories { get { if (categories is null) categories = db.WellOperationCategories.ToList(); return categories; } } private List sections = null; public List Sections { get { if (sections is null) sections = db.WellSectionTypes.ToList(); return sections; } } public WellOperationImportService(IAsbCloudDbContext db) { this.db = db; } public IEnumerable ParseFile(string excelFilePath) { if (!File.Exists(excelFilePath)) throw new FileNotFoundException($"Файл {excelFilePath} не найден."); return ParseWorkbook(excelFilePath); } private IEnumerable ParseWorkbook(string excelFilePath) { using var sourceExcelWorkbook = new XLWorkbook(excelFilePath, XLEventTracking.Disabled); var sheetPlan = sourceExcelWorkbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan); if (sheetPlan is null) throw new FileFormatException($"Файл {excelFilePath} не не содержит листа {sheetNamePlan}."); var sheetFact = sourceExcelWorkbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNameFact); if (sheetFact is null) throw new FileFormatException($"Файл {excelFilePath} не не содержит листа {sheetNameFact}."); //sheetPlan.RangeUsed().RangeAddress.LastAddress.ColumnNumber var wellOperations = new List(); var wellOperationsPlan = ParseSheet(sheetPlan, 0); wellOperations.AddRange(wellOperationsPlan); var wellOperationsFact = ParseSheet(sheetFact, 1); wellOperations.AddRange(wellOperationsFact); return wellOperations; } private IEnumerable ParseSheet(IXLWorksheet sheet, int idType) { const int headerRowsCount = 1; if (sheet.RangeUsed().RangeAddress.LastAddress.ColumnNumber < 7) throw new FileFormatException($"Лист {sheet.Name} содержит меньшее количество столбцев."); var count = sheet.RowsUsed().Count() - headerRowsCount; if (count > 1024) throw new FileFormatException($"Лист {sheet.Name} содержит слишком большое количество операций."); if (count <= 0) return new List(); var operations = new List(count); var parseErrors = new List(); DateTime lastOperationDateStart = new DateTime(); for (int i = 0; i < count; i++) { var row = sheet.Row(1 + i + headerRowsCount); try { var operation = ParseRow(row, idType); operations.Add(operation); if (lastOperationDateStart > operation.DateStart) parseErrors.Add($"Лист {sheet.Name} строка {row.RowNumber()} дата позднее даты предыдущей операции."); lastOperationDateStart = operation.DateStart; } catch(FileFormatException ex) { parseErrors.Add(ex.Message); } }; // проверка диапазона дат if(operations.Min(o => o.DateStart) - operations.Max(o => o.DateStart) > drillingDurationLimitMax) parseErrors.Add($"Лист {sheet.Name} содержит диапазон дат больше {drillingDurationLimitMax}"); if (parseErrors.Any()) throw new FileFormatException(string.Join("\r\n", parseErrors)); return operations; } private WellOperationDto ParseRow(IXLRow row, int idType) { const int columnSection = 1; const int columnCategory = 2; const int columnCategoryInfo = 3; const int columnDepthStart = 4; const int columnDepthEnd = 5; const int columnDate = 6; const int columnDuration = 7; const int columnComment = 8; var vSection = row.Cell(columnSection).Value; var vCategory = row.Cell(columnCategory).Value; var vCategoryInfo = row.Cell(columnCategoryInfo).Value; var vDepthStart = row.Cell(columnDepthStart).Value; var vDepthEnd = row.Cell(columnDepthEnd).Value; var vDate = row.Cell(columnDate).Value; var vDuration = row.Cell(columnDuration).Value; var vComment = row.Cell(columnComment).Value; var operation = new WellOperationDto{IdType = idType}; if (vSection is string sectionName) { var section = Sections.Find(c => c.Caption.ToLower() == sectionName.ToLower()); if (section is null) throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} указана некорректная секция"); operation.IdWellSectionType = section.Id; operation.WellSectionTypeName = section.Caption; } else throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана секция"); if (vCategory is string categoryName) { var category = Categories.Find(c => c.Name.ToLower() == categoryName.ToLower()); if(category is null) throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} указана некорректная операция"); operation.IdCategory = category.Id; operation.CategoryName = category.Name; } else throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана операция"); if (vCategoryInfo is not null) operation.CategoryInfo = vCategoryInfo.ToString(); if (vDepthStart is double depthStart && depthStart >= 0d && depthStart <= 20_000d) operation.DepthStart = depthStart; else throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана глубина на начало операции"); if (vDepthEnd is double depthEnd && depthEnd >= 0d && depthEnd <= 20_000d) operation.DepthEnd = depthEnd; else throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана глубина при завершении операции"); if (vDate is DateTime date && date > dateLimitMin && date < dateLimitMax) operation.DateStart = date; else throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} неправильно указана дата/время начала операции"); if (vDuration is double duration && duration >= 0d && duration <= 240d) operation.DurationHours = duration; else throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} не указана длительность операции"); if (vComment is not null) operation.Comment = vComment.ToString(); return operation; } } }