From 153f5894adcda5792854a26ec179aa35a5fc6eee Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Mon, 25 Nov 2024 13:49:07 +0500
Subject: [PATCH 01/12] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?=
 =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5?=
 =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=85=D0=BD=D0=BE=D0=BB=D0=BE?=
 =?UTF-8?q?=D0=B3=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D1=85=20=D1=81=D0=BE?=
 =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Controllers/TechMessagesController.cs     | 44 ++++++++++++++++
 .../PersistenceDbContext.cs                   |  5 +-
 Persistence.Database/Entity/TechMessage.cs    | 30 +++++++++++
 .../Model/IPersistenceDbContext.cs            |  2 +
 .../Repositories/TechMessagesRepository.cs    | 51 +++++++++++++++++++
 Persistence/API/ITechMessages.cs              | 35 +++++++++++++
 Persistence/Models/TechMessageDto.cs          | 40 +++++++++++++++
 .../Repositories/ITechMessagesRepository.cs   | 34 +++++++++++++
 8 files changed, 240 insertions(+), 1 deletion(-)
 create mode 100644 Persistence.API/Controllers/TechMessagesController.cs
 create mode 100644 Persistence.Database/Entity/TechMessage.cs
 create mode 100644 Persistence.Repository/Repositories/TechMessagesRepository.cs
 create mode 100644 Persistence/API/ITechMessages.cs
 create mode 100644 Persistence/Models/TechMessageDto.cs
 create mode 100644 Persistence/Repositories/ITechMessagesRepository.cs

diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs
new file mode 100644
index 0000000..0eb183d
--- /dev/null
+++ b/Persistence.API/Controllers/TechMessagesController.cs
@@ -0,0 +1,44 @@
+using Microsoft.AspNetCore.Mvc;
+using Persistence.Models;
+using Persistence.Repositories;
+
+namespace Persistence.API.Controllers
+{
+	[ApiController]
+	[Route("api/[controller]")]
+	public class TechMessagesController : ControllerBase, ITechMessages
+	{
+		private readonly ITechMessagesRepository techMessagesRepository;
+
+		public TechMessagesController(ITechMessagesRepository techMessagesRepository)
+		{
+			this.techMessagesRepository = techMessagesRepository;
+		}
+
+		public Task<ActionResult<PaginationContainer<TechMessageDto>>> GetPage(RequestDto request, CancellationToken token)
+		{
+			throw new NotImplementedException();
+		}
+
+		public async Task<ActionResult<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
+		{
+			var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token);
+
+			return Ok(result);
+		}
+
+		public async Task<ActionResult<IEnumerable<string>>> GetSystems(CancellationToken token)
+		{
+			var result = await techMessagesRepository.GetSystems(token);
+
+			return Ok(result);
+		}
+
+		public async Task<ActionResult<int>> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
+		{
+			var result = await techMessagesRepository.InsertRange(dtos, token);
+
+			return Ok(result);
+		}
+	}
+}
diff --git a/Persistence.Database.Postgres/PersistenceDbContext.cs b/Persistence.Database.Postgres/PersistenceDbContext.cs
index 58d201c..75fd28a 100644
--- a/Persistence.Database.Postgres/PersistenceDbContext.cs
+++ b/Persistence.Database.Postgres/PersistenceDbContext.cs
@@ -1,4 +1,5 @@
 using Microsoft.EntityFrameworkCore;
+using Persistence.Database.Entity;
 using System.Data.Common;
 
 namespace Persistence.Database.Model;
@@ -8,7 +9,9 @@ public partial class PersistenceDbContext : DbContext, IPersistenceDbContext
 
 	public DbSet<Setpoint> Setpoint => Set<Setpoint>();
 
-    public PersistenceDbContext()
+	public DbSet<TechMessage> TechMessage => Set<TechMessage>();
+
+	public PersistenceDbContext()
         : base()
     {
 
diff --git a/Persistence.Database/Entity/TechMessage.cs b/Persistence.Database/Entity/TechMessage.cs
new file mode 100644
index 0000000..b4135c1
--- /dev/null
+++ b/Persistence.Database/Entity/TechMessage.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Microsoft.EntityFrameworkCore;
+
+namespace Persistence.Database.Entity
+{
+	public class TechMessage
+	{
+		[Key, Comment("Id события")]
+		public Guid EventId { get; set; }
+
+		[Comment("Id Категории важности")]
+		public int ImportantId { get; set; }
+
+		[Comment("Дата возникновения")]
+		public DateTimeOffset OccurrenceDate { get; set; }
+
+		[Comment("Глубина забоя")]
+		public double? Depth { get; set; }
+
+		[Column(TypeName = "varchar(512)"), Comment("Текст сообщения")]
+		public string? MessageText { get; set; }
+
+		[Column(TypeName = "varchar(256)"), Comment("Система автобурения, к которой относится сообщение")]
+		public string? AutoDrillingSystem { get; set; }
+
+		[Comment("Id пользователя за пультом бурильщика")]
+		public Guid UserId { get; set; }
+	}
+}
diff --git a/Persistence.Database/Model/IPersistenceDbContext.cs b/Persistence.Database/Model/IPersistenceDbContext.cs
index 2c1aebb..759c4a2 100644
--- a/Persistence.Database/Model/IPersistenceDbContext.cs
+++ b/Persistence.Database/Model/IPersistenceDbContext.cs
@@ -1,5 +1,6 @@
 using Microsoft.EntityFrameworkCore;
 using Microsoft.EntityFrameworkCore.Infrastructure;
+using Persistence.Database.Entity;
 using System;
 using System.Collections.Generic;
 using System.Diagnostics.Metrics;
@@ -12,6 +13,7 @@ public interface IPersistenceDbContext : IDisposable
 {
     DbSet<DataSaub> DataSaub { get; }
 	DbSet<Setpoint> Setpoint { get; }
+	DbSet<TechMessage> TechMessage { get; }
 	DatabaseFacade Database { get; }
     Task<int> SaveChangesAsync(CancellationToken cancellationToken);
 }
diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs
new file mode 100644
index 0000000..e7a37bc
--- /dev/null
+++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs
@@ -0,0 +1,51 @@
+using Mapster;
+using Microsoft.EntityFrameworkCore;
+using Persistence.Database.Entity;
+using Persistence.Models;
+using Persistence.Repositories;
+
+namespace Persistence.Repository.Repositories
+{
+	public class TechMessagesRepository : ITechMessagesRepository
+	{
+		private DbContext db;
+		public TechMessagesRepository(DbContext db)
+		{
+			this.db = db;
+		}
+
+		protected virtual IQueryable<TechMessage> GetQueryReadOnly() => db.Set<TechMessage>();
+
+		public async Task<int> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
+		{
+			var query = GetQueryReadOnly();
+			var count = await query
+				.Where(e => e.ImportantId == importantId && e.AutoDrillingSystem == autoDrillingSystem)
+				.CountAsync();
+
+			return count;
+		}
+
+		public async Task<IEnumerable<string>> GetSystems(CancellationToken token)
+		{
+			var query = GetQueryReadOnly();
+			var entities = await query
+				.Select(e => e.AutoDrillingSystem ?? string.Empty)
+				.Distinct()
+				.ToArrayAsync(token);
+			var dtos = entities.Order();
+
+			return dtos;
+		}
+
+		public async Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
+		{
+			var entities = dtos.Select(d => d.Adapt<TechMessage>());
+
+			await db.Set<TechMessage>().AddRangeAsync(entities, token);
+			var result = await db.SaveChangesAsync(token);
+
+			return result;
+		}
+	}
+}
diff --git a/Persistence/API/ITechMessages.cs b/Persistence/API/ITechMessages.cs
new file mode 100644
index 0000000..95af161
--- /dev/null
+++ b/Persistence/API/ITechMessages.cs
@@ -0,0 +1,35 @@
+using Microsoft.AspNetCore.Mvc;
+using Persistence.Models;
+
+namespace Persistence.API
+{
+	/// <summary>
+	/// Интерфейс для API сообщений о состояниях работы систем автобурения (АБ)
+	/// </summary>
+	public interface ITechMessages : ITableDataApi<TechMessageDto, RequestDto>
+	{
+		/// <summary>
+		/// Добавление новых сообщений
+		/// </summary>
+		/// <param name="dtos"></param>
+		/// <param name="token"></param>
+		/// <returns></returns>
+		Task<ActionResult<int>> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token);
+
+		/// <summary>
+		/// Получение списка систем АБ
+		/// </summary>
+		/// <param name="token"></param>
+		/// <returns></returns>
+		Task<ActionResult<IEnumerable<string>>> GetSystems(CancellationToken token);
+
+		/// <summary>
+		/// Получение статистики
+		/// </summary>
+		/// <param name="importantId">Id Категории важности</param>
+		/// <param name="autoDrillingSystem">Система АБ</param>
+		/// <param name="token"></param>
+		/// <returns></returns>
+		Task<ActionResult<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token); 
+	}
+}
diff --git a/Persistence/Models/TechMessageDto.cs b/Persistence/Models/TechMessageDto.cs
new file mode 100644
index 0000000..274a1d5
--- /dev/null
+++ b/Persistence/Models/TechMessageDto.cs
@@ -0,0 +1,40 @@
+namespace Persistence.Models
+{
+	public class TechMessageDto
+	{
+		/// <summary>
+		/// Id события
+		/// </summary>
+		public Guid EventId { get; set; }
+
+		/// <summary>
+		/// Id Категории важности
+		/// </summary>
+		public int ImportantId {  get; set; }
+
+		/// <summary>
+		/// Дата возникновения
+		/// </summary>
+		public DateTimeOffset OccurrenceDate { get; set; }
+
+		/// <summary>
+		/// Глубина забоя
+		/// </summary>
+		public double? Depth {  get; set; }
+
+		/// <summary>
+		/// Текст сообщения
+		/// </summary>
+		public string? MessageText { get; set; }
+
+		/// <summary>
+		/// Система автобурения, к которой относится сообщение
+		/// </summary>
+		public string? AutoDrillingSystem { get; set; }
+
+		/// <summary>
+		/// Id пользователя за пультом бурильщика
+		/// </summary>
+		public Guid UserId { get; set; }
+	}
+}
diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs
new file mode 100644
index 0000000..c681184
--- /dev/null
+++ b/Persistence/Repositories/ITechMessagesRepository.cs
@@ -0,0 +1,34 @@
+using Persistence.Models;
+
+namespace Persistence.Repositories
+{
+	/// <summary>
+	/// Интерфейс по работе с технологическими сообщениями
+	/// </summary>
+	public interface ITechMessagesRepository
+	{
+		/// <summary>
+		/// Добавление новых сообщений
+		/// </summary>
+		/// <param name="dtos"></param>
+		/// <param name="token"></param>
+		/// <returns></returns>
+		Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token);
+
+		/// <summary>
+		/// Получение списка уникальных названий систем АБ
+		/// </summary>
+		/// <param name="token"></param>
+		/// <returns></returns>
+		Task<IEnumerable<string>> GetSystems(CancellationToken token);
+
+		/// <summary>
+		/// Получение количества сообщений по категориям и системам автобурения
+		/// </summary>
+		/// <param name="importantId">Id Категории важности</param>
+		/// <param name="autoDrillingSystem">Система автобурения</param>
+		/// <param name="token"></param>
+		/// <returns></returns>
+		Task<int> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token);
+	}
+}
-- 
2.45.2


From acc9e6494a9df663deb0843a7f35394962857cdb Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Tue, 26 Nov 2024 10:23:48 +0500
Subject: [PATCH 02/12] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?=
 =?UTF-8?q?=D1=82=D0=B0=D1=82=D1=8C=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?=
 =?UTF-8?q?=D0=B8=D0=B5=20=D1=82=D0=B5=D1=85=D0=BD=D0=BE=D0=BB=D0=BE=D0=B3?=
 =?UTF-8?q?=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D1=85=20=D1=81=D0=BE=D0=BE?=
 =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Controllers/TechMessagesController.cs     |  12 +-
 ...126044756_TechMessageMigration.Designer.cs | 175 ++++++++++++
 .../20241126044756_TechMessageMigration.cs    |  59 ++++
 .../PersistenceDbContextModelSnapshot.cs      |  38 ++-
 Persistence.Repository/DependencyInjection.cs |   1 +
 .../Extensions/EFExtensionsSortBy.cs          | 267 ++++++++++++++++++
 .../Repositories/TechMessagesRepository.cs    |  19 ++
 .../Repositories/ISetpointRepository.cs       |  14 +-
 .../Repositories/ITechMessagesRepository.cs   |   8 +
 9 files changed, 582 insertions(+), 11 deletions(-)
 create mode 100644 Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs
 create mode 100644 Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs
 create mode 100644 Persistence.Repository/Extensions/EFExtensionsSortBy.cs

diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs
index 0eb183d..16db89c 100644
--- a/Persistence.API/Controllers/TechMessagesController.cs
+++ b/Persistence.API/Controllers/TechMessagesController.cs
@@ -15,11 +15,15 @@ namespace Persistence.API.Controllers
 			this.techMessagesRepository = techMessagesRepository;
 		}
 
-		public Task<ActionResult<PaginationContainer<TechMessageDto>>> GetPage(RequestDto request, CancellationToken token)
+		[HttpGet]
+		public async Task<ActionResult<PaginationContainer<TechMessageDto>>> GetPage([FromQuery] RequestDto request, CancellationToken token)
 		{
-			throw new NotImplementedException();
+			var result = await techMessagesRepository.GetPage(request, token);
+
+			return Ok(result);
 		}
 
+		[HttpGet("statistics")]
 		public async Task<ActionResult<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
 		{
 			var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token);
@@ -27,6 +31,7 @@ namespace Persistence.API.Controllers
 			return Ok(result);
 		}
 
+		[HttpGet("systems")]
 		public async Task<ActionResult<IEnumerable<string>>> GetSystems(CancellationToken token)
 		{
 			var result = await techMessagesRepository.GetSystems(token);
@@ -34,7 +39,8 @@ namespace Persistence.API.Controllers
 			return Ok(result);
 		}
 
