using AsbCloudApp.Data; using AsbCloudApp.Data.WellOperationImport; using AsbCloudApp.Data.WellOperationImport.Options; using AsbCloudApp.Exceptions; using AsbCloudApp.Repositories; using AsbCloudApp.Requests; using AsbCloudApp.Services; using AsbCloudApp.Services.WellOperationImport; using AsbCloudDb.Model; using AsbCloudInfrastructure; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudWebApi.Controllers { /// <summary> /// Буровые операции (вводимые вручную) /// </summary> [Route("api/well/{idWell}/wellOperations")] [ApiController] [Authorize] public class WellOperationController : ControllerBase { private readonly IWellOperationRepository operationRepository; private readonly IWellService wellService; private readonly IWellOperationExportService wellOperationExportService; private readonly IWellOperationImportTemplateService wellOperationImportTemplateService; private readonly IWellOperationImportService wellOperationImportService; private readonly IWellOperationCategoryRepository wellOperationCategoryRepository; private readonly IWellOperationExcelParser<WellOperationImportDefaultOptionsDto> wellOperationDefaultExcelParser; private readonly IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto> wellOperationGazpromKhantosExcelParser; private readonly IUserRepository userRepository; public WellOperationController(IWellOperationRepository operationRepository, IWellService wellService, IWellOperationImportTemplateService wellOperationImportTemplateService, IWellOperationExportService wellOperationExportService, IWellOperationImportService wellOperationImportService, IWellOperationCategoryRepository wellOperationCategoryRepository, IWellOperationExcelParser<WellOperationImportDefaultOptionsDto> wellOperationDefaultExcelParser, IWellOperationExcelParser<WellOperationImportGazpromKhantosOptionsDto> wellOperationGazpromKhantosExcelParser, IUserRepository userRepository) { this.operationRepository = operationRepository; this.wellService = wellService; this.wellOperationImportTemplateService = wellOperationImportTemplateService; this.wellOperationExportService = wellOperationExportService; this.wellOperationImportService = wellOperationImportService; this.wellOperationCategoryRepository = wellOperationCategoryRepository; this.wellOperationDefaultExcelParser = wellOperationDefaultExcelParser; this.wellOperationGazpromKhantosExcelParser = wellOperationGazpromKhantosExcelParser; this.userRepository = userRepository; } /// <summary> /// Возвращает словарь типов секций /// </summary> /// <returns></returns> [HttpGet("sectionTypes")] [Permission] [ProducesResponseType(typeof(IEnumerable<WellSectionTypeDto>), (int)System.Net.HttpStatusCode.OK)] public IActionResult GetSectionTypes() { var result = operationRepository.GetSectionTypes(); return Ok(result); } /// <summary> /// Возвращает список имен типов операций на скважине /// </summary> /// <param name="includeParents">флаг, нужно ли включать родителей в список</param> /// <returns></returns> [HttpGet("categories")] [Permission] [ProducesResponseType(typeof(IEnumerable<WellOperationCategoryDto>), (int)System.Net.HttpStatusCode.OK)] public IActionResult GetCategories(bool includeParents = true) { var result = wellOperationCategoryRepository.Get(includeParents); return Ok(result); } /// <summary> /// Возвращает список плановых операций для сопоставления /// </summary> /// <param name="idWell">id скважины</param> /// <param name="currentDate">дата для нахождения последней сопоставленной плановой операции</param> /// <param name="token"></param> /// <returns></returns> [HttpGet("operationsPlan")] [ProducesResponseType(typeof(WellOperationPlanDto), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> GetOperationsPlanAsync( [FromRoute] int idWell, [FromQuery] DateTime currentDate, CancellationToken token) { if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) return Forbid(); var result = await operationRepository .GetOperationsPlanAsync(idWell, currentDate, token) .ConfigureAwait(false); return Ok(result); } /// <summary> /// Отфильтрованный список фактических операций на скважине. /// Если не применять фильтр, то вернется весь список. Сортированный по глубине затем по дате /// </summary> /// <param name="idWell">id скважины</param> /// <param name="request"></param> /// <param name="token"></param> /// <returns>Список операций на скважине</returns> [HttpGet("fact")] [Permission] [ProducesResponseType(typeof(IEnumerable<WellOperationDto>), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> GetPageOperationsFactAsync( [FromRoute] int idWell, [FromQuery] WellOperationRequestBase request, CancellationToken token) { if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) return Forbid(); var requestToService = new WellOperationRequest(request, idWell); var result = await operationRepository.GetAsync( requestToService, token) .ConfigureAwait(false); return Ok(result); } /// <summary> /// Отфильтрованный список плановых операций на скважине. /// Если не применять фильтр, то вернется весь список. Сортированный по глубине затем по дате /// </summary> /// <param name="idWell">id скважины</param> /// <param name="request"></param> /// <param name="token"></param> /// <returns>Список операций на скважине в контейнере для постраничного просмотра</returns> [HttpGet("plan")] [Permission] [ProducesResponseType(typeof(PaginationContainer<WellOperationDto>), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> GetPageOperationsPlanAsync( [FromRoute] int idWell, [FromQuery] WellOperationRequestBase request, CancellationToken token) { if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) return Forbid(); var requestToService = new WellOperationRequest(request, idWell); var result = await operationRepository.GetPageAsync( requestToService, token) .ConfigureAwait(false); return Ok(result); } /// <summary> /// Статистика операций по скважине, группированная по категориям /// </summary> /// <param name="idWell">id скважины</param> /// <param name="request"></param> /// <param name="token"></param> /// <returns></returns> [HttpGet("groupStat")] [Permission] [ProducesResponseType(typeof(IEnumerable<WellGroupOpertionDto>), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> GetGroupOperationsAsync( [FromRoute] int idWell, [FromQuery] WellOperationRequestBase request, CancellationToken token) { if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) return Forbid(); var requestToService = new WellOperationRequest(request, idWell); var result = await operationRepository.GetGroupOperationsStatAsync( requestToService, token) .ConfigureAwait(false); return Ok(result); } /// <summary> /// Возвращает нужную операцию на скважине /// </summary> /// <param name="idWell">id скважины</param> /// <param name="idOperation">id нужной операции</param> /// <param name="token">Токен отмены задачи</param> /// <returns>Нужную операцию на скважине</returns> [HttpGet("{idOperation}")] [Permission] [ProducesResponseType(typeof(WellOperationDto), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> GetOrDefaultAsync(int idWell, int idOperation, CancellationToken token) { if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false)) return Forbid(); var result = await operationRepository.GetOrDefaultAsync(idOperation, token).ConfigureAwait(false); return Ok(result); } /// <summary> /// Добавляет новую операцию на скважину /// </summary> /// <param name="idWell">Id скважины</param> /// <param name="idType">Тип добавляемой операции</param> /// <param name="wellOperation">Добавляемая операция</param> /// <param name="cancellationToken"></param> /// <returns>Количество добавленных в БД записей</returns> [HttpPost("{idType:int}")] [Permission] [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] public async Task<IActionResult> 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.IdUser = User.GetUserId(); wellOperation.IdType = idType; var result = await operationRepository.InsertRangeAsync(new[] { wellOperation }, cancellationToken); return Ok(result); } /// <summary> /// Добавляет новые операции на скважине /// </summary> /// <param name="idWell">Id скважины</param> /// <param name="wellOperations">Добавляемые операции</param> /// <param name="idType">Тип добавляемых операций</param> /// <param name="deleteBeforeInsert">Удалить операции перед сохранением</param> /// <param name="cancellationToken"></param> /// <returns>Количество добавленных в БД записей</returns> [HttpPost("{idType:int}/{deleteBeforeInsert:bool}")] [Permission] [ProducesResponseType(typeof(int), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] public async Task<IActionResult> InsertRangeAsync( [Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")] int idWell, [Range(0, 1, ErrorMessage = "Тип операции недопустим. Допустимые: 0, 1")] int idType, bool deleteBeforeInsert, [FromBody] IEnumerable<WellOperationDto> wellOperations, CancellationToken cancellationToken) { if (!await CanUserAccessToWellAsync(idWell, cancellationToken)) return Forbid(); if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) return Forbid(); if (deleteBeforeInsert) { 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.IdUser = User.GetUserId(); wellOperation.IdType = idType; } var result = await operationRepository.InsertRangeAsync(wellOperations, cancellationToken); return Ok(result); } /// <summary> /// Обновляет выбранную операцию на скважине /// </summary> /// <param name="idWell">id скважины</param> /// <param name="idOperation">id выбранной операции</param> /// <param name="value">Новые данные для выбранной операции</param> /// <param name="token">Токен отмены задачи</param> /// <returns>Количество обновленных в БД строк</returns> [HttpPut("{idOperation}")] [Permission] [ProducesResponseType(typeof(WellOperationDto), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> UpdateAsync(int idWell, int idOperation, [FromBody] WellOperationDto value, CancellationToken token) { if (!await CanUserAccessToWellAsync(idWell, token)) return Forbid(); if (!await CanUserEditWellOperationsAsync(idWell, token)) return Forbid(); value.IdWell = idWell; value.Id = idOperation; value.IdUser = User.GetUserId(); var result = await operationRepository.UpdateAsync(value, token) .ConfigureAwait(false); return Ok(result); } /// <summary> /// Удаляет выбранную операцию на скважине /// </summary> /// <param name="idWell">id скважины</param> /// <param name="idOperation">id выбранной операции</param> /// <param name="token">Токен отмены задачи</param> /// <returns>Количество удаленных из БД строк</returns> [HttpDelete("{idOperation}")] [Permission] [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> DeleteAsync(int idWell, int idOperation, CancellationToken token) { if (!await CanUserAccessToWellAsync(idWell, token)) return Forbid(); if (!await CanUserEditWellOperationsAsync(idWell, token)) return Forbid(); var result = await operationRepository.DeleteAsync(new int[] { idOperation }, token) .ConfigureAwait(false); return Ok(result); } /// <summary> /// Импорт фактических операций из excel (xlsx) файла. Стандартный заполненный шаблон /// </summary> /// <param name="idWell">id скважины</param> /// <param name="files">Коллекция из одного файла xlsx</param> /// <param name="deleteBeforeInsert">Удалить операции перед сохранением</param> /// <param name="cancellationToken"></param> /// <returns></returns> [HttpPost("import/fact/default/{deleteBeforeInsert:bool}")] [ProducesResponseType(typeof(IEnumerable<WellOperationDto>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [Permission] public Task<IActionResult> ImportFactDefaultExcelFileAsync(int idWell, [FromForm] IFormFileCollection files, bool deleteBeforeInsert, CancellationToken cancellationToken) { var options = new WellOperationImportDefaultOptionsDto { IdType = WellOperation.IdOperationTypeFact }; return ImportExcelFileAsync(idWell, files, options, (stream, _) => wellOperationDefaultExcelParser.Parse(stream, options), deleteBeforeInsert, cancellationToken); } /// <summary> /// Импорт плановых операций из excel (xlsx) файла. Стандартный заполненный шаблон /// </summary> /// <param name="idWell">id скважины</param> /// <param name="files">Коллекция из одного файла xlsx</param> /// <param name="cancellationToken"></param> /// <returns></returns> [HttpPost("import/plan/default")] [ProducesResponseType(typeof(IEnumerable<WellOperationDto>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [Permission] public Task<IActionResult> ImportPlanDefaultExcelFileAsync(int idWell, [FromForm] IFormFileCollection files, CancellationToken cancellationToken) { var options = new WellOperationImportDefaultOptionsDto { IdType = WellOperation.IdOperationTypePlan }; return ImportExcelFileAsync(idWell, files, options, (stream, _) => wellOperationDefaultExcelParser.Parse(stream, options), null, cancellationToken); } /// <summary> /// Импорт операций из excel (xlsx) файла. ГПНХ (Хантос) /// </summary> /// <param name="idWell">Id скважины</param> /// <param name="options">Параметры парсинга</param> /// <param name="files">Коллекция из одного файла xlsx</param> /// <param name="cancellationToken"></param> /// <returns></returns> [HttpPost("import/plan/gazpromKhantos")] [ProducesResponseType(typeof(IEnumerable<WellOperationDto>), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] [Permission] public Task<IActionResult> ImportPlanGazpromKhantosExcelFileAsync(int idWell, [FromQuery] WellOperationImportGazpromKhantosOptionsDto options, [FromForm] IFormFileCollection files, CancellationToken cancellationToken) { options.IdType = WellOperation.IdOperationTypePlan; return ImportExcelFileAsync(idWell, files, options, (stream, _) => wellOperationGazpromKhantosExcelParser.Parse(stream, options), null, cancellationToken); } /// <summary> /// Создает excel файл с операциями по скважине /// </summary> /// <param name="idWell">id скважины</param> /// <param name="token">Токен отмены задачи </param> /// <returns>Запрашиваемый файл</returns> [HttpGet("export")] [Permission] [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task<IActionResult> ExportAsync([FromRoute] int idWell, CancellationToken token) { int? idCompany = User.GetCompanyId(); if (idCompany is null) return Forbid(); if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, idWell, token).ConfigureAwait(false)) return Forbid(); var stream = await wellOperationExportService.ExportAsync(idWell, token); var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_operations.xlsx"; return File(stream, "application/octet-stream", fileName); } /// <summary> /// Создает excel файл с "сетевым графиком" /// </summary> /// <param name="idWell">id скважины</param> /// <param name="scheduleReportService"></param> /// <param name="token"> Токен отмены задачи</param> /// <returns>Запрашиваемый файл</returns> [HttpGet("scheduleReport")] [Permission] [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> ScheduleReportAsync([FromRoute] int idWell, [FromServices] IScheduleReportService scheduleReportService, CancellationToken token) { int? idCompany = User.GetCompanyId(); if (idCompany is null) return Forbid(); if (!await wellService.IsCompanyInvolvedInWellAsync((int)idCompany, idWell, token).ConfigureAwait(false)) return Forbid(); var stream = await scheduleReportService.MakeReportAsync(idWell, token); var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_ScheduleReport.xlsx"; return File(stream, "application/octet-stream", fileName); } /// <summary> /// Удаляет полые дубликаты операций /// </summary> /// <param name="token"></param> /// <returns></returns> [HttpPost("/api/well/wellOperations/RemoveDuplicates")] [Permission] [Obsolete] [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> RemoveDuplicates(CancellationToken token) { var result = await operationRepository.RemoveDuplicates((_, _) => { }, token); return Ok(result); } /// <summary> /// Удаляет полностью пересекающиеся операции или "подрезает" более поздние их по глубине и дате. /// </summary> /// <param name="geDate"></param> /// <param name="leDate"></param> /// <param name="token"></param> /// <returns></returns> [HttpPost("/api/well/wellOperations/TrimOverlapping")] [Permission] [Obsolete] [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)] public async Task<IActionResult> TrimOverlapping(DateTimeOffset? geDate, DateTimeOffset leDate, CancellationToken token) { var result = await operationRepository.TrimOverlapping(geDate, leDate, (_, _) => { }, token); return Ok(result); } /// <summary> /// Возвращает шаблон файла импорта /// </summary> /// <returns>Запрашиваемый файл</returns> [HttpGet("template")] [AllowAnonymous] [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")] public IActionResult GetTemplate() { var stream = wellOperationImportTemplateService.GetExcelTemplateStream(); var fileName = "ЕЦП_шаблон_файла_операций.xlsx"; return File(stream, "application/octet-stream", fileName); } //TODO: deleteBeforeInsert тоже быстрый костыль private async Task<IActionResult> ImportExcelFileAsync<TOptions>(int idWell, [FromForm] IFormFileCollection files, TOptions options, Func<Stream, TOptions, SheetDto> parseMethod, bool? deleteBeforeInsert, CancellationToken cancellationToken) where TOptions : IWellOperationImportOptions { var idCompany = User.GetCompanyId(); var idUser = User.GetUserId(); if (!idCompany.HasValue || !idUser.HasValue) throw new ForbidException("Неизвестный пользователь"); if (!await CanUserAccessToWellAsync(idWell, cancellationToken)) throw new ForbidException("Нет доступа к скважине"); if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken)) throw new ForbidException("Недостаточно прав для редактирования ГГД на завершенной скважине"); 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.MinOrDefault(w => w.DateStart); foreach (var wellOperation in wellOperations) { if (dateStart.HasValue) wellOperation.Day = (wellOperation.DateStart - dateStart.Value).TotalDays; } //TODO: очень быстрый костыль if (deleteBeforeInsert is not null && options.IdType == WellOperation.IdOperationTypeFact) { return await InsertRangeAsync(idWell, options.IdType, deleteBeforeInsert.Value, wellOperations, cancellationToken); } return Ok(wellOperations); } catch (FileFormatException ex) { return this.ValidationBadRequest(nameof(files), ex.Message); } } private async Task<bool> 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<bool> CanUserEditWellOperationsAsync(int idWell, CancellationToken token) { var idUser = User.GetUserId(); if (!idUser.HasValue) return false; var well = await wellService.GetOrDefaultAsync(idWell, token); if (well is null) return false; return well.IdState != 2 || userRepository.HasPermission(idUser.Value, "WellOperation.editCompletedWell"); } } }