diff --git a/backend/Tnsu.ApprovalRoute.Api/Controllers/RequestTypeAccessController.cs b/backend/Tnsu.ApprovalRoute.Api/Controllers/RequestTypeAccessController.cs
new file mode 100644
index 0000000..5e8b8f9
--- /dev/null
+++ b/backend/Tnsu.ApprovalRoute.Api/Controllers/RequestTypeAccessController.cs
@@ -0,0 +1,44 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Tnsu.ApprovalRoute.Api.Auth;
+using Tnsu.ApprovalRoute.Api.Common;
+using Tnsu.ApprovalRoute.Application.RequestTypeAccesses;
+
+namespace Tnsu.ApprovalRoute.Api.Controllers;
+
+///
+/// Настройка доступа «видеть все заявки типа», а не только свои. Список
+/// email'ов по типу дёргает и портал заявок (проверить текущего пользователя),
+/// и Power Automate — поэтому политика AnyAuth, как у RemindersController.
+///
+[ApiController]
+[Authorize(Policy = AuthPolicies.Any)]
+[Route("api/request-type-access")]
+[Produces("application/json")]
+public sealed class RequestTypeAccessController(RequestTypeAccessService service) : ControllerBase
+{
+ [HttpGet]
+ [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)]
+ public async Task List([FromQuery] int? requestTypeId, CancellationToken ct) =>
+ (await service.ListAsync(requestTypeId, ct)).ToActionResult();
+
+ [HttpGet("{requestTypeId:int}/emails")]
+ [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ApiError), StatusCodes.Status404NotFound)]
+ public async Task ListEmails(int requestTypeId, CancellationToken ct) =>
+ (await service.ListEmailsAsync(requestTypeId, ct)).ToActionResult();
+
+ [HttpPost]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(ApiError), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(typeof(ApiError), StatusCodes.Status404NotFound)]
+ [ProducesResponseType(typeof(ApiError), StatusCodes.Status409Conflict)]
+ public async Task Create(CreateRequestTypeAccessRequest body, CancellationToken ct) =>
+ (await service.CreateAsync(body, ct)).ToActionResult();
+
+ [HttpDelete("{id:int}")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(typeof(ApiError), StatusCodes.Status404NotFound)]
+ public async Task Delete(int id, CancellationToken ct) =>
+ (await service.DeleteAsync(id, ct)).ToActionResult();
+}
diff --git a/backend/Tnsu.ApprovalRoute.Application/ApplicationServiceCollectionExtensions.cs b/backend/Tnsu.ApprovalRoute.Application/ApplicationServiceCollectionExtensions.cs
index 5d50eff..4ae58c7 100644
--- a/backend/Tnsu.ApprovalRoute.Application/ApplicationServiceCollectionExtensions.cs
+++ b/backend/Tnsu.ApprovalRoute.Application/ApplicationServiceCollectionExtensions.cs
@@ -6,6 +6,7 @@ using Tnsu.ApprovalRoute.Application.Reminders;
using Tnsu.ApprovalRoute.Application.ListPartitioning;
using Tnsu.ApprovalRoute.Application.Reports;
using Tnsu.ApprovalRoute.Application.RequestManage;
+using Tnsu.ApprovalRoute.Application.RequestTypeAccesses;
using Tnsu.ApprovalRoute.Application.RequestTypes;
using Tnsu.ApprovalRoute.Application.TemplateRoutes;
using Tnsu.ApprovalRoute.Application.TemplateRouteSteps;
@@ -18,6 +19,7 @@ public static class ApplicationServiceCollectionExtensions
{
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/backend/Tnsu.ApprovalRoute.Application/RequestTypeAccesses/RequestTypeAccessDto.cs b/backend/Tnsu.ApprovalRoute.Application/RequestTypeAccesses/RequestTypeAccessDto.cs
new file mode 100644
index 0000000..3cb7950
--- /dev/null
+++ b/backend/Tnsu.ApprovalRoute.Application/RequestTypeAccesses/RequestTypeAccessDto.cs
@@ -0,0 +1,5 @@
+namespace Tnsu.ApprovalRoute.Application.RequestTypeAccesses;
+
+public sealed record RequestTypeAccessDto(int Id, int RequestTypeId, string Email);
+
+public sealed record CreateRequestTypeAccessRequest(int RequestTypeId, string Email);
diff --git a/backend/Tnsu.ApprovalRoute.Application/RequestTypeAccesses/RequestTypeAccessService.cs b/backend/Tnsu.ApprovalRoute.Application/RequestTypeAccesses/RequestTypeAccessService.cs
new file mode 100644
index 0000000..6de3477
--- /dev/null
+++ b/backend/Tnsu.ApprovalRoute.Application/RequestTypeAccesses/RequestTypeAccessService.cs
@@ -0,0 +1,78 @@
+using FluentResults;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using Tnsu.ApprovalRoute.Application.Common.Errors;
+using Tnsu.ApprovalRoute.Application.Common.Interfaces;
+using Tnsu.ApprovalRoute.Domain.Entities;
+using Tnsu.ApprovalRoute.Infrastructure.Persistence;
+
+namespace Tnsu.ApprovalRoute.Application.RequestTypeAccesses;
+
+public sealed class RequestTypeAccessService(
+ ApprovalRouteDbContext db, ICurrentUser currentUser, ILogger logger)
+{
+ public async Task>> ListAsync(
+ int? requestTypeId, CancellationToken cancellationToken)
+ {
+ var items = await db.RequestTypeAccesses.AsNoTracking()
+ .Where(x => requestTypeId == null || x.RequestTypeId == requestTypeId)
+ .OrderBy(x => x.RequestTypeId).ThenBy(x => x.Email)
+ .Select(x => new RequestTypeAccessDto(x.Id, x.RequestTypeId, x.Email))
+ .ToListAsync(cancellationToken);
+
+ return Result.Ok>(items);
+ }
+
+ /// Список email'ов с доступом к типу заявок — дёргает Power Automate.
+ public async Task>> ListEmailsAsync(int requestTypeId, CancellationToken cancellationToken)
+ {
+ if (!await db.RequestTypes.AsNoTracking().AnyAsync(x => x.Id == requestTypeId, cancellationToken))
+ return Result.Fail>(new NotFoundError("Тип заявки", requestTypeId));
+
+ var emails = await db.RequestTypeAccesses.AsNoTracking()
+ .Where(x => x.RequestTypeId == requestTypeId)
+ .OrderBy(x => x.Email)
+ .Select(x => x.Email)
+ .ToListAsync(cancellationToken);
+
+ return Result.Ok>(emails);
+ }
+
+ public async Task> CreateAsync(
+ CreateRequestTypeAccessRequest request, CancellationToken cancellationToken)
+ {
+ if (!await db.RequestTypes.AsNoTracking().AnyAsync(x => x.Id == request.RequestTypeId, cancellationToken))
+ return Result.Fail(new NotFoundError("Тип заявки", request.RequestTypeId));
+
+ var email = request.Email.Trim();
+ var exists = await db.RequestTypeAccesses.AsNoTracking()
+ .AnyAsync(x => x.RequestTypeId == request.RequestTypeId && x.Email == email, cancellationToken);
+ if (exists)
+ return Result.Fail(
+ new ConflictError($"У {email} уже есть доступ к этому типу заявок."));
+
+ var entity = new RequestTypeAccess { RequestTypeId = request.RequestTypeId, Email = email };
+ db.RequestTypeAccesses.Add(entity);
+ await db.SaveChangesAsync(cancellationToken);
+ logger.LogInformation(
+ "Доступ к типу заявки {RequestTypeId} выдан {Email} пользователем {Author}",
+ entity.RequestTypeId, entity.Email, currentUser.Email);
+
+ return Result.Ok(new RequestTypeAccessDto(entity.Id, entity.RequestTypeId, entity.Email));
+ }
+
+ public async Task DeleteAsync(int id, CancellationToken cancellationToken)
+ {
+ var entity = await db.RequestTypeAccesses.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
+ if (entity is null)
+ return Result.Fail(new NotFoundError("Доступ к типу заявки", id));
+
+ db.RequestTypeAccesses.Remove(entity);
+ await db.SaveChangesAsync(cancellationToken);
+ logger.LogInformation(
+ "Доступ к типу заявки {RequestTypeId} для {Email} отозван пользователем {Author}",
+ entity.RequestTypeId, entity.Email, currentUser.Email);
+
+ return Result.Ok();
+ }
+}
diff --git a/backend/Tnsu.ApprovalRoute.Application/RequestTypeAccesses/RequestTypeAccessValidators.cs b/backend/Tnsu.ApprovalRoute.Application/RequestTypeAccesses/RequestTypeAccessValidators.cs
new file mode 100644
index 0000000..44fb104
--- /dev/null
+++ b/backend/Tnsu.ApprovalRoute.Application/RequestTypeAccesses/RequestTypeAccessValidators.cs
@@ -0,0 +1,12 @@
+using FluentValidation;
+
+namespace Tnsu.ApprovalRoute.Application.RequestTypeAccesses;
+
+public sealed class CreateRequestTypeAccessRequestValidator : AbstractValidator
+{
+ public CreateRequestTypeAccessRequestValidator()
+ {
+ RuleFor(x => x.RequestTypeId).GreaterThan(0).WithMessage("Тип заявки обязателен.");
+ RuleFor(x => x.Email).NotEmpty().EmailAddress().WithMessage("Укажите корректный email.");
+ }
+}
diff --git a/backend/Tnsu.ApprovalRoute.Domain/Entities/RequestTypeAccess.cs b/backend/Tnsu.ApprovalRoute.Domain/Entities/RequestTypeAccess.cs
new file mode 100644
index 0000000..8aa457a
--- /dev/null
+++ b/backend/Tnsu.ApprovalRoute.Domain/Entities/RequestTypeAccess.cs
@@ -0,0 +1,14 @@
+namespace Tnsu.ApprovalRoute.Domain.Entities;
+
+///
+/// Право одного пользователя видеть ВСЕ заявки одного типа в портале заявок
+/// (а не только свои). У одного человека может быть по строке на каждый тип
+/// заявок, к которому ему открыт полный доступ.
+///
+public class RequestTypeAccess
+{
+ public int Id { get; set; }
+ public int RequestTypeId { get; set; }
+ public RequestType? RequestType { get; set; }
+ public string Email { get; set; } = string.Empty;
+}
diff --git a/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/ApprovalRouteDbContext.cs b/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/ApprovalRouteDbContext.cs
index 733319c..cec17fd 100644
--- a/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/ApprovalRouteDbContext.cs
+++ b/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/ApprovalRouteDbContext.cs
@@ -7,6 +7,7 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence;
public class ApprovalRouteDbContext(DbContextOptions options) : DbContext(options)
{
public DbSet RequestTypes => Set();
+ public DbSet RequestTypeAccesses => Set();
public DbSet RequestListPartitions => Set();
public DbSet TemplateRoutes => Set();
public DbSet TemplateRouteSteps => Set();
@@ -35,6 +36,15 @@ public class ApprovalRouteDbContext(DbContextOptions opt
e.Property(x => x.Section).HasMaxLength(256).IsRequired();
});
+ modelBuilder.Entity(e =>
+ {
+ e.HasKey(x => x.Id);
+ e.Property(x => x.Email).HasMaxLength(256).IsRequired();
+ e.HasOne(x => x.RequestType).WithMany().HasForeignKey(x => x.RequestTypeId);
+ // Одна и та же пара тип+email не должна выдаваться дважды.
+ e.HasIndex(x => new { x.RequestTypeId, x.Email }).IsUnique();
+ });
+
modelBuilder.Entity(e =>
{
e.HasKey(x => x.Id);
diff --git a/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/20260925092352_AddRequestTypeAccess.Designer.cs b/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/20260925092352_AddRequestTypeAccess.Designer.cs
new file mode 100644
index 0000000..0dc3b82
--- /dev/null
+++ b/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/20260925092352_AddRequestTypeAccess.Designer.cs
@@ -0,0 +1,411 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using Tnsu.ApprovalRoute.Infrastructure.Persistence;
+
+#nullable disable
+
+namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
+{
+ [DbContext(typeof(ApprovalRouteDbContext))]
+ [Migration("20260925092352_AddRequestTypeAccess")]
+ partial class AddRequestTypeAccess
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.10")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestListPartition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("created_at");
+
+ b.Property("ListId")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasColumnName("list_id");
+
+ b.Property("Number")
+ .HasColumnType("integer")
+ .HasColumnName("number");
+
+ b.Property("RequestTypeId")
+ .HasColumnType("integer")
+ .HasColumnName("request_type_id");
+
+ b.Property("Title")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasColumnName("title");
+
+ b.HasKey("Id")
+ .HasName("pk_request_list_partitions");
+
+ b.HasIndex("RequestTypeId", "Number")
+ .IsUnique()
+ .HasDatabaseName("ix_request_list_partitions_request_type_id_number");
+
+ b.ToTable("request_list_partitions", (string)null);
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestType", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AskOverdueReason")
+ .HasColumnType("boolean")
+ .HasColumnName("ask_overdue_reason");
+
+ b.Property("ListId")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasColumnName("list_id");
+
+ b.Property("ListPartitioningStartedAt")
+ .HasColumnType("timestamp with time zone")
+ .HasColumnName("list_partitioning_started_at");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("name");
+
+ b.Property("PageUrl")
+ .IsRequired()
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasColumnName("page_url");
+
+ b.Property("Section")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("section");
+
+ b.Property("SiteUrl")
+ .HasMaxLength(512)
+ .HasColumnType("character varying(512)")
+ .HasColumnName("site_url");
+
+ b.Property("SlaHours")
+ .HasColumnType("integer")
+ .HasColumnName("sla_hours");
+
+ b.HasKey("Id")
+ .HasName("pk_request_types");
+
+ b.ToTable("request_types", (string)null);
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestTypeAccess", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Email")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("email");
+
+ b.Property("RequestTypeId")
+ .HasColumnType("integer")
+ .HasColumnName("request_type_id");
+
+ b.HasKey("Id")
+ .HasName("pk_request_type_accesses");
+
+ b.HasIndex("RequestTypeId", "Email")
+ .IsUnique()
+ .HasDatabaseName("ix_request_type_accesses_request_type_id_email");
+
+ b.ToTable("request_type_accesses", (string)null);
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ByProject")
+ .HasColumnType("boolean")
+ .HasColumnName("by_project");
+
+ b.Property("CompanyId")
+ .HasColumnType("uuid")
+ .HasColumnName("company_id");
+
+ b.Property("DepartmentId")
+ .HasColumnType("uuid")
+ .HasColumnName("department_id");
+
+ b.Property("RequestTypeId")
+ .HasColumnType("integer")
+ .HasColumnName("request_type_id");
+
+ b.Property("RequiresControl")
+ .HasColumnType("boolean")
+ .HasColumnName("requires_control");
+
+ b.HasKey("Id")
+ .HasName("pk_template_routes");
+
+ b.HasIndex("RequestTypeId", "DepartmentId", "CompanyId", "ByProject")
+ .IsUnique()
+ .HasDatabaseName("ix_template_routes_request_type_id_department_id_company_id_by");
+
+ b.ToTable("template_routes", (string)null);
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRouteStep", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AllowReassignToOtherApprovers")
+ .HasColumnType("boolean")
+ .HasColumnName("allow_reassign_to_other_approvers");
+
+ b.Property("AskOverdueReason")
+ .HasColumnType("boolean")
+ .HasColumnName("ask_overdue_reason");
+
+ b.Property("AutoApprove")
+ .HasColumnType("boolean")
+ .HasColumnName("auto_approve");
+
+ b.Property("ConditionsLogic")
+ .IsRequired()
+ .HasMaxLength(16)
+ .HasColumnType("character varying(16)")
+ .HasColumnName("conditions_logic");
+
+ b.Property("ExecutorEmail")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("executor_email");
+
+ b.Property("HeadType")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("head_type");
+
+ b.Property("OrderNo")
+ .HasColumnType("integer")
+ .HasColumnName("order_no");
+
+ b.Property("Position")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("position");
+
+ b.Property("ProjectRole")
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("project_role");
+
+ b.Property("RouteId")
+ .HasColumnType("integer")
+ .HasColumnName("route_id");
+
+ b.Property("SlaHours")
+ .HasColumnType("integer")
+ .HasColumnName("sla_hours");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("status");
+
+ b.HasKey("Id")
+ .HasName("pk_template_route_steps");
+
+ b.HasIndex("RouteId", "OrderNo")
+ .HasDatabaseName("ix_template_route_steps_route_id_order_no");
+
+ b.ToTable("template_route_steps", (string)null);
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRouteStepCondition", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("FieldName")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("field_name");
+
+ b.Property("Operation")
+ .IsRequired()
+ .HasMaxLength(32)
+ .HasColumnType("character varying(32)")
+ .HasColumnName("operation");
+
+ b.Property("OrderNo")
+ .HasColumnType("integer")
+ .HasColumnName("order_no");
+
+ b.Property("StepId")
+ .HasColumnType("integer")
+ .HasColumnName("step_id");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("value");
+
+ b.HasKey("Id")
+ .HasName("pk_template_route_step_conditions");
+
+ b.HasIndex("StepId", "OrderNo")
+ .HasDatabaseName("ix_template_route_step_conditions_step_id_order_no");
+
+ b.ToTable("template_route_step_conditions", (string)null);
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Infrastructure.Persistence.Entities.AppSetting", b =>
+ {
+ b.Property("OptionName")
+ .HasMaxLength(128)
+ .HasColumnType("character varying(128)")
+ .HasColumnName("option_name");
+
+ b.Property("FileData")
+ .HasColumnType("bytea")
+ .HasColumnName("file_data");
+
+ b.Property("Value")
+ .HasColumnType("text")
+ .HasColumnName("value");
+
+ b.HasKey("OptionName")
+ .HasName("pk_settings");
+
+ b.ToTable("settings", (string)null);
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestListPartition", b =>
+ {
+ b.HasOne("Tnsu.ApprovalRoute.Domain.Entities.RequestType", "RequestType")
+ .WithMany("ListPartitions")
+ .HasForeignKey("RequestTypeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_request_list_partitions_request_types_request_type_id");
+
+ b.Navigation("RequestType");
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestTypeAccess", b =>
+ {
+ b.HasOne("Tnsu.ApprovalRoute.Domain.Entities.RequestType", "RequestType")
+ .WithMany()
+ .HasForeignKey("RequestTypeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_request_type_accesses_request_types_request_type_id");
+
+ b.Navigation("RequestType");
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", b =>
+ {
+ b.HasOne("Tnsu.ApprovalRoute.Domain.Entities.RequestType", "RequestType")
+ .WithMany("Routes")
+ .HasForeignKey("RequestTypeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_template_routes_request_types_request_type_id");
+
+ b.Navigation("RequestType");
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRouteStep", b =>
+ {
+ b.HasOne("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", "Route")
+ .WithMany("Steps")
+ .HasForeignKey("RouteId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_template_route_steps_template_routes_route_id");
+
+ b.Navigation("Route");
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRouteStepCondition", b =>
+ {
+ b.HasOne("Tnsu.ApprovalRoute.Domain.Entities.TemplateRouteStep", "Step")
+ .WithMany("Conditions")
+ .HasForeignKey("StepId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_template_route_step_conditions_template_route_steps_step_id");
+
+ b.Navigation("Step");
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestType", b =>
+ {
+ b.Navigation("ListPartitions");
+
+ b.Navigation("Routes");
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", b =>
+ {
+ b.Navigation("Steps");
+ });
+
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRouteStep", b =>
+ {
+ b.Navigation("Conditions");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/20260925092352_AddRequestTypeAccess.cs b/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/20260925092352_AddRequestTypeAccess.cs
new file mode 100644
index 0000000..a378d54
--- /dev/null
+++ b/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/20260925092352_AddRequestTypeAccess.cs
@@ -0,0 +1,48 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
+{
+ ///
+ public partial class AddRequestTypeAccess : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "request_type_accesses",
+ columns: table => new
+ {
+ id = table.Column(type: "integer", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ request_type_id = table.Column(type: "integer", nullable: false),
+ email = table.Column(type: "character varying(256)", maxLength: 256, nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("pk_request_type_accesses", x => x.id);
+ table.ForeignKey(
+ name: "fk_request_type_accesses_request_types_request_type_id",
+ column: x => x.request_type_id,
+ principalTable: "request_types",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "ix_request_type_accesses_request_type_id_email",
+ table: "request_type_accesses",
+ columns: new[] { "request_type_id", "email" },
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "request_type_accesses");
+ }
+ }
+}
diff --git a/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/ApprovalRouteDbContextModelSnapshot.cs b/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/ApprovalRouteDbContextModelSnapshot.cs
index da9803b..34002d7 100644
--- a/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/ApprovalRouteDbContextModelSnapshot.cs
+++ b/backend/Tnsu.ApprovalRoute.Infrastructure/Persistence/Migrations/ApprovalRouteDbContextModelSnapshot.cs
@@ -119,6 +119,35 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
b.ToTable("request_types", (string)null);
});
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestTypeAccess", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer")
+ .HasColumnName("id");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Email")
+ .IsRequired()
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)")
+ .HasColumnName("email");
+
+ b.Property("RequestTypeId")
+ .HasColumnType("integer")
+ .HasColumnName("request_type_id");
+
+ b.HasKey("Id")
+ .HasName("pk_request_type_accesses");
+
+ b.HasIndex("RequestTypeId", "Email")
+ .IsUnique()
+ .HasDatabaseName("ix_request_type_accesses_request_type_id_email");
+
+ b.ToTable("request_type_accesses", (string)null);
+ });
+
modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", b =>
{
b.Property("Id")
@@ -309,6 +338,18 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
b.Navigation("RequestType");
});
+ modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestTypeAccess", b =>
+ {
+ b.HasOne("Tnsu.ApprovalRoute.Domain.Entities.RequestType", "RequestType")
+ .WithMany()
+ .HasForeignKey("RequestTypeId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired()
+ .HasConstraintName("fk_request_type_accesses_request_types_request_type_id");
+
+ b.Navigation("RequestType");
+ });
+
modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", b =>
{
b.HasOne("Tnsu.ApprovalRoute.Domain.Entities.RequestType", "RequestType")