-		public async Task<ActionResult<int>> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
+		[HttpPost]
+		public async Task<ActionResult<int>> InsertRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
 		{
 			var result = await techMessagesRepository.InsertRange(dtos, token);
 
diff --git a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs
new file mode 100644
index 0000000..5d6817e
--- /dev/null
+++ b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs
@@ -0,0 +1,175 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using Persistence.Database.Model;
+
+#nullable disable
+
+namespace Persistence.Database.Postgres.Migrations
+{
+    [DbContext(typeof(PersistenceDbContext))]
+    [Migration("20241126044756_TechMessageMigration")]
+    partial class TechMessageMigration
+    {
+        /// <inheritdoc />
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .UseCollation("Russian_Russia.1251")
+                .HasAnnotation("ProductVersion", "8.0.10")
+                .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+            NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
+            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+            modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
+                {
+                    b.Property<Guid>("EventId")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasComment("Id события");
+
+                    b.Property<string>("AutoDrillingSystem")
+                        .HasColumnType("varchar(256)")
+                        .HasComment("Система автобурения, к которой относится сообщение");
+
+                    b.Property<double?>("Depth")
+                        .HasColumnType("double precision")
+                        .HasComment("Глубина забоя");
+
+                    b.Property<int>("ImportantId")
+                        .HasColumnType("integer")
+                        .HasComment("Id Категории важности");
+
+                    b.Property<string>("MessageText")
+                        .HasColumnType("varchar(512)")
+                        .HasComment("Текст сообщения");
+
+                    b.Property<DateTimeOffset>("OccurrenceDate")
+                        .HasColumnType("timestamp with time zone")
+                        .HasComment("Дата возникновения");
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("uuid")
+                        .HasComment("Id пользователя за пультом бурильщика");
+
+                    b.HasKey("EventId");
+
+                    b.ToTable("TechMessage");
+                });
+
+            modelBuilder.Entity("Persistence.Database.Model.DataSaub", b =>
+                {
+                    b.Property<DateTimeOffset>("Date")
+                        .HasColumnType("timestamp with time zone")
+                        .HasColumnName("date");
+
+                    b.Property<double?>("AxialLoad")
+                        .HasColumnType("double precision")
+                        .HasColumnName("axialLoad");
+
+                    b.Property<double?>("BitDepth")
+                        .HasColumnType("double precision")
+                        .HasColumnName("bitDepth");
+
+                    b.Property<double?>("BlockPosition")
+                        .HasColumnType("double precision")
+                        .HasColumnName("blockPosition");
+
+                    b.Property<double?>("BlockSpeed")
+                        .HasColumnType("double precision")
+                        .HasColumnName("blockSpeed");
+
+                    b.Property<double?>("Flow")
+                        .HasColumnType("double precision")
+                        .HasColumnName("flow");
+
+                    b.Property<double?>("HookWeight")
+                        .HasColumnType("double precision")
+                        .HasColumnName("hookWeight");
+
+                    b.Property<int>("IdFeedRegulator")
+                        .HasColumnType("integer")
+                        .HasColumnName("idFeedRegulator");
+
+                    b.Property<int?>("Mode")
+                        .HasColumnType("integer")
+                        .HasColumnName("mode");
+
+                    b.Property<double?>("Mse")
+                        .HasColumnType("double precision")
+                        .HasColumnName("mse");
+
+                    b.Property<short>("MseState")
+                        .HasColumnType("smallint")
+                        .HasColumnName("mseState");
+
+                    b.Property<double?>("Pressure")
+                        .HasColumnType("double precision")
+                        .HasColumnName("pressure");
+
+                    b.Property<double?>("Pump0Flow")
+                        .HasColumnType("double precision")
+                        .HasColumnName("pump0Flow");
+
+                    b.Property<double?>("Pump1Flow")
+                        .HasColumnType("double precision")
+                        .HasColumnName("pump1Flow");
+
+                    b.Property<double?>("Pump2Flow")
+                        .HasColumnType("double precision")
+                        .HasColumnName("pump2Flow");
+
+                    b.Property<double?>("RotorSpeed")
+                        .HasColumnType("double precision")
+                        .HasColumnName("rotorSpeed");
+
+                    b.Property<double?>("RotorTorque")
+                        .HasColumnType("double precision")
+                        .HasColumnName("rotorTorque");
+
+                    b.Property<string>("User")
+                        .HasColumnType("text")
+                        .HasColumnName("user");
+
+                    b.Property<double?>("WellDepth")
+                        .HasColumnType("double precision")
+                        .HasColumnName("wellDepth");
+
+                    b.HasKey("Date");
+
+                    b.ToTable("DataSaub");
+                });
+
+            modelBuilder.Entity("Persistence.Database.Model.Setpoint", b =>
+                {
+                    b.Property<Guid>("Key")
+                        .HasColumnType("uuid")
+                        .HasComment("Ключ");
+
+                    b.Property<DateTimeOffset>("Created")
+                        .HasColumnType("timestamp with time zone")
+                        .HasComment("Дата создания уставки");
+
+                    b.Property<int>("IdUser")
+                        .HasColumnType("integer")
+                        .HasComment("Id автора последнего изменения");
+
+                    b.Property<object>("Value")
+                        .IsRequired()
+                        .HasColumnType("jsonb")
+                        .HasComment("Значение уставки");
+
+                    b.HasKey("Key", "Created");
+
+                    b.ToTable("Setpoint");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}
diff --git a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs
new file mode 100644
index 0000000..46a71a0
--- /dev/null
+++ b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs
@@ -0,0 +1,59 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Persistence.Database.Postgres.Migrations
+{
+    /// <inheritdoc />
+    public partial class TechMessageMigration : Migration
+    {
+        /// <inheritdoc />
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AlterColumn<DateTimeOffset>(
+                name: "Created",
+                table: "Setpoint",
+                type: "timestamp with time zone",
+                nullable: false,
+                comment: "Дата создания уставки",
+                oldClrType: typeof(DateTimeOffset),
+                oldType: "timestamp with time zone",
+                oldComment: "Дата изменения уставки");
+
+            migrationBuilder.CreateTable(
+                name: "TechMessage",
+                columns: table => new
+                {
+                    EventId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id события"),
+                    ImportantId = table.Column<int>(type: "integer", nullable: false, comment: "Id Категории важности"),
+                    OccurrenceDate = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"),
+                    Depth = table.Column<double>(type: "double precision", nullable: true, comment: "Глубина забоя"),
+                    MessageText = table.Column<string>(type: "varchar(512)", nullable: true, comment: "Текст сообщения"),
+                    AutoDrillingSystem = table.Column<string>(type: "varchar(256)", nullable: true, comment: "Система автобурения, к которой относится сообщение"),
+                    UserId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id пользователя за пультом бурильщика")
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_TechMessage", x => x.EventId);
+                });
+        }
+
+        /// <inheritdoc />
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropTable(
+                name: "TechMessage");
+
+            migrationBuilder.AlterColumn<DateTimeOffset>(
+                name: "Created",
+                table: "Setpoint",
+                type: "timestamp with time zone",
+                nullable: false,
+                comment: "Дата изменения уставки",
+                oldClrType: typeof(DateTimeOffset),
+                oldType: "timestamp with time zone",
+                oldComment: "Дата создания уставки");
+        }
+    }
+}
diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
index f41f669..4446a1c 100644
--- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
+++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
@@ -24,6 +24,42 @@ namespace Persistence.Database.Postgres.Migrations
             NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 
+            modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
+                {
+                    b.Property<Guid>("EventId")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasComment("Id события");
+
+                    b.Property<string>("AutoDrillingSystem")
+                        .HasColumnType("varchar(256)")
+                        .HasComment("Система автобурения, к которой относится сообщение");
+
+                    b.Property<double?>("Depth")
+                        .HasColumnType("double precision")
+                        .HasComment("Глубина забоя");
+
+                    b.Property<int>("ImportantId")
+                        .HasColumnType("integer")
+                        .HasComment("Id Категории важности");
+
+                    b.Property<string>("MessageText")
+                        .HasColumnType("varchar(512)")
+                        .HasComment("Текст сообщения");
+
+                    b.Property<DateTimeOffset>("OccurrenceDate")
+                        .HasColumnType("timestamp with time zone")
+                        .HasComment("Дата возникновения");
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("uuid")
+                        .HasComment("Id пользователя за пультом бурильщика");
+
+                    b.HasKey("EventId");
+
+                    b.ToTable("TechMessage");
+                });
+
             modelBuilder.Entity("Persistence.Database.Model.DataSaub", b =>
                 {
                     b.Property<DateTimeOffset>("Date")
@@ -115,7 +151,7 @@ namespace Persistence.Database.Postgres.Migrations
 
                     b.Property<DateTimeOffset>("Created")
                         .HasColumnType("timestamp with time zone")
-                        .HasComment("Дата изменения уставки");
+                        .HasComment("Дата создания уставки");
 
                     b.Property<int>("IdUser")
                         .HasColumnType("integer")
diff --git a/Persistence.Repository/DependencyInjection.cs b/Persistence.Repository/DependencyInjection.cs
index 27063cb..a8a08cc 100644
--- a/Persistence.Repository/DependencyInjection.cs
+++ b/Persistence.Repository/DependencyInjection.cs
@@ -17,6 +17,7 @@ public static class DependencyInjection
 
         services.AddTransient<ITimeSeriesDataRepository<DataSaubDto>, TimeSeriesDataRepository<DataSaub, DataSaubDto>>();
         services.AddTransient<ISetpointRepository, SetpointRepository>();
+		services.AddTransient<ITechMessagesRepository, TechMessagesRepository>();
 
         return services;
     }
diff --git a/Persistence.Repository/Extensions/EFExtensionsSortBy.cs b/Persistence.Repository/Extensions/EFExtensionsSortBy.cs
new file mode 100644
index 0000000..03b9c65
--- /dev/null
+++ b/Persistence.Repository/Extensions/EFExtensionsSortBy.cs
@@ -0,0 +1,267 @@
+using System.Collections.Concurrent;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace Persistence.Repository.Extensions;
+
+public static class EFExtensionsSortBy
+{
+    struct TypeAccessor
+    {
+        public LambdaExpression KeySelector { get; set; }
+        public MethodInfo OrderBy { get; set; }
+        public MethodInfo OrderByDescending { get; set; }
+        public MethodInfo ThenBy { get; set; }
+        public MethodInfo ThenByDescending { get; set; }
+    }
+
+    private static ConcurrentDictionary<Type, Dictionary<string, TypeAccessor>> TypePropSelectors { get; set; } =
+        new();
+
+    private static readonly MethodInfo methodOrderBy = GetExtOrderMethod("OrderBy");
+
+    private static readonly MethodInfo methodOrderByDescending = GetExtOrderMethod("OrderByDescending");
+
+    private static readonly MethodInfo methodThenBy = GetExtOrderMethod("ThenBy");
+
+    private static readonly MethodInfo methodThenByDescending = GetExtOrderMethod("ThenByDescending");
+
+    private static MethodInfo GetExtOrderMethod(string methodName)
+        => typeof(Queryable)
+            .GetMethods()
+            .Where(m => m.Name == methodName &&
+                        m.IsGenericMethodDefinition &&
+                        m.GetParameters().Length == 2 &&
+                        m.GetParameters()[1].ParameterType.IsAssignableTo(typeof(LambdaExpression)))
+            .Single();
+
+    private static Dictionary<string, TypeAccessor> MakeTypeAccessors(Type type)
+    {
+        var propContainer = new Dictionary<string, TypeAccessor>();
+        var properties = type.GetProperties();
+        foreach (var propertyInfo in properties)
+        {
+            var name = propertyInfo.Name.ToLower();
+            ParameterExpression arg = Expression.Parameter(type, "x");
+            MemberExpression property = Expression.Property(arg, propertyInfo.Name);
+            var selector = Expression.Lambda(property, new ParameterExpression[] { arg });
+            var typeAccessor = new TypeAccessor
+            {
+                KeySelector = selector,
+                OrderBy = methodOrderBy.MakeGenericMethod(type, propertyInfo.PropertyType),
+                OrderByDescending = methodOrderByDescending.MakeGenericMethod(type, propertyInfo.PropertyType),
+                ThenBy = methodThenBy.MakeGenericMethod(type, propertyInfo.PropertyType),
+                ThenByDescending = methodThenByDescending.MakeGenericMethod(type, propertyInfo.PropertyType),
+            };
+
+            propContainer.Add(name, typeAccessor);
+        }
+
+        return propContainer;
+    }
+
+    /// <summary>
+    /// Добавить в запрос сортировку по возрастанию или убыванию.
+    /// </summary>
+    /// <typeparam name="TSource"></typeparam>
+    /// <param name="query"></param>
+    /// <param name="propertySort">
+    /// Свойство сортировки.
+    /// Состоит из названия свойства (в любом регистре) 
+    /// и опционально указания направления сортировки "asc" или "desc"
+    /// </param>
+    /// <example>
+    /// var query = query("Date desc");
+    /// </example>
+    /// <returns>Запрос с примененной сортировкой</returns>
+    public static IOrderedQueryable<TSource> SortBy<TSource>(
+        this IQueryable<TSource> query,
+        IEnumerable<string> propertySorts)
+    {
+        if (propertySorts?.Any() != true)
+            return (IOrderedQueryable<TSource>)query;
+
+        var sortEnum = propertySorts.GetEnumerator();
+        sortEnum.MoveNext();
+        var orderedQuery = query.SortBy(sortEnum.Current);
+
+        while (sortEnum.MoveNext())
+            orderedQuery = orderedQuery.ThenSortBy(sortEnum.Current);
+
+        return orderedQuery;
+    }
+
+    /// <summary>
+    /// Добавить в запрос сортировку по возрастанию или убыванию.
+    /// Этот метод сбросит ранее наложенные сортировки.
+    /// </summary>
+    /// <typeparam name="TSource"></typeparam>
+    /// <param name="query"></param>
+    /// <param name="propertySort">
+    /// Свойство сортировки.
+    /// Состоит из названия свойства (в любом регистре) 
+    /// и опционально указания направления сортировки "asc" или "desc"
+    /// </param>
+    /// <example>
+    /// var query = query("Date desc");
+    /// </example>
+    /// <returns>Запрос с примененной сортировкой</returns>
+    public static IOrderedQueryable<TSource> SortBy<TSource>(
+        this IQueryable<TSource> query,
+        string propertySort)
+    {
+        var parts = propertySort.Split(" ", 2, StringSplitOptions.RemoveEmptyEntries);
+        var isDesc = parts.Length >= 2 && parts[1].ToLower().Trim() == "desc";
+        var propertyName = parts[0];
+
+        var newQuery = query.SortBy(propertyName, isDesc);
+        return newQuery;
+    }
+
+    /// <summary>
+    /// Добавить в запрос дополнительную сортировку по возрастанию или убыванию.
+    /// </summary>
+    /// <typeparam name="TSource"></typeparam>
+    /// <param name="query"></param>
+    /// <param name="propertySort">
+    /// Свойство сортировки.
+    /// Состоит из названия свойства (в любом регистре) 
+    /// и опционально указания направления сортировки "asc" или "desc"
+    /// </param>
+    /// <example>
+    /// var query = query("Date desc");
+    /// </example>
+    /// <returns>Запрос с примененной сортировкой</returns>
+    public static IOrderedQueryable<TSource> ThenSortBy<TSource>(
+        this IOrderedQueryable<TSource> query,
+        string propertySort)
+    {
+        var parts = propertySort.Split(" ", 2, StringSplitOptions.RemoveEmptyEntries);
+        var isDesc = parts.Length >= 2 && parts[1].ToLower().Trim() == "desc";
+        var propertyName = parts[0];
+
+        var newQuery = query.ThenSortBy(propertyName, isDesc);
+        return newQuery;
+    }
+
+    /// <summary>
+    /// Добавить в запрос сортировку по возрастанию или убыванию
+    /// </summary>
+    /// <typeparam name="TSource"></typeparam>
+    /// <param name="query"></param>
+    /// <param name="propertyName">Название свойства (в любом регистре)</param>
+    /// <param name="isDesc">Сортировать по убыванию</param>
+    /// <returns>Запрос с примененной сортировкой</returns>
+    public static IOrderedQueryable<TSource> SortBy<TSource>(
+        this IQueryable<TSource> query,
+        string propertyName,
+        bool isDesc)
+    {
+        Type rootType = typeof(TSource);
+        var typePropSelector = TypePropSelectors.GetOrAdd(rootType, MakeTypeAccessors);
+        var propertyNameLower = propertyName.ToLower();
+
+        MethodInfo orderByDescending;
+        MethodInfo orderByAscending;
+
+        LambdaExpression? lambdaExpression = null;
+
+        if (propertyName.Contains('.'))
+        {
+            Type type = rootType;
+            ParameterExpression rootExpression = Expression.Parameter(rootType, "x");
+            Expression expr = rootExpression;
+
+            var propertyPath = propertyName.Split(".", StringSplitOptions.RemoveEmptyEntries);
+
+            for (int i = 0; i < propertyPath.Length; i++)
+            {
+                PropertyInfo pi = type.GetProperty(propertyPath[i])!;
+                expr = Expression.Property(expr, pi);
+                type = pi.PropertyType;
+            }
+
+            Type delegateType = typeof(Func<,>).MakeGenericType(rootType, type);
+            lambdaExpression = Expression.Lambda(delegateType, expr, rootExpression);
+
+            orderByAscending = methodOrderBy.MakeGenericMethod(rootType, type);
+            orderByDescending = methodOrderByDescending.MakeGenericMethod(rootType, type);
+        }
+        else
+        {
+            var rootTypeAccessor = typePropSelector[propertyNameLower];
+            orderByAscending = rootTypeAccessor.OrderBy;
+            orderByDescending = rootTypeAccessor.OrderByDescending;
+            lambdaExpression = rootTypeAccessor.KeySelector;
+        }
+
+        var genericMethod = isDesc
+            ? orderByDescending
+            : orderByAscending;
+
+        var newQuery = (IOrderedQueryable<TSource>)genericMethod
+            .Invoke(genericMethod, new object[] { query, lambdaExpression })!;
+        return newQuery;
+    }
+
+    /// <summary>
+    /// Добавить в запрос дополнительную сортировку по возрастанию или убыванию
+    /// </summary>
+    /// <typeparam name="TSource"></typeparam>
+    /// <param name="query"></param>
+    /// <param name="propertyName">Название свойства (в любом регистре)</param>
+    /// <param name="isDesc">Сортировать по убыванию</param>
+    /// <returns>Запрос с примененной сортировкой</returns>
+    public static IOrderedQueryable<TSource> ThenSortBy<TSource>(
+        this IOrderedQueryable<TSource> query,
+        string propertyName,
+        bool isDesc)
+    {
+        Type rootType = typeof(TSource);
+        var typePropSelector = TypePropSelectors.GetOrAdd(rootType, MakeTypeAccessors);
+        var propertyNameLower = propertyName.ToLower();
+
+        MethodInfo orderByDescending;
+        MethodInfo orderByAscending;
+
+        LambdaExpression? lambdaExpression = null;
+
+        // TODO: Устранить дублирование кода
+        if (propertyName.Contains('.'))
+        {
+            Type type = rootType;
+            ParameterExpression rootExpression = Expression.Parameter(rootType, "x");
+            Expression expr = rootExpression;
+
+            var propertyPath = propertyName.Split(".", StringSplitOptions.RemoveEmptyEntries);
+
+            for (int i = 0; i < propertyPath.Length; i++)
+            {
+                PropertyInfo pi = type.GetProperty(propertyPath[i])!;
+                expr = Expression.Property(expr, pi);
+                type = pi.PropertyType;
+            }
+
+            Type delegateType = typeof(Func<,>).MakeGenericType(rootType, type);
+            lambdaExpression = Expression.Lambda(delegateType, expr, rootExpression);
+
+            orderByAscending = methodThenBy.MakeGenericMethod(rootType, type);
+            orderByDescending = methodThenByDescending.MakeGenericMethod(rootType, type);
+        }
+        else
+        {
+            var rootTypeAccessor = typePropSelector[propertyNameLower];
+            orderByAscending = rootTypeAccessor.ThenBy;
+            orderByDescending = rootTypeAccessor.ThenByDescending;
+            lambdaExpression = rootTypeAccessor.KeySelector;
+        }
+
+        var genericMethod = isDesc
+            ? orderByDescending
+            : orderByAscending;
+
+        var newQuery = (IOrderedQueryable<TSource>)genericMethod
+            .Invoke(genericMethod, new object[] { query, lambdaExpression })!;
+        return newQuery;
+    }
+}
\ No newline at end of file
diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs
index e7a37bc..f5ddb35 100644
--- a/Persistence.Repository/Repositories/TechMessagesRepository.cs
+++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs
@@ -16,6 +16,25 @@ namespace Persistence.Repository.Repositories
 
 		protected virtual IQueryable<TechMessage> GetQueryReadOnly() => db.Set<TechMessage>();
 
+		public async Task<PaginationContainer<TechMessageDto>> GetPage(RequestDto request, CancellationToken token)
+		{
+			var query = GetQueryReadOnly();
+			var entities = await query
+				.SortBy(request.SortSettings)
+				.Skip(request.Skip)
+				.Take(request.Take)
+				.ToListAsync();
+			var dto = new PaginationContainer<TechMessageDto>()
+			{
+				Skip = request.Skip,
+				Take = request.Take,
+				Count = entities.Count,
+				Items = entities.Select(e => e.Adapt<TechMessageDto>())
+			};
+
+			return dto;
+		}
+
 		public async Task<int> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
 		{
 			var query = GetQueryReadOnly();
diff --git a/Persistence/Repositories/ISetpointRepository.cs b/Persistence/Repositories/ISetpointRepository.cs
index 1d82b16..db6838c 100644
--- a/Persistence/Repositories/ISetpointRepository.cs
+++ b/Persistence/Repositories/ISetpointRepository.cs
@@ -7,13 +7,13 @@ namespace Persistence.Repositories;
 /// </summary>
 public interface ISetpointRepository
 {
-	/// <summary>
-	/// Получить значения уставок по набору ключей
-	/// </summary>
-	/// <param name="setpointKeys"></param>
-	/// <param name="token"></param>
-	/// <returns></returns>
-	Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token);
+    /// <summary>
+    /// Получить значения уставок по набору ключей
+    /// </summary>
+    /// <param name="setpointKeys"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    Task<IEnumerable<SetpointValueDto>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token);
 
 	/// <summary>
 	/// Получить значения уставок за определенный момент времени
diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs
index c681184..1be90e9 100644
--- a/Persistence/Repositories/ITechMessagesRepository.cs
+++ b/Persistence/Repositories/ITechMessagesRepository.cs
@@ -7,6 +7,14 @@ namespace Persistence.Repositories
 	/// </summary>
 	public interface ITechMessagesRepository
 	{
+		/// <summary>
+		/// Получить страницу списка объектов
+		/// </summary>
+		/// <param name="request"></param>
+		/// <param name="token"></param>
+		/// <returns></returns>
+		Task<PaginationContainer<TechMessageDto>> GetPage(RequestDto request, CancellationToken token);
+
 		/// <summary>
 		/// Добавление новых сообщений
 		/// </summary>
-- 
2.45.2


From 7807e4aa1e956694f72dfc71541d6ed258f6d896 Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Tue, 26 Nov 2024 12:27:52 +0500
Subject: [PATCH 03/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?=
 =?UTF-8?q?=D1=82=D1=8C=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D0=B4=D0=BB?=
 =?UTF-8?q?=D1=8F=20=D1=82=D0=B5=D1=85=D0=BD=D0=BE=D0=BB=D0=BE=D0=B3=D0=B8?=
 =?UTF-8?q?=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D1=85=20=D1=81=D0=BE=D0=BE=D0=B1?=
 =?UTF-8?q?=D1=89=D0=B5=D0=BD=D0=B8=D0=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Controllers/TechMessagesController.cs     |   6 +-
 Persistence.Client/Clients/ISetpointClient.cs |   2 +-
 .../Clients/ITechMessagesClient.cs            |  25 +++
 .../Clients/ITimeSeriesClient.cs              |   3 +-
 .../Controllers/TechMessagesControllerTest.cs | 178 ++++++++++++++++++
 .../Extensions/EFCoreExtensions.cs            |  14 ++
 Persistence.Repository/Data/SetpointDto.cs    |  28 ---
 .../Repositories/TechMessagesRepository.cs    |   1 +
 Persistence/Models/TechMessageDto.cs          |   3 +
 9 files changed, 227 insertions(+), 33 deletions(-)
 create mode 100644 Persistence.Client/Clients/ITechMessagesClient.cs
 create mode 100644 Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
 create mode 100644 Persistence.IntegrationTests/Extensions/EFCoreExtensions.cs
 delete mode 100644 Persistence.Repository/Data/SetpointDto.cs

diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs
index 16db89c..47d79d5 100644
--- a/Persistence.API/Controllers/TechMessagesController.cs
+++ b/Persistence.API/Controllers/TechMessagesController.cs
@@ -1,10 +1,12 @@
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
 using Persistence.Models;
 using Persistence.Repositories;
 
 namespace Persistence.API.Controllers
 {
 	[ApiController]
+	[Authorize]
 	[Route("api/[controller]")]
 	public class TechMessagesController : ControllerBase, ITechMessages
 	{
@@ -44,7 +46,7 @@ namespace Persistence.API.Controllers
 		{
 			var result = await techMessagesRepository.InsertRange(dtos, token);
 
-			return Ok(result);
+			return CreatedAtAction(nameof(InsertRange), result);
 		}
 	}
 }
diff --git a/Persistence.Client/Clients/ISetpointClient.cs b/Persistence.Client/Clients/ISetpointClient.cs
index 49733f0..72d76e1 100644
--- a/Persistence.Client/Clients/ISetpointClient.cs
+++ b/Persistence.Client/Clients/ISetpointClient.cs
@@ -4,7 +4,7 @@ using Refit;
 namespace Persistence.Client.Clients;
 
 /// <summary>
-/// Интерфейс для тестирования API, предназначенного для работы с уставками
+/// Интерфейс клиента для работы с уставками
 /// </summary>
 public interface ISetpointClient
 {
diff --git a/Persistence.Client/Clients/ITechMessagesClient.cs b/Persistence.Client/Clients/ITechMessagesClient.cs
new file mode 100644
index 0000000..1426b54
--- /dev/null
+++ b/Persistence.Client/Clients/ITechMessagesClient.cs
@@ -0,0 +1,25 @@
+using Persistence.Models;
+using Refit;
+
+namespace Persistence.Client.Clients
+{
+	/// <summary>
+	/// Интерфейс клиента для хранения технологических сообщений
+	/// </summary>
+	public interface ITechMessagesClient
+	{
+		private const string BaseRoute = "/api/techMessages";
+
+		[Get($"{BaseRoute}")]
+		Task<IApiResponse<PaginationContainer<TechMessageDto>>> GetPage([Query] RequestDto request, CancellationToken token);
+
+		[Post($"{BaseRoute}")]
+		Task<IApiResponse<int>> InsertRange([Body] IEnumerable<TechMessageDto> dtos, CancellationToken token);
+
+		[Get($"{BaseRoute}/systems")]
+		Task<IApiResponse<IEnumerable<string>>> GetSystems(CancellationToken token);
+
+		[Get($"{BaseRoute}/statistics")]
+		Task<IApiResponse<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token);
+	}
+}
diff --git a/Persistence.Client/Clients/ITimeSeriesClient.cs b/Persistence.Client/Clients/ITimeSeriesClient.cs
index 8f7ef0e..349337b 100644
--- a/Persistence.Client/Clients/ITimeSeriesClient.cs
+++ b/Persistence.Client/Clients/ITimeSeriesClient.cs
@@ -1,5 +1,4 @@
-using Microsoft.AspNetCore.Mvc;
-using Persistence.Models;
+using Persistence.Models;
 using Refit;
 
 namespace Persistence.Client.Clients;
diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
new file mode 100644
index 0000000..9eb0e9d
--- /dev/null
+++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
@@ -0,0 +1,178 @@
+using System.Net;
+using Microsoft.Extensions.DependencyInjection;
+using Persistence.Client;
+using Persistence.Client.Clients;
+using Persistence.Database.Entity;
+using Persistence.Models;
+using Xunit;
+
+namespace Persistence.IntegrationTests.Controllers
+{
+	public class TechMessagesControllerTest : BaseIntegrationTest
+	{
+		private readonly ITechMessagesClient techMessagesClient;
+		public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory)
+		{
+			var scope = factory.Services.CreateScope();
+			var persistenceClientFactory = scope.ServiceProvider
+				.GetRequiredService<PersistenceClientFactory>();
+
+			techMessagesClient = persistenceClientFactory.GetClient<ITechMessagesClient>();
+		}
+
+		[Fact]
+		public async Task GetPage_returns_success()
+		{
+			//arrange
+			dbContext.CleanupDbSet<TechMessage>();
+			var requestDto = new RequestDto()
+			{
+				Skip = 1,
+				Take = 2,
+				SortSettings = nameof(TechMessageDto.ImportantId)
+			};
+
+			//act
+			var response = await techMessagesClient.GetPage(requestDto, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.Empty(response.Content.Items);
+			Assert.Equal(requestDto.Skip, response.Content.Skip);
+			Assert.Equal(requestDto.Take, response.Content.Take);
+		}
+
+		[Fact]
+		public async Task GetPage_AfterSave_returns_success()
+		{
+			//arrange
+			var dtos = await InsertRange();
+			var dtosCount = dtos.Count();
+			var requestDto = new RequestDto()
+			{
+				Skip = 0,
+				Take = 2,
+				SortSettings = nameof(TechMessageDto.ImportantId)
+			};
+
+			//act
+			var response = await techMessagesClient.GetPage(requestDto, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.Equal(dtosCount, response.Content.Count);
+		}
+
+		[Fact]
+		public async Task InsertRange_returns_success()
+		{
+			await InsertRange();
+		}
+
+		[Fact]
+		public async Task GetSystems_returns_success()
+		{
+			//act
+			dbContext.CleanupDbSet<TechMessage>();
+			var response = await techMessagesClient.GetSystems(new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.Empty(response.Content);
+		}
+
+		[Fact]
+		public async Task GetSystems_AfterSave_returns_success()
+		{
+			//arrange
+			var dtos = await InsertRange();
+			var systems = dtos
+				.Select(e => e.AutoDrillingSystem)
+				.Distinct()
+				.ToArray();
+
+			//act
+			var response = await techMessagesClient.GetSystems(new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			string?[]? content = response.Content?.ToArray();
+			Assert.Equal(systems, content);
+		}
+
+		[Fact]
+		public async Task GetStatistics_returns_success()
+		{
+			//arrange
+			dbContext.CleanupDbSet<TechMessage>();
+			var imortantId = 1;
+			var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem);
+
+			//act
+			var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.Equal(0, response.Content);
+		}
+
+		[Fact]
+		public async Task GetStatistics_AfterSave_returns_success()
+		{
+			//arrange
+			var imortantId = 1;
+			var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem);
+			var dtos = await InsertRange();
+			var filteredDtos = dtos.Where(e => e.ImportantId == imortantId && e.AutoDrillingSystem == e.AutoDrillingSystem);
+
+			//act
+			var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.Equal(filteredDtos.Count(), response.Content);
+		}
+
+		public async Task<IEnumerable<TechMessageDto>> InsertRange()
+		{
+			//arrange
+			var dtos = new List<TechMessageDto>()
+			{
+				new TechMessageDto()
+				{
+					EventId = Guid.NewGuid(),
+					ImportantId = 1,
+					OccurrenceDate = DateTimeOffset.UtcNow,
+					Depth = 1.11,
+					MessageText = nameof(TechMessageDto.MessageText),
+					AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem),
+					UserId = Guid.NewGuid()
+				},
+				new TechMessageDto()
+				{
+					EventId = Guid.NewGuid(),
+					ImportantId = 2,
+					OccurrenceDate = DateTimeOffset.UtcNow,
+					Depth = 2.22,
+					MessageText = nameof(TechMessageDto.MessageText),
+					AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem),
+					UserId = Guid.NewGuid()
+				}
+			};
+
+
+			//act
+			var response = await techMessagesClient.InsertRange(dtos, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.Created, response.StatusCode);
+			Assert.Equal(dtos.Count, response.Content);
+
+			return dtos;
+		}
+	}
+}
diff --git a/Persistence.IntegrationTests/Extensions/EFCoreExtensions.cs b/Persistence.IntegrationTests/Extensions/EFCoreExtensions.cs
new file mode 100644
index 0000000..6b09587
--- /dev/null
+++ b/Persistence.IntegrationTests/Extensions/EFCoreExtensions.cs
@@ -0,0 +1,14 @@
+using Persistence.Database.Model;
+
+namespace Persistence.IntegrationTests.Extensions;
+
+public static class EFCoreExtensions
+{
+	public static void CleanupDbSet<T>(this PersistenceDbContext dbContext)
+	   where T : class
+	{
+		var dbset = dbContext.Set<T>();
+		dbset.RemoveRange(dbset);
+		dbContext.SaveChanges();
+	}
+}
diff --git a/Persistence.Repository/Data/SetpointDto.cs b/Persistence.Repository/Data/SetpointDto.cs
deleted file mode 100644
index 4a20aa4..0000000
--- a/Persistence.Repository/Data/SetpointDto.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Persistence.Repository.Data
-{
-	/// <summary>
-	/// Модель для работы с уставкой
-	/// </summary>
-	public class SetpointDto
-	{
-		/// <summary>
-		/// Идентификатор уставки
-		/// </summary>
-		public int Id { get; set; }
-
-		/// <summary>
-		/// Значение уставки
-		/// </summary>
-		public required object Value { get; set; }
-
-		/// <summary>
-		/// Дата сохранения уставки
-		/// </summary>
-		public DateTimeOffset Edit { get; set; }
-
-		/// <summary>
-		/// Ключ пользователя
-		/// </summary>
-		public int IdUser { get; set; }
-	}
-}
diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs
index f5ddb35..9abda86 100644
--- a/Persistence.Repository/Repositories/TechMessagesRepository.cs
+++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs
@@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore;
 using Persistence.Database.Entity;
 using Persistence.Models;
 using Persistence.Repositories;
+using Persistence.Repository.Extensions;
 
 namespace Persistence.Repository.Repositories
 {
diff --git a/Persistence/Models/TechMessageDto.cs b/Persistence/Models/TechMessageDto.cs
index 274a1d5..625b22f 100644
--- a/Persistence/Models/TechMessageDto.cs
+++ b/Persistence/Models/TechMessageDto.cs
@@ -1,5 +1,8 @@
 namespace Persistence.Models
 {
+	/// <summary>
+	/// Модель технологического сообщения
+	/// </summary>
 	public class TechMessageDto
 	{
 		/// <summary>
-- 
2.45.2


From 81474ea297cf252ee3c6bd7ff1132eaded62db42 Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Tue, 26 Nov 2024 14:07:36 +0500
Subject: [PATCH 04/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?=
 =?UTF-8?q?=D1=82=D1=8C=20=D0=BA=D0=B0=D1=82=D0=B5=D0=B3=D0=BE=D1=80=D0=B8?=
 =?UTF-8?q?=D0=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Controllers/TechMessagesController.cs         | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs
index 47d79d5..676ae8e 100644
--- a/Persistence.API/Controllers/TechMessagesController.cs
+++ b/Persistence.API/Controllers/TechMessagesController.cs
@@ -48,5 +48,20 @@ namespace Persistence.API.Controllers
 
 			return CreatedAtAction(nameof(InsertRange), result);
 		}
+
+		[HttpGet("categories")]
+		public ActionResult<Dictionary<int, string>> GetImportantCategories()
+		{
+			var result = new Dictionary<int, string>()
+			{
+				{ 0, "System" },
+				{ 1, "Авария" },
+				{ 2, "Предупреждение" },
+				{ 3, "Инфо" },
+				{ 4, "Прочее" }
+			};
+
+			return Ok(result);
+		}
 	}
 }
-- 
2.45.2


From 0234f2096d53e730518d07a92dbee6db7b1b2dec Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Wed, 27 Nov 2024 10:45:31 +0500
Subject: [PATCH 05/12] =?UTF-8?q?=D0=A4=D0=B8=D0=BA=D1=81=20=D0=B0=D0=B2?=
 =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D1=87?=
 =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B7=20Keycloak?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Persistence.API/Persistence.API.csproj | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/Persistence.API/Persistence.API.csproj b/Persistence.API/Persistence.API.csproj
index 2b8cb73..c14b509 100644
--- a/Persistence.API/Persistence.API.csproj
+++ b/Persistence.API/Persistence.API.csproj
@@ -8,7 +8,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
+    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.11" />
+    <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
     <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.6" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
   </ItemGroup>
-- 
2.45.2


From dc66522c0ff4c8f7c073ed0b2a07bbe8d5773341 Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Wed, 27 Nov 2024 13:08:06 +0500
Subject: [PATCH 06/12] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?=
 =?UTF-8?q?=D1=82=D1=8C=20DocumentationFile,=20=D0=BE=D0=BF=D0=B8=D1=81?=
 =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE?=
 =?UTF-8?q?=D0=BB=D0=BB=D0=B5=D1=80=D0=BE=D0=B2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Controllers/DataSaubController.cs         |   4 +
 .../Controllers/SetpointController.cs         | 100 ++++++++-----
 .../Controllers/TechMessagesController.cs     | 140 +++++++++++-------
 .../Controllers/TimeSeriesController.cs       |  40 ++++-
 Persistence.API/DependencyInjection.cs        |  10 +-
 Persistence.API/Persistence.API.csproj        |   2 +
 6 files changed, 193 insertions(+), 103 deletions(-)

diff --git a/Persistence.API/Controllers/DataSaubController.cs b/Persistence.API/Controllers/DataSaubController.cs
index 63069a9..437aee3 100644
--- a/Persistence.API/Controllers/DataSaubController.cs
+++ b/Persistence.API/Controllers/DataSaubController.cs
@@ -4,6 +4,10 @@ using Persistence.Repositories;
 using Persistence.Repository.Data;
 
 namespace Persistence.API.Controllers;
+
+/// <summary>
+/// ������ � ���������� �������
+/// </summary>
 [ApiController]
 [Authorize]
 [Route("api/[controller]")]
diff --git a/Persistence.API/Controllers/SetpointController.cs b/Persistence.API/Controllers/SetpointController.cs
index 9a6bd61..5a5cb52 100644
--- a/Persistence.API/Controllers/SetpointController.cs
+++ b/Persistence.API/Controllers/SetpointController.cs
@@ -3,51 +3,79 @@ using Microsoft.AspNetCore.Mvc;
 using Persistence.Models;
 using Persistence.Repositories;
 
-namespace Persistence.API.Controllers
+namespace Persistence.API.Controllers;
+
+/// <summary>
+/// Работа с уставками
+/// </summary>
+[ApiController]
+[Authorize]
+[Route("api/[controller]")]
+public class SetpointController : ControllerBase, ISetpointApi
 {
-	[ApiController]
-	[Authorize]
-	[Route("api/[controller]")]
-	public class SetpointController : ControllerBase, ISetpointApi
+	private readonly ISetpointRepository setpointRepository;
+
+	public SetpointController(ISetpointRepository setpointRepository)
 	{
-		private readonly ISetpointRepository setpointRepository;
+		this.setpointRepository = setpointRepository;
+	}
 
-		public SetpointController(ISetpointRepository setpointRepository)
-		{
-			this.setpointRepository = setpointRepository;
-		}
+	/// <summary>
+	/// Получить актуальные значения уставок
+	/// </summary>
+	/// <param name="setpointKeys"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("current")]
+	public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetCurrent([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token)
+	{
+		var result = await setpointRepository.GetCurrent(setpointKeys, token);
 
-		[HttpGet("current")]
-		public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetCurrent([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token)
-		{
-			var result = await setpointRepository.GetCurrent(setpointKeys, token);
+		return Ok(result);
+	}
 
-			return Ok(result);
-		}
+	/// <summary>
+	/// Получить значения уставок за определенный момент времени
+	/// </summary>
+	/// <param name="setpointKeys"></param>
+	/// <param name="historyMoment"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("history")]
+	public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetHistory([FromQuery] IEnumerable<Guid> setpointKeys, [FromQuery] DateTimeOffset historyMoment, CancellationToken token)
+	{
+		var result = await setpointRepository.GetHistory(setpointKeys, historyMoment, token);
 
-		[HttpGet("history")]
-		public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetHistory([FromQuery] IEnumerable<Guid> setpointKeys, [FromQuery] DateTimeOffset historyMoment, CancellationToken token)
-		{
-			var result = await setpointRepository.GetHistory(setpointKeys, historyMoment, token);
+		return Ok(result);
+	}
 
-			return Ok(result);
-		}
+	/// <summary>
+	/// Получить историю изменений значений уставок
+	/// </summary>
+	/// <param name="setpointKeys"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("log")]
+	public async Task<ActionResult<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token)
+	{
+		var result = await setpointRepository.GetLog(setpointKeys, token);
 
-		[HttpGet("log")]
-		public async Task<ActionResult<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token)
-		{
-			var result = await setpointRepository.GetLog(setpointKeys, token);
+		return Ok(result);
+	}
 
-			return Ok(result);
-		}
+	/// <summary>
+	/// Сохранить уставку
+	/// </summary>
+	/// <param name="setpointKey"></param>
+	/// <param name="newValue"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpPost]
+	public async Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token)
+	{
+		// ToDo: вычитка idUser
+		await setpointRepository.Save(setpointKey, newValue, 0, token);
 
-		[HttpPost]
-		public async Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token)
-		{
-			// ToDo: вычитка idUser
-			await setpointRepository.Save(setpointKey, newValue, 0, token);
-
-			return Ok();
-		}
+		return Ok();
 	}
 }
diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs
index 676ae8e..7411c19 100644
--- a/Persistence.API/Controllers/TechMessagesController.cs
+++ b/Persistence.API/Controllers/TechMessagesController.cs
@@ -3,65 +3,95 @@ using Microsoft.AspNetCore.Mvc;
 using Persistence.Models;
 using Persistence.Repositories;
 
-namespace Persistence.API.Controllers
+namespace Persistence.API.Controllers;
+
+/// <summary>
+/// Работа с состояниями систем автобурения (АБ)
+/// </summary>
+[ApiController]
+[Authorize]
+[Route("api/[controller]")]
+public class TechMessagesController : ControllerBase, ITechMessages
 {
-	[ApiController]
-	[Authorize]
-	[Route("api/[controller]")]
-	public class TechMessagesController : ControllerBase, ITechMessages
+	private readonly ITechMessagesRepository techMessagesRepository;
+
+	public TechMessagesController(ITechMessagesRepository techMessagesRepository)
 	{
-		private readonly ITechMessagesRepository techMessagesRepository;
+		this.techMessagesRepository = techMessagesRepository;
+	}
 
-		public TechMessagesController(ITechMessagesRepository techMessagesRepository)
+	/// <summary>
+	/// Получить список технологических сообщений в виде страницы
+	/// </summary>
+	/// <param name="request"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet]
+	public async Task<ActionResult<PaginationContainer<TechMessageDto>>> GetPage([FromQuery] RequestDto request, CancellationToken token)
+	{
+		var result = await techMessagesRepository.GetPage(request, token);
+
+		return Ok(result);
+	}
+
+	/// <summary>
+	/// Получить статистику по системам
+	/// </summary>
+	/// <param name="importantId"></param>
+	/// <param name="autoDrillingSystem"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("statistics")]
+	public async Task<ActionResult<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
+	{
+		var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token);
+
+		return Ok(result);
+	}
+
+	/// <summary>
+	/// Получить список всех систем
+	/// </summary>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("systems")]
+	public async Task<ActionResult<IEnumerable<string>>> GetSystems(CancellationToken token)
+	{
+		var result = await techMessagesRepository.GetSystems(token);
+
+		return Ok(result);
+	}
+
+	/// <summary>
+	/// Добавить новые технологические сообщения
+	/// </summary>
+	/// <param name="dtos"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpPost]
+	public async Task<ActionResult<int>> InsertRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
+	{
+		var result = await techMessagesRepository.InsertRange(dtos, token);
+
+		return CreatedAtAction(nameof(InsertRange), result);
+	}
+
+	/// <summary>
+	/// Получить словарь категорий
+	/// </summary>
+	/// <returns></returns>
+	[HttpGet("categories")]
+	public ActionResult<Dictionary<int, string>> GetImportantCategories()
+	{
+		var result = new Dictionary<int, string>()
 		{
-			this.techMessagesRepository = techMessagesRepository;
-		}
+			{ 0, "System" },
+			{ 1, "Авария" },
+			{ 2, "Предупреждение" },
+			{ 3, "Инфо" },
+			{ 4, "Прочее" }
+		};
 
-		[HttpGet]
-		public async Task<ActionResult<PaginationContainer<TechMessageDto>>> GetPage([FromQuery] RequestDto request, CancellationToken token)
-		{
-			var result = await techMessagesRepository.GetPage(request, token);
-
-			return Ok(result);
-		}
-
-		[HttpGet("statistics")]
-		public async Task<ActionResult<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
-		{
-			var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token);
-
-			return Ok(result);
-		}
-
-		[HttpGet("systems")]
-		public async Task<ActionResult<IEnumerable<string>>> GetSystems(CancellationToken token)
-		{
-			var result = await techMessagesRepository.GetSystems(token);
-
-			return Ok(result);
-		}
-
-		[HttpPost]
-		public async Task<ActionResult<int>> InsertRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
-		{
-			var result = await techMessagesRepository.InsertRange(dtos, token);
-
-			return CreatedAtAction(nameof(InsertRange), result);
-		}
-
-		[HttpGet("categories")]
-		public ActionResult<Dictionary<int, string>> GetImportantCategories()
-		{
-			var result = new Dictionary<int, string>()
-			{
-				{ 0, "System" },
-				{ 1, "Авария" },
-				{ 2, "Предупреждение" },
-				{ 3, "Инфо" },
-				{ 4, "Прочее" }
-			};
-
-			return Ok(result);
-		}
+		return Ok(result);
 	}
 }
