make WellOperations import and export. ExcelTemplate embedded to infrastructure.

This commit is contained in:
Фролов 2021-10-09 20:16:22 +05:00
parent 2868304546
commit e604d8a031
10 changed files with 246 additions and 42 deletions

View File

@ -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);
}
}

View File

@ -8,6 +8,10 @@
<NoWarn>1701;1702;IDE0090;IDE0063;IDE0066</NoWarn>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Services\WellOperationService\WellOperationImportTemplate.xltx" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ClosedXML" Version="0.95.4" />
<PackageReference Include="itext7" Version="7.1.15" />

View File

@ -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<IFileService, FileService>();
services.AddTransient<IWellOperationService, WellOperationService>();
services.AddTransient<IWellOperationsStatService, WellOperationsStatService>();
services.AddTransient<IWellOperationImportService, WellOperationImportService>();
services.AddTransient<IMeasureService, MeasureService>();
services.AddTransient<IDrillingProgramService, DrillingProgramService>();
services.AddTransient<IDrillingProgramApacheService, DrillingProgramApacheService>();

View File

@ -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<WellOperationCategory> categories = null;
public List<WellOperationCategory> Categories {
get {
public List<WellOperationCategory> 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<WellOperationDto> 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<WellOperationDto> 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<WellOperation> 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<WellOperation> 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<WellOperation> 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<WellOperationDto> 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<WellOperation>());
db.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
private IEnumerable<WellOperationDto> ParseFileStream(Stream stream)
{
using var workbook = new XLWorkbook(stream, XLEventTracking.Disabled);
return ParseWorkbook(workbook);
}
private IEnumerable<WellOperationDto> 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<WellOperationDto>();
@ -77,13 +191,12 @@ namespace AsbCloudInfrastructure.Services
private IEnumerable<WellOperationDto> 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;

View File

@ -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
{

View File

@ -10,7 +10,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace AsbCloudInfrastructure.Services
namespace AsbCloudInfrastructure.Services.WellOperationService
{
class Race
{

View File

@ -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;
}
/// <summary>
@ -170,6 +174,78 @@ namespace AsbCloudWebApi.Controllers
return Ok(result);
}
/// <summary>
/// Импортирует операции из excel (xlsx) файла
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="files">Коллекция файлов - 1 файл xlsx</param>
/// <param name="deleteWellOperationsBeforeImport">Удалить операции перед импортом, если фал валидный</param>
/// <param name="token"> Токен отмены задачи </param>
/// <returns></returns>
[HttpPost]
[Route("import")]
[ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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();
}
/// <summary>
/// Возвращает файл с диска на сервере
/// </summary>
/// <param name="idWell">id скважины</param>
/// <param name="token"> Токен отмены задачи </param>
/// <returns>Запрашиваемый файл</returns>
[HttpGet]
[Route("export")]
[ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)]
public async Task<IActionResult> 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<bool> CanUserAccessToWellAsync(int idWell, CancellationToken token = default)
{
int? idCompany = User.GetCompanyId();

View File

@ -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("_");
}
}

View File

@ -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;