From e604d8a03180d13794b8fe03d6f664f9a1042b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A4=D1=80=D0=BE=D0=BB=D0=BE=D0=B2?= Date: Sat, 9 Oct 2021 20:16:22 +0500 Subject: [PATCH] make WellOperations import and export. ExcelTemplate embedded to infrastructure. --- .../Services/IWellOperationImportService.cs | 10 + .../AsbCloudInfrastructure.csproj | 4 + AsbCloudInfrastructure/DependencyInjection.cs | 2 + .../WellOperationImportService.cs | 178 ++++++++++++++---- .../WellOperationImportTemplate.xltx | Bin 0 -> 61004 bytes .../WellOperationService.cs | 2 +- .../WellOperationsStatService.cs | 2 +- .../Controllers/WellOperationController.cs | 78 +++++++- .../DebugWellOperationImportService.cs | 11 +- ConsoleApp1/DebugWellOperationsStatService.cs | 1 + 10 files changed, 246 insertions(+), 42 deletions(-) create mode 100644 AsbCloudApp/Services/IWellOperationImportService.cs rename AsbCloudInfrastructure/Services/{ => WellOperationService}/WellOperationImportService.cs (53%) create mode 100644 AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportTemplate.xltx rename AsbCloudInfrastructure/Services/{ => WellOperationService}/WellOperationService.cs (98%) rename AsbCloudInfrastructure/Services/{ => WellOperationService}/WellOperationsStatService.cs (99%) diff --git a/AsbCloudApp/Services/IWellOperationImportService.cs b/AsbCloudApp/Services/IWellOperationImportService.cs new file mode 100644 index 00000000..7ef96ab3 --- /dev/null +++ b/AsbCloudApp/Services/IWellOperationImportService.cs @@ -0,0 +1,10 @@ +using System.IO; + +namespace AsbCloudApp.Services +{ + public interface IWellOperationImportService + { + Stream Export(int idWell); + void Import(int idWell, Stream stream, bool deleteWellOperationsBeforeImport = false); + } +} \ No newline at end of file diff --git a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj index 375a1a5e..0c41213b 100644 --- a/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj +++ b/AsbCloudInfrastructure/AsbCloudInfrastructure.csproj @@ -8,6 +8,10 @@ 1701;1702;IDE0090;IDE0063;IDE0066 + + + + diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 2989e82d..d4ca1513 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -4,6 +4,7 @@ using AsbCloudDb.Model; using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services.Analysis; using AsbCloudInfrastructure.Services.Cache; +using AsbCloudInfrastructure.Services.WellOperationService; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -39,6 +40,7 @@ namespace AsbCloudInfrastructure services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/AsbCloudInfrastructure/Services/WellOperationImportService.cs b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportService.cs similarity index 53% rename from AsbCloudInfrastructure/Services/WellOperationImportService.cs rename to AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportService.cs index 8b2b0f16..87f76289 100644 --- a/AsbCloudInfrastructure/Services/WellOperationImportService.cs +++ b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportService.cs @@ -5,25 +5,43 @@ using System.Linq; using ClosedXML.Excel; using AsbCloudApp.Data; using AsbCloudDb.Model; +using Mapster; +using Microsoft.EntityFrameworkCore; +using AsbCloudApp.Services; -namespace AsbCloudInfrastructure.Services +namespace AsbCloudInfrastructure.Services.WellOperationService { - public class WellOperationImportService + public class WellOperationImportService : IWellOperationImportService { 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); + const int headerRowsCount = 1; + + 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; + + 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 { + public List Categories + { + get + { if (categories is null) - categories = db.WellOperationCategories.ToList(); + categories = db.WellOperationCategories + .AsNoTracking() + .ToList(); return categories; } } @@ -34,7 +52,9 @@ namespace AsbCloudInfrastructure.Services get { if (sections is null) - sections = db.WellSectionTypes.ToList(); + sections = db.WellSectionTypes + .AsNoTracking() + .ToList(); return sections; } } @@ -44,24 +64,118 @@ namespace AsbCloudInfrastructure.Services this.db = db; } - public IEnumerable ParseFile(string excelFilePath) + public void Import(int idWell, Stream stream, bool deleteWellOperationsBeforeImport = false) { - if (!File.Exists(excelFilePath)) - throw new FileNotFoundException($"Файл {excelFilePath} не найден."); + using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled); + var operations = ParseFileStream(stream); + foreach (var operation in operations) + operation.IdWell = idWell; - return ParseWorkbook(excelFilePath); + SaveOperations(idWell, operations, deleteWellOperationsBeforeImport); } - private IEnumerable ParseWorkbook(string excelFilePath) + public Stream Export(int idWell) { - 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 operations = db.WellOperations + .Include(o => o.WellSectionType) + .Include(o => o.OperationCategory) + .Where(o => o.IdWell == idWell) + .OrderBy(o => o.DateStart) + .AsNoTracking() + .ToList(); - var sheetFact = sourceExcelWorkbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNameFact); + if (!operations.Any()) + return null; + + return MakeExelFileStream(operations); + } + + private Stream MakeExelFileStream(IEnumerable operations) + { + using Stream ecxelTemplateStream = System.Reflection.Assembly.GetExecutingAssembly() + .GetManifestResourceStream("AsbCloudInfrastructure.Services.WellOperationService.WellOperationImportTemplate.xltx"); + + using var workbook = new XLWorkbook(ecxelTemplateStream, XLEventTracking.Disabled); + AddOperationsToWorkbook(workbook, operations); + + MemoryStream memoryStream = new MemoryStream(); + workbook.SaveAs(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream; + } + + private void AddOperationsToWorkbook(XLWorkbook workbook, IEnumerable operations) + { + var planOperations = operations.Where(o => o.IdType == 0); + if (planOperations.Any()) + { + var sheetPlan = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan); + AddOperationsToSheet(sheetPlan, planOperations); + } + + var factOperations = operations.Where(o => o.IdType == 1); + if (factOperations.Any()) + { + var sheetFact = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNameFact); + AddOperationsToSheet(sheetFact, factOperations); + } + } + + private void AddOperationsToSheet(IXLWorksheet sheet, IEnumerable operations) + { + var operationsList = operations.ToList(); + for (int i = 0; i < operationsList.Count(); i++) + { + var row = sheet.Row(1 + i + headerRowsCount); + AddOperationToRow(row, operationsList[i]); + } + } + + private void AddOperationToRow(IXLRow row, WellOperation operation) + { + row.Cell(columnSection).Value = operation.WellSectionType?.Caption; + row.Cell(columnCategory).Value = operation.OperationCategory?.Name; + row.Cell(columnCategoryInfo).Value = operation.CategoryInfo; + row.Cell(columnDepthStart).Value = operation.DepthStart; + row.Cell(columnDepthEnd).Value = operation.DepthEnd; + row.Cell(columnDate).Value = operation.DateStart; + row.Cell(columnDuration).Value = operation.DurationHours; + row.Cell(columnComment).Value = operation.Comment; + } + + private void SaveOperations(int idWell, IEnumerable operations, bool deleteWellOperationsBeforeImport = false) + { + var transaction = db.Database.BeginTransaction(); + try + { + if (deleteWellOperationsBeforeImport) + db.WellOperations.RemoveRange(db.WellOperations.Where(o => o.IdWell == idWell)); + db.WellOperations.AddRange(operations.Adapt()); + db.SaveChanges(); + transaction.Commit(); + } + catch + { + transaction.Rollback(); + throw; + } + } + + private IEnumerable ParseFileStream(Stream stream) + { + using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled); + return ParseWorkbook(workbook); + } + + private IEnumerable ParseWorkbook(IXLWorkbook workbook) + { + var sheetPlan = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNamePlan); + if (sheetPlan is null) + throw new FileFormatException($"Книга excel не содержит листа {sheetNamePlan}."); + + var sheetFact = workbook.Worksheets.FirstOrDefault(ws => ws.Name == sheetNameFact); if (sheetFact is null) - throw new FileFormatException($"Файл {excelFilePath} не не содержит листа {sheetNameFact}."); + throw new FileFormatException($"Книга excel не содержит листа {sheetNameFact}."); //sheetPlan.RangeUsed().RangeAddress.LastAddress.ColumnNumber var wellOperations = new List(); @@ -77,13 +191,12 @@ namespace AsbCloudInfrastructure.Services 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} содержит слишком большое количество операций."); @@ -100,20 +213,20 @@ namespace AsbCloudInfrastructure.Services { var operation = ParseRow(row, idType); operations.Add(operation); - + if (lastOperationDateStart > operation.DateStart) parseErrors.Add($"Лист {sheet.Name} строка {row.RowNumber()} дата позднее даты предыдущей операции."); lastOperationDateStart = operation.DateStart; } - catch(FileFormatException ex) + catch (FileFormatException ex) { parseErrors.Add(ex.Message); - } + } }; // проверка диапазона дат - if(operations.Min(o => o.DateStart) - operations.Max(o => o.DateStart) > drillingDurationLimitMax) + if (operations.Min(o => o.DateStart) - operations.Max(o => o.DateStart) > drillingDurationLimitMax) parseErrors.Add($"Лист {sheet.Name} содержит диапазон дат больше {drillingDurationLimitMax}"); if (parseErrors.Any()) @@ -124,15 +237,6 @@ namespace AsbCloudInfrastructure.Services 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; @@ -142,14 +246,14 @@ namespace AsbCloudInfrastructure.Services var vDuration = row.Cell(columnDuration).Value; var vComment = row.Cell(columnComment).Value; - var operation = new WellOperationDto{IdType = idType}; + 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; } @@ -159,7 +263,7 @@ namespace AsbCloudInfrastructure.Services if (vCategory is string categoryName) { var category = Categories.Find(c => c.Name.ToLower() == categoryName.ToLower()); - if(category is null) + if (category is null) throw new FileFormatException($"Лист {row.Worksheet.Name}. Строка {row.RowNumber()} указана некорректная операция"); operation.IdCategory = category.Id; diff --git a/AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportTemplate.xltx b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportTemplate.xltx new file mode 100644 index 0000000000000000000000000000000000000000..0197218c219ae8297d9edd17e42c0eef735a3ef8 GIT binary patch literal 61004 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 literal 0 HcmV?d00001 diff --git a/AsbCloudInfrastructure/Services/WellOperationService.cs b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs similarity index 98% rename from AsbCloudInfrastructure/Services/WellOperationService.cs rename to AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs index 51e63e9c..865e95f2 100644 --- a/AsbCloudInfrastructure/Services/WellOperationService.cs +++ b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationService.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Services +namespace AsbCloudInfrastructure.Services.WellOperationService { public class WellOperationService : IWellOperationService { diff --git a/AsbCloudInfrastructure/Services/WellOperationsStatService.cs b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationsStatService.cs similarity index 99% rename from AsbCloudInfrastructure/Services/WellOperationsStatService.cs rename to AsbCloudInfrastructure/Services/WellOperationService/WellOperationsStatService.cs index 1aaba862..9b273efe 100644 --- a/AsbCloudInfrastructure/Services/WellOperationsStatService.cs +++ b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationsStatService.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace AsbCloudInfrastructure.Services +namespace AsbCloudInfrastructure.Services.WellOperationService { class Race { diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index e6684ba1..9c218049 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -1,9 +1,11 @@ using AsbCloudApp.Data; using AsbCloudApp.Services; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -19,11 +21,13 @@ namespace AsbCloudWebApi.Controllers { private readonly IWellOperationService operationService; private readonly IWellService wellService; + private readonly IWellOperationImportService wellOperationImportService; - public WellOperationController(IWellOperationService operationService, IWellService wellService) + public WellOperationController(IWellOperationService operationService, IWellService wellService, IWellOperationImportService wellOperationImportService) { this.operationService = operationService; this.wellService = wellService; + this.wellOperationImportService = wellOperationImportService; } /// @@ -170,6 +174,78 @@ namespace AsbCloudWebApi.Controllers return Ok(result); } + + /// + /// Импортирует операции из excel (xlsx) файла + /// + /// id скважины + /// Коллекция файлов - 1 файл xlsx + /// Удалить операции перед импортом, если фал валидный + /// Токен отмены задачи + /// + [HttpPost] + [Route("import")] + [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] + public async Task ImportAsync(int idWell, + [FromForm] IFormFileCollection files, + bool deleteWellOperationsBeforeImport = false, + CancellationToken token = default) + { + int? idCompany = User.GetCompanyId(); + int? idUser = User.GetUserId(); + + if (idCompany is null || idUser is null) + return Forbid(); + + if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, + idWell, token).ConfigureAwait(false)) + return Forbid(); + + if (files.Count < 1) + return BadRequest("нет файла"); + + var file = files[0]; + if(Path.GetExtension( file.FileName).ToLower() != "*.xlsx") + return BadRequest("Требуется xlsx файл."); + using Stream stream = file.OpenReadStream(); + + try + { + wellOperationImportService.Import(idWell, stream, deleteWellOperationsBeforeImport); + } + catch(FileFormatException ex) + { + return BadRequest(ex.Message); + } + + return Ok(); + } + + /// + /// Возвращает файл с диска на сервере + /// + /// id скважины + /// Токен отмены задачи + /// Запрашиваемый файл + [HttpGet] + [Route("export")] + [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)] + public async Task ExportAsync([FromRoute] int idWell, CancellationToken token = default) + { + int? idCompany = User.GetCompanyId(); + + if (idCompany is null) + return Forbid(); + + if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, + idWell, token).ConfigureAwait(false)) + return Forbid(); + + var stream = wellOperationImportService.Export(idWell); + var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_operations.xlsx"; + return File(stream, "application/octet-stream", fileName); + } + private async Task CanUserAccessToWellAsync(int idWell, CancellationToken token = default) { int? idCompany = User.GetCompanyId(); diff --git a/ConsoleApp1/DebugWellOperationImportService.cs b/ConsoleApp1/DebugWellOperationImportService.cs index 4fde35ce..d2fe92c8 100644 --- a/ConsoleApp1/DebugWellOperationImportService.cs +++ b/ConsoleApp1/DebugWellOperationImportService.cs @@ -1,6 +1,7 @@ using AsbCloudDb.Model; using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services.Cache; +using AsbCloudInfrastructure.Services.WellOperationService; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; @@ -22,8 +23,14 @@ namespace ConsoleApp1 var wellOperationImportService = new WellOperationImportService(db); - var ops = wellOperationImportService.ParseFile(@"C:\temp\Миграция.xlsx"); - + //var inStream = System.IO.File.OpenRead(@"C:\temp\Миграция.xlsx"); + //wellOperationImportService.Import(1, inStream); + var stream = wellOperationImportService.Export(1); + var fs = System.IO.File.Create(@"C:\temp\2.xlsx"); + stream.CopyTo(fs); + fs.Flush(); + fs.Close(); + fs.Dispose(); Console.WriteLine("_"); } } diff --git a/ConsoleApp1/DebugWellOperationsStatService.cs b/ConsoleApp1/DebugWellOperationsStatService.cs index 35a6ee1e..bd938666 100644 --- a/ConsoleApp1/DebugWellOperationsStatService.cs +++ b/ConsoleApp1/DebugWellOperationsStatService.cs @@ -2,6 +2,7 @@ using AsbCloudDb.Model; using AsbCloudInfrastructure.Services; using AsbCloudInfrastructure.Services.Cache; +using AsbCloudInfrastructure.Services.WellOperationService; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic;