diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs index 00349a6c..c6edb30b 100644 --- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs +++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs @@ -60,16 +60,15 @@ public class WellOperationRepository : IWellOperationRepository OperationType = WellOperation.IdOperationTypePlan, }; - var entities = await BuildQuery(request) + var dtos = await BuildQuery(request) .AsNoTracking() - .ToArrayAsync(token) - .ConfigureAwait(false); + .ToArrayAsync(token); var dateLastAssosiatedPlanOperation = await GetDateLastAssosiatedPlanOperationAsync(idWell, currentDate, timezone.Hours, token); - + var result = new WellOperationPlanDto() { - WellOperationsPlan = entities, + WellOperationsPlan = dtos.Select(Convert), DateLastAssosiatedPlanOperation = dateLastAssosiatedPlanOperation }; @@ -211,7 +210,7 @@ public class WellOperationRepository : IWellOperationRepository var dtos = await query.ToArrayAsync(token); - return dtos; + return dtos.Select(Convert); } /// @@ -219,21 +218,19 @@ public class WellOperationRepository : IWellOperationRepository WellOperationRequest request, CancellationToken token) { - var query = BuildQuery(request) - .AsNoTracking(); - + var query = BuildQuery(request); + var result = new PaginationContainer { Skip = request.Skip ?? 0, Take = request.Take ?? 32, - Count = await query.CountAsync(token).ConfigureAwait(false), + Count = await query.CountAsync(token), }; - query = query - .Skip(result.Skip) - .Take(result.Take); + var dtos = await query.ToArrayAsync(token); + + result.Items = dtos.Select(Convert); - result.Items = await query.ToArrayAsync(token); return result; } @@ -384,12 +381,13 @@ public class WellOperationRepository : IWellOperationRepository /// В результате попрежнему требуется конвертировать дату /// /// + /// /// private IQueryable BuildQuery(WellOperationRequest request) { var timezone = wellService.GetTimezone(request.IdWell); - var timeZoneOffset = TimeSpan.FromHours(timezone.Hours); - + var timeZoneOffset = timezone.Hours; + var query = db.WellOperations .Include(s => s.WellSectionType) .Include(s => s.OperationCategory) @@ -413,20 +411,20 @@ public class WellOperationRepository : IWellOperationRepository if (request.GeDate.HasValue) { - var geDateOffset = request.GeDate.Value.ToUtcDateTimeOffset(timezone.Hours); + var geDateOffset = request.GeDate.Value.ToUtcDateTimeOffset(timeZoneOffset); query = query.Where(e => e.DateStart >= geDateOffset); } if (request.LtDate.HasValue) { - var ltDateOffset = request.LtDate.Value.ToUtcDateTimeOffset(timezone.Hours); + var ltDateOffset = request.LtDate.Value.ToUtcDateTimeOffset(timeZoneOffset); query = query.Where(e => e.DateStart < ltDateOffset); } var currentWellOperations = db.WellOperations .Where(subOp => subOp.IdWell == request.IdWell); - var wellOperationsWithCategoryNPT = currentWellOperations + var wellOperationsWithCategoryNpt = currentWellOperations .Where(subOp => subOp.IdType == 1) .Where(subOp => WellOperationCategory.NonProductiveTimeSubIds.Contains(subOp.IdCategory)); @@ -442,14 +440,14 @@ public class WellOperationRepository : IWellOperationRepository CategoryName = o.OperationCategory.Name, WellSectionTypeName = o.WellSectionType.Caption, - DateStart = DateTime.SpecifyKind(o.DateStart.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified), + DateStart = o.DateStart, DepthStart = o.DepthStart, DepthEnd = o.DepthEnd, DurationHours = o.DurationHours, CategoryInfo = o.CategoryInfo, Comment = o.Comment, - NptHours = wellOperationsWithCategoryNPT + NptHours = wellOperationsWithCategoryNpt .Where(subOp => subOp.DateStart <= o.DateStart) .Select(subOp => subOp.DurationHours) .Sum(), @@ -460,22 +458,39 @@ public class WellOperationRepository : IWellOperationRepository .Min(subOp => subOp.DateStart)) .TotalDays, IdUser = o.IdUser, - LastUpdateDate = DateTime.SpecifyKind(o.LastUpdateDate.UtcDateTime + timeZoneOffset, DateTimeKind.Unspecified) + LastUpdateDate = o.LastUpdateDate, }); if (request.SortFields?.Any() == true) { dtos = dtos.SortBy(request.SortFields); } - else - { - dtos = dtos - .OrderBy(e => e.DateStart) - .ThenBy(e => e.DepthEnd) - .ThenBy(e => e.Id); - } - return dtos; + dtos = dtos + .OrderBy(e => e.DateStart) + .ThenBy(e => e.DepthEnd) + .ThenBy(e => e.Id); + + if (request.Skip.HasValue) + dtos = dtos.Skip(request.Skip.Value); + + if (request.Take.HasValue) + dtos = dtos.Take(request.Take.Value); + + return dtos.AsNoTracking(); + } + + private WellOperationDto Convert(WellOperationDto dto) + { + var timezone = wellService.GetTimezone(dto.IdWell); + var timezoneOffset = TimeSpan.FromHours(timezone.Hours); + + var dtoWithRemoteDateTime = dto.Adapt(); + + dtoWithRemoteDateTime.DateStart = dto.DateStart.ToOffset(TimeSpan.FromHours(timezoneOffset.Hours)); + dtoWithRemoteDateTime.LastUpdateDate = dto.LastUpdateDate?.ToOffset(TimeSpan.FromHours(timezoneOffset.Hours)); + + return dtoWithRemoteDateTime; } public async Task RemoveDuplicates(Action onProgressCallback, CancellationToken token) diff --git a/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs index b8042275..800979e5 100644 --- a/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs +++ b/AsbCloudInfrastructure/Services/WellOperationImport/WellOperationImportService.cs @@ -5,20 +5,24 @@ using System.Linq; using AsbCloudApp.Data; using AsbCloudApp.Data.WellOperationImport; using AsbCloudApp.Repositories; +using AsbCloudApp.Services; using AsbCloudApp.Services.WellOperationImport; namespace AsbCloudInfrastructure.Services.WellOperationImport; public class WellOperationImportService : IWellOperationImportService { + private readonly IWellService wellService; private readonly IWellOperationRepository wellOperationRepository; private static readonly DateTime dateLimitMin = new(2001, 1, 1, 0, 0, 0); private static readonly DateTime dateLimitMax = new(2099, 1, 1, 0, 0, 0); private static readonly TimeSpan drillingDurationLimitMax = TimeSpan.FromDays(366); - public WellOperationImportService(IWellOperationRepository wellOperationRepository) + public WellOperationImportService(IWellService wellService, + IWellOperationRepository wellOperationRepository) { + this.wellService = wellService; this.wellOperationRepository = wellOperationRepository; } @@ -30,8 +34,12 @@ public class WellOperationImportService : IWellOperationImportService var categories = wellOperationRepository.GetCategories(false); var wellOperations = new List(); - - foreach (var row in sheet.Rows) + + var rows = sheet.Rows.OrderBy(r => r.Date); + + var prevRow = new RowDto(); + + foreach (var row in rows) { try { @@ -58,15 +66,18 @@ public class WellOperationImportService : IWellOperationImportService if (row.Date < dateLimitMin && row.Date > dateLimitMax) throw new FileFormatException( $"Лист '{sheet.Name}'. Строка '{row.Number}' неправильно получена дата начала операции"); - - if (wellOperations.LastOrDefault()?.DateStart > row.Date) + + if (prevRow.Date > row.Date) throw new FileFormatException( $"Лист '{sheet.Name}' строка '{row.Number}' дата позднее даты предыдущей операции"); if (row.Duration is not (>= 0d and <= 240d)) throw new FileFormatException($"Лист '{sheet.Name}'. Строка '{row.Number}' некорректная длительность операции"); - wellOperations.Add(new WellOperationDto + var timezone = wellService.GetTimezone(idWell); + var timezoneOffset = TimeSpan.FromHours(timezone.Hours); + + var wellOperation = new WellOperationDto { IdWell = idWell, IdUser = idUser, @@ -76,10 +87,14 @@ public class WellOperationImportService : IWellOperationImportService CategoryInfo = row.CategoryInfo, DepthStart = row.DepthStart, DepthEnd = row.DepthEnd, - DateStart = row.Date, + DateStart = new DateTimeOffset(row.Date, timezoneOffset), DurationHours = row.Duration, Comment = row.Comment - }); + }; + + wellOperations.Add(wellOperation); + + prevRow = row; } catch (FileFormatException ex) { diff --git a/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj b/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj index a9d7713a..1c641f69 100644 --- a/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj +++ b/AsbCloudWebApi.IntegrationTests/AsbCloudWebApi.IntegrationTests.csproj @@ -18,6 +18,14 @@ + + + + + + + + diff --git a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs index 07754ed1..8e2db98f 100644 --- a/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs +++ b/AsbCloudWebApi.IntegrationTests/Clients/IWellOperationClient.cs @@ -6,16 +6,22 @@ namespace AsbCloudWebApi.IntegrationTests.Clients; public interface IWellOperationClient { - private const string BaseRoute = "/api/well/{idWell}/wellOperations"; + private const string BaseRoute = "/api/well/{idWell}/wellOperations"; - [Post(BaseRoute + "/{idType}/{deleteBeforeInsert}")] - Task> InsertRangeAsync(int idWell, int idType, bool deleteBeforeInsert, [Body] IEnumerable dtos); + [Post(BaseRoute + "/{idType}/{deleteBeforeInsert}")] + Task> InsertRangeAsync(int idWell, int idType, bool deleteBeforeInsert, [Body] IEnumerable dtos); - [Put(BaseRoute + "/{idOperation}")] - Task> UpdateAsync(int idWell, int idOperation, [Body] WellOperationDto value, CancellationToken token); + [Put(BaseRoute + "/{idOperation}")] + Task> UpdateAsync(int idWell, int idOperation, [Body] WellOperationDto value, CancellationToken token); [Get(BaseRoute + "/plan")] Task>> GetPageOperationsPlanAsync(int idWell, [Query] WellOperationRequestBase request, CancellationToken token); + + [Multipart] + [Post(BaseRoute + "/import/plan/default")] + Task>> ImportPlanDefaultExcelFileAsync(int idWell, + [AliasAs("files")] IEnumerable streams, + CancellationToken token); } \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs index 46ce1d4d..a47d5f09 100644 --- a/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs +++ b/AsbCloudWebApi.IntegrationTests/Controllers/WellOperationControllerTest.cs @@ -2,7 +2,10 @@ using AsbCloudApp.Data; using AsbCloudDb.Model; using AsbCloudWebApi.IntegrationTests.Clients; using System.Net; +using System.Reflection; using AsbCloudApp.Requests; +using AsbCloudWebApi.IntegrationTests.Data; +using Refit; using Xunit; namespace AsbCloudWebApi.IntegrationTests.Controllers; @@ -28,7 +31,7 @@ public class WellOperationControllerTest : BaseIntegrationTest DepthEnd = 20.0, Day = 0.0, NptHours = 0.0, - DateStart = new DateTimeOffset(new DateTime(2023, 02, 03, 1, 0, 0, DateTimeKind.Unspecified)), + DateStart = new DateTimeOffset(new DateTime(2023, 1, 10), TimeSpan.FromHours(Defaults.Wells[0].Timezone.Hours)), DurationHours = 1.0, Comment = "1", IdUser = 1, @@ -117,4 +120,37 @@ public class WellOperationControllerTest : BaseIntegrationTest var excludeProps = new[] { nameof(WellOperationDto.Id) }; MatchHelper.Match(dto, wellOperation, excludeProps); } + + [Fact] + public async Task ImportPlanDefaultExcelFileAsync_returns_success() + { + //arrange + //TODO: вынести в метод расширения. Сделать когда доберёмся до рефакторинга операций по скважине + var resourceName = Assembly.GetExecutingAssembly() + .GetManifestResourceNames() + .FirstOrDefault(n => n.EndsWith("WellOperationsPlan.xlsx")); + + if (string.IsNullOrWhiteSpace(resourceName)) + throw new ArgumentNullException(nameof(resourceName)); + + var stream = Assembly.GetExecutingAssembly() + .GetManifestResourceStream(resourceName); + + if (stream is null) + throw new ArgumentNullException(nameof(stream)); + + var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + memoryStream.Position = 0; + + //act + var streamPart = new StreamPart(memoryStream, "WellOperations.xlsx", "application/octet-stream"); + + var response = await client.ImportPlanDefaultExcelFileAsync(idWell, new[] { streamPart }, CancellationToken.None); + + //assert + Assert.NotNull(response.Content); + Assert.Equal(4, response.Content.Count()); + Assert.True(response.Content.All(w => Math.Abs(w.DateStart.Offset.Hours - Defaults.Wells[0].Timezone.Hours) < 0.1)); + } } \ No newline at end of file diff --git a/AsbCloudWebApi.IntegrationTests/WellOperationsPlan.xlsx b/AsbCloudWebApi.IntegrationTests/WellOperationsPlan.xlsx new file mode 100644 index 00000000..dd1a2781 Binary files /dev/null and b/AsbCloudWebApi.IntegrationTests/WellOperationsPlan.xlsx differ diff --git a/AsbCloudWebApi.Tests/Services/WellOperationExport/WellOperationExportServiceTest.cs b/AsbCloudWebApi.Tests/Services/WellOperationExport/WellOperationExportServiceTest.cs index 2590df26..3d66aab9 100644 --- a/AsbCloudWebApi.Tests/Services/WellOperationExport/WellOperationExportServiceTest.cs +++ b/AsbCloudWebApi.Tests/Services/WellOperationExport/WellOperationExportServiceTest.cs @@ -117,7 +117,7 @@ namespace AsbCloudWebApi.Tests.Services.WellOperationExport wellOperationImportTemplateService = new WellOperationImportTemplateService(); wellOperationExportService = new WellOperationExportService(wellOperationRepository, wellService, wellOperationImportTemplateService); - wellOperationImportService = new WellOperationImportService(wellOperationRepository); + wellOperationImportService = new WellOperationImportService(wellService, wellOperationRepository); wellOperationDefaultExcelParser = new WellOperationDefaultExcelParser(); this.output = output; } diff --git a/AsbCloudWebApi/Controllers/WellOperationController.cs b/AsbCloudWebApi/Controllers/WellOperationController.cs index 83f2987a..0d53a4ad 100644 --- a/AsbCloudWebApi/Controllers/WellOperationController.cs +++ b/AsbCloudWebApi/Controllers/WellOperationController.cs @@ -281,7 +281,6 @@ namespace AsbCloudWebApi.Controllers foreach (var wellOperation in wellOperations) { wellOperation.IdWell = idWell; - wellOperation.LastUpdateDate = DateTimeOffset.UtcNow; wellOperation.IdUser = User.GetUserId(); wellOperation.IdType = idType; } @@ -313,7 +312,6 @@ namespace AsbCloudWebApi.Controllers value.IdWell = idWell; value.Id = idOperation; - value.LastUpdateDate = DateTimeOffset.UtcNow; value.IdUser = User.GetUserId(); var result = await operationRepository.UpdateAsync(value, token)