diff --git a/Persistence.API/Controllers/TimeSeriesController.cs b/Persistence.API/Controllers/TimeSeriesController.cs
index a4a860a..a4f00f3 100644
--- a/Persistence.API/Controllers/TimeSeriesController.cs
+++ b/Persistence.API/Controllers/TimeSeriesController.cs
@@ -4,6 +4,7 @@ using Persistence.Models;
 using Persistence.Repositories;
 
 namespace Persistence.API.Controllers;
+
 [ApiController]
 [Authorize]
 [Route("api/[controller]")]
@@ -12,12 +13,18 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
 {
     private ITimeSeriesDataRepository<TDto> timeSeriesDataRepository;
 
-    public TimeSeriesController(ITimeSeriesDataRepository<TDto> timeSeriesDataRepository)
-    {
-        this.timeSeriesDataRepository = timeSeriesDataRepository;
+	public TimeSeriesController(ITimeSeriesDataRepository<TDto> timeSeriesDataRepository)
+	{
+		this.timeSeriesDataRepository = timeSeriesDataRepository;
     }
 
-    [HttpGet]
+	/// <summary>
+	/// �������� ������ ��������, ��������������� ��������� ���
+	/// </summary>
+	/// <param name="dateBegin"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet]
     [ProducesResponseType(StatusCodes.Status200OK)]
     public async Task<IActionResult> Get(DateTimeOffset dateBegin, CancellationToken token)
     {
@@ -25,21 +32,40 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
         return Ok(result);
     }
 
-    [HttpGet("datesRange")]
+	/// <summary>
+	/// �������� �������� ���, ��� ������� ���� ������ � �����������
+	/// </summary>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("datesRange")]
     public async Task<IActionResult> GetDatesRange(CancellationToken token)
     {
         var result = await this.timeSeriesDataRepository.GetDatesRange(token);
         return Ok(result);
     }
 
-    [HttpGet("resampled")]
+	/// <summary>
+	/// �������� ������ �������� � �������������, ��������������� ��������� ���
+	/// </summary>
+	/// <param name="dateBegin"></param>
+	/// <param name="intervalSec"></param>
+	/// <param name="approxPointsCount"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("resampled")]
     public async Task<IActionResult> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default)
     {
         var result = await this.timeSeriesDataRepository.GetResampledData(dateBegin, intervalSec, approxPointsCount, token);
         return Ok(result);
     }
 
-    [HttpPost]
+	/// <summary>
+	/// �������� ������
+	/// </summary>
+	/// <param name="dtos"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpPost]
     public async Task<IActionResult> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
     {
         var result = await this.timeSeriesDataRepository.InsertRange(dtos, token);
diff --git a/Persistence.API/DependencyInjection.cs b/Persistence.API/DependencyInjection.cs
index cdfca4c..5d7c646 100644
--- a/Persistence.API/DependencyInjection.cs
+++ b/Persistence.API/DependencyInjection.cs
@@ -1,3 +1,4 @@
+using System.Reflection;
 using System.Text.Json.Nodes;
 using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.IdentityModel.Tokens;
@@ -38,11 +39,10 @@ public static class DependencyInjection
 				c.AddKeycloackSecurity(configuration);
 			else c.AddDefaultSecurity(configuration);
 
-			//var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
-			//var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
-			//var includeControllerXmlComment = true;
-			//options.IncludeXmlComments(xmlPath, includeControllerXmlComment);
-			//options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "AsbCloudApp.xml"), includeControllerXmlComment);
+			var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
+			var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
+			var includeControllerXmlComment = true;
+			c.IncludeXmlComments(xmlPath, includeControllerXmlComment);
 		});
     }
 
diff --git a/Persistence.API/Persistence.API.csproj b/Persistence.API/Persistence.API.csproj
index c14b509..dfff363 100644
--- a/Persistence.API/Persistence.API.csproj
+++ b/Persistence.API/Persistence.API.csproj
@@ -5,6 +5,8 @@
     <Nullable>enable</Nullable>
     <ImplicitUsings>enable</ImplicitUsings>
     <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
+	<NoWarn>$(NoWarn);1591</NoWarn>
   </PropertyGroup>
 
   <ItemGroup>
-- 
2.45.2


From 1e87523ab948ce168385456cf1ad9ee6dcd6b9e0 Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Thu, 28 Nov 2024 08:55:50 +0500
Subject: [PATCH 07/12] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20?=
 =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?=
 =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D1=80=D0=B5=D0=B2?=
 =?UTF-8?q?=D1=8C=D1=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Controllers/TechMessagesController.cs     |  6 +-
 Persistence.API/DependencyInjection.cs        |  9 ++
 Persistence.API/Startup.cs                    |  3 +
 .../20241126044756_TechMessageMigration.cs    | 59 -------------
 ...27123045_TechMessageMigration.Designer.cs} | 68 +++++++++++++--
 .../20241127123045_TechMessageMigration.cs    | 83 +++++++++++++++++++
 .../PersistenceDbContextModelSnapshot.cs      | 66 ++++++++++++++-
 Persistence.Database/Entity/ADSystem.cs       | 16 ++++
 Persistence.Database/Entity/TechMessage.cs    |  9 +-
 .../Controllers/TechMessagesControllerTest.cs | 24 +++---
 .../Repositories/TechMessagesRepository.cs    | 71 +++++++++++++---
 Persistence/API/ITechMessages.cs              | 35 --------
 Persistence/Models/ADSystemDto.cs             | 22 +++++
 Persistence/Models/TechMessageDto.cs          | 19 +++--
 .../Repositories/ITechMessagesRepository.cs   |  7 +-
 15 files changed, 355 insertions(+), 142 deletions(-)
 delete mode 100644 Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs
 rename Persistence.Database.Postgres/Migrations/{20241126044756_TechMessageMigration.Designer.cs => 20241127123045_TechMessageMigration.Designer.cs} (71%)
 create mode 100644 Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs
 create mode 100644 Persistence.Database/Entity/ADSystem.cs
 delete mode 100644 Persistence/API/ITechMessages.cs
 create mode 100644 Persistence/Models/ADSystemDto.cs

diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs
index 7411c19..fb3315e 100644
--- a/Persistence.API/Controllers/TechMessagesController.cs
+++ b/Persistence.API/Controllers/TechMessagesController.cs
@@ -11,7 +11,7 @@ namespace Persistence.API.Controllers;
 [ApiController]
 [Authorize]
 [Route("api/[controller]")]
