food-market/src/food-market.infrastructure/Persistence/Configurations/OrganizationsHrConfigurations.cs
nns f38d34f42d feat(domain): Employee, EmployeeRole, RolePermissions entities + migration
Базовый каркас модуля «Сотрудники и Роли» (по образу сторонняя система):

Domain:
- Employee — сотрудник организации (UserId nullable: запись может
  существовать без логина), ФИО + Position + Email/Phone + Role + IsActive
  + FiredAt + RetailPointAssignments.
- EmployeeRole — роль с IsSystem флагом и owned RolePermissions.
- RolePermissions — 21 булев флаг по группам (Каталог/Закупки/Продажи/
  Контрагенты/Отчёты/Настройки) + helper All() для админа.
- EmployeeRetailPointAssignment — ассоциация сотрудника с RetailPoint
  (для роли Кассир — к каким кассам привязан).

Infrastructure:
- OrganizationsHrConfigurations с OwnsOne(...).ToJson("permissions")
  для permissions — JSONB-колонка вместо отдельной таблицы.
- DbSet<EmployeeRole/Employee/EmployeeRetailPointAssignment>.
- Уникальные индексы: (OrgId, RoleName), (OrgId, UserId) с filter
  WHERE UserId IS NOT NULL, (EmployeeId, RetailPointId).

Migration Phase4_EmployeesAndRoles создаёт три таблицы. Сидер
системных ролей и привязка существующего admin'а к Employee —
следующим коммитом, контроллеры и UI — далее.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:00:30 +05:00

45 lines
2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using foodmarket.Domain.Organizations;
using Microsoft.EntityFrameworkCore;
namespace foodmarket.Infrastructure.Persistence.Configurations;
public static class OrganizationsHrConfigurations
{
public static void ConfigureOrganizationsHr(this ModelBuilder b)
{
b.Entity<EmployeeRole>(e =>
{
e.ToTable("employee_roles");
e.Property(x => x.Name).HasMaxLength(100).IsRequired();
e.Property(x => x.Description).HasMaxLength(500);
// Permissions — owned JSON-колонка. Один объект = один сериализованный
// блоб на роль. Все поля булевы, по умолчанию false.
e.OwnsOne(x => x.Permissions, p => p.ToJson("permissions"));
e.HasIndex(x => new { x.OrganizationId, x.Name }).IsUnique();
});
b.Entity<Employee>(e =>
{
e.ToTable("employees");
e.Property(x => x.LastName).HasMaxLength(100).IsRequired();
e.Property(x => x.FirstName).HasMaxLength(100).IsRequired();
e.Property(x => x.MiddleName).HasMaxLength(100);
e.Property(x => x.Position).HasMaxLength(150);
e.Property(x => x.Email).HasMaxLength(200);
e.Property(x => x.Phone).HasMaxLength(50);
e.HasOne(x => x.Role).WithMany().HasForeignKey(x => x.RoleId).OnDelete(DeleteBehavior.Restrict);
e.HasMany(x => x.RetailPointAssignments).WithOne(a => a.Employee)
.HasForeignKey(a => a.EmployeeId).OnDelete(DeleteBehavior.Cascade);
e.HasIndex(x => new { x.OrganizationId, x.UserId }).IsUnique()
.HasFilter("\"UserId\" IS NOT NULL");
e.HasIndex(x => new { x.OrganizationId, x.LastName });
});
b.Entity<EmployeeRetailPointAssignment>(e =>
{
e.ToTable("employee_retail_point_assignments");
e.HasIndex(x => new { x.EmployeeId, x.RetailPointId }).IsUnique();
});
}
}