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 00000000..0197218c
Binary files /dev/null and b/AsbCloudInfrastructure/Services/WellOperationService/WellOperationImportTemplate.xltx differ
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;