-public class TechMessagesController : ControllerBase, ITechMessages
+public class TechMessagesController : ControllerBase
 {
 	private readonly ITechMessagesRepository techMessagesRepository;
 
@@ -41,8 +41,8 @@ public class TechMessagesController : ControllerBase, ITechMessages
 	/// <param name="autoDrillingSystem"></param>
 	/// <param name="token"></param>
 	/// <returns></returns>
-	[HttpGet("statistics")]
-	public async Task<ActionResult<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
+	[HttpGet("statistics/{autoDrillingSystem}")]
+	public async Task<ActionResult<int>> GetStatistics([FromRoute] string? autoDrillingSystem, int? importantId, CancellationToken token)
 	{
 		var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token);
 
diff --git a/Persistence.API/DependencyInjection.cs b/Persistence.API/DependencyInjection.cs
index 5d7c646..19cedc9 100644
--- a/Persistence.API/DependencyInjection.cs
+++ b/Persistence.API/DependencyInjection.cs
@@ -1,9 +1,12 @@
 using System.Reflection;
 using System.Text.Json.Nodes;
+using Mapster;
 using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.IdentityModel.Tokens;
 using Microsoft.OpenApi.Any;
 using Microsoft.OpenApi.Models;
+using Persistence.Database.Entity;
+using Persistence.Models;
 using Persistence.Models.Configurations;
 using Swashbuckle.AspNetCore.SwaggerGen;
 
@@ -11,6 +14,12 @@ namespace Persistence.API;
 
 public static class DependencyInjection
 {
+	public static void MapsterSetup()
+	{
+		TypeAdapterConfig.GlobalSettings.Default.Config
+			.ForType<TechMessageDto, TechMessage>()
+			.Ignore(dest => dest.System, dest => dest.SystemId);
+	}
     public static void AddSwagger(this IServiceCollection services, IConfiguration configuration)
     {
         services.AddSwaggerGen(c =>
diff --git a/Persistence.API/Startup.cs b/Persistence.API/Startup.cs
index e074845..98ad4aa 100644
--- a/Persistence.API/Startup.cs
+++ b/Persistence.API/Startup.cs
@@ -23,6 +23,9 @@ public class Startup
         services.AddInfrastructure();
         services.AddPersistenceDbContext(Configuration);
         services.AddJWTAuthentication(Configuration);
+        services.AddMemoryCache();
+
+        DependencyInjection.MapsterSetup();
     }
 
     public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
diff --git a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs
deleted file mode 100644
index 46a71a0..0000000
--- a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace Persistence.Database.Postgres.Migrations
-{
-    /// <inheritdoc />
-    public partial class TechMessageMigration : Migration
-    {
-        /// <inheritdoc />
-        protected override void Up(MigrationBuilder migrationBuilder)
-        {
-            migrationBuilder.AlterColumn<DateTimeOffset>(
-                name: "Created",
-                table: "Setpoint",
-                type: "timestamp with time zone",
-                nullable: false,
-                comment: "Дата создания уставки",
-                oldClrType: typeof(DateTimeOffset),
-                oldType: "timestamp with time zone",
-                oldComment: "Дата изменения уставки");
-
-            migrationBuilder.CreateTable(
-                name: "TechMessage",
-                columns: table => new
-                {
-                    EventId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id события"),
-                    ImportantId = table.Column<int>(type: "integer", nullable: false, comment: "Id Категории важности"),
-                    OccurrenceDate = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"),
-                    Depth = table.Column<double>(type: "double precision", nullable: true, comment: "Глубина забоя"),
-                    MessageText = table.Column<string>(type: "varchar(512)", nullable: true, comment: "Текст сообщения"),
-                    AutoDrillingSystem = table.Column<string>(type: "varchar(256)", nullable: true, comment: "Система автобурения, к которой относится сообщение"),
-                    UserId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id пользователя за пультом бурильщика")
-                },
-                constraints: table =>
-                {
-                    table.PrimaryKey("PK_TechMessage", x => x.EventId);
-                });
-        }
-
-        /// <inheritdoc />
-        protected override void Down(MigrationBuilder migrationBuilder)
-        {
-            migrationBuilder.DropTable(
-                name: "TechMessage");
-
-            migrationBuilder.AlterColumn<DateTimeOffset>(
-                name: "Created",
-                table: "Setpoint",
-                type: "timestamp with time zone",
-                nullable: false,
-                comment: "Дата изменения уставки",
-                oldClrType: typeof(DateTimeOffset),
-                oldType: "timestamp with time zone",
-                oldComment: "Дата создания уставки");
-        }
-    }
-}
diff --git a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs
similarity index 71%
rename from Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs
rename to Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs
index 5d6817e..2e5a30a 100644
--- a/Persistence.Database.Postgres/Migrations/20241126044756_TechMessageMigration.Designer.cs
+++ b/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs
@@ -12,7 +12,7 @@ using Persistence.Database.Model;
 namespace Persistence.Database.Postgres.Migrations
 {
     [DbContext(typeof(PersistenceDbContext))]
-    [Migration("20241126044756_TechMessageMigration")]
+    [Migration("20241127123045_TechMessageMigration")]
     partial class TechMessageMigration
     {
         /// <inheritdoc />
@@ -27,6 +27,27 @@ namespace Persistence.Database.Postgres.Migrations
             NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 
+            modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b =>
+                {
+                    b.Property<Guid>("SystemId")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasComment("Id системы автобурения");
+
+                    b.Property<string>("Description")
+                        .HasColumnType("text")
+                        .HasComment("Описание системы автобурения");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("varchar(256)")
+                        .HasComment("Наименование системы автобурения");
+
+                    b.HasKey("SystemId");
+
+                    b.ToTable("ADSystem");
+                });
+
             modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
                 {
                     b.Property<Guid>("EventId")
@@ -34,10 +55,6 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasColumnType("uuid")
                         .HasComment("Id события");
 
-                    b.Property<string>("AutoDrillingSystem")
-                        .HasColumnType("varchar(256)")
-                        .HasComment("Система автобурения, к которой относится сообщение");
-
                     b.Property<double?>("Depth")
                         .HasColumnType("double precision")
                         .HasComment("Глубина забоя");
@@ -47,6 +64,7 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasComment("Id Категории важности");
 
                     b.Property<string>("MessageText")
+                        .IsRequired()
                         .HasColumnType("varchar(512)")
                         .HasComment("Текст сообщения");
 
@@ -54,15 +72,44 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasColumnType("timestamp with time zone")
                         .HasComment("Дата возникновения");
 
+                    b.Property<Guid>("SystemId")
+                        .HasColumnType("uuid")
+                        .HasComment("Id системы автобурения, к которой относится сообщение");
+
                     b.Property<Guid>("UserId")
                         .HasColumnType("uuid")
                         .HasComment("Id пользователя за пультом бурильщика");
 
                     b.HasKey("EventId");
 
+                    b.HasIndex("SystemId");
+
                     b.ToTable("TechMessage");
                 });
 
+            modelBuilder.Entity("Persistence.Database.Entity.TimestampedSet", b =>
+                {
+                    b.Property<Guid>("IdDiscriminator")
+                        .HasColumnType("uuid")
+                        .HasComment("Дискриминатор ссылка на тип сохраняемых данных");
+
+                    b.Property<DateTimeOffset>("Timestamp")
+                        .HasColumnType("timestamp with time zone")
+                        .HasComment("Отметка времени, строго в UTC");
+
+                    b.Property<string>("Set")
+                        .IsRequired()
+                        .HasColumnType("jsonb")
+                        .HasComment("Набор сохраняемых данных");
+
+                    b.HasKey("IdDiscriminator", "Timestamp");
+
+                    b.ToTable("TimestampedSets", t =>
+                        {
+                            t.HasComment("Общая таблица данных временных рядов");
+                        });
+                });
+
             modelBuilder.Entity("Persistence.Database.Model.DataSaub", b =>
                 {
                     b.Property<DateTimeOffset>("Date")
@@ -169,6 +216,17 @@ namespace Persistence.Database.Postgres.Migrations
 
                     b.ToTable("Setpoint");
                 });
+
+            modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
+                {
+                    b.HasOne("Persistence.Database.Entity.ADSystem", "System")
+                        .WithMany()
+                        .HasForeignKey("SystemId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("System");
+                });
 #pragma warning restore 612, 618
         }
     }
diff --git a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs
new file mode 100644
index 0000000..d3a5ac2
--- /dev/null
+++ b/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs
@@ -0,0 +1,83 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Persistence.Database.Postgres.Migrations
+{
+    /// <inheritdoc />
+    public partial class TechMessageMigration : Migration
+    {
+        /// <inheritdoc />
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.CreateTable(
+                name: "ADSystem",
+                columns: table => new
+                {
+                    SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы автобурения"),
+                    Name = table.Column<string>(type: "varchar(256)", nullable: false, comment: "Наименование системы автобурения"),
+                    Description = table.Column<string>(type: "text", nullable: true, comment: "Описание системы автобурения")
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_ADSystem", x => x.SystemId);
+                });
+
+            migrationBuilder.CreateTable(
+                name: "TimestampedSets",
+                columns: table => new
+                {
+                    IdDiscriminator = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор ссылка на тип сохраняемых данных"),
+                    Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Отметка времени, строго в UTC"),
+                    Set = table.Column<string>(type: "jsonb", nullable: false, comment: "Набор сохраняемых данных")
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_TimestampedSets", x => new { x.IdDiscriminator, x.Timestamp });
+                },
+                comment: "Общая таблица данных временных рядов");
+
+            migrationBuilder.CreateTable(
+                name: "TechMessage",
+                columns: table => new
+                {
+                    EventId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id события"),
+                    ImportantId = table.Column<int>(type: "integer", nullable: false, comment: "Id Категории важности"),
+                    OccurrenceDate = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"),
+                    Depth = table.Column<double>(type: "double precision", nullable: true, comment: "Глубина забоя"),
+                    MessageText = table.Column<string>(type: "varchar(512)", nullable: false, comment: "Текст сообщения"),
+                    SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы автобурения, к которой относится сообщение"),
+                    UserId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id пользователя за пультом бурильщика")
+                },
+                constraints: table =>
+                {
+                    table.PrimaryKey("PK_TechMessage", x => x.EventId);
+                    table.ForeignKey(
+                        name: "FK_TechMessage_ADSystem_SystemId",
+                        column: x => x.SystemId,
+                        principalTable: "ADSystem",
+                        principalColumn: "SystemId",
+                        onDelete: ReferentialAction.Cascade);
+                });
+
+            migrationBuilder.CreateIndex(
+                name: "IX_TechMessage_SystemId",
+                table: "TechMessage",
+                column: "SystemId");
+        }
+
+        /// <inheritdoc />
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropTable(
+                name: "TechMessage");
+
+            migrationBuilder.DropTable(
+                name: "TimestampedSets");
+
+            migrationBuilder.DropTable(
+                name: "ADSystem");
+        }
+    }
+}
diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
index 4446a1c..317c445 100644
--- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
+++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
@@ -24,6 +24,27 @@ namespace Persistence.Database.Postgres.Migrations
             NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 
+            modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b =>
+                {
+                    b.Property<Guid>("SystemId")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid")
+                        .HasComment("Id системы автобурения");
+
+                    b.Property<string>("Description")
+                        .HasColumnType("text")
+                        .HasComment("Описание системы автобурения");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("varchar(256)")
+                        .HasComment("Наименование системы автобурения");
+
+                    b.HasKey("SystemId");
+
+                    b.ToTable("ADSystem");
+                });
+
             modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
                 {
                     b.Property<Guid>("EventId")
@@ -31,10 +52,6 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasColumnType("uuid")
                         .HasComment("Id события");
 
-                    b.Property<string>("AutoDrillingSystem")
-                        .HasColumnType("varchar(256)")
-                        .HasComment("Система автобурения, к которой относится сообщение");
-
                     b.Property<double?>("Depth")
                         .HasColumnType("double precision")
                         .HasComment("Глубина забоя");
@@ -44,6 +61,7 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasComment("Id Категории важности");
 
                     b.Property<string>("MessageText")
+                        .IsRequired()
                         .HasColumnType("varchar(512)")
                         .HasComment("Текст сообщения");
 
@@ -51,15 +69,44 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasColumnType("timestamp with time zone")
                         .HasComment("Дата возникновения");
 
+                    b.Property<Guid>("SystemId")
+                        .HasColumnType("uuid")
+                        .HasComment("Id системы автобурения, к которой относится сообщение");
+
                     b.Property<Guid>("UserId")
                         .HasColumnType("uuid")
                         .HasComment("Id пользователя за пультом бурильщика");
 
                     b.HasKey("EventId");
 
+                    b.HasIndex("SystemId");
+
                     b.ToTable("TechMessage");
                 });
 
+            modelBuilder.Entity("Persistence.Database.Entity.TimestampedSet", b =>
+                {
+                    b.Property<Guid>("IdDiscriminator")
+                        .HasColumnType("uuid")
+                        .HasComment("Дискриминатор ссылка на тип сохраняемых данных");
+
+                    b.Property<DateTimeOffset>("Timestamp")
+                        .HasColumnType("timestamp with time zone")
+                        .HasComment("Отметка времени, строго в UTC");
+
+                    b.Property<string>("Set")
+                        .IsRequired()
+                        .HasColumnType("jsonb")
+                        .HasComment("Набор сохраняемых данных");
+
+                    b.HasKey("IdDiscriminator", "Timestamp");
+
+                    b.ToTable("TimestampedSets", t =>
+                        {
+                            t.HasComment("Общая таблица данных временных рядов");
+                        });
+                });
+
             modelBuilder.Entity("Persistence.Database.Model.DataSaub", b =>
                 {
                     b.Property<DateTimeOffset>("Date")
@@ -166,6 +213,17 @@ namespace Persistence.Database.Postgres.Migrations
 
                     b.ToTable("Setpoint");
                 });
+
+            modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
+                {
+                    b.HasOne("Persistence.Database.Entity.ADSystem", "System")
+                        .WithMany()
+                        .HasForeignKey("SystemId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("System");
+                });
 #pragma warning restore 612, 618
         }
     }
diff --git a/Persistence.Database/Entity/ADSystem.cs b/Persistence.Database/Entity/ADSystem.cs
new file mode 100644
index 0000000..525134c
--- /dev/null
+++ b/Persistence.Database/Entity/ADSystem.cs
@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using Microsoft.EntityFrameworkCore;
+
+namespace Persistence.Database.Entity;
+public class ADSystem
+{
+	[Key, Comment("Id системы автобурения")]
+	public Guid SystemId { get; set; }
+
+	[Required, Column(TypeName = "varchar(256)"), Comment("Наименование системы автобурения")]
+	public required string Name { get; set; }
+
+	[Comment("Описание системы автобурения")]
+	public string? Description { get; set; }
+}
diff --git a/Persistence.Database/Entity/TechMessage.cs b/Persistence.Database/Entity/TechMessage.cs
index b4135c1..dce897c 100644
--- a/Persistence.Database/Entity/TechMessage.cs
+++ b/Persistence.Database/Entity/TechMessage.cs
@@ -19,10 +19,13 @@ namespace Persistence.Database.Entity
 		public double? Depth { get; set; }
 
 		[Column(TypeName = "varchar(512)"), Comment("Текст сообщения")]
-		public string? MessageText { get; set; }
+		public required string MessageText { get; set; }
 
-		[Column(TypeName = "varchar(256)"), Comment("Система автобурения, к которой относится сообщение")]
-		public string? AutoDrillingSystem { get; set; }
+		[Required, Comment("Id системы автобурения, к которой относится сообщение")]
+		public required Guid SystemId { get; set; }
+
+		[Required, ForeignKey(nameof(SystemId)), Comment("Система автобурения, к которой относится сообщение")]
+		public virtual required ADSystem System { get; set; }
 
 		[Comment("Id пользователя за пультом бурильщика")]
 		public Guid UserId { get; set; }
diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
index 9eb0e9d..73414c4 100644
--- a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
+++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
@@ -29,7 +29,7 @@ namespace Persistence.IntegrationTests.Controllers
 			{
 				Skip = 1,
 				Take = 2,
-				SortSettings = nameof(TechMessageDto.ImportantId)
+				SortSettings = nameof(TechMessageDto.CategoryId)
 			};
 
 			//act
@@ -53,7 +53,7 @@ namespace Persistence.IntegrationTests.Controllers
 			{
 				Skip = 0,
 				Take = 2,
-				SortSettings = nameof(TechMessageDto.ImportantId)
+				SortSettings = nameof(TechMessageDto.CategoryId)
 			};
 
 			//act
@@ -90,7 +90,7 @@ namespace Persistence.IntegrationTests.Controllers
 			//arrange
 			var dtos = await InsertRange();
 			var systems = dtos
-				.Select(e => e.AutoDrillingSystem)
+				.Select(e => e.System)
 				.Distinct()
 				.ToArray();
 
@@ -110,7 +110,7 @@ namespace Persistence.IntegrationTests.Controllers
 			//arrange
 			dbContext.CleanupDbSet<TechMessage>();
 			var imortantId = 1;
-			var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem);
+			var autoDrillingSystem = nameof(TechMessageDto.System);
 
 			//act
 			var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken());
@@ -125,9 +125,9 @@ namespace Persistence.IntegrationTests.Controllers
 		{
 			//arrange
 			var imortantId = 1;
-			var autoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem);
+			var autoDrillingSystem = nameof(TechMessageDto.System);
 			var dtos = await InsertRange();
-			var filteredDtos = dtos.Where(e => e.ImportantId == imortantId && e.AutoDrillingSystem == e.AutoDrillingSystem);
+			var filteredDtos = dtos.Where(e => e.CategoryId == imortantId && e.System == e.System);
 
 			//act
 			var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken());
@@ -145,21 +145,21 @@ namespace Persistence.IntegrationTests.Controllers
 				new TechMessageDto()
 				{
 					EventId = Guid.NewGuid(),
-					ImportantId = 1,
-					OccurrenceDate = DateTimeOffset.UtcNow,
+					CategoryId = 1,
+					Timestamp = DateTimeOffset.UtcNow,
 					Depth = 1.11,
 					MessageText = nameof(TechMessageDto.MessageText),
-					AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem),
+					System = nameof(TechMessageDto.System),
 					UserId = Guid.NewGuid()
 				},
 				new TechMessageDto()
 				{
 					EventId = Guid.NewGuid(),
-					ImportantId = 2,
-					OccurrenceDate = DateTimeOffset.UtcNow,
+					CategoryId = 2,
+					Timestamp = DateTimeOffset.UtcNow,
 					Depth = 2.22,
 					MessageText = nameof(TechMessageDto.MessageText),
-					AutoDrillingSystem = nameof(TechMessageDto.AutoDrillingSystem),
+					System = nameof(TechMessageDto.System),
 					UserId = Guid.NewGuid()
 				}
 			};
diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs
index 9abda86..427346b 100644
--- a/Persistence.Repository/Repositories/TechMessagesRepository.cs
+++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs
@@ -1,5 +1,7 @@
 using Mapster;
 using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Caching.Memory;
+using Newtonsoft.Json.Linq;
 using Persistence.Database.Entity;
 using Persistence.Models;
 using Persistence.Repositories;
@@ -9,9 +11,13 @@ namespace Persistence.Repository.Repositories
 {
 	public class TechMessagesRepository : ITechMessagesRepository
 	{
+		private static readonly string SystemCacheKey = $"{typeof(ADSystem).FullName}CacheKey";
+		private readonly IMemoryCache memoryCache;
 		private DbContext db;
-		public TechMessagesRepository(DbContext db)
+
+		public TechMessagesRepository(DbContext db, IMemoryCache memoryCache)
 		{
+			this.memoryCache = memoryCache;
 			this.db = db;
 		}
 
@@ -36,36 +42,75 @@ namespace Persistence.Repository.Repositories
 			return dto;
 		}
 
-		public async Task<int> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token)
+		public async Task<Dictionary<string, int>> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token)
 		{
 			var query = GetQueryReadOnly();
 			var count = await query
-				.Where(e => e.ImportantId == importantId && e.AutoDrillingSystem == autoDrillingSystem)
-				.CountAsync();
+				.Where(e => importantId == null || e.ImportantId == importantId)
+				.Where(e => autoDrillingSystem == null || e.System.Name == autoDrillingSystem)
+				.GroupBy(e => e.System.Name)
+				.ToDictionaryAsync(e => e.Key, v => v.Count());
 
 			return count;
 		}
 
-		public async Task<IEnumerable<string>> GetSystems(CancellationToken token)
+		public async Task<IEnumerable<ADSystemDto>> GetSystems(CancellationToken token)
 		{
-			var query = GetQueryReadOnly();
-			var entities = await query
-				.Select(e => e.AutoDrillingSystem ?? string.Empty)
-				.Distinct()
-				.ToArrayAsync(token);
-			var dtos = entities.Order();
+			var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f =>
+			{
+				f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
 
-			return dtos;
+				var query = db.Set<ADSystem>();
+				var entities = await query.ToListAsync();
+				var dtos = entities.Select(e => e.Adapt<ADSystemDto>());
+
+				return dtos;
+			});
+
+			return systems ?? [];
 		}
 
 		public async Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
 		{
-			var entities = dtos.Select(d => d.Adapt<TechMessage>());
+			var entities = dtos.Select(dto =>
+			{
+				var task = Task.Run(async () =>
+				{
+					var entity = dto.Adapt<TechMessage>();
+					var systems = await GetSystems(token);
+					var systemId = systems.FirstOrDefault(e => e.Name == dto.System)?.SystemId
+						?? await CreateSystem(dto.System);
+
+					entity.SystemId = systemId;
+
+					return entity;
+				});
+				task.Wait();
+
+				return task.Result;
+			});
 
 			await db.Set<TechMessage>().AddRangeAsync(entities, token);
 			var result = await db.SaveChangesAsync(token);
 
 			return result;
 		}
+
+		private async Task<Guid> CreateSystem(string name)
+		{
+			memoryCache.Remove(SystemCacheKey);
+
+			var systemId = Guid.NewGuid();
+			var entity = new ADSystem()
+			{
+				SystemId = systemId,
+				Name = name
+			};
+
+			await db.Set<ADSystem>().AddAsync(entity);
+			await db.SaveChangesAsync();
+
+			return Guid.NewGuid();
+		}
 	}
 }
diff --git a/Persistence/API/ITechMessages.cs b/Persistence/API/ITechMessages.cs
deleted file mode 100644
index 95af161..0000000
--- a/Persistence/API/ITechMessages.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Persistence.Models;
-
-namespace Persistence.API
-{
-	/// <summary>
-	/// Интерфейс для API сообщений о состояниях работы систем автобурения (АБ)
-	/// </summary>
-	public interface ITechMessages : ITableDataApi<TechMessageDto, RequestDto>
-	{
-		/// <summary>
-		/// Добавление новых сообщений
-		/// </summary>
-		/// <param name="dtos"></param>
-		/// <param name="token"></param>
-		/// <returns></returns>
-		Task<ActionResult<int>> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token);
-
-		/// <summary>
-		/// Получение списка систем АБ
-		/// </summary>
-		/// <param name="token"></param>
-		/// <returns></returns>
-		Task<ActionResult<IEnumerable<string>>> GetSystems(CancellationToken token);
-
-		/// <summary>
-		/// Получение статистики
-		/// </summary>
-		/// <param name="importantId">Id Категории важности</param>
-		/// <param name="autoDrillingSystem">Система АБ</param>
-		/// <param name="token"></param>
-		/// <returns></returns>
-		Task<ActionResult<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token); 
-	}
-}
diff --git a/Persistence/Models/ADSystemDto.cs b/Persistence/Models/ADSystemDto.cs
new file mode 100644
index 0000000..d11dfc7
--- /dev/null
+++ b/Persistence/Models/ADSystemDto.cs
@@ -0,0 +1,22 @@
+namespace Persistence.Models;
+
+/// <summary>
+/// Модель системы автобурения
+/// </summary>
+public class ADSystemDto
+{
+	/// <summary>
+	/// Ключ
+	/// </summary>
+	public Guid SystemId { get; set; }
+
+	/// <summary>
+	/// Наименование
+	/// </summary>
+	public required string Name { get; set; }
+
+	/// <summary>
+	/// Описание
+	/// </summary>
+	public string? Description { get; set; }
+}
diff --git a/Persistence/Models/TechMessageDto.cs b/Persistence/Models/TechMessageDto.cs
index 625b22f..84da656 100644
--- a/Persistence/Models/TechMessageDto.cs
+++ b/Persistence/Models/TechMessageDto.cs
@@ -1,4 +1,6 @@
-namespace Persistence.Models
+using System.ComponentModel.DataAnnotations;
+
+namespace Persistence.Models
 {
 	/// <summary>
 	/// Модель технологического сообщения
@@ -8,32 +10,39 @@
 		/// <summary>
 		/// Id события
 		/// </summary>
+		[Required]
 		public Guid EventId { get; set; }
 
 		/// <summary>
 		/// Id Категории важности
 		/// </summary>
-		public int ImportantId {  get; set; }
+		[Range(0, int.MaxValue, ErrorMessage = "Id Категории важности не может быть меньше 0")]
+		public int CategoryId {  get; set; }
 
 		/// <summary>
 		/// Дата возникновения
 		/// </summary>
-		public DateTimeOffset OccurrenceDate { get; set; }
+		public DateTimeOffset Timestamp { get; set; }
 
 		/// <summary>
 		/// Глубина забоя
 		/// </summary>
+		[Range(0, double.MaxValue, ErrorMessage = "Глубина забоя не может быть меньше 0")]
 		public double? Depth {  get; set; }
 
 		/// <summary>
 		/// Текст сообщения
 		/// </summary>
-		public string? MessageText { get; set; }
+		[Required]
+		[StringLength(512, MinimumLength = 1, ErrorMessage = "Допустимая длина текста сообщения от 1 до 512 символов")]
+		public required string MessageText { get; set; }
 
 		/// <summary>
 		/// Система автобурения, к которой относится сообщение
 		/// </summary>
-		public string? AutoDrillingSystem { get; set; }
+		[Required]
+		[StringLength(256, MinimumLength = 1, ErrorMessage = "Допустимая длина наименования системы АБ от 1 до 256 символов")]
+		public required string System { get; set; }
 
 		/// <summary>
 		/// Id пользователя за пультом бурильщика
diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs
index 1be90e9..b8fb194 100644
--- a/Persistence/Repositories/ITechMessagesRepository.cs
+++ b/Persistence/Repositories/ITechMessagesRepository.cs
@@ -1,4 +1,5 @@
-using Persistence.Models;
+using System.Threading.Tasks;
+using Persistence.Models;
 
 namespace Persistence.Repositories
 {
@@ -28,7 +29,7 @@ namespace Persistence.Repositories
 		/// </summary>
 		/// <param name="token"></param>
 		/// <returns></returns>
-		Task<IEnumerable<string>> GetSystems(CancellationToken token);
+		Task<IEnumerable<ADSystemDto>> GetSystems(CancellationToken token);
 
 		/// <summary>
 		/// Получение количества сообщений по категориям и системам автобурения
@@ -37,6 +38,6 @@ namespace Persistence.Repositories
 		/// <param name="autoDrillingSystem">Система автобурения</param>
 		/// <param name="token"></param>
 		/// <returns></returns>
-		Task<int> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token);
+		Task<Dictionary<string, int>> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token);
 	}
 }
-- 
2.45.2


From 097b422310ae70aef9c94c59d0e38fcc4535385f Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Thu, 28 Nov 2024 13:13:07 +0500
Subject: [PATCH 08/12] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?=
 =?UTF-8?q?=D1=82=D1=8C=20TechMessagesRepository,=20=D0=B4=D0=BE=D0=B1?=
 =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20=D1=82=D0=B5=D1=81=D1=82=D0=B8?=
 =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=B0=D0=BB?=
 =?UTF-8?q?=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D0=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Clients/ITechMessagesClient.cs            |  7 +-
 ...28074729_TechMessageMigration.Designer.cs} | 18 ++---
 ...=> 20241128074729_TechMessageMigration.cs} | 21 +-----
 .../PersistenceDbContextModelSnapshot.cs      | 16 ++---
 .../PersistenceDbContext.cs                   | 13 +++-
 Persistence.Database/Entity/TechMessage.cs    |  4 +-
 .../Controllers/TechMessagesControllerTest.cs | 44 ++++++++++--
 .../Repositories/TechMessagesRepository.cs    | 67 ++++++++++---------
 .../Repositories/ITechMessagesRepository.cs   |  2 +-
 9 files changed, 110 insertions(+), 82 deletions(-)
 rename Persistence.Database.Postgres/Migrations/{20241127123045_TechMessageMigration.Designer.cs => 20241128074729_TechMessageMigration.Designer.cs} (98%)
 rename Persistence.Database.Postgres/Migrations/{20241127123045_TechMessageMigration.cs => 20241128074729_TechMessageMigration.cs} (70%)

diff --git a/Persistence.Client/Clients/ITechMessagesClient.cs b/Persistence.Client/Clients/ITechMessagesClient.cs
index 1426b54..43839fe 100644
--- a/Persistence.Client/Clients/ITechMessagesClient.cs
+++ b/Persistence.Client/Clients/ITechMessagesClient.cs
@@ -1,4 +1,5 @@
-using Persistence.Models;
+using Microsoft.AspNetCore.Mvc;
+using Persistence.Models;
 using Refit;
 
 namespace Persistence.Client.Clients
