diff --git a/AsbCloudApp/Services/WellOperationImport/IWellOperationImportService.cs b/AsbCloudApp/Services/WellOperationImport/IWellOperationImportService.cs index 54e813d8..f65d8888 100644 --- a/AsbCloudApp/Services/WellOperationImport/IWellOperationImportService.cs +++ b/AsbCloudApp/Services/WellOperationImport/IWellOperationImportService.cs @@ -1,5 +1,5 @@ -using System.Threading; -using System.Threading.Tasks; +using System.Collections.Generic; +using AsbCloudApp.Data; using AsbCloudApp.Data.WellOperationImport; namespace AsbCloudApp.Services.WellOperationImport; @@ -16,7 +16,5 @@ public interface IWellOperationImportService /// /// /// - /// - /// - Task ImportAsync(int idWell, int idUser, int idType, SheetDto sheet, bool deleteBeforeImport, CancellationToken cancellationToken); + IEnumerable Import(int idWell, int idUser, int idType, SheetDto sheet); } \ No newline at end of file diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/Files/WellOperationImportTemplate.xlsx b/AsbCloudInfrastructure/Services/WellOperationImport/Files/WellOperationImportTemplate.xlsx index 48e19c6c..1e89ad31 100644 Binary files a/AsbCloudInfrastructure/Services/WellOperationImport/Files/WellOperationImportTemplate.xlsx and b/AsbCloudInfrastructure/Services/WellOperationImport/Files/WellOperationImportTemplate.xlsx differ diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs index 9da293c6..bf551edc 100644 --- a/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs +++ b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs @@ -2,12 +2,9 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading; -using System.Threading.Tasks; using AsbCloudApp.Data; using AsbCloudApp.Data.WellOperationImport; using AsbCloudApp.Repositories; -using AsbCloudApp.Requests; using AsbCloudApp.Services.WellOperationImport; namespace AsbCloudInfrastructure.Services.WellOperationImport; @@ -25,14 +22,14 @@ public class WellOperationImportService : IWellOperationImportService this.wellOperationRepository = wellOperationRepository; } - public async Task ImportAsync(int idWell, int idUser, int idType, SheetDto sheet, bool deleteBeforeImport, CancellationToken cancellationToken) + public IEnumerable Import(int idWell, int idUser, int idType, SheetDto sheet) { var validationErrors = new List(); var sections = wellOperationRepository.GetSectionTypes(); var categories = wellOperationRepository.GetCategories(false); - var operations = new List(); + var wellOperations = new List(); foreach (var row in sheet.Rows) { @@ -62,14 +59,14 @@ public class WellOperationImportService : IWellOperationImportService throw new FileFormatException( $"Лист '{sheet.Name}'. Строка '{row.Number}' неправильно получена дата начала операции"); - if (operations.LastOrDefault()?.DateStart > row.Date) + if (wellOperations.LastOrDefault()?.DateStart > row.Date) throw new FileFormatException( $"Лист '{sheet.Name}' строка '{row.Number}' дата позднее даты предыдущей операции"); if (row.Duration is not (>= 0d and <= 240d)) throw new FileFormatException($"Лист '{sheet.Name}'. Строка '{row.Number}' некорректная длительность операции"); - operations.Add(new WellOperationDto + wellOperations.Add(new WellOperationDto { IdWell = idWell, IdUser = idUser, @@ -89,26 +86,12 @@ public class WellOperationImportService : IWellOperationImportService } } - if (operations.Any() && operations.Min(o => o.DateStart) - operations.Max(o => o.DateStart) > drillingDurationLimitMax) + if (wellOperations.Any() && wellOperations.Min(o => o.DateStart) - wellOperations.Max(o => o.DateStart) > drillingDurationLimitMax) validationErrors.Add($"Лист {sheet.Name} содержит диапазон дат больше {drillingDurationLimitMax}"); if (validationErrors.Any()) throw new FileFormatException(string.Join("\r\n", validationErrors)); - - if (!operations.Any()) - return; - - if (deleteBeforeImport) - { - var existingOperations = await wellOperationRepository.GetAsync(new WellOperationRequest - { - IdWell = idWell, - OperationType = idType - }, cancellationToken); - - await wellOperationRepository.DeleteAsync(existingOperations.Select(o => o.Id), cancellationToken); - } - - await wellOperationRepository.InsertRangeAsync(operations, cancellationToken); + + return wellOperations; } } \ No newline at end of file diff --git a/AsbCloudWebApi.Tests/UnitTests/Services/DailyReportServiceTest.cs b/AsbCloudWebApi.Tests/UnitTests/Services/DailyReportServiceTest.cs index 8202a95f..95008c7a 100644 --- a/AsbCloudWebApi.Tests/UnitTests/Services/DailyReportServiceTest.cs +++ b/AsbCloudWebApi.Tests/UnitTests/Services/DailyReportServiceTest.cs @@ -254,7 +254,7 @@ public class DailyReportServiceTest wellServiceMock.GetOrDefaultAsync(Arg.Any(), Arg.Any()) .ReturnsForAnyArgs(fakeWell); - trajectoryFactNnbRepositoryMock.GetAsync(Arg.Any(), Arg.Any()) + trajectoryFactNnbRepositoryMock.GetByRequestAsync(Arg.Any(), Arg.Any()) .ReturnsForAnyArgs(new[] { fakeLastFactTrajectory }); wellOperationRepositoryMock.GetAsync(Arg.Any(), Arg.Any()) diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index 54270f49..45db3f0e 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -9,8 +9,10 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; +using AsbCloudApp.Data.WellOperationImport; using AsbCloudApp.Services.WellOperationImport; using AsbCloudApp.Data.WellOperationImport.Options; using AsbCloudApp.Exceptions; @@ -204,37 +206,87 @@ namespace AsbCloudWebApi.Controllers return Ok(result); } + /// + /// Добавляет новую операцию на скважину + /// + /// Id скважины + /// Тип добавляемой операции + /// Добавляемая операция + /// + /// Количество добавленных в БД записей + [HttpPost("{idType:int}")] + [Permission] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + public async Task InsertAsync( + [Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")] int idWell, + [Range(0, 1, ErrorMessage = "Тип операции недопустим. Допустимые: 0, 1")] int idType, + WellOperationDto wellOperation, + CancellationToken cancellationToken) + { + if (!await CanUserAccessToWellAsync(idWell, cancellationToken)) + return Forbid(); + + if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) + return Forbid(); + + wellOperation.IdWell = idWell; + wellOperation.LastUpdateDate = DateTimeOffset.UtcNow; + wellOperation.IdUser = User.GetUserId(); + wellOperation.IdType = idType; + + var result = await operationRepository.InsertRangeAsync(new[] { wellOperation }, cancellationToken); + + return Ok(result); + } + /// /// Добавляет новые операции на скважине /// - /// id скважины - /// Данные о добавляемых операциях - /// Токен отмены задачи - /// Количество добавленных в БД строк - [HttpPost] + /// Id скважины + /// Добавляемые операции + /// Тип добавляемых операций + /// Удалить операции перед сохранением + /// + /// Количество добавленных в БД записей + [HttpPost("{idType:int}/{deleteBeforeInsert:bool}")] [Permission] - [ProducesResponseType(typeof(IEnumerable), (int)System.Net.HttpStatusCode.OK)] + [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] public async Task InsertRangeAsync( - [Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")] int idWell, - [FromBody] IEnumerable values, - CancellationToken token) + [Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")] int idWell, + [Range(0, 1, ErrorMessage = "Тип операции недопустим. Допустимые: 0, 1")] int idType, + bool deleteBeforeInsert, + [FromBody] IEnumerable wellOperations, + CancellationToken cancellationToken) { - if (!await CanUserAccessToWellAsync(idWell, token)) - return Forbid(); - - if (!await CanUserEditWellOperationsAsync(idWell, token)) + if (!await CanUserAccessToWellAsync(idWell, cancellationToken)) return Forbid(); - foreach (var value in values) + if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) + return Forbid(); + + if (deleteBeforeInsert && wellOperations.Any()) { - value.IdWell = idWell; - value.LastUpdateDate = DateTimeOffset.UtcNow; - value.IdUser = User.GetUserId(); + var existingOperations = await operationRepository.GetAsync(new WellOperationRequest + { + IdWell = idWell, + OperationType = idType + }, cancellationToken); + + await operationRepository.DeleteAsync(existingOperations.Select(o => o.Id), cancellationToken); + } + + foreach (var wellOperation in wellOperations) + { + wellOperation.IdWell = idWell; + wellOperation.LastUpdateDate = DateTimeOffset.UtcNow; + wellOperation.IdUser = User.GetUserId(); + wellOperation.IdType = idType; } - var result = await operationRepository.InsertRangeAsync(values, token) - .ConfigureAwait(false); - + var result = await operationRepository.InsertRangeAsync(wellOperations, cancellationToken); + return Ok(result); } @@ -299,46 +351,19 @@ namespace AsbCloudWebApi.Controllers /// id скважины /// Параметры для парсинга файла /// Коллекция из одного файла xlsx - /// Удалить операции перед импортом = 1, если файл валидный - /// + /// /// - [HttpPost("import/default/{deleteBeforeImport}")] + [HttpPost("import/default")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [Permission] - public async Task ImportDefaultExcelFileAsync(int idWell, + public Task ImportDefaultExcelFileAsync(int idWell, [FromQuery] WellOperationImportDefaultOptionsDto options, [FromForm] IFormFileCollection files, - [Range(0, 1, ErrorMessage = "Недопустимое значение. Допустимые: 0, 1")] int deleteBeforeImport, - CancellationToken token) - { - var idUser = User.GetUserId(); - - if (!idUser.HasValue) - throw new ForbidException("Неизвестный пользователь"); - - await AssertUserHasAccessToImportWellOperationsAsync(idWell, token); - - if (files.Count < 1) - return this.ValidationBadRequest(nameof(files), "Нет файла"); - - var file = files[0]; - if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") - return this.ValidationBadRequest(nameof(files), "Требуется xlsx файл."); - - using Stream stream = file.OpenReadStream(); - - try - { - var sheet = wellOperationDefaultExcelParser.Parse(stream, options); - - await wellOperationImportService.ImportAsync(idWell, idUser.Value, options.IdType, sheet, (deleteBeforeImport & 1) > 0, token); - } - catch (FileFormatException ex) - { - return this.ValidationBadRequest(nameof(files), ex.Message); - } - - return Ok(); - } + CancellationToken cancellationToken) => ImportExcelFileAsync(idWell, files, options, + (stream, _) => wellOperationDefaultExcelParser.Parse(stream, options), + cancellationToken); /// /// Импорт операций из excel (xlsx) файла. ГПНХ (Хантос) @@ -346,46 +371,19 @@ namespace AsbCloudWebApi.Controllers /// id скважины /// Параметры для парсинга файла /// Коллекция из одного файла xlsx - /// Удалить операции перед импортом = 1, если файл валидный - /// + /// /// - [HttpPost("import/gazpromKhantos/{deleteBeforeImport}")] + [HttpPost("import/gazpromKhantos")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status204NoContent)] [Permission] - public async Task ImportGazpromKhantosExcelFileAsync(int idWell, - [FromQuery] WellOperationImportGazpromKhantosOptionsDto options, - [FromForm] IFormFileCollection files, - [Range(0, 1, ErrorMessage = "Недопустимое значение. Допустимые: 0, 1")] int deleteBeforeImport, - CancellationToken token) - { - var idUser = User.GetUserId(); - - if (!idUser.HasValue) - throw new ForbidException("Неизвестный пользователь"); - - await AssertUserHasAccessToImportWellOperationsAsync(idWell, token); - - if (files.Count < 1) - return this.ValidationBadRequest(nameof(files), "Нет файла"); - - var file = files[0]; - if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") - return this.ValidationBadRequest(nameof(files), "Требуется xlsx файл."); - - using Stream stream = file.OpenReadStream(); - - try - { - var sheet = wellOperationGazpromKhantosExcelParser.Parse(stream, options); - - await wellOperationImportService.ImportAsync(idWell, idUser.Value, options.IdType, sheet, (deleteBeforeImport & 1) > 0, token); - } - catch (FileFormatException ex) - { - return this.ValidationBadRequest(nameof(files), ex.Message); - } - - return Ok(); - } + public Task ImportGazpromKhantosExcelFileAsync(int idWell, + [FromQuery] WellOperationImportGazpromKhantosOptionsDto options, + [FromForm] IFormFileCollection files, + CancellationToken cancellationToken) => ImportExcelFileAsync(idWell, files, options, + (stream, _) => wellOperationGazpromKhantosExcelParser.Parse(stream, options), + cancellationToken); /// /// Создает excel файл с операциями по скважине @@ -453,7 +451,11 @@ namespace AsbCloudWebApi.Controllers return File(stream, "application/octet-stream", fileName); } - private async Task AssertUserHasAccessToImportWellOperationsAsync(int idWell, CancellationToken token) + private async Task ImportExcelFileAsync(int idWell, [FromForm] IFormFileCollection files, + TOptions options, + Func parseMethod, + CancellationToken cancellationToken) + where TOptions : IWellOperationImportOptions { var idCompany = User.GetCompanyId(); var idUser = User.GetUserId(); @@ -461,16 +463,54 @@ namespace AsbCloudWebApi.Controllers if (!idCompany.HasValue || !idUser.HasValue) throw new ForbidException("Неизвестный пользователь"); - if (!await CanUserAccessToWellAsync(idWell, token)) + if (!await CanUserAccessToWellAsync(idWell, cancellationToken)) throw new ForbidException("Нет доступа к скважине"); - if (!await CanUserEditWellOperationsAsync(idWell, token)) + if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) throw new ForbidException("Недостаточно прав для редактирования ГГД на завершенной скважине"); - if (!await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, token)) + if (!await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, cancellationToken)) throw new ForbidException("Скважина недоступна для компании"); - } + if (files.Count < 1) + return this.ValidationBadRequest(nameof(files), "Нет файла"); + + var file = files[0]; + if (Path.GetExtension(file.FileName).ToLower() != ".xlsx") + return this.ValidationBadRequest(nameof(files), "Требуется xlsx файл."); + + using Stream stream = file.OpenReadStream(); + + try + { + var sheet = parseMethod(stream, options); + + var wellOperations = wellOperationImportService.Import(idWell, idUser.Value, options.IdType, sheet) + .OrderBy(w => w.DateStart); + + var dateStart = wellOperations.Min(w => w.DateStart); + + foreach (var wellOperation in wellOperations) + wellOperation.Day = (wellOperation.DateStart - dateStart).TotalDays; + + if (!wellOperations.Any()) + return NoContent(); + + return Ok(wellOperations); + } + catch (FileFormatException ex) + { + return this.ValidationBadRequest(nameof(files), ex.Message); + } + } + + private async Task CanUserAccessToWellAsync(int idWell, CancellationToken token) + { + int? idCompany = User.GetCompanyId(); + return idCompany is not null && await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, + idWell, token).ConfigureAwait(false); + } + private async Task CanUserEditWellOperationsAsync(int idWell, CancellationToken token) { var idUser = User.GetUserId(); @@ -485,13 +525,5 @@ namespace AsbCloudWebApi.Controllers return well.IdState != 2 || userRepository.HasPermission(idUser.Value, "WellOperation.editCompletedWell"); } - - private async Task CanUserAccessToWellAsync(int idWell, CancellationToken token) - { - int? idCompany = User.GetCompanyId(); - return idCompany is not null && await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, - idWell, token).ConfigureAwait(false); - } } - -} +} \ No newline at end of file