diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs
index d37a1e90..12246578 100644
--- a/AsbCloudApp/Repositories/IWellOperationRepository.cs
+++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs
@@ -1,9 +1,9 @@
using AsbCloudApp.Data;
+using AsbCloudApp.Data.WellOperation;
+using AsbCloudApp.Requests;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-using AsbCloudApp.Data.WellOperation;
-using AsbCloudApp.Requests;
namespace AsbCloudApp.Repositories
{
@@ -17,8 +17,8 @@ namespace AsbCloudApp.Repositories
///
///
IEnumerable GetSectionTypes();
-
- ///
+
+ ///
/// Получить страницу списка операций
///
///
@@ -26,7 +26,7 @@ namespace AsbCloudApp.Repositories
///
Task> GetAsync(WellOperationRequest request, CancellationToken token);
- ///
+ ///
/// Получить страницу списка операций
///
///
@@ -34,7 +34,7 @@ namespace AsbCloudApp.Repositories
///
Task> GetPageAsync(WellOperationRequest request, CancellationToken token);
- ///
+ ///
/// Получить статистику операции по скважине с группировкой по категориям
///
///
@@ -42,14 +42,14 @@ namespace AsbCloudApp.Repositories
///
Task> GetGroupOperationsStatAsync(WellOperationRequest request, CancellationToken token);
- ///
- /// Добавить несколько операций
- ///
- ///
- ///
- ///
- ///
- Task InsertRangeAsync(IEnumerable dtos, bool deleteBeforeInsert, CancellationToken token);
+ ///
+ /// Добавить несколько операций
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task InsertRangeAsync(IEnumerable dtos, bool deleteBeforeInsert, CancellationToken token);
///
/// Обновить существующую операцию
@@ -75,13 +75,20 @@ namespace AsbCloudApp.Repositories
///
Task> GetSectionsAsync(IEnumerable idsWells, CancellationToken token);
- ///
- /// Получить диапазон дат выполнения операций
- ///
- ///
- ///
- ///
- ///
- Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken);
- }
+ ///
+ /// Получить диапазон дат выполнения операций
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task GetDatesRangeAsync(int idWell, int idType, CancellationToken cancellationToken);
+
+ ///
+ /// Возвращает первую и последнюю фактическую операцию
+ ///
+ ///
+ ///
+ (WellOperationDto First, WellOperationDto Last)? GetFirstAndLastFact(int idWell);
+ }
}
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Repository/DepositRepository.cs b/AsbCloudInfrastructure/Repository/DepositRepository.cs
index 804d130e..497ef3b6 100644
--- a/AsbCloudInfrastructure/Repository/DepositRepository.cs
+++ b/AsbCloudInfrastructure/Repository/DepositRepository.cs
@@ -16,12 +16,12 @@ namespace AsbCloudInfrastructure.Repository
public class DepositRepository : IDepositRepository
{
private readonly IAsbCloudDbContext db;
- private readonly IWellService wellService;
+ private readonly ITelemetryService telemetryService;
- public DepositRepository(IAsbCloudDbContext db, IWellService wellService)
+ public DepositRepository(IAsbCloudDbContext db, ITelemetryService telemetryService)
{
this.db = db;
- this.wellService = wellService;
+ this.telemetryService = telemetryService;
}
///
@@ -112,8 +112,12 @@ namespace AsbCloudInfrastructure.Repository
{
var dto = well.Adapt();
dto.WellType = well.WellType.Caption;
- dto.LastTelemetryDate = wellService.GetLastTelemetryDate(well.Id)
- .ToOffset(TimeSpan.FromHours(well.Timezone.Hours));
+
+ dto.LastTelemetryDate = DateTimeOffset.MinValue;
+
+ if (well.IdTelemetry != null)
+ dto.LastTelemetryDate = telemetryService.GetDatesRange(well.IdTelemetry.Value).To;
+
dto.Cluster = gCluster.Key.Caption;
dto.Deposit = gDeposit.Key.Caption;
return dto;
diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
index 920702b1..1ab3de01 100644
--- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
+++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
@@ -20,6 +20,7 @@ namespace AsbCloudInfrastructure.Repository;
public class WellOperationRepository : CrudRepositoryBase,
IWellOperationRepository
{
+ private const string cacheKeyWellOperations = "FirstAndLastFactWellsOperations";
private readonly IMemoryCache memoryCache;
private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
private readonly IWellService wellService;
@@ -54,15 +55,15 @@ public class WellOperationRepository : CrudRepositoryBase> GetPageAsync(WellOperationRequest request, CancellationToken token)
{
- var skip = request.Skip ?? 0;
- var take = request.Take ?? 32;
+ request.Skip = request.Skip ?? 0;
+ request.Take = request.Take ?? 32;
var (items, count) = await GetWithDaysAndNpvAsync(request, token);
var paginationContainer = new PaginationContainer
{
- Skip = skip,
- Take = take,
+ Skip = request.Skip!.Value,
+ Take = request.Take!.Value,
Count = count,
Items = items
};
@@ -167,11 +168,17 @@ public class WellOperationRepository : CrudRepositoryBase UpdateRangeAsync(IEnumerable dtos, CancellationToken token)
+ public override async Task UpdateRangeAsync(IEnumerable dtos, CancellationToken token)
{
EnsureValidWellOperations(dtos);
- return base.UpdateRangeAsync(dtos, token);
+ var result = await base.UpdateRangeAsync(dtos, token);
+
+ if (result > 0)
+ memoryCache.Remove(cacheKeyWellOperations);
+
+ return result;
+
}
private static void EnsureValidWellOperations(IEnumerable dtos)
@@ -194,9 +201,6 @@ public class WellOperationRepository : CrudRepositoryBase items, int count)> GetWithDaysAndNpvAsync(WellOperationRequest request, CancellationToken token)
{
- var skip = request.Skip ?? 0;
- var take = request.Take ?? 32;
-
var entities = await GetByIdsWells(request.IdsWell, token);
var groupedByWellAndType = entities
.GroupBy(e => new { e.IdWell, e.IdType });
@@ -214,11 +218,14 @@ public class WellOperationRepository : CrudRepositoryBase filteredWellOperations = FilterByRequest(wellOperationsWithType.AsQueryable(), request);
- var filteredWellOperationsPart = filteredWellOperations
- .Skip(skip)
- .Take(take);
+ count += filteredWellOperations.Count();
- var dtos = filteredWellOperationsPart
+ if (request.Skip != null)
+ filteredWellOperations = filteredWellOperations.Skip((int)request.Skip);
+ if (request.Take != null)
+ filteredWellOperations = filteredWellOperations.Take((int)request.Take);
+
+ var dtos = filteredWellOperations
.Select(entity =>
{
var dto = Convert(entity);
@@ -230,7 +237,6 @@ public class WellOperationRepository : CrudRepositoryBase
+ {
+ entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
+ var query = dbContext.Set()
+ .Where(o => o.IdType == WellOperation.IdOperationTypeFact)
+ .GroupBy(o => o.IdWell)
+ .Select(group => new
+ {
+ IdWell = group.Key,
+ FirstFact = group.OrderBy(o => o.DateStart).First(),
+ LastFact = group.OrderBy(o => o.DateStart).Last(),
+ });
+
+ var entities = query.ToArray();
+
+ var dictionary = entities.ToDictionary(s => s.IdWell, s => (Convert(s.FirstFact), Convert(s.LastFact)));
+ entry.Value = dictionary;
+
+ return dictionary;
+
+ })!;
+
+ var firstAndLast = cachedDictionary.GetValueOrDefault(idWell);
+ return firstAndLast;
+
+ }
+
+ public override async Task DeleteAsync(int id, CancellationToken token)
+ {
+ var result = await base.DeleteAsync(id, token);
+ if (result > 0)
+ memoryCache.Remove(cacheKeyWellOperations);
+
+ return result;
+ }
+
+ public override async Task DeleteRangeAsync(IEnumerable ids, CancellationToken token)
+ {
+ var result = await base.DeleteRangeAsync(ids, token);
+ if (result > 0)
+ memoryCache.Remove(cacheKeyWellOperations);
+
+ return result;
+ }
+
protected override WellOperation Convert(WellOperationDto src)
{
var entity = src.Adapt();
diff --git a/AsbCloudInfrastructure/Services/SAUB/MessageService.cs b/AsbCloudInfrastructure/Services/SAUB/MessageService.cs
index 17d92d8d..ea6cc79e 100644
--- a/AsbCloudInfrastructure/Services/SAUB/MessageService.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/MessageService.cs
@@ -142,14 +142,13 @@ namespace AsbCloudInfrastructure.Services.SAUB
return Task.CompletedTask;
var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid);
- var timezone = telemetryService.GetTimezone(telemetry.Id);
-
+
foreach (var dto in dtos)
{
var entity = dto.Adapt();
entity.Id = 0;
entity.IdTelemetry = telemetry.Id;
- entity.DateTime = dto.Date.ToOffset(TimeSpan.FromHours(timezone.Hours));
+ entity.DateTime = dto.Date.ToUniversalTime();
db.TelemetryMessages.Add(entity);
}
diff --git a/AsbCloudInfrastructure/Services/WellInfoService.cs b/AsbCloudInfrastructure/Services/WellInfoService.cs
index 23054a2d..5d5f3bf3 100644
--- a/AsbCloudInfrastructure/Services/WellInfoService.cs
+++ b/AsbCloudInfrastructure/Services/WellInfoService.cs
@@ -37,7 +37,8 @@ public class WellInfoService
var telemetryDataSaubCache = services.GetRequiredService>();
var messageHub = services.GetRequiredService>();
- var wells = await wellService.GetAllAsync(token);
+ var entries = await wellService.GetAllAsync(token);
+ var wells = entries.ToList();
var activeWells = wells.Where(well => well.IdState == 1);
var wellsIds = activeWells.Select(w => w.Id);
diff --git a/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs b/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs
index 430bf92f..66e52fd8 100644
--- a/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs
+++ b/AsbCloudInfrastructure/Services/WellOperations/WellOperationExport.cs
@@ -42,7 +42,7 @@ public class WellOperationExport : ExcelExportService well.Id == idWell);
if (wellInfo is null)
return well.Adapt();
-
+
wellInfo.IdState = well.IdState;
return wellInfo;
}
@@ -153,7 +153,7 @@ namespace AsbCloudInfrastructure.Services
{
if (IsTelemetryAssignedToDifferentWell(dto))
throw new ArgumentInvalidException(nameof(dto), "Телеметрия уже была привязана к другой скважине.");
-
+
if (dto.Id != 0 && (await GetCacheAsync(token)).Any(w => w.Id == dto.Id))
throw new ArgumentInvalidException(nameof(dto), $"Нельзя повторно добавить скважину с id: {dto.Id}");
@@ -177,12 +177,12 @@ namespace AsbCloudInfrastructure.Services
throw new NotImplementedException();
}
- public override async Task UpdateAsync(WellDto dto,
+ public override async Task UpdateAsync(WellDto dto,
CancellationToken token)
{
if (IsTelemetryAssignedToDifferentWell(dto))
throw new ArgumentInvalidException(nameof(dto), "Телеметрия уже была привязана к другой скважине.");
-
+
var oldRelations = (await GetCacheRelationCompanyWellAsync(token))
.Where(r => r.IdWell == dto.Id).ToArray();
@@ -192,16 +192,16 @@ namespace AsbCloudInfrastructure.Services
dbContext.RelationCompaniesWells
.RemoveRange(dbContext.RelationCompaniesWells
.Where(r => r.IdWell == dto.Id));
-
+
DropCacheRelationCompanyWell();
var newRelations = dto.Companies
.Select(c => new RelationCompanyWell
{
- IdWell = dto.Id,
+ IdWell = dto.Id,
IdCompany = c.Id
});
-
+
dbContext.RelationCompaniesWells.AddRange(newRelations);
}
@@ -215,7 +215,7 @@ namespace AsbCloudInfrastructure.Services
public async Task GetWellCaptionByIdAsync(int idWell, CancellationToken token)
{
- var entity = await GetOrDefaultAsync(idWell, token).ConfigureAwait(false);
+ var entity = await GetOrDefaultAsync(idWell, token).ConfigureAwait(false);
return entity!.Caption;
}
@@ -272,10 +272,9 @@ namespace AsbCloudInfrastructure.Services
if (entity.Timezone is null)
dto.Timezone = GetTimezone(entity.Id);
-
- dto.StartDate = dbContext.WellOperations.Where(e => e.IdType == WellOperation.IdOperationTypeFact)
- .AsNoTracking()
- .MinOrDefault(e => e.DateStart)?.ToRemoteDateTime(dto.Timezone.Hours);
+
+ dto.StartDate = wellOperationRepository
+ .GetFirstAndLastFact(entity.Id)?.First?.DateStart;
dto.WellType = entity.WellType.Caption;
dto.Cluster = entity.Cluster.Caption;
dto.Deposit = entity.Cluster.Deposit.Caption;
diff --git a/AsbCloudWebApi.IntegrationTests/Clients/ITelemetryControllerClient.cs b/AsbCloudWebApi.IntegrationTests/Clients/ITelemetryControllerClient.cs
new file mode 100644
index 00000000..94f4293e
--- /dev/null
+++ b/AsbCloudWebApi.IntegrationTests/Clients/ITelemetryControllerClient.cs
@@ -0,0 +1,23 @@
+using AsbCloudApp.Data.SAUB;
+using Refit;
+
+namespace AsbCloudWebApi.IntegrationTests.Clients;
+public interface ITelemetryControllerClient
+{
+ private const string BaseRoute = "/api/telemetry";
+
+ [Get($"{BaseRoute}/Active")]
+ Task GetTelemetriesInfoByLastData(CancellationToken token);
+
+ [Post($"{BaseRoute}/{{uid}}/info")]
+ Task PostInfoAsync(string uid, [Body] TelemetryInfoDto info, CancellationToken token);
+
+ [Post($"{BaseRoute}/{{uid}}/message")]
+ Task PostMessagesAsync(string uid, [Body] IEnumerable dtos, CancellationToken token);
+
+ [Post($"{BaseRoute}/{{uid}}/event")]
+ Task PostEventsAsync(string uid, [Body] IEnumerable dtos, CancellationToken token);
+
+ [Post($"{BaseRoute}/{{uid}}/user")]
+ Task PostUsersAsync(string uid, [Body] IEnumerable dtos, CancellationToken token);
+}
diff --git a/AsbCloudWebApi.IntegrationTests/Controllers/TelemetryControllerTest.cs b/AsbCloudWebApi.IntegrationTests/Controllers/TelemetryControllerTest.cs
new file mode 100644
index 00000000..e8abd06f
--- /dev/null
+++ b/AsbCloudWebApi.IntegrationTests/Controllers/TelemetryControllerTest.cs
@@ -0,0 +1,109 @@
+using AsbCloudApp.Data.SAUB;
+using AsbCloudDb.Model;
+using AsbCloudWebApi.IntegrationTests.Clients;
+using Xunit;
+
+namespace AsbCloudWebApi.IntegrationTests.Controllers;
+public class TelemetryControllerTest : BaseIntegrationTest
+{
+ private ITelemetryControllerClient client;
+ static readonly string uid = DateTime.UtcNow.ToString("yyyyMMdd_HHmmssfff");
+
+ private static readonly SimpleTimezone timezone = new() {TimezoneId = "a", Hours = 5 };
+ private static readonly Telemetry telemetry = new Telemetry() {Id = 1, RemoteUid = uid, TimeZone = timezone, Info = new() };
+ private readonly IEnumerable events = [new() { Id = 1, EventType = 1, IdCategory = 1, IdSound = 1, Message = "there is no spoon {tag1}", Tag = "tag1" }];
+ private readonly IEnumerable users = [new TelemetryUserDto() { Id = 1, Level = 0, Name = "Neo", Patronymic = "Kianovich", Surname = "Theone" }];
+ private readonly IEnumerable messages = [new TelemetryMessageDto() { Id = 100, IdEvent = 1, WellDepth = 5, Date = DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(5)), Arg0 = "3.14", IdTelemetryUser = 1 }];
+ private readonly IEnumerable telemetryDataSaubEntities = [new TelemetryDataSaub()
+ {
+ IdTelemetry = telemetry.Id,
+ DateTime = DateTimeOffset.UtcNow,
+ AxialLoad = 2,
+ WellDepth = 5,
+ BitDepth = 5,
+ BlockPosition = 5,
+ BlockSpeed = 5,
+ }];
+
+ public TelemetryControllerTest(WebAppFactoryFixture factory)
+ : base(factory)
+ {
+ client = factory.GetAuthorizedHttpClient(string.Empty);
+ }
+
+ [Fact]
+ public async Task GetTelemetriesInfoByLastData()
+ {
+ // Arrange
+ dbContext.CleanupDbSet();
+ dbContext.Set().Add(telemetry);
+ dbContext.Set().AddRange(telemetryDataSaubEntities);
+ dbContext.SaveChanges();
+
+ // Act
+ var response = await client.GetTelemetriesInfoByLastData(CancellationToken.None);
+
+ // Assert
+ Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task PostUsersAsync()
+ {
+ // arrange
+ dbContext.CleanupDbSet();
+
+ // act
+ var response = await client.PostUsersAsync(uid, users, CancellationToken.None);
+
+ // Assert
+ Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
+ var telemetriesCount = dbContext.Set().Count();
+ var telemetryUserCount = dbContext.Set().Count();
+
+ Assert.Equal(1, telemetriesCount);
+ Assert.Equal(1, telemetryUserCount);
+ }
+
+ [Fact]
+ public async Task PostEventsAsync()
+ {
+ // arrange
+ dbContext.CleanupDbSet();
+
+ // act
+ var response = await client.PostEventsAsync(uid, events, CancellationToken.None);
+
+ // Assert
+ Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
+ var telemetriesCount = dbContext.Set().Count();
+ var telemetryEventCount = dbContext.Set().Count();
+
+ Assert.Equal(1, telemetriesCount);
+ Assert.Equal(1, telemetryEventCount);
+ }
+
+ [Fact]
+ public async Task PostMessagesAsync()
+ {
+ // arrange
+ dbContext.CleanupDbSet();
+
+ // act
+ _ = await client.PostEventsAsync(uid, events, CancellationToken.None);
+ _ = await client.PostUsersAsync(uid, users, CancellationToken.None);
+ var response = await client.PostMessagesAsync(uid, messages, CancellationToken.None);
+
+ // Assert
+ Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
+ var telemetriesCount = dbContext.Set().Count();
+ var telemetryEventCount = dbContext.Set().Count();
+ var telemetryUserCount = dbContext.Set().Count();
+ var telemetryMessageCount = dbContext.Set().Count();
+
+ Assert.Equal(1, telemetriesCount);
+ Assert.Equal(1, telemetryEventCount);
+ Assert.Equal(1, telemetryUserCount);
+ Assert.Equal(1, telemetryMessageCount);
+ }
+}
diff --git a/AsbCloudWebApi.Tests/Background/WorkTest.cs b/AsbCloudWebApi.Tests/Background/WorkTest.cs
index 97a0c6c2..75628370 100644
--- a/AsbCloudWebApi.Tests/Background/WorkTest.cs
+++ b/AsbCloudWebApi.Tests/Background/WorkTest.cs
@@ -21,7 +21,7 @@ public class WorkTest
((ISupportRequiredService)serviceProviderMock).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactoryMock);
}
- [Fact]
+ [Fact, MethodImpl(MethodImplOptions.NoOptimization)]
public async Task Start_ShouldReturn_Success()
{
//arrange
@@ -50,7 +50,7 @@ public class WorkTest
Assert.InRange(lastState.ExecutionTime, TimeSpan.Zero, executionTime);
}
- [Fact]
+ [Fact, MethodImpl(MethodImplOptions.NoOptimization)]
public async Task ExecutionWork_Invokes_Callback()
{
//arrange