@@ -19,7 +20,7 @@ namespace Persistence.Client.Clients
 		[Get($"{BaseRoute}/systems")]
 		Task<IApiResponse<IEnumerable<string>>> GetSystems(CancellationToken token);
 
-		[Get($"{BaseRoute}/statistics")]
-		Task<IApiResponse<int>> GetStatistics(int importantId, string autoDrillingSystem, CancellationToken token);
+		[Get($"{BaseRoute}/statistics/" + "{autoDrillingSystem}")]
+		Task<IApiResponse<int>> GetStatistics(string? autoDrillingSystem, int? importantId, CancellationToken token);
 	}
 }
diff --git a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs
similarity index 98%
rename from Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs
rename to Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs
index 2e5a30a..6678078 100644
--- a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.Designer.cs
+++ b/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs
@@ -12,7 +12,7 @@ using Persistence.Database.Model;
 namespace Persistence.Database.Postgres.Migrations
 {
     [DbContext(typeof(PersistenceDbContext))]
-    [Migration("20241127123045_TechMessageMigration")]
+    [Migration("20241128074729_TechMessageMigration")]
     partial class TechMessageMigration
     {
         /// <inheritdoc />
@@ -55,27 +55,27 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasColumnType("uuid")
                         .HasComment("Id события");
 
+                    b.Property<int>("CategoryId")
+                        .HasColumnType("integer")
+                        .HasComment("Id Категории важности");
+
                     b.Property<double?>("Depth")
                         .HasColumnType("double precision")
                         .HasComment("Глубина забоя");
 
-                    b.Property<int>("ImportantId")
-                        .HasColumnType("integer")
-                        .HasComment("Id Категории важности");
-
                     b.Property<string>("MessageText")
                         .IsRequired()
                         .HasColumnType("varchar(512)")
                         .HasComment("Текст сообщения");
 
-                    b.Property<DateTimeOffset>("OccurrenceDate")
-                        .HasColumnType("timestamp with time zone")
-                        .HasComment("Дата возникновения");
-
                     b.Property<Guid>("SystemId")
                         .HasColumnType("uuid")
                         .HasComment("Id системы автобурения, к которой относится сообщение");
 
+                    b.Property<DateTimeOffset>("Timestamp")
+                        .HasColumnType("timestamp with time zone")
+                        .HasComment("Дата возникновения");
+
                     b.Property<Guid>("UserId")
                         .HasColumnType("uuid")
                         .HasComment("Id пользователя за пультом бурильщика");
diff --git a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs
similarity index 70%
rename from Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs
rename to Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs
index d3a5ac2..047ad52 100644
--- a/Persistence.Database.Postgres/Migrations/20241127123045_TechMessageMigration.cs
+++ b/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs
@@ -24,27 +24,13 @@ namespace Persistence.Database.Postgres.Migrations
                     table.PrimaryKey("PK_ADSystem", x => x.SystemId);
                 });
 
-            migrationBuilder.CreateTable(
-                name: "TimestampedSets",
-                columns: table => new
-                {
-                    IdDiscriminator = table.Column<Guid>(type: "uuid", nullable: false, comment: "Дискриминатор ссылка на тип сохраняемых данных"),
-                    Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Отметка времени, строго в UTC"),
-                    Set = table.Column<string>(type: "jsonb", nullable: false, comment: "Набор сохраняемых данных")
-                },
-                constraints: table =>
-                {
-                    table.PrimaryKey("PK_TimestampedSets", x => new { x.IdDiscriminator, x.Timestamp });
-                },
-                comment: "Общая таблица данных временных рядов");
-
             migrationBuilder.CreateTable(
                 name: "TechMessage",
                 columns: table => new
                 {
                     EventId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id события"),
-                    ImportantId = table.Column<int>(type: "integer", nullable: false, comment: "Id Категории важности"),
-                    OccurrenceDate = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"),
+                    CategoryId = table.Column<int>(type: "integer", nullable: false, comment: "Id Категории важности"),
+                    Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата возникновения"),
                     Depth = table.Column<double>(type: "double precision", nullable: true, comment: "Глубина забоя"),
                     MessageText = table.Column<string>(type: "varchar(512)", nullable: false, comment: "Текст сообщения"),
                     SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы автобурения, к которой относится сообщение"),
@@ -73,9 +59,6 @@ namespace Persistence.Database.Postgres.Migrations
             migrationBuilder.DropTable(
                 name: "TechMessage");
 
-            migrationBuilder.DropTable(
-                name: "TimestampedSets");
-
             migrationBuilder.DropTable(
                 name: "ADSystem");
         }
diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
index 317c445..f5533cc 100644
--- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
+++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
@@ -52,27 +52,27 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasColumnType("uuid")
                         .HasComment("Id события");
 
+                    b.Property<int>("CategoryId")
+                        .HasColumnType("integer")
+                        .HasComment("Id Категории важности");
+
                     b.Property<double?>("Depth")
                         .HasColumnType("double precision")
                         .HasComment("Глубина забоя");
 
-                    b.Property<int>("ImportantId")
-                        .HasColumnType("integer")
-                        .HasComment("Id Категории важности");
-
                     b.Property<string>("MessageText")
                         .IsRequired()
                         .HasColumnType("varchar(512)")
                         .HasComment("Текст сообщения");
 
-                    b.Property<DateTimeOffset>("OccurrenceDate")
-                        .HasColumnType("timestamp with time zone")
-                        .HasComment("Дата возникновения");
-
                     b.Property<Guid>("SystemId")
                         .HasColumnType("uuid")
                         .HasComment("Id системы автобурения, к которой относится сообщение");
 
+                    b.Property<DateTimeOffset>("Timestamp")
+                        .HasColumnType("timestamp with time zone")
+                        .HasComment("Дата возникновения");
+
                     b.Property<Guid>("UserId")
                         .HasColumnType("uuid")
                         .HasComment("Id пользователя за пультом бурильщика");
diff --git a/Persistence.Database.Postgres/PersistenceDbContext.cs b/Persistence.Database.Postgres/PersistenceDbContext.cs
index f9f9a16..89b09db 100644
--- a/Persistence.Database.Postgres/PersistenceDbContext.cs
+++ b/Persistence.Database.Postgres/PersistenceDbContext.cs
@@ -12,7 +12,7 @@ public partial class PersistenceDbContext : DbContext
 
 	public DbSet<TechMessage> TechMessage => Set<TechMessage>();
 
-    public DbSet<TimestampedSet> TimestampedSets => Set<TimestampedSet>();
+	public DbSet<TimestampedSet> TimestampedSets => Set<TimestampedSet>();
 
     public PersistenceDbContext()
         : base()
@@ -42,5 +42,14 @@ public partial class PersistenceDbContext : DbContext
         modelBuilder.Entity<TimestampedSet>()
             .Property(e => e.Set)
             .HasJsonConversion();
-    }
+
+        modelBuilder.Entity<TechMessage>(entity =>
+        {
+            entity.HasOne(t => t.System)
+                .WithMany()
+                .HasForeignKey(t => t.SystemId)
+                .OnDelete(DeleteBehavior.Cascade)
+				.IsRequired();
+		});
+	}
 }
diff --git a/Persistence.Database/Entity/TechMessage.cs b/Persistence.Database/Entity/TechMessage.cs
index dce897c..eacc234 100644
--- a/Persistence.Database/Entity/TechMessage.cs
+++ b/Persistence.Database/Entity/TechMessage.cs
@@ -10,10 +10,10 @@ namespace Persistence.Database.Entity
 		public Guid EventId { get; set; }
 
 		[Comment("Id Категории важности")]
-		public int ImportantId { get; set; }
+		public int CategoryId { get; set; }
 
 		[Comment("Дата возникновения")]
-		public DateTimeOffset OccurrenceDate { get; set; }
+		public DateTimeOffset Timestamp { get; set; }
 
 		[Comment("Глубина забоя")]
 		public double? Depth { get; set; }
diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
index 73414c4..3902681 100644
--- a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
+++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
@@ -1,4 +1,5 @@
 using System.Net;
+using Microsoft.Extensions.Caching.Memory;
 using Microsoft.Extensions.DependencyInjection;
 using Persistence.Client;
 using Persistence.Client.Clients;
@@ -10,7 +11,9 @@ namespace Persistence.IntegrationTests.Controllers
 {
 	public class TechMessagesControllerTest : BaseIntegrationTest
 	{
+		private static readonly string SystemCacheKey = $"{typeof(ADSystem).FullName}CacheKey";
 		private readonly ITechMessagesClient techMessagesClient;
+		private readonly IMemoryCache memoryCache;
 		public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory)
 		{
 			var scope = factory.Services.CreateScope();
@@ -18,6 +21,7 @@ namespace Persistence.IntegrationTests.Controllers
 				.GetRequiredService<PersistenceClientFactory>();
 
 			techMessagesClient = persistenceClientFactory.GetClient<ITechMessagesClient>();
+			memoryCache = scope.ServiceProvider.GetRequiredService<IMemoryCache>();
 		}
 
 		[Fact]
@@ -29,7 +33,7 @@ namespace Persistence.IntegrationTests.Controllers
 			{
 				Skip = 1,
 				Take = 2,
-				SortSettings = nameof(TechMessageDto.CategoryId)
+				SortSettings = nameof(TechMessage.CategoryId)
 			};
 
 			//act
@@ -53,7 +57,7 @@ namespace Persistence.IntegrationTests.Controllers
 			{
 				Skip = 0,
 				Take = 2,
-				SortSettings = nameof(TechMessageDto.CategoryId)
+				SortSettings = nameof(TechMessage.CategoryId)
 			};
 
 			//act
@@ -71,11 +75,39 @@ namespace Persistence.IntegrationTests.Controllers
 			await InsertRange();
 		}
 
+		[Fact]
+		public async Task InsertRange_returns_BadRequest()
+		{
+			//arrange
+			var dtos = new List<TechMessageDto>()
+			{
+				new TechMessageDto()
+				{
+					EventId = Guid.NewGuid(),
+					CategoryId = -1, // < 0
+					Timestamp = DateTimeOffset.UtcNow,
+					Depth = -1, // < 0
+					MessageText = string.Empty, // length < 0
+					System = string.Concat(Enumerable.Repeat(nameof(TechMessageDto.System), 100)), // length > 256
+					UserId = Guid.NewGuid()
+				}
+			};
+
+			//act
+			var response = await techMessagesClient.InsertRange(dtos, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+		}
+
 		[Fact]
 		public async Task GetSystems_returns_success()
 		{
+			//arrange
+			dbContext.CleanupDbSet<ADSystem>();
+			memoryCache.Remove(SystemCacheKey);
+
 			//act
-			dbContext.CleanupDbSet<TechMessage>();
 			var response = await techMessagesClient.GetSystems(new CancellationToken());
 
 			//assert
@@ -113,7 +145,7 @@ namespace Persistence.IntegrationTests.Controllers
 			var autoDrillingSystem = nameof(TechMessageDto.System);
 
 			//act
-			var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken());
+			var response = await techMessagesClient.GetStatistics(autoDrillingSystem, imortantId, new CancellationToken());
 
 			//assert
 			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -124,13 +156,13 @@ namespace Persistence.IntegrationTests.Controllers
 		public async Task GetStatistics_AfterSave_returns_success()
 		{
 			//arrange
-			var imortantId = 1;
+			var imortantId = 0;
 			var autoDrillingSystem = nameof(TechMessageDto.System);
 			var dtos = await InsertRange();
 			var filteredDtos = dtos.Where(e => e.CategoryId == imortantId && e.System == e.System);
 
 			//act
-			var response = await techMessagesClient.GetStatistics(imortantId, autoDrillingSystem, new CancellationToken());
+			var response = await techMessagesClient.GetStatistics(autoDrillingSystem, imortantId, new CancellationToken());
 
 			//assert
 			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs
index 427346b..478ba0f 100644
--- a/Persistence.Repository/Repositories/TechMessagesRepository.cs
+++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs
@@ -1,7 +1,6 @@
 using Mapster;
 using Microsoft.EntityFrameworkCore;
 using Microsoft.Extensions.Caching.Memory;
-using Newtonsoft.Json.Linq;
 using Persistence.Database.Entity;
 using Persistence.Models;
 using Persistence.Repositories;
@@ -21,7 +20,8 @@ namespace Persistence.Repository.Repositories
 			this.db = db;
 		}
 
-		protected virtual IQueryable<TechMessage> GetQueryReadOnly() => db.Set<TechMessage>();
+		protected virtual IQueryable<TechMessage> GetQueryReadOnly() => db.Set<TechMessage>()
+			.Include(e => e.System);
 
 		public async Task<PaginationContainer<TechMessageDto>> GetPage(RequestDto request, CancellationToken token)
 		{
@@ -46,7 +46,7 @@ namespace Persistence.Repository.Repositories
 		{
 			var query = GetQueryReadOnly();
 			var count = await query
-				.Where(e => importantId == null || e.ImportantId == importantId)
+				.Where(e => importantId == null || e.CategoryId == importantId)
 				.Where(e => autoDrillingSystem == null || e.System.Name == autoDrillingSystem)
 				.GroupBy(e => e.System.Name)
 				.ToDictionaryAsync(e => e.Key, v => v.Count());
@@ -54,7 +54,37 @@ namespace Persistence.Repository.Repositories
 			return count;
 		}
 
-		public async Task<IEnumerable<ADSystemDto>> GetSystems(CancellationToken token)
+		public async Task<IEnumerable<string>> GetSystems(CancellationToken token)
+		{
+			var entities = await GetSystems();
+			var systems = entities.Select(e => e.Name);
+
+			return systems ?? [];
+		}
+
+		public async Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
+		{
+
+			var entities = new List<TechMessage>();
+			foreach (var dto in dtos)
+			{
+				var entity = dto.Adapt<TechMessage>();
+				var systems = await GetSystems();
+				var systemId = systems.FirstOrDefault(e => e.Name == dto.System)?.SystemId
+					?? await CreateSystem(dto.System);
+
+				entity.SystemId = systemId;
+
+				entities.Add(entity);
+			}
+
+			await db.Set<TechMessage>().AddRangeAsync(entities, token);
+			var result = await db.SaveChangesAsync(token);
+
+			return result;
+		}
+
+		private async Task<IEnumerable<ADSystemDto>> GetSystems()
 		{
 			var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f =>
 			{
@@ -69,33 +99,6 @@ namespace Persistence.Repository.Repositories
 
 			return systems ?? [];
 		}
-
-		public async Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
-		{
-			var entities = dtos.Select(dto =>
-			{
-				var task = Task.Run(async () =>
-				{
-					var entity = dto.Adapt<TechMessage>();
-					var systems = await GetSystems(token);
-					var systemId = systems.FirstOrDefault(e => e.Name == dto.System)?.SystemId
-						?? await CreateSystem(dto.System);
-
-					entity.SystemId = systemId;
-
-					return entity;
-				});
-				task.Wait();
-
-				return task.Result;
-			});
-
-			await db.Set<TechMessage>().AddRangeAsync(entities, token);
-			var result = await db.SaveChangesAsync(token);
-
-			return result;
-		}
-
 		private async Task<Guid> CreateSystem(string name)
 		{
 			memoryCache.Remove(SystemCacheKey);
@@ -110,7 +113,7 @@ namespace Persistence.Repository.Repositories
 			await db.Set<ADSystem>().AddAsync(entity);
 			await db.SaveChangesAsync();
 
-			return Guid.NewGuid();
+			return systemId;
 		}
 	}
 }
diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs
index b8fb194..74b2bf5 100644
--- a/Persistence/Repositories/ITechMessagesRepository.cs
+++ b/Persistence/Repositories/ITechMessagesRepository.cs
@@ -29,7 +29,7 @@ namespace Persistence.Repositories
 		/// </summary>
 		/// <param name="token"></param>
 		/// <returns></returns>
-		Task<IEnumerable<ADSystemDto>> GetSystems(CancellationToken token);
+		Task<IEnumerable<string>> GetSystems(CancellationToken token);
 
 		/// <summary>
 		/// Получение количества сообщений по категориям и системам автобурения
-- 
2.45.2


From efed33e6486ec657d08eef95975078afcef1c43b Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Mon, 2 Dec 2024 15:14:00 +0500
Subject: [PATCH 09/12] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20?=
 =?UTF-8?q?=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B2?=
 =?UTF-8?q?=20Setpoint=20=D0=B8=20TechMessage?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Controllers/DataSaubController.cs         |   2 +-
 .../Controllers/SetpointController.cs         |  38 +++++-
 .../Controllers/TechMessagesController.cs     |  73 ++++++++---
 .../Controllers/TimeSeriesController.cs       |  16 +--
 Persistence.Client/Clients/ISetpointClient.cs |   6 +
 .../Clients/ITechMessagesClient.cs            |  13 +-
 Persistence.Client/Helpers/ApiTokenHelper.cs  |   2 +
 .../20241118052225_SetpointMigration.cs       |   2 +-
 ...02072250_TechMessageMigration.Designer.cs} |  12 +-
 ...=> 20241202072250_TechMessageMigration.cs} |  10 +-
 .../PersistenceDbContextModelSnapshot.cs      |  10 +-
 .../Entity/{ADSystem.cs => DrillingSystem.cs} |   2 +-
 Persistence.Database/Entity/Setpoint.cs       |   2 +-
 Persistence.Database/Entity/TechMessage.cs    |   2 +-
 .../Controllers/SetpointControllerTest.cs     |  67 ++++++++++
 .../Controllers/TechMessagesControllerTest.cs |  94 ++++++++++++--
 .../Repositories/SetpointRepository.cs        |  34 ++++-
 .../Repositories/TechMessagesRepository.cs    | 117 +++++++++++++-----
 Persistence/API/ISetpointApi.cs               |  18 +--
 Persistence/API/ISyncApi.cs                   |   2 +-
 .../{ADSystemDto.cs => DrillingSystemDto.cs}  |   2 +-
 Persistence/Models/MessagesStatisticDto.cs    |  17 +++
 Persistence/Models/SetpointLogDto.cs          |   2 +-
 .../Repositories/ISetpointRepository.cs       |  39 ++++--
 .../Repositories/ITechMessagesRepository.cs   |  20 ++-
 25 files changed, 482 insertions(+), 120 deletions(-)
 rename Persistence.Database.Postgres/Migrations/{20241128074729_TechMessageMigration.Designer.cs => 20241202072250_TechMessageMigration.Designer.cs} (96%)
 rename Persistence.Database.Postgres/Migrations/{20241128074729_TechMessageMigration.cs => 20241202072250_TechMessageMigration.cs} (91%)
 rename Persistence.Database/Entity/{ADSystem.cs => DrillingSystem.cs} (95%)
 rename Persistence/Models/{ADSystemDto.cs => DrillingSystemDto.cs} (92%)
 create mode 100644 Persistence/Models/MessagesStatisticDto.cs

diff --git a/Persistence.API/Controllers/DataSaubController.cs b/Persistence.API/Controllers/DataSaubController.cs
index 437aee3..202e527 100644
--- a/Persistence.API/Controllers/DataSaubController.cs
+++ b/Persistence.API/Controllers/DataSaubController.cs
@@ -6,7 +6,7 @@ using Persistence.Repository.Data;
 namespace Persistence.API.Controllers;
 
 /// <summary>
-/// ������ � ���������� �������
+/// Работа с временными данными
 /// </summary>
 [ApiController]
 [Authorize]
diff --git a/Persistence.API/Controllers/SetpointController.cs b/Persistence.API/Controllers/SetpointController.cs
index 5a5cb52..108c3ef 100644
--- a/Persistence.API/Controllers/SetpointController.cs
+++ b/Persistence.API/Controllers/SetpointController.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Authorization;
+using System.Net;
+using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Persistence.Models;
 using Persistence.Repositories;
@@ -63,18 +64,47 @@ public class SetpointController : ControllerBase, ISetpointApi
 		return Ok(result);
 	}
 
+	/// <summary>
+	/// Получить диапазон дат, для которых есть данные в репозитории
+	/// </summary>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("range")]
+	public async Task<ActionResult<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token)
+	{
+		var result = await setpointRepository.GetDatesRangeAsync(token);
+
+		return Ok(result);
+	}
+
+	/// <summary>
+	/// Получить порцию записей, начиная с заданной даты 
+	/// </summary>
+	/// <param name="dateBegin"></param>
+	/// <param name="take"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("part")]
+	public async Task<ActionResult<IEnumerable<SetpointLogDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token)
+	{
+		var result = await setpointRepository.GetPart(dateBegin, take, token);
+
+		return Ok(result);
+	}
+
 	/// <summary>
 	/// Сохранить уставку
 	/// </summary>
 	/// <param name="setpointKey"></param>
 	/// <param name="newValue"></param>
+	/// <param name="idUser"></param>
 	/// <param name="token"></param>
 	/// <returns></returns>
 	[HttpPost]
-	public async Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token)
+	[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
+	public async Task<IActionResult> Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token)
 	{
-		// ToDo: вычитка idUser
-		await setpointRepository.Save(setpointKey, newValue, 0, token);
+		await setpointRepository.Save(setpointKey, newValue, idUser, token);
 
 		return Ok();
 	}
diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs
index fb3315e..4e91802 100644
--- a/Persistence.API/Controllers/TechMessagesController.cs
+++ b/Persistence.API/Controllers/TechMessagesController.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Authorization;
+using System.Net;
+using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Persistence.Models;
 using Persistence.Repositories;
@@ -14,6 +15,14 @@ namespace Persistence.API.Controllers;
 public class TechMessagesController : ControllerBase
 {
 	private readonly ITechMessagesRepository techMessagesRepository;
+	private static readonly Dictionary<int, string> categories = new Dictionary<int, string>()
+	{
+		{ 0, "System" },
+		{ 1, "Авария" },
+		{ 2, "Предупреждение" },
+		{ 3, "Инфо" },
+		{ 4, "Прочее" }
+	};
 
 	public TechMessagesController(ITechMessagesRepository techMessagesRepository)
 	{
@@ -37,14 +46,14 @@ public class TechMessagesController : ControllerBase
 	/// <summary>
 	/// Получить статистику по системам
 	/// </summary>
-	/// <param name="importantId"></param>
 	/// <param name="autoDrillingSystem"></param>
+	/// <param name="categoryIds"></param>
 	/// <param name="token"></param>
 	/// <returns></returns>
-	[HttpGet("statistics/{autoDrillingSystem}")]
-	public async Task<ActionResult<int>> GetStatistics([FromRoute] string? autoDrillingSystem, int? importantId, CancellationToken token)
+	[HttpGet("statistics")]
+	public async Task<ActionResult<IEnumerable<MessagesStatisticDto>>> GetStatistics([FromQuery] IEnumerable<string> autoDrillingSystem, [FromQuery] IEnumerable<int> categoryIds, CancellationToken token)
 	{
-		var result = await techMessagesRepository.GetStatistics(importantId, autoDrillingSystem, token);
+		var result = await techMessagesRepository.GetStatistics(autoDrillingSystem, categoryIds, token);
 
 		return Ok(result);
 	}
@@ -55,13 +64,41 @@ public class TechMessagesController : ControllerBase
 	/// <param name="token"></param>
 	/// <returns></returns>
 	[HttpGet("systems")]
-	public async Task<ActionResult<IEnumerable<string>>> GetSystems(CancellationToken token)
+	public async Task<ActionResult<Dictionary<string, int>>> GetSystems(CancellationToken token)
 	{
 		var result = await techMessagesRepository.GetSystems(token);
 
 		return Ok(result);
 	}
 
+	/// <summary>
+	/// Получить диапазон дат, для которых есть данные в репозитории
+	/// </summary>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("range")]
+	public async Task<ActionResult<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token)
+	{
+		var result = await techMessagesRepository.GetDatesRangeAsync(token);
+
+		return Ok(result);
+	}
+
+	/// <summary>
+	/// Получить порцию записей, начиная с заданной даты 
+	/// </summary>
+	/// <param name="dateBegin"></param>
+	/// <param name="take"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	[HttpGet("part")]
+	public async Task<ActionResult<IEnumerable<SetpointLogDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token)
+	{
+		var result = await techMessagesRepository.GetPart(dateBegin, take, token);
+
+		return Ok(result);
+	}
+
 	/// <summary>
 	/// Добавить новые технологические сообщения
 	/// </summary>
@@ -69,9 +106,16 @@ public class TechMessagesController : ControllerBase
 	/// <param name="token"></param>
 	/// <returns></returns>
 	[HttpPost]
-	public async Task<ActionResult<int>> InsertRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
+	[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
+	public async Task<IActionResult> InsertRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
 	{
-		var result = await techMessagesRepository.InsertRange(dtos, token);
+		var userId = User.GetUserId<Guid>();
+        foreach (var dto in dtos)
+        {
+			dto.UserId = userId;
+        }
+
+        var result = await techMessagesRepository.InsertRange(dtos, token);
 
 		return CreatedAtAction(nameof(InsertRange), result);
 	}
@@ -83,15 +127,6 @@ public class TechMessagesController : ControllerBase
 	[HttpGet("categories")]
 	public ActionResult<Dictionary<int, string>> GetImportantCategories()
 	{
-		var result = new Dictionary<int, string>()
-		{
-			{ 0, "System" },
-			{ 1, "Авария" },
-			{ 2, "Предупреждение" },
-			{ 3, "Инфо" },
-			{ 4, "Прочее" }
-		};
-
-		return Ok(result);
+		return Ok(categories);
 	}
-}
+}
\ No newline at end of file
diff --git a/Persistence.API/Controllers/TimeSeriesController.cs b/Persistence.API/Controllers/TimeSeriesController.cs
index a4f00f3..90c78c8 100644
--- a/Persistence.API/Controllers/TimeSeriesController.cs
+++ b/Persistence.API/Controllers/TimeSeriesController.cs
@@ -19,7 +19,7 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
     }
 
 	/// <summary>
-	/// �������� ������ ��������, ��������������� ��������� ���
+	/// Получить список объектов, удовлетворяющий диапазону дат
 	/// </summary>
 	/// <param name="dateBegin"></param>
 	/// <param name="token"></param>
@@ -28,24 +28,24 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
     [ProducesResponseType(StatusCodes.Status200OK)]
     public async Task<IActionResult> Get(DateTimeOffset dateBegin, CancellationToken token)
     {
-        var result = await this.timeSeriesDataRepository.GetGtDate(dateBegin, token);
+        var result = await timeSeriesDataRepository.GetGtDate(dateBegin, token);
         return Ok(result);
     }
 
 	/// <summary>
-	/// �������� �������� ���, ��� ������� ���� ������ � �����������
+	/// Получить диапазон дат, для которых есть данные в репозиторие
 	/// </summary>
 	/// <param name="token"></param>
 	/// <returns></returns>
 	[HttpGet("datesRange")]
     public async Task<IActionResult> GetDatesRange(CancellationToken token)
     {
-        var result = await this.timeSeriesDataRepository.GetDatesRange(token);
+        var result = await timeSeriesDataRepository.GetDatesRange(token);
         return Ok(result);
     }
 
 	/// <summary>
-	/// �������� ������ �������� � �������������, ��������������� ��������� ���
+	/// Получить список объектов с прореживанием, удовлетворяющий диапазону дат
 	/// </summary>
 	/// <param name="dateBegin"></param>
 	/// <param name="intervalSec"></param>
@@ -55,12 +55,12 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
 	[HttpGet("resampled")]
     public async Task<IActionResult> GetResampledData(DateTimeOffset dateBegin, double intervalSec = 600d, int approxPointsCount = 1024, CancellationToken token = default)
     {
-        var result = await this.timeSeriesDataRepository.GetResampledData(dateBegin, intervalSec, approxPointsCount, token);
+        var result = await timeSeriesDataRepository.GetResampledData(dateBegin, intervalSec, approxPointsCount, token);
         return Ok(result);
     }
 
 	/// <summary>
-	/// �������� ������
+	/// Добавить записи
 	/// </summary>
 	/// <param name="dtos"></param>
 	/// <param name="token"></param>
@@ -68,7 +68,7 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
 	[HttpPost]
     public async Task<IActionResult> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
     {
-        var result = await this.timeSeriesDataRepository.InsertRange(dtos, token);
+        var result = await timeSeriesDataRepository.InsertRange(dtos, token);
         return Ok(result);
     }
 
diff --git a/Persistence.Client/Clients/ISetpointClient.cs b/Persistence.Client/Clients/ISetpointClient.cs
index 72d76e1..886e034 100644
--- a/Persistence.Client/Clients/ISetpointClient.cs
+++ b/Persistence.Client/Clients/ISetpointClient.cs
@@ -19,6 +19,12 @@ public interface ISetpointClient
 	[Get($"{BaseRoute}/log")]
 	Task<IApiResponse<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys);
 
+	[Get($"{BaseRoute}/range")]
+	Task<IApiResponse<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token);
+
+	[Get($"{BaseRoute}/part")]
+	Task<IApiResponse<IEnumerable<SetpointLogDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
+
 	[Post($"{BaseRoute}/")]
 	Task<IApiResponse> Save(Guid setpointKey, object newValue);
 }
diff --git a/Persistence.Client/Clients/ITechMessagesClient.cs b/Persistence.Client/Clients/ITechMessagesClient.cs
index 43839fe..cbdf7ef 100644
--- a/Persistence.Client/Clients/ITechMessagesClient.cs
+++ b/Persistence.Client/Clients/ITechMessagesClient.cs
@@ -1,5 +1,4 @@
-using Microsoft.AspNetCore.Mvc;
-using Persistence.Models;
+using Persistence.Models;
 using Refit;
 
 namespace Persistence.Client.Clients
@@ -20,7 +19,13 @@ namespace Persistence.Client.Clients
 		[Get($"{BaseRoute}/systems")]
 		Task<IApiResponse<IEnumerable<string>>> GetSystems(CancellationToken token);
 
-		[Get($"{BaseRoute}/statistics/" + "{autoDrillingSystem}")]
-		Task<IApiResponse<int>> GetStatistics(string? autoDrillingSystem, int? importantId, CancellationToken token);
+		[Get($"{BaseRoute}/range")]
+		Task<IApiResponse<DatesRangeDto>> GetDatesRangeAsync(CancellationToken token);
+
+		[Get($"{BaseRoute}/part")]
+		Task<IApiResponse<IEnumerable<TechMessageDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
+
+		[Get($"{BaseRoute}/statistics")]
+		Task<IApiResponse<IEnumerable<MessagesStatisticDto>>> GetStatistics([Query] string autoDrillingSystem, [Query] int categoryId, CancellationToken token);
 	}
 }
diff --git a/Persistence.Client/Helpers/ApiTokenHelper.cs b/Persistence.Client/Helpers/ApiTokenHelper.cs
index e508922..5eed66e 100644
--- a/Persistence.Client/Helpers/ApiTokenHelper.cs
+++ b/Persistence.Client/Helpers/ApiTokenHelper.cs
@@ -29,8 +29,10 @@ public static class ApiTokenHelper
 
 	private static string CreateDefaultJwtToken(this AuthUser authUser)
 	{
+		var nameIdetifier = Guid.NewGuid().ToString();
 		var claims = new List<Claim>()
 		{
+			new(ClaimTypes.NameIdentifier, nameIdetifier),
 			new("client_id", authUser.ClientId),
 			new("username", authUser.Username),
 			new("password", authUser.Password),
diff --git a/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs b/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs
index 49e438a..ea6fccf 100644
--- a/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs
+++ b/Persistence.Database.Postgres/Migrations/20241118052225_SetpointMigration.cs
@@ -18,7 +18,7 @@ namespace Persistence.Database.Postgres.Migrations
                     Key = table.Column<Guid>(type: "uuid", nullable: false, comment: "Ключ"),
                     Created = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, comment: "Дата изменения уставки"),
                     Value = table.Column<object>(type: "jsonb", nullable: false, comment: "Значение уставки"),
-                    IdUser = table.Column<int>(type: "integer", nullable: false, comment: "Id автора последнего изменения")
+                    IdUser = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id автора последнего изменения")
                 },
                 constraints: table =>
                 {
diff --git a/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs b/Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.Designer.cs
similarity index 96%
rename from Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs
rename to Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.Designer.cs
index 6678078..6ed33f7 100644
--- a/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.Designer.cs
+++ b/Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.Designer.cs
@@ -12,7 +12,7 @@ using Persistence.Database.Model;
 namespace Persistence.Database.Postgres.Migrations
 {
     [DbContext(typeof(PersistenceDbContext))]
-    [Migration("20241128074729_TechMessageMigration")]
+    [Migration("20241202072250_TechMessageMigration")]
     partial class TechMessageMigration
     {
         /// <inheritdoc />
@@ -27,7 +27,7 @@ namespace Persistence.Database.Postgres.Migrations
             NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 
-            modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b =>
+            modelBuilder.Entity("Persistence.Database.Entity.DrillingSystem", b =>
                 {
                     b.Property<Guid>("SystemId")
                         .ValueGeneratedOnAdd()
@@ -45,7 +45,7 @@ namespace Persistence.Database.Postgres.Migrations
 
                     b.HasKey("SystemId");
 
-                    b.ToTable("ADSystem");
+                    b.ToTable("DrillingSystem");
                 });
 
             modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
@@ -203,8 +203,8 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasColumnType("timestamp with time zone")
                         .HasComment("Дата создания уставки");
 
-                    b.Property<int>("IdUser")
-                        .HasColumnType("integer")
+                    b.Property<Guid>("IdUser")
+                        .HasColumnType("uuid")
                         .HasComment("Id автора последнего изменения");
 
                     b.Property<object>("Value")
@@ -219,7 +219,7 @@ namespace Persistence.Database.Postgres.Migrations
 
             modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
                 {
-                    b.HasOne("Persistence.Database.Entity.ADSystem", "System")
+                    b.HasOne("Persistence.Database.Entity.DrillingSystem", "System")
                         .WithMany()
                         .HasForeignKey("SystemId")
                         .OnDelete(DeleteBehavior.Cascade)
diff --git a/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs b/Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.cs
similarity index 91%
rename from Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs
rename to Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.cs
index 047ad52..ccf18d4 100644
--- a/Persistence.Database.Postgres/Migrations/20241128074729_TechMessageMigration.cs
+++ b/Persistence.Database.Postgres/Migrations/20241202072250_TechMessageMigration.cs
@@ -12,7 +12,7 @@ namespace Persistence.Database.Postgres.Migrations
         protected override void Up(MigrationBuilder migrationBuilder)
         {
             migrationBuilder.CreateTable(
-                name: "ADSystem",
+                name: "DrillingSystem",
                 columns: table => new
                 {
                     SystemId = table.Column<Guid>(type: "uuid", nullable: false, comment: "Id системы автобурения"),
@@ -21,7 +21,7 @@ namespace Persistence.Database.Postgres.Migrations
                 },
                 constraints: table =>
                 {
-                    table.PrimaryKey("PK_ADSystem", x => x.SystemId);
+                    table.PrimaryKey("PK_DrillingSystem", x => x.SystemId);
                 });
 
             migrationBuilder.CreateTable(
@@ -40,9 +40,9 @@ namespace Persistence.Database.Postgres.Migrations
                 {
                     table.PrimaryKey("PK_TechMessage", x => x.EventId);
                     table.ForeignKey(
-                        name: "FK_TechMessage_ADSystem_SystemId",
+                        name: "FK_TechMessage_DrillingSystem_SystemId",
                         column: x => x.SystemId,
-                        principalTable: "ADSystem",
+                        principalTable: "DrillingSystem",
                         principalColumn: "SystemId",
                         onDelete: ReferentialAction.Cascade);
                 });
@@ -60,7 +60,7 @@ namespace Persistence.Database.Postgres.Migrations
                 name: "TechMessage");
 
             migrationBuilder.DropTable(
-                name: "ADSystem");
+                name: "DrillingSystem");
         }
     }
 }
diff --git a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
index f5533cc..e53c81a 100644
--- a/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
+++ b/Persistence.Database.Postgres/Migrations/PersistenceDbContextModelSnapshot.cs
@@ -24,7 +24,7 @@ namespace Persistence.Database.Postgres.Migrations
             NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "adminpack");
             NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
 
-            modelBuilder.Entity("Persistence.Database.Entity.ADSystem", b =>
+            modelBuilder.Entity("Persistence.Database.Entity.DrillingSystem", b =>
                 {
                     b.Property<Guid>("SystemId")
                         .ValueGeneratedOnAdd()
@@ -42,7 +42,7 @@ namespace Persistence.Database.Postgres.Migrations
 
                     b.HasKey("SystemId");
 
-                    b.ToTable("ADSystem");
+                    b.ToTable("DrillingSystem");
                 });
 
             modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
@@ -200,8 +200,8 @@ namespace Persistence.Database.Postgres.Migrations
                         .HasColumnType("timestamp with time zone")
                         .HasComment("Дата создания уставки");
 
-                    b.Property<int>("IdUser")
-                        .HasColumnType("integer")
+                    b.Property<Guid>("IdUser")
+                        .HasColumnType("uuid")
                         .HasComment("Id автора последнего изменения");
 
                     b.Property<object>("Value")
@@ -216,7 +216,7 @@ namespace Persistence.Database.Postgres.Migrations
 
             modelBuilder.Entity("Persistence.Database.Entity.TechMessage", b =>
                 {
-                    b.HasOne("Persistence.Database.Entity.ADSystem", "System")
+                    b.HasOne("Persistence.Database.Entity.DrillingSystem", "System")
                         .WithMany()
                         .HasForeignKey("SystemId")
                         .OnDelete(DeleteBehavior.Cascade)
diff --git a/Persistence.Database/Entity/ADSystem.cs b/Persistence.Database/Entity/DrillingSystem.cs
similarity index 95%
rename from Persistence.Database/Entity/ADSystem.cs
rename to Persistence.Database/Entity/DrillingSystem.cs
index 525134c..6588fb0 100644
--- a/Persistence.Database/Entity/ADSystem.cs
+++ b/Persistence.Database/Entity/DrillingSystem.cs
@@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations.Schema;
 using Microsoft.EntityFrameworkCore;
 
 namespace Persistence.Database.Entity;
-public class ADSystem
+public class DrillingSystem
 {
 	[Key, Comment("Id системы автобурения")]
 	public Guid SystemId { get; set; }
diff --git a/Persistence.Database/Entity/Setpoint.cs b/Persistence.Database/Entity/Setpoint.cs
index ef6b5dc..6ca2c27 100644
--- a/Persistence.Database/Entity/Setpoint.cs
+++ b/Persistence.Database/Entity/Setpoint.cs
@@ -16,6 +16,6 @@ namespace Persistence.Database.Model
 		public DateTimeOffset Created { get; set; }
 
 		[Comment("Id автора последнего изменения")]
-		public int IdUser { get; set; }
+		public Guid IdUser { get; set; }
 	}
 }
diff --git a/Persistence.Database/Entity/TechMessage.cs b/Persistence.Database/Entity/TechMessage.cs
index eacc234..ea29cc2 100644
--- a/Persistence.Database/Entity/TechMessage.cs
+++ b/Persistence.Database/Entity/TechMessage.cs
@@ -25,7 +25,7 @@ namespace Persistence.Database.Entity
 		public required Guid SystemId { get; set; }
 
 		[Required, ForeignKey(nameof(SystemId)), Comment("Система автобурения, к которой относится сообщение")]
-		public virtual required ADSystem System { get; set; }
+		public virtual required DrillingSystem System { get; set; }
 
 		[Comment("Id пользователя за пультом бурильщика")]
 		public Guid UserId { get; set; }
diff --git a/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs
index faa0147..d314cec 100644
--- a/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs
+++ b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs
@@ -2,6 +2,7 @@
 using Microsoft.Extensions.DependencyInjection;
 using Persistence.Client;
 using Persistence.Client.Clients;
+using Persistence.Database.Model;
 using Xunit;
 
 namespace Persistence.IntegrationTests.Controllers
@@ -131,6 +132,72 @@ namespace Persistence.IntegrationTests.Controllers
 			Assert.Equal(setpointKey, response.Content.FirstOrDefault().Key);
 		}
 
+		[Fact]
+		public async Task GetDatesRange_returns_success()
+		{
+			//arrange
+			dbContext.CleanupDbSet<Setpoint>();
+
+			//act
+			var response = await setpointClient.GetDatesRangeAsync(new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.Equal(DateTimeOffset.MinValue, response.Content?.From);
+			Assert.Equal(DateTimeOffset.MaxValue, response.Content?.To);
+		}
+
+		[Fact]
+		public async Task GetDatesRange_AfterSave_returns_success()
+		{
+			//arrange
+			dbContext.CleanupDbSet<Setpoint>();
+			await Save();
+
+			//act
+			var response = await setpointClient.GetDatesRangeAsync(new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.NotNull(response.Content?.From);
+			Assert.NotNull(response.Content?.To);
+		}
+
+		[Fact]
+		public async Task GetPart_returns_success()
+		{
+			//arrange
+			var dateBegin = DateTimeOffset.UtcNow;
+			var take = 2;
+
+			//act
+			var response = await setpointClient.GetPart(dateBegin, take, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.Empty(response.Content);
+		}
+
+		[Fact]
+		public async Task GetPart_AfterSave_returns_success()
+		{
+			//arrange
+			var dateBegin = DateTimeOffset.UtcNow;
+			var take = 1;
+			await Save();
+
+			//act
+			var response = await setpointClient.GetPart(dateBegin, take, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.NotEmpty(response.Content);
+		}
+
 		[Fact]
 		public async Task Save_returns_success()
 		{
diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
index 3902681..ec4e40a 100644
--- a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
+++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
@@ -11,7 +11,7 @@ namespace Persistence.IntegrationTests.Controllers
 {
 	public class TechMessagesControllerTest : BaseIntegrationTest
 	{
-		private static readonly string SystemCacheKey = $"{typeof(ADSystem).FullName}CacheKey";
+		private static readonly string SystemCacheKey = $"{typeof(Database.Entity.DrillingSystem).FullName}CacheKey";
 		private readonly ITechMessagesClient techMessagesClient;
 		private readonly IMemoryCache memoryCache;
 		public TechMessagesControllerTest(WebAppFactoryFixture factory) : base(factory)
@@ -28,7 +28,10 @@ namespace Persistence.IntegrationTests.Controllers
 		public async Task GetPage_returns_success()
 		{
 			//arrange
+			memoryCache.Remove(SystemCacheKey);
 			dbContext.CleanupDbSet<TechMessage>();
+			dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
+
 			var requestDto = new RequestDto()
 			{
 				Skip = 1,
@@ -104,8 +107,9 @@ namespace Persistence.IntegrationTests.Controllers
 		public async Task GetSystems_returns_success()
 		{
 			//arrange
-			dbContext.CleanupDbSet<ADSystem>();
 			memoryCache.Remove(SystemCacheKey);
+			dbContext.CleanupDbSet<TechMessage>();
+			dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
 
 			//act
 			var response = await techMessagesClient.GetSystems(new CancellationToken());
@@ -140,7 +144,10 @@ namespace Persistence.IntegrationTests.Controllers
 		public async Task GetStatistics_returns_success()
 		{
 			//arrange
+			memoryCache.Remove(SystemCacheKey);
 			dbContext.CleanupDbSet<TechMessage>();
+			dbContext.CleanupDbSet<Database.Entity.DrillingSystem>();
+
 			var imortantId = 1;
 			var autoDrillingSystem = nameof(TechMessageDto.System);
 
@@ -149,7 +156,8 @@ namespace Persistence.IntegrationTests.Controllers
 
 			//assert
 			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-			Assert.Equal(0, response.Content);
+			Assert.NotNull(response.Content);
+			Assert.Empty(response.Content);
 		}
 
 		[Fact]
@@ -159,19 +167,89 @@ namespace Persistence.IntegrationTests.Controllers
 			var imortantId = 0;
 			var autoDrillingSystem = nameof(TechMessageDto.System);
 			var dtos = await InsertRange();
-			var filteredDtos = dtos.Where(e => e.CategoryId == imortantId && e.System == e.System);
+			var filteredDtos = dtos.Where(e => e.CategoryId == imortantId && e.System == autoDrillingSystem);
 
 			//act
 			var response = await techMessagesClient.GetStatistics(autoDrillingSystem, imortantId, new CancellationToken());
 
 			//assert
 			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-			Assert.Equal(filteredDtos.Count(), response.Content);
+			Assert.NotNull(response.Content);
+			var categories = response.Content
+				.FirstOrDefault()?.Categories
+				.FirstOrDefault(e => e.Key == 0).Value;
+			Assert.Equal(filteredDtos.Count(), categories);
 		}
 
-		public async Task<IEnumerable<TechMessageDto>> InsertRange()
+		[Fact]
+		public async Task GetDatesRange_returns_success()
+		{
+			//act
+			var response = await techMessagesClient.GetDatesRangeAsync(new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			//Assert.Equal(DateTimeOffset.MinValue, response.Content?.From);
+			//Assert.Equal(DateTimeOffset.MaxValue, response.Content?.To);
+		}
+
+		[Fact]
+		public async Task GetDatesRange_AfterSave_returns_success()
 		{
 			//arrange
+			await InsertRange();
+
+			//act
+			var response = await techMessagesClient.GetDatesRangeAsync(new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.NotNull(response.Content?.From);
+			Assert.NotNull(response.Content?.To);
+		}
+
+		[Fact]
+		public async Task GetPart_returns_success()
+		{
+			//arrange
+			var dateBegin = DateTimeOffset.UtcNow;
+			var take = 2;
+
+			//act
+			var response = await techMessagesClient.GetPart(dateBegin, take, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.Empty(response.Content);
+		}
+
+		[Fact]
+		public async Task GetPart_AfterSave_returns_success()
+		{
+			//arrange
+			var dateBegin = DateTimeOffset.UtcNow;
+			var take = 1;
+			await InsertRange();
+
+			//act
+			var response = await techMessagesClient.GetPart(dateBegin, take, new CancellationToken());
+
+			//assert
+			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.NotNull(response.Content);
+			Assert.NotEmpty(response.Content);
+		}
+
+		private async Task<IEnumerable<TechMessageDto>> InsertRange()
+		{
+			//arrange
+			memoryCache.Remove(SystemCacheKey);
+			dbContext.CleanupDbSet<TechMessage>();
+			dbContext.CleanupDbSet<DrillingSystem>();
+
 			var dtos = new List<TechMessageDto>()
 			{
 				new TechMessageDto()
@@ -181,7 +259,7 @@ namespace Persistence.IntegrationTests.Controllers
 					Timestamp = DateTimeOffset.UtcNow,
 					Depth = 1.11,
 					MessageText = nameof(TechMessageDto.MessageText),
-					System = nameof(TechMessageDto.System),
+					System = nameof(TechMessageDto.System).ToLower(),
 					UserId = Guid.NewGuid()
 				},
 				new TechMessageDto()
@@ -191,7 +269,7 @@ namespace Persistence.IntegrationTests.Controllers
 					Timestamp = DateTimeOffset.UtcNow,
 					Depth = 2.22,
 					MessageText = nameof(TechMessageDto.MessageText),
-					System = nameof(TechMessageDto.System),
+					System = nameof(TechMessageDto.System).ToLower(),
 					UserId = Guid.NewGuid()
 				}
 			};
diff --git a/Persistence.Repository/Repositories/SetpointRepository.cs b/Persistence.Repository/Repositories/SetpointRepository.cs
index 7f81c29..b4557aa 100644
--- a/Persistence.Repository/Repositories/SetpointRepository.cs
+++ b/Persistence.Repository/Repositories/SetpointRepository.cs
@@ -43,6 +43,38 @@ namespace Persistence.Repository.Repositories
 			return dtos;
 		}
 
+		public async Task<IEnumerable<SetpointLogDto>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token)
+		{
+			var query = GetQueryReadOnly();
+			var entities = await query
+				.Where(e => e.Created > dateBegin)
+				.Take(take)
+				.ToArrayAsync(token);
+			var dtos = entities
+				.Select(e => e.Adapt<SetpointLogDto>());
+
+			return dtos;
+		}
+
+		public async Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token)
+		{
+			var query = GetQueryReadOnly()
+				.GroupBy(e => 1)
+				.Select(group => new
+				{
+					Min = group.Min(e => e.Created),
+					Max = group.Max(e => e.Created),
+				});
+			var values = await query.FirstOrDefaultAsync(token);
+			var result = new DatesRangeDto()
+			{
+				From = values?.Min ?? DateTimeOffset.MinValue,
+				To = values?.Max ?? DateTimeOffset.MaxValue
+			};
+
+			return result;
+		}
+
 		public async Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token)
 		{
 			var query = GetQueryReadOnly();
@@ -56,7 +88,7 @@ namespace Persistence.Repository.Repositories
 			return dtos;
 		}
 
-		public async Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token)
+		public async Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token)
 		{
 			var entity = new Setpoint()
 			{
diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs
index 478ba0f..fad8acf 100644
--- a/Persistence.Repository/Repositories/TechMessagesRepository.cs
+++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs
@@ -10,7 +10,8 @@ namespace Persistence.Repository.Repositories
 {
 	public class TechMessagesRepository : ITechMessagesRepository
 	{
-		private static readonly string SystemCacheKey = $"{typeof(ADSystem).FullName}CacheKey";
+		private static readonly string SystemCacheKey = $"{typeof(Database.Entity.DrillingSystem).FullName}CacheKey";
+		private const int CacheExpirationInMinutes = 60;
 		private readonly IMemoryCache memoryCache;
 		private DbContext db;
 
@@ -26,40 +27,65 @@ namespace Persistence.Repository.Repositories
 		public async Task<PaginationContainer<TechMessageDto>> GetPage(RequestDto request, CancellationToken token)
 		{
 			var query = GetQueryReadOnly();
+			var count = await query.CountAsync(token);
+
+			var sort = request.SortSettings != string.Empty 
+				? request.SortSettings 
+				: nameof(TechMessage.Timestamp);
 			var entities = await query
 				.SortBy(request.SortSettings)
 				.Skip(request.Skip)
 				.Take(request.Take)
-				.ToListAsync();
+				.ToArrayAsync(token);
+
 			var dto = new PaginationContainer<TechMessageDto>()
 			{
 				Skip = request.Skip,
 				Take = request.Take,
-				Count = entities.Count,
+				Count = count,
 				Items = entities.Select(e => e.Adapt<TechMessageDto>())
 			};
 
 			return dto;
 		}
 
-		public async Task<Dictionary<string, int>> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token)
+		public async Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<string> autoDrillingSystem, IEnumerable<int> categoryIds, CancellationToken token)
 		{
 			var query = GetQueryReadOnly();
-			var count = await query
-				.Where(e => importantId == null || e.CategoryId == importantId)
-				.Where(e => autoDrillingSystem == null || e.System.Name == autoDrillingSystem)
-				.GroupBy(e => e.System.Name)
-				.ToDictionaryAsync(e => e.Key, v => v.Count());
+			var systems = autoDrillingSystem.Select(s => s.ToLower().Trim());
+			var result = await query
+				.Where(e => systems.Count() == 0 || systems.Contains(e.System.Name.ToLower().Trim()))
+				.GroupBy(e => e.System.Name, (key, group) => new
+				{
+					System = key,
+					Categories = group
+						.Where(g => categoryIds.Count() == 0 || categoryIds.Contains(g.CategoryId))
+				})
+				.ToArrayAsync(token);
 
-			return count;
+			var entities = new List<MessagesStatisticDto>();
+			foreach (var e in result)
+			{
+				var categories = e.Categories
+					.GroupBy(g => g.CategoryId)
+					.ToDictionary(c => c.Key, v => v.Count());
+				var entity = new MessagesStatisticDto()
+				{
+					System = e.System,
+					Categories = categories
+				};
+				entities.Add(entity);
+			}
+
+			return entities;
 		}
 
 		public async Task<IEnumerable<string>> GetSystems(CancellationToken token)
 		{
-			var entities = await GetSystems();
-			var systems = entities.Select(e => e.Name);
+			var entities = await GetDrillingSystems(token);
+			var result = entities.Select(e => e.Name);
 
-			return systems ?? [];
+			return result;
 		}
 
 		public async Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
@@ -69,9 +95,9 @@ namespace Persistence.Repository.Repositories
 			foreach (var dto in dtos)
 			{
 				var entity = dto.Adapt<TechMessage>();
-				var systems = await GetSystems();
-				var systemId = systems.FirstOrDefault(e => e.Name == dto.System)?.SystemId
-					?? await CreateSystem(dto.System);
+				var systems = await GetDrillingSystems(token);
+				var systemId = systems.FirstOrDefault(e => e.Name.ToLower().Trim() == dto.System.ToLower().Trim())?.SystemId
+					?? await CreateDrillingSystem(dto.System, token);
 
 				entity.SystemId = systemId;
 
@@ -84,36 +110,67 @@ namespace Persistence.Repository.Repositories
 			return result;
 		}
 
-		private async Task<IEnumerable<ADSystemDto>> GetSystems()
+		public async Task<IEnumerable<TechMessageDto>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token)
+		{
+			var query = GetQueryReadOnly();
+			var entities = await query
+				.Where(e => e.Timestamp > dateBegin)
+				.Take(take)
+				.ToArrayAsync(token);
+			var dtos = entities
+				.Select(e => e.Adapt<TechMessageDto>());
+
+			return dtos;
+		}
+
+		public async Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token)
+		{
+			var query = GetQueryReadOnly()
+				.GroupBy(e => 1)
+				.Select(group => new
+				{
+					Min = group.Min(e => e.Timestamp),
+					Max = group.Max(e => e.Timestamp),
+				});
+			var values = await query.FirstOrDefaultAsync(token);
+			var result = new DatesRangeDto()
+			{
+				From = values?.Min ?? DateTimeOffset.MinValue,
+				To = values?.Max ?? DateTimeOffset.MaxValue
+			};
+
+			return result;
+		}
+
+		private async Task<IEnumerable<Models.DrillingSystemDto>> GetDrillingSystems(CancellationToken token)
 		{
 			var systems = await memoryCache.GetOrCreateAsync(SystemCacheKey, async f =>
 			{
-				f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(60);
+				f.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheExpirationInMinutes);
 
-				var query = db.Set<ADSystem>();
-				var entities = await query.ToListAsync();
-				var dtos = entities.Select(e => e.Adapt<ADSystemDto>());
+				var query = db.Set<Database.Entity.DrillingSystem>();
+				var entities = await query.ToListAsync(token);
+				var dtos = entities.Select(e => e.Adapt<Models.DrillingSystemDto>());
 
 				return dtos;
 			});
 
-			return systems ?? [];
+			return systems!;
 		}
-		private async Task<Guid> CreateSystem(string name)
+		private async Task<Guid> CreateDrillingSystem(string name, CancellationToken token)
 		{
 			memoryCache.Remove(SystemCacheKey);
 
-			var systemId = Guid.NewGuid();
-			var entity = new ADSystem()
+			var entity = new Database.Entity.DrillingSystem()
 			{
-				SystemId = systemId,
-				Name = name
+				SystemId = default,
+				Name = name.ToLower().Trim()
 			};
 
-			await db.Set<ADSystem>().AddAsync(entity);
-			await db.SaveChangesAsync();
+			await db.Set<Database.Entity.DrillingSystem>().AddAsync(entity);
+			await db.SaveChangesAsync(token);
 
-			return systemId;
+			return entity.SystemId;
 		}
 	}
 }
diff --git a/Persistence/API/ISetpointApi.cs b/Persistence/API/ISetpointApi.cs
index 7af0895..d05fe12 100644
--- a/Persistence/API/ISetpointApi.cs
+++ b/Persistence/API/ISetpointApi.cs
@@ -6,7 +6,7 @@ namespace Persistence.API;
 /// <summary>
 /// Интерфейс для API, предназначенного для работы с уставками
 /// </summary>
-public interface ISetpointApi
+public interface ISetpointApi : ISyncApi<SetpointLogDto>
 {
     /// <summary>
     /// Получить актуальные значения уставок
@@ -33,12 +33,12 @@ public interface ISetpointApi
     /// <returns></returns>
     Task<ActionResult<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog(IEnumerable<Guid> setpoitKeys, CancellationToken token);
 
-    /// <summary>
-    /// Метод сохранения уставки
-    /// </summary>
-    /// <param name="setpointKey">ключ уставки</param>
-    /// <param name="newValue">значение</param>
-    /// <param name="token"></param>
-    /// <returns></returns>
-    Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token);
+	/// <summary>
+	/// Метод сохранения уставки
+	/// </summary>
+	/// <param name="setpointKey">ключ уставки</param>
+	/// <param name="newValue">значение</param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	Task<IActionResult> Save(Guid setpointKey, object newValue, Guid userId, CancellationToken token);
 }
diff --git a/Persistence/API/ISyncApi.cs b/Persistence/API/ISyncApi.cs
index 7f72812..e630ee7 100644
--- a/Persistence/API/ISyncApi.cs
+++ b/Persistence/API/ISyncApi.cs
@@ -6,7 +6,7 @@ namespace Persistence.API;
 /// <summary>
 /// Интерфейс для API, предназначенного для синхронизации данных
 /// </summary>
-public interface ISyncApi<TDto> where TDto : class, new()
+public interface ISyncApi<TDto>
 {
     /// <summary>
     /// Получить порцию записей, начиная с заданной даты 
diff --git a/Persistence/Models/ADSystemDto.cs b/Persistence/Models/DrillingSystemDto.cs
similarity index 92%
rename from Persistence/Models/ADSystemDto.cs
rename to Persistence/Models/DrillingSystemDto.cs
index d11dfc7..c2e7abc 100644
--- a/Persistence/Models/ADSystemDto.cs
+++ b/Persistence/Models/DrillingSystemDto.cs
@@ -3,7 +3,7 @@
 /// <summary>
 /// Модель системы автобурения
 /// </summary>
-public class ADSystemDto
+public class DrillingSystemDto
 {
 	/// <summary>
 	/// Ключ
diff --git a/Persistence/Models/MessagesStatisticDto.cs b/Persistence/Models/MessagesStatisticDto.cs
new file mode 100644
index 0000000..08f0edd
--- /dev/null
+++ b/Persistence/Models/MessagesStatisticDto.cs
@@ -0,0 +1,17 @@
+namespace Persistence.Models;
+
+/// <summary>
+/// Статистика сообщений по системам бурения
+/// </summary>
+public class MessagesStatisticDto
+{
+	/// <summary>
+	/// Система бурения
+	/// </summary>
+	public required string System {  get; set; }
+
+	/// <summary>
+	/// Количество сообщений в соответствии с категориями важности
+	/// </summary>
+	public required Dictionary<int, int> Categories { get; set; }
+}
diff --git a/Persistence/Models/SetpointLogDto.cs b/Persistence/Models/SetpointLogDto.cs
index 484be7a..4aa61b5 100644
--- a/Persistence/Models/SetpointLogDto.cs
+++ b/Persistence/Models/SetpointLogDto.cs
@@ -13,5 +13,5 @@ public class SetpointLogDto : SetpointValueDto
     /// <summary>
     /// Ключ пользователя
     /// </summary>
-    public int IdUser { get; set; }
+    public Guid IdUser { get; set; }
 }
diff --git a/Persistence/Repositories/ISetpointRepository.cs b/Persistence/Repositories/ISetpointRepository.cs
index db6838c..1fe41de 100644
--- a/Persistence/Repositories/ISetpointRepository.cs
+++ b/Persistence/Repositories/ISetpointRepository.cs
@@ -1,3 +1,4 @@
+using Microsoft.AspNetCore.Mvc;
 using Persistence.Models;
 
 namespace Persistence.Repositories;
@@ -32,15 +33,31 @@ public interface ISetpointRepository
     /// <returns></returns>
     Task<Dictionary<Guid, IEnumerable<SetpointLogDto>>> GetLog(IEnumerable<Guid> setpointKeys, CancellationToken token);
 
-    /// <summary>
-    /// Метод сохранения уставки
-    /// </summary>
-    /// <param name="setpointKey">ключ операции</param>
-    /// <param name="idUser">ключ пользователя</param>
-    /// <param name="newValue">значение</param>
-    /// <param name="token"></param>
-    /// <returns></returns>
-    /// to do
-    /// id User учесть в соответствующем методе репозитория
-    Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token);
+	/// <summary>
+	/// Получить порцию записей, начиная с заданной даты 
+	/// </summary>
+	/// <param name="dateBegin"></param>
+	/// <param name="take"></param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	Task<IEnumerable<SetpointLogDto>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
+
+	/// <summary>
+	/// Получить диапазон дат, для которых есть данные в репозитории
+	/// </summary>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token);
+
+	/// <summary>
+	/// Метод сохранения уставки
+	/// </summary>
+	/// <param name="setpointKey">ключ операции</param>
+	/// <param name="idUser">ключ пользователя</param>
+	/// <param name="newValue">значение</param>
+	/// <param name="token"></param>
+	/// <returns></returns>
+	/// to do
+	/// id User учесть в соответствующем методе репозитория
+	Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token);
 }
diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs
index 74b2bf5..853f234 100644
--- a/Persistence/Repositories/ITechMessagesRepository.cs
+++ b/Persistence/Repositories/ITechMessagesRepository.cs
@@ -34,10 +34,26 @@ namespace Persistence.Repositories
 		/// <summary>
 		/// Получение количества сообщений по категориям и системам автобурения
 		/// </summary>
-		/// <param name="importantId">Id Категории важности</param>
+		/// <param name="categoryId">Id Категории важности</param>
 		/// <param name="autoDrillingSystem">Система автобурения</param>
 		/// <param name="token"></param>
 		/// <returns></returns>
-		Task<Dictionary<string, int>> GetStatistics(int? importantId, string? autoDrillingSystem, CancellationToken token);
+		Task<IEnumerable<MessagesStatisticDto>> GetStatistics(IEnumerable<string> autoDrillingSystem, IEnumerable<int> categoryIds, CancellationToken token);
+
+		/// <summary>
+		/// Получить порцию записей, начиная с заданной даты 
+		/// </summary>
+		/// <param name="dateBegin"></param>
+		/// <param name="take"></param>
+		/// <param name="token"></param>
+		/// <returns></returns>
+		Task<IEnumerable<TechMessageDto>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
+
+		/// <summary>
+		/// Получить диапазон дат, для которых есть данные в репозитории
+		/// </summary>
+		/// <param name="token"></param>
+		/// <returns></returns>
+		Task<DatesRangeDto> GetDatesRangeAsync(CancellationToken token);
 	}
 }
-- 
2.45.2


From 7092324a5461ff6402de11484159a2eb4e0139f9 Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Wed, 4 Dec 2024 14:18:57 +0500
Subject: [PATCH 10/12] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B8=D0=BC?=
 =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20Insert=20=D0=BD?=
 =?UTF-8?q?=D0=B0=20Add?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Persistence.API/Controllers/SetpointController.cs           | 6 +++---
 Persistence.API/Controllers/TechMessagesController.cs       | 6 +++---
 Persistence.API/Controllers/TimeSeriesController.cs         | 4 ++--
 Persistence.API/Controllers/TimestampedSetController.cs     | 4 ++--
 Persistence.Repository/Repositories/SetpointRepository.cs   | 2 +-
 .../Repositories/TechMessagesRepository.cs                  | 2 +-
 .../Repositories/TimeSeriesDataCachedRepository.cs          | 4 ++--
 .../Repositories/TimeSeriesDataRepository.cs                | 2 +-
 .../Repositories/TimestampedSetRepository.cs                | 2 +-
 Persistence/API/ISetpointApi.cs                             | 2 +-
 Persistence/API/ITimeSeriesDataApi.cs                       | 2 +-
 Persistence/Repositories/AbstractChangeLogRepository.cs     | 2 +-
 Persistence/Repositories/ISetpointRepository.cs             | 2 +-
 Persistence/Repositories/ITechMessagesRepository.cs         | 2 +-
 Persistence/Repositories/ITimeSeriesDataRepository.cs       | 2 +-
 Persistence/Repositories/ITimestampedSetRepository.cs       | 2 +-
 16 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/Persistence.API/Controllers/SetpointController.cs b/Persistence.API/Controllers/SetpointController.cs
index 108c3ef..1bae2c6 100644
--- a/Persistence.API/Controllers/SetpointController.cs
+++ b/Persistence.API/Controllers/SetpointController.cs
@@ -102,10 +102,10 @@ public class SetpointController : ControllerBase, ISetpointApi
 	/// <returns></returns>
 	[HttpPost]
 	[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
-	public async Task<IActionResult> Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token)
+	public async Task<IActionResult> Add(Guid setpointKey, object newValue, Guid idUser, CancellationToken token)
 	{
-		await setpointRepository.Save(setpointKey, newValue, idUser, token);
+		await setpointRepository.Add(setpointKey, newValue, idUser, token);
 
-		return Ok();
+		return CreatedAtAction(nameof(Add), true);
 	}
 }
diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs
index 4e91802..bb26c6a 100644
--- a/Persistence.API/Controllers/TechMessagesController.cs
+++ b/Persistence.API/Controllers/TechMessagesController.cs
@@ -107,7 +107,7 @@ public class TechMessagesController : ControllerBase
 	/// <returns></returns>
 	[HttpPost]
 	[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
-	public async Task<IActionResult> InsertRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
+	public async Task<IActionResult> AddRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
 	{
 		var userId = User.GetUserId<Guid>();
         foreach (var dto in dtos)
@@ -115,9 +115,9 @@ public class TechMessagesController : ControllerBase
 			dto.UserId = userId;
         }
 
-        var result = await techMessagesRepository.InsertRange(dtos, token);
+        var result = await techMessagesRepository.AddRange(dtos, token);
 
-		return CreatedAtAction(nameof(InsertRange), result);
+		return CreatedAtAction(nameof(AddRange), result);
 	}
 
 	/// <summary>
diff --git a/Persistence.API/Controllers/TimeSeriesController.cs b/Persistence.API/Controllers/TimeSeriesController.cs
index 90c78c8..6991759 100644
--- a/Persistence.API/Controllers/TimeSeriesController.cs
+++ b/Persistence.API/Controllers/TimeSeriesController.cs
@@ -66,9 +66,9 @@ public class TimeSeriesController<TDto> : ControllerBase, ITimeSeriesDataApi<TDt
 	/// <param name="token"></param>
 	/// <returns></returns>
 	[HttpPost]
-    public async Task<IActionResult> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
+    public async Task<IActionResult> AddRange(IEnumerable<TDto> dtos, CancellationToken token)
     {
-        var result = await timeSeriesDataRepository.InsertRange(dtos, token);
+        var result = await timeSeriesDataRepository.AddRange(dtos, token);
         return Ok(result);
     }
 
diff --git a/Persistence.API/Controllers/TimestampedSetController.cs b/Persistence.API/Controllers/TimestampedSetController.cs
index f18e4c8..bd0e97e 100644
--- a/Persistence.API/Controllers/TimestampedSetController.cs
+++ b/Persistence.API/Controllers/TimestampedSetController.cs
@@ -32,9 +32,9 @@ public class TimestampedSetController : ControllerBase
     /// <returns>кол-во затронутых записей</returns>
     [HttpPost]
     [ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
-    public async Task<IActionResult> InsertRange([FromRoute]Guid idDiscriminator, [FromBody]IEnumerable<TimestampedSetDto> sets, CancellationToken token)
+    public async Task<IActionResult> AddRange([FromRoute]Guid idDiscriminator, [FromBody]IEnumerable<TimestampedSetDto> sets, CancellationToken token)
     {
-        var result = await repository.InsertRange(idDiscriminator, sets, token);
+        var result = await repository.AddRange(idDiscriminator, sets, token);
         return Ok(result);
     }
 
diff --git a/Persistence.Repository/Repositories/SetpointRepository.cs b/Persistence.Repository/Repositories/SetpointRepository.cs
index b4557aa..8f0492c 100644
--- a/Persistence.Repository/Repositories/SetpointRepository.cs
+++ b/Persistence.Repository/Repositories/SetpointRepository.cs
@@ -88,7 +88,7 @@ namespace Persistence.Repository.Repositories
 			return dtos;
 		}
 
-		public async Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token)
+		public async Task Add(Guid setpointKey, object newValue, Guid idUser, CancellationToken token)
 		{
 			var entity = new Setpoint()
 			{
diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs
index fad8acf..04bab02 100644
--- a/Persistence.Repository/Repositories/TechMessagesRepository.cs
+++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs
@@ -88,7 +88,7 @@ namespace Persistence.Repository.Repositories
 			return result;
 		}
 
-		public async Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
+		public async Task<int> AddRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
 		{
 
 			var entities = new List<TechMessage>();
diff --git a/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs b/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs
index 20dbe00..2037f09 100644
--- a/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs
+++ b/Persistence.Repository/Repositories/TimeSeriesDataCachedRepository.cs
@@ -48,9 +48,9 @@ public class TimeSeriesDataCachedRepository<TEntity, TDto> : TimeSeriesDataRepos
         return items;
     }
 
-    public override async Task<int> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
+    public override async Task<int> AddRange(IEnumerable<TDto> dtos, CancellationToken token)
     {
-        var result = await base.InsertRange(dtos, token);
+        var result = await base.AddRange(dtos, token);
         if (result > 0)
         {
             
diff --git a/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs b/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs
index d73caf7..0e58c76 100644
--- a/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs
+++ b/Persistence.Repository/Repositories/TimeSeriesDataRepository.cs
@@ -41,7 +41,7 @@ public class TimeSeriesDataRepository<TEntity, TDto> : ITimeSeriesDataRepository
         return dtos;
     }
 
-    public virtual async Task<int> InsertRange(IEnumerable<TDto> dtos, CancellationToken token)
+    public virtual async Task<int> AddRange(IEnumerable<TDto> dtos, CancellationToken token)
     {
         var entities = dtos.Select(d => d.Adapt<TEntity>());
 
diff --git a/Persistence.Repository/Repositories/TimestampedSetRepository.cs b/Persistence.Repository/Repositories/TimestampedSetRepository.cs
index ad9a6cf..a67b823 100644
--- a/Persistence.Repository/Repositories/TimestampedSetRepository.cs
+++ b/Persistence.Repository/Repositories/TimestampedSetRepository.cs
@@ -20,7 +20,7 @@ public class TimestampedSetRepository : ITimestampedSetRepository
         this.db = db;
     }
 
-    public Task<int> InsertRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets, CancellationToken token)
+    public Task<int> AddRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets, CancellationToken token)
     {
         var entities = sets.Select(set => new TimestampedSet(idDiscriminator, set.Timestamp.ToUniversalTime(), set.Set));
         var dbSet = db.Set<TimestampedSet>();
diff --git a/Persistence/API/ISetpointApi.cs b/Persistence/API/ISetpointApi.cs
index d05fe12..0d23d69 100644
--- a/Persistence/API/ISetpointApi.cs
+++ b/Persistence/API/ISetpointApi.cs
@@ -40,5 +40,5 @@ public interface ISetpointApi : ISyncApi<SetpointLogDto>
 	/// <param name="newValue">значение</param>
 	/// <param name="token"></param>
 	/// <returns></returns>
-	Task<IActionResult> Save(Guid setpointKey, object newValue, Guid userId, CancellationToken token);
+	Task<IActionResult> Add(Guid setpointKey, object newValue, Guid userId, CancellationToken token);
 }
diff --git a/Persistence/API/ITimeSeriesDataApi.cs b/Persistence/API/ITimeSeriesDataApi.cs
index a2406aa..51b9332 100644
--- a/Persistence/API/ITimeSeriesDataApi.cs
+++ b/Persistence/API/ITimeSeriesDataApi.cs
@@ -28,7 +28,7 @@ public interface ITimeSeriesDataApi<TDto> : ITimeSeriesBaseDataApi<TDto>
     /// <param name="dtos"></param>
     /// <param name="token"></param>
     /// <returns></returns>
-    Task<IActionResult> InsertRange(IEnumerable<TDto> dtos, CancellationToken token);
+    Task<IActionResult> AddRange(IEnumerable<TDto> dtos, CancellationToken token);
 
 
 }
diff --git a/Persistence/Repositories/AbstractChangeLogRepository.cs b/Persistence/Repositories/AbstractChangeLogRepository.cs
index 88cf511..d7b1cbc 100644
--- a/Persistence/Repositories/AbstractChangeLogRepository.cs
+++ b/Persistence/Repositories/AbstractChangeLogRepository.cs
@@ -74,7 +74,7 @@ namespace Persistence.Repositories;
 //        throw new NotImplementedException();
 //    }
 
-//    public async Task<int> InsertRange(int idUser, IEnumerable<TDto> dtos, CancellationToken token)
+//    public async Task<int> AddRange(int idUser, IEnumerable<TDto> dtos, CancellationToken token)
 //    {
 //        using var transaction = dbContext.Database.BeginTransaction();
 //        try
diff --git a/Persistence/Repositories/ISetpointRepository.cs b/Persistence/Repositories/ISetpointRepository.cs
index 1fe41de..502b44a 100644
--- a/Persistence/Repositories/ISetpointRepository.cs
+++ b/Persistence/Repositories/ISetpointRepository.cs
@@ -59,5 +59,5 @@ public interface ISetpointRepository
 	/// <returns></returns>
 	/// to do
 	/// id User учесть в соответствующем методе репозитория
-	Task Save(Guid setpointKey, object newValue, Guid idUser, CancellationToken token);
+	Task Add(Guid setpointKey, object newValue, Guid idUser, CancellationToken token);
 }
diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs
index 853f234..2ddf71c 100644
--- a/Persistence/Repositories/ITechMessagesRepository.cs
+++ b/Persistence/Repositories/ITechMessagesRepository.cs
@@ -22,7 +22,7 @@ namespace Persistence.Repositories
 		/// <param name="dtos"></param>
 		/// <param name="token"></param>
 		/// <returns></returns>
-		Task<int> InsertRange(IEnumerable<TechMessageDto> dtos, CancellationToken token);
+		Task<int> AddRange(IEnumerable<TechMessageDto> dtos, CancellationToken token);
 
 		/// <summary>
 		/// Получение списка уникальных названий систем АБ
diff --git a/Persistence/Repositories/ITimeSeriesDataRepository.cs b/Persistence/Repositories/ITimeSeriesDataRepository.cs
index 9de7fc7..aa2c9ff 100644
--- a/Persistence/Repositories/ITimeSeriesDataRepository.cs
+++ b/Persistence/Repositories/ITimeSeriesDataRepository.cs
@@ -15,5 +15,5 @@ public interface ITimeSeriesDataRepository<TDto> : ISyncRepository<TDto>, ITimeS
     /// <param name="dtos"></param>
     /// <param name="token"></param>
     /// <returns></returns>
-    Task<int> InsertRange(IEnumerable<TDto> dtos, CancellationToken token);
+    Task<int> AddRange(IEnumerable<TDto> dtos, CancellationToken token);
 }
diff --git a/Persistence/Repositories/ITimestampedSetRepository.cs b/Persistence/Repositories/ITimestampedSetRepository.cs
index 27627c3..c350739 100644
--- a/Persistence/Repositories/ITimestampedSetRepository.cs
+++ b/Persistence/Repositories/ITimestampedSetRepository.cs
@@ -55,5 +55,5 @@ public interface ITimestampedSetRepository
     /// <param name="sets"></param>
     /// <param name="token"></param>
     /// <returns></returns>
-    Task<int> InsertRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets, CancellationToken token);
+    Task<int> AddRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets, CancellationToken token);
 }
\ No newline at end of file
-- 
2.45.2


From dd888793fd5d4a54cdd63400b5d62d3534ba4d8d Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Wed, 4 Dec 2024 14:44:33 +0500
Subject: [PATCH 11/12] =?UTF-8?q?=D0=92=D0=BD=D0=B5=D1=81=D1=82=D0=B8=20?=
 =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?=
 =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D1=80=D0=B5=D0=B2?=
 =?UTF-8?q?=D1=8C=D1=8E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Persistence.Client/Clients/ISetpointClient.cs |  2 +-
 .../Clients/ITechMessagesClient.cs            |  2 +-
 .../Clients/ITimeSeriesClient.cs              |  2 +-
 .../Clients/ITimestampedSetClient.cs          |  2 +-
 .../Controllers/SetpointControllerTest.cs     | 41 +++++++++++++------
 .../Controllers/TechMessagesControllerTest.cs |  4 +-
 .../TimeSeriesBaseControllerTest.cs           |  2 +-
 .../TimestampedSetControllerTest.cs           | 18 ++++----
 .../Repositories/SetpointRepository.cs        |  2 +-
 .../Repositories/TechMessagesRepository.cs    |  2 +-
 10 files changed, 46 insertions(+), 31 deletions(-)

diff --git a/Persistence.Client/Clients/ISetpointClient.cs b/Persistence.Client/Clients/ISetpointClient.cs
index 886e034..0b81ffa 100644
--- a/Persistence.Client/Clients/ISetpointClient.cs
+++ b/Persistence.Client/Clients/ISetpointClient.cs
@@ -26,5 +26,5 @@ public interface ISetpointClient
 	Task<IApiResponse<IEnumerable<SetpointLogDto>>> GetPart(DateTimeOffset dateBegin, int take, CancellationToken token);
 
 	[Post($"{BaseRoute}/")]
-	Task<IApiResponse> Save(Guid setpointKey, object newValue);
+	Task<IApiResponse> Add(Guid setpointKey, object newValue);
 }
diff --git a/Persistence.Client/Clients/ITechMessagesClient.cs b/Persistence.Client/Clients/ITechMessagesClient.cs
index cbdf7ef..878c6cf 100644
--- a/Persistence.Client/Clients/ITechMessagesClient.cs
+++ b/Persistence.Client/Clients/ITechMessagesClient.cs
@@ -14,7 +14,7 @@ namespace Persistence.Client.Clients
 		Task<IApiResponse<PaginationContainer<TechMessageDto>>> GetPage([Query] RequestDto request, CancellationToken token);
 
 		[Post($"{BaseRoute}")]
-		Task<IApiResponse<int>> InsertRange([Body] IEnumerable<TechMessageDto> dtos, CancellationToken token);
+		Task<IApiResponse<int>> AddRange([Body] IEnumerable<TechMessageDto> dtos, CancellationToken token);
 
 		[Get($"{BaseRoute}/systems")]
 		Task<IApiResponse<IEnumerable<string>>> GetSystems(CancellationToken token);
diff --git a/Persistence.Client/Clients/ITimeSeriesClient.cs b/Persistence.Client/Clients/ITimeSeriesClient.cs
index 349337b..8e97836 100644
--- a/Persistence.Client/Clients/ITimeSeriesClient.cs
+++ b/Persistence.Client/Clients/ITimeSeriesClient.cs
@@ -8,7 +8,7 @@ public interface ITimeSeriesClient<TDto>
     private const string BaseRoute = "/api/dataSaub";
 
     [Post($"{BaseRoute}")]
-    Task<IApiResponse<int>> InsertRange(IEnumerable<TDto> dtos);
+    Task<IApiResponse<int>> AddRange(IEnumerable<TDto> dtos);
 
     [Get($"{BaseRoute}")]
     Task<IApiResponse<IEnumerable<TDto>>> Get(DateTimeOffset dateBegin, DateTimeOffset dateEnd);
diff --git a/Persistence.Client/Clients/ITimestampedSetClient.cs b/Persistence.Client/Clients/ITimestampedSetClient.cs
index 95e8bd1..bbff603 100644
--- a/Persistence.Client/Clients/ITimestampedSetClient.cs
+++ b/Persistence.Client/Clients/ITimestampedSetClient.cs
@@ -20,7 +20,7 @@ public interface ITimestampedSetClient
     /// <param name="sets"></param>
     /// <returns></returns>
     [Post(baseUrl)]
-    Task<IApiResponse<int>> InsertRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets);
+    Task<IApiResponse<int>> AddRange(Guid idDiscriminator, IEnumerable<TimestampedSetDto> sets);
 
     /// <summary>
     /// Получение данных с фильтрацией. Значение фильтра null - отключен
diff --git a/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs
index d314cec..2c455a0 100644
--- a/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs
+++ b/Persistence.IntegrationTests/Controllers/SetpointControllerTest.cs
@@ -1,4 +1,5 @@
 using System.Net;
+using Microsoft.AspNetCore.Mvc.TagHelpers.Cache;
 using Microsoft.Extensions.DependencyInjection;
 using Persistence.Client;
 using Persistence.Client.Clients;
@@ -47,7 +48,7 @@ namespace Persistence.IntegrationTests.Controllers
 		public async Task GetCurrent_AfterSave_returns_success()
 		{
 			//arrange
-			var setpointKey = await Save();
+			var setpointKey = await Add();
 
 			//act
 			var response = await setpointClient.GetCurrent([setpointKey]);
@@ -83,7 +84,7 @@ namespace Persistence.IntegrationTests.Controllers
 		public async Task GetHistory_AfterSave_returns_success()
 		{
 			//arrange
-			var setpointKey = await Save();
+			var setpointKey = await Add();
 			var historyMoment = DateTimeOffset.UtcNow;
 			historyMoment = historyMoment.AddDays(1);
 
@@ -120,7 +121,7 @@ namespace Persistence.IntegrationTests.Controllers
 		public async Task GetLog_AfterSave_returns_success()
 		{
 			//arrange
-			var setpointKey = await Save();
+			var setpointKey = await Add();
 
 			//act
 			var response = await setpointClient.GetLog([setpointKey]);
@@ -144,8 +145,8 @@ namespace Persistence.IntegrationTests.Controllers
 			//assert
 			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
 			Assert.NotNull(response.Content);
-			Assert.Equal(DateTimeOffset.MinValue, response.Content?.From);
-			Assert.Equal(DateTimeOffset.MaxValue, response.Content?.To);
+			Assert.Equal(DateTimeOffset.MinValue, response.Content!.From);
+			Assert.Equal(DateTimeOffset.MaxValue, response.Content!.To);
 		}
 
 		[Fact]
@@ -153,7 +154,12 @@ namespace Persistence.IntegrationTests.Controllers
 		{
 			//arrange
 			dbContext.CleanupDbSet<Setpoint>();
-			await Save();
+			
+			await Add();
+
+			var dateBegin = DateTimeOffset.MinValue;
+			var take = 1;
+			var part = await setpointClient.GetPart(dateBegin, take, new CancellationToken());
 
 			//act
 			var response = await setpointClient.GetDatesRangeAsync(new CancellationToken());
@@ -161,8 +167,17 @@ namespace Persistence.IntegrationTests.Controllers
 			//assert
 			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
 			Assert.NotNull(response.Content);
-			Assert.NotNull(response.Content?.From);
-			Assert.NotNull(response.Content?.To);
+
+			var expectedValue = part.Content!
+				.FirstOrDefault()!.Created
+				.ToString("dd.MM.yyyy-HH:mm:ss");
+			var actualValueFrom = response.Content.From
+				.ToString("dd.MM.yyyy-HH:mm:ss");
+			Assert.Equal(expectedValue, actualValueFrom);
+
+			var actualValueTo = response.Content.To
+				.ToString("dd.MM.yyyy-HH:mm:ss");
+			Assert.Equal(expectedValue, actualValueTo);
 		}
 
 		[Fact]
@@ -187,7 +202,7 @@ namespace Persistence.IntegrationTests.Controllers
 			//arrange
 			var dateBegin = DateTimeOffset.UtcNow;
 			var take = 1;
-			await Save();
+			await Add();
 
 			//act
 			var response = await setpointClient.GetPart(dateBegin, take, new CancellationToken());
@@ -201,10 +216,10 @@ namespace Persistence.IntegrationTests.Controllers
 		[Fact]
 		public async Task Save_returns_success()
 		{
-			await Save();
+			await Add();
 		}
 
-		private async Task<Guid> Save()
+		private async Task<Guid> Add()
 		{
 			//arrange
 			var setpointKey = Guid.NewGuid();
@@ -215,10 +230,10 @@ namespace Persistence.IntegrationTests.Controllers
 			};
 
 			//act
-			var response = await setpointClient.Save(setpointKey, setpointValue);
+			var response = await setpointClient.Add(setpointKey, setpointValue);
 
 			//assert
-			Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+			Assert.Equal(HttpStatusCode.Created, response.StatusCode);
 
 			return setpointKey;
 		}
diff --git a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
index ec4e40a..1f194ad 100644
--- a/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
+++ b/Persistence.IntegrationTests/Controllers/TechMessagesControllerTest.cs
@@ -97,7 +97,7 @@ namespace Persistence.IntegrationTests.Controllers
 			};
 
 			//act
-			var response = await techMessagesClient.InsertRange(dtos, new CancellationToken());
+			var response = await techMessagesClient.AddRange(dtos, new CancellationToken());
 
 			//assert
 			Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
@@ -276,7 +276,7 @@ namespace Persistence.IntegrationTests.Controllers
 
 
 			//act
-			var response = await techMessagesClient.InsertRange(dtos, new CancellationToken());
+			var response = await techMessagesClient.AddRange(dtos, new CancellationToken());
 
 			//assert
 			Assert.Equal(HttpStatusCode.Created, response.StatusCode);
diff --git a/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs b/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs
index 87efa43..08bb11e 100644
--- a/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs
+++ b/Persistence.IntegrationTests/Controllers/TimeSeriesBaseControllerTest.cs
@@ -30,7 +30,7 @@ public abstract class TimeSeriesBaseControllerTest<TEntity, TDto> : BaseIntegrat
         var expected = dto.Adapt<TDto>();
 
         //act
-        var response = await timeSeriesClient.InsertRange(new TDto[] { expected });
+        var response = await timeSeriesClient.AddRange(new TDto[] { expected });
 
         //assert
         Assert.Equal(HttpStatusCode.OK, response.StatusCode);
diff --git a/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs b/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs
index aa33e1b..3fc7dff 100644
--- a/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs
+++ b/Persistence.IntegrationTests/Controllers/TimestampedSetControllerTest.cs
@@ -25,7 +25,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest
         IEnumerable<TimestampedSetDto> testSets = Generate(10, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
 
         // act
-        var response = await client.InsertRange(idDiscriminator, testSets);
+        var response = await client.AddRange(idDiscriminator, testSets);
 
         // assert
         Assert.Equal(System.Net.HttpStatusCode.OK, response.StatusCode);
@@ -39,7 +39,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest
         Guid idDiscriminator = Guid.NewGuid();
         int count = 10;
         IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
-        var insertResponse = await client.InsertRange(idDiscriminator, testSets);
+        var insertResponse = await client.AddRange(idDiscriminator, testSets);
 
         // act
         var response = await client.Get(idDiscriminator, null, null, 0, int.MaxValue);
@@ -58,7 +58,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest
         Guid idDiscriminator = Guid.NewGuid();
         int count = 10;
         IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
-        var insertResponse = await client.InsertRange(idDiscriminator, testSets);
+        var insertResponse = await client.AddRange(idDiscriminator, testSets);
         string[] props = ["A"];
 
         // act
@@ -86,7 +86,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest
         var dateMin = DateTimeOffset.Now;
         var dateMax = DateTimeOffset.Now.AddSeconds(count);
         IEnumerable<TimestampedSetDto> testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7)));        
-        var insertResponse = await client.InsertRange(idDiscriminator, testSets);
+        var insertResponse = await client.AddRange(idDiscriminator, testSets);
         var tail = testSets.OrderBy(t => t.Timestamp).Skip(count / 2).Take(int.MaxValue);
         var geDate = tail.First().Timestamp;
         var tolerance = TimeSpan.FromSeconds(1);
@@ -111,7 +111,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest
         Guid idDiscriminator = Guid.NewGuid();
         int count = 10;
         IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
-        var insertResponse = await client.InsertRange(idDiscriminator, testSets);
+        var insertResponse = await client.AddRange(idDiscriminator, testSets);
         var expectedCount = count / 2;
 
         // act
@@ -133,7 +133,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest
         var expectedCount = 1;
         int count = 10 + expectedCount;
         IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
-        var insertResponse = await client.InsertRange(idDiscriminator, testSets);
+        var insertResponse = await client.AddRange(idDiscriminator, testSets);
 
         // act
         var response = await client.Get(idDiscriminator, null, null, count - expectedCount, count);
@@ -152,7 +152,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest
         Guid idDiscriminator = Guid.NewGuid();
         int count = 10;
         IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
-        var insertResponse = await client.InsertRange(idDiscriminator, testSets);
+        var insertResponse = await client.AddRange(idDiscriminator, testSets);
         var expectedCount = 8;
 
         // act
@@ -174,7 +174,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest
         var dateMin = DateTimeOffset.Now;
         var dateMax = DateTimeOffset.Now.AddSeconds(count-1);
         IEnumerable<TimestampedSetDto> testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7)));
-        var insertResponse = await client.InsertRange(idDiscriminator, testSets);
+        var insertResponse = await client.AddRange(idDiscriminator, testSets);
         var tolerance = TimeSpan.FromSeconds(1);
 
         // act
@@ -195,7 +195,7 @@ public class TimestampedSetControllerTest : BaseIntegrationTest
         Guid idDiscriminator = Guid.NewGuid();
         int count = 144;
         IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
-        var insertResponse = await client.InsertRange(idDiscriminator, testSets);
+        var insertResponse = await client.AddRange(idDiscriminator, testSets);
 
         // act
         var response = await client.Count(idDiscriminator);
diff --git a/Persistence.Repository/Repositories/SetpointRepository.cs b/Persistence.Repository/Repositories/SetpointRepository.cs
index 8f0492c..f0e921f 100644
--- a/Persistence.Repository/Repositories/SetpointRepository.cs
+++ b/Persistence.Repository/Repositories/SetpointRepository.cs
@@ -47,7 +47,7 @@ namespace Persistence.Repository.Repositories
 		{
 			var query = GetQueryReadOnly();
 			var entities = await query
-				.Where(e => e.Created > dateBegin)
+				.Where(e => e.Created >= dateBegin)
 				.Take(take)
 				.ToArrayAsync(token);
 			var dtos = entities
diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs
index 04bab02..38d24e8 100644
--- a/Persistence.Repository/Repositories/TechMessagesRepository.cs
+++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs
@@ -114,7 +114,7 @@ namespace Persistence.Repository.Repositories
 		{
 			var query = GetQueryReadOnly();
 			var entities = await query
-				.Where(e => e.Timestamp > dateBegin)
+				.Where(e => e.Timestamp >= dateBegin)
 				.Take(take)
 				.ToArrayAsync(token);
 			var dtos = entities
-- 
2.45.2


From 2228c841397a1532c492e7d52dcfba36938a82d4 Mon Sep 17 00:00:00 2001
From: Roman Efremov <rs.efremov@digitaldrilling.ru>
Date: Wed, 4 Dec 2024 16:29:15 +0500
Subject: [PATCH 12/12] =?UTF-8?q?=D0=94=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD?=
 =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?=
 =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Persistence.API/Controllers/SetpointController.cs          | 7 ++++---
 Persistence.API/Controllers/TechMessagesController.cs      | 6 +-----
 .../Repositories/TechMessagesRepository.cs                 | 3 ++-
 Persistence/API/ISetpointApi.cs                            | 2 +-
 Persistence/Repositories/ITechMessagesRepository.cs        | 2 +-
 5 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/Persistence.API/Controllers/SetpointController.cs b/Persistence.API/Controllers/SetpointController.cs
index 1bae2c6..42a5ff5 100644
--- a/Persistence.API/Controllers/SetpointController.cs
+++ b/Persistence.API/Controllers/SetpointController.cs
@@ -101,10 +101,11 @@ public class SetpointController : ControllerBase, ISetpointApi
 	/// <param name="token"></param>
 	/// <returns></returns>
 	[HttpPost]
-	[ProducesResponseType(typeof(int), (int)HttpStatusCode.OK)]
-	public async Task<IActionResult> Add(Guid setpointKey, object newValue, Guid idUser, CancellationToken token)
+	[ProducesResponseType(typeof(int), (int)HttpStatusCode.Created)]
+	public async Task<IActionResult> Add(Guid setpointKey, object newValue, CancellationToken token)
 	{
-		await setpointRepository.Add(setpointKey, newValue, idUser, token);
+		var userId = User.GetUserId<Guid>();
+		await setpointRepository.Add(setpointKey, newValue, userId, token);
 
 		return CreatedAtAction(nameof(Add), true);
 	}
diff --git a/Persistence.API/Controllers/TechMessagesController.cs b/Persistence.API/Controllers/TechMessagesController.cs
index bb26c6a..d2691c1 100644
--- a/Persistence.API/Controllers/TechMessagesController.cs
+++ b/Persistence.API/Controllers/TechMessagesController.cs
@@ -110,12 +110,8 @@ public class TechMessagesController : ControllerBase
 	public async Task<IActionResult> AddRange([FromBody] IEnumerable<TechMessageDto> dtos, CancellationToken token)
 	{
 		var userId = User.GetUserId<Guid>();
-        foreach (var dto in dtos)
-        {
-			dto.UserId = userId;
-        }
 
-        var result = await techMessagesRepository.AddRange(dtos, token);
+        var result = await techMessagesRepository.AddRange(dtos, userId, token);
 
 		return CreatedAtAction(nameof(AddRange), result);
 	}
diff --git a/Persistence.Repository/Repositories/TechMessagesRepository.cs b/Persistence.Repository/Repositories/TechMessagesRepository.cs
index 38d24e8..c838619 100644
--- a/Persistence.Repository/Repositories/TechMessagesRepository.cs
+++ b/Persistence.Repository/Repositories/TechMessagesRepository.cs
@@ -88,7 +88,7 @@ namespace Persistence.Repository.Repositories
 			return result;
 		}
 
-		public async Task<int> AddRange(IEnumerable<TechMessageDto> dtos, CancellationToken token)
+		public async Task<int> AddRange(IEnumerable<TechMessageDto> dtos, Guid userId, CancellationToken token)
 		{
 
 			var entities = new List<TechMessage>();
@@ -100,6 +100,7 @@ namespace Persistence.Repository.Repositories
 					?? await CreateDrillingSystem(dto.System, token);
 
 				entity.SystemId = systemId;
+				entity.UserId = userId;
 
 				entities.Add(entity);
 			}
diff --git a/Persistence/API/ISetpointApi.cs b/Persistence/API/ISetpointApi.cs
index 0d23d69..b1504a2 100644
--- a/Persistence/API/ISetpointApi.cs
+++ b/Persistence/API/ISetpointApi.cs
@@ -40,5 +40,5 @@ public interface ISetpointApi : ISyncApi<SetpointLogDto>
 	/// <param name="newValue">значение</param>
 	/// <param name="token"></param>
 	/// <returns></returns>
-	Task<IActionResult> Add(Guid setpointKey, object newValue, Guid userId, CancellationToken token);
+	Task<IActionResult> Add(Guid setpointKey, object newValue, CancellationToken token);
 }
diff --git a/Persistence/Repositories/ITechMessagesRepository.cs b/Persistence/Repositories/ITechMessagesRepository.cs
index 2ddf71c..92e8f70 100644
--- a/Persistence/Repositories/ITechMessagesRepository.cs
+++ b/Persistence/Repositories/ITechMessagesRepository.cs
@@ -22,7 +22,7 @@ namespace Persistence.Repositories
 		/// <param name="dtos"></param>
 		/// <param name="token"></param>
 		/// <returns></returns>
-		Task<int> AddRange(IEnumerable<TechMessageDto> dtos, CancellationToken token);
+		Task<int> AddRange(IEnumerable<TechMessageDto> dtos, Guid userId, CancellationToken token);
 
 		/// <summary>
 		/// Получение списка уникальных названий систем АБ
-- 
2.45.2