diff --git a/src/food-market.domain/Organizations/Employee.cs b/src/food-market.domain/Organizations/Employee.cs new file mode 100644 index 0000000..12725ac --- /dev/null +++ b/src/food-market.domain/Organizations/Employee.cs @@ -0,0 +1,41 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Organizations; + +/// Сотрудник организации. Может быть привязан к учётной записи +/// (UserId), а может существовать без логина (например, кассир, которого +/// добавили в HR, но логин ещё не выдали). +public class Employee : TenantEntity +{ + /// FK на Identity-юзера. Заполняется когда сотруднику выдан логин. + /// Null — запись без учётки. + public Guid? UserId { get; set; } + + public string LastName { get; set; } = ""; + public string FirstName { get; set; } = ""; + public string? MiddleName { get; set; } + public string? Position { get; set; } + + public string? Email { get; set; } + public string? Phone { get; set; } + + public Guid RoleId { get; set; } + public EmployeeRole Role { get; set; } = null!; + + /// Активен ли сотрудник. False — заблокирован, не может логиниться. + /// Удаление физически не делаем (FK из документов), просто IsActive=false. + public bool IsActive { get; set; } = true; + public DateTime? FiredAt { get; set; } + + public ICollection RetailPointAssignments { get; set; } + = new List(); +} + +/// Привязка сотрудника к кассе (для роли Кассир): к каким RetailPoint'ам +/// он может вставать. Если назначений нет — может ко всем (поведение по умолчанию). +public class EmployeeRetailPointAssignment : TenantEntity +{ + public Guid EmployeeId { get; set; } + public Employee Employee { get; set; } = null!; + public Guid RetailPointId { get; set; } +} diff --git a/src/food-market.domain/Organizations/EmployeeRole.cs b/src/food-market.domain/Organizations/EmployeeRole.cs new file mode 100644 index 0000000..5a1ead7 --- /dev/null +++ b/src/food-market.domain/Organizations/EmployeeRole.cs @@ -0,0 +1,17 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Organizations; + +/// Роль сотрудника в организации. Системные (IsSystem=true) сидируются +/// при создании Organization (Администратор/Менеджер/Кладовщик/Кассир/Закупщик/ +/// Бухгалтер) — нельзя удалить, имя менять можно. Кастомные — полный CRUD. +public class EmployeeRole : TenantEntity +{ + public string Name { get; set; } = ""; + public string? Description { get; set; } + public bool IsSystem { get; set; } + public int SortOrder { get; set; } + + /// Permissions — owned JSON-колонка (см. EF config). + public RolePermissions Permissions { get; set; } = new(); +} diff --git a/src/food-market.domain/Organizations/RolePermissions.cs b/src/food-market.domain/Organizations/RolePermissions.cs new file mode 100644 index 0000000..616f48e --- /dev/null +++ b/src/food-market.domain/Organizations/RolePermissions.cs @@ -0,0 +1,51 @@ +namespace foodmarket.Domain.Organizations; + +/// Набор флагов разрешений роли. Хранится JSON-колонкой +/// (owned by EmployeeRole). Семантика: false = доступ запрещён. +public class RolePermissions +{ + // Каталог + public bool ProductsView { get; set; } + public bool ProductsEdit { get; set; } + public bool ProductsDelete { get; set; } + public bool ProductGroupsManage { get; set; } + public bool PriceTypesManage { get; set; } + + // Закупки + public bool SuppliesView { get; set; } + public bool SuppliesEdit { get; set; } + public bool SuppliesPost { get; set; } + public bool SuppliesDelete { get; set; } + + // Продажи (POS) + public bool RetailSalesOperate { get; set; } + public bool RetailSalesRefund { get; set; } + + // Контрагенты + public bool CounterpartiesView { get; set; } + public bool CounterpartiesEdit { get; set; } + + // Отчёты и остатки + public bool ReportsView { get; set; } + public bool StocksView { get; set; } + + // Настройки организации + public bool OrgSettingsManage { get; set; } + public bool EmployeesManage { get; set; } + public bool RolesManage { get; set; } + public bool StoresManage { get; set; } + public bool RetailPointsManage { get; set; } + + /// Полный набор всех true — для системной роли «Администратор». + public static RolePermissions All() => new() + { + ProductsView = true, ProductsEdit = true, ProductsDelete = true, + ProductGroupsManage = true, PriceTypesManage = true, + SuppliesView = true, SuppliesEdit = true, SuppliesPost = true, SuppliesDelete = true, + RetailSalesOperate = true, RetailSalesRefund = true, + CounterpartiesView = true, CounterpartiesEdit = true, + ReportsView = true, StocksView = true, + OrgSettingsManage = true, EmployeesManage = true, RolesManage = true, + StoresManage = true, RetailPointsManage = true, + }; +} diff --git a/src/food-market.infrastructure/Persistence/AppDbContext.cs b/src/food-market.infrastructure/Persistence/AppDbContext.cs index e55f69f..0c531e2 100644 --- a/src/food-market.infrastructure/Persistence/AppDbContext.cs +++ b/src/food-market.infrastructure/Persistence/AppDbContext.cs @@ -46,6 +46,10 @@ public AppDbContext(DbContextOptions options, ITenantContext tenan public DbSet RetailSales => Set(); public DbSet RetailSaleLines => Set(); + public DbSet EmployeeRoles => Set(); + public DbSet Employees => Set(); + public DbSet EmployeeRetailPointAssignments => Set(); + protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); @@ -78,6 +82,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.ConfigureInventory(); builder.ConfigurePurchases(); builder.ConfigureSales(); + builder.ConfigureOrganizationsHr(); // Apply multi-tenant query filter to every entity that implements ITenantEntity foreach (var entityType in builder.Model.GetEntityTypes()) diff --git a/src/food-market.infrastructure/Persistence/Configurations/OrganizationsHrConfigurations.cs b/src/food-market.infrastructure/Persistence/Configurations/OrganizationsHrConfigurations.cs new file mode 100644 index 0000000..cf23362 --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Configurations/OrganizationsHrConfigurations.cs @@ -0,0 +1,44 @@ +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(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(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(e => + { + e.ToTable("employee_retail_point_assignments"); + e.HasIndex(x => new { x.EmployeeId, x.RetailPointId }).IsUnique(); + }); + } +} diff --git a/src/food-market.infrastructure/Persistence/Migrations/20260427010000_Phase4_EmployeesAndRoles.Designer.cs b/src/food-market.infrastructure/Persistence/Migrations/20260427010000_Phase4_EmployeesAndRoles.Designer.cs new file mode 100644 index 0000000..6800585 --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Migrations/20260427010000_Phase4_EmployeesAndRoles.Designer.cs @@ -0,0 +1,2001 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using foodmarket.Infrastructure.Persistence; + +#nullable disable + +namespace foodmarket.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260427010000_Phase4_EmployeesAndRoles")] + partial class Phase4_EmployeesAndRoles + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("public") + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", "public"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", "public"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", "public"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", "public"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Counterparty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BankName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Bik") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Bin") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ContactPerson") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Iin") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("LegalName") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Phone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TaxNumber") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("OrganizationId", "Bin"); + + b.HasIndex("OrganizationId", "Name"); + + b.ToTable("counterparties", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultCurrencyId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VatRate") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("DefaultCurrencyId"); + + b.ToTable("countries", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MinorUnit") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Symbol") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("currencies", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.PriceType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsRequired") + .HasColumnType("boolean"); + + b.Property("IsRetail") + .HasColumnType("boolean"); + + b.Property("IsSystem") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("price_types", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Article") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("CountryOfOriginId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultSupplierId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ImageUrl") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IsMarked") + .HasColumnType("boolean"); + + b.Property("IsService") + .HasColumnType("boolean"); + + b.Property("Packaging") + .HasColumnType("boolean"); + + b.Property("MaxStock") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("MinStock") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductGroupId") + .HasColumnType("uuid"); + + b.Property("Cost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("LastSupplyAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PurchaseCurrencyId") + .HasColumnType("uuid"); + + b.Property("ReferencePrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReferencePriceUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UnitOfMeasureId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Vat") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.Property("VatEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.HasKey("Id"); + + b.HasIndex("CountryOfOriginId"); + + b.HasIndex("DefaultSupplierId"); + + b.HasIndex("ProductGroupId"); + + b.HasIndex("PurchaseCurrencyId"); + + b.HasIndex("UnitOfMeasureId"); + + b.HasIndex("OrganizationId", "Article"); + + b.HasIndex("OrganizationId", "Name"); + + b.HasIndex("OrganizationId", "ProductGroupId"); + + b.ToTable("products", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductBarcode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("OrganizationId", "Code") + .IsUnique(); + + b.ToTable("product_barcodes", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MarkupPercent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.HasIndex("OrganizationId", "ParentId"); + + b.HasIndex("OrganizationId", "Path"); + + b.ToTable("product_groups", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsMain") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.ToTable("product_images", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PriceTypeId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.HasIndex("PriceTypeId"); + + b.HasIndex("ProductId", "PriceTypeId") + .IsUnique(); + + b.ToTable("product_prices", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.RetailPoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("Code") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FiscalRegNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("FiscalSerial") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Phone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StoreId"); + + b.HasIndex("OrganizationId", "Name"); + + b.ToTable("retail_points", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("Code") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsMain") + .HasColumnType("boolean"); + + b.Property("ManagerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Phone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name"); + + b.ToTable("stores", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.UnitOfMeasure", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Code") + .IsUnique(); + + b.ToTable("units_of_measure", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Inventory.Stock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("StoreId"); + + b.HasIndex("OrganizationId", "StoreId"); + + b.HasIndex("OrganizationId", "ProductId", "StoreId") + .IsUnique(); + + b.ToTable("stocks", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Inventory.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DocumentNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("StoreId"); + + b.HasIndex("DocumentType", "DocumentId"); + + b.HasIndex("OrganizationId", "ProductId", "OccurredAt"); + + b.HasIndex("OrganizationId", "StoreId", "OccurredAt"); + + b.ToTable("stock_movements", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("AllowFractionalPrices") + .HasColumnType("boolean"); + + b.Property("Bin") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CountryCode") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("DefaultCurrencyId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("MoySkladToken") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("MultiCurrencyEnabled") + .HasColumnType("boolean"); + + b.Property("ShowCountryOfOriginOnProduct") + .HasColumnType("boolean"); + + b.Property("ShowDescriptionOnProduct") + .HasColumnType("boolean"); + + b.Property("ShowMarkedOnProduct") + .HasColumnType("boolean"); + + b.Property("ShowReferencePriceOnProduct") + .HasColumnType("boolean"); + + b.Property("ShowMinMaxStock") + .HasColumnType("boolean"); + + b.Property("ShowServiceOnProduct") + .HasColumnType("boolean"); + + b.Property("ShowVatEnabledOnProduct") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DefaultCurrencyId"); + + b.HasIndex("Name"); + + b.ToTable("organizations", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.Supply", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PostedByUserId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("Total") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.HasIndex("StoreId"); + + b.HasIndex("SupplierId"); + + b.HasIndex("OrganizationId", "Date"); + + b.HasIndex("OrganizationId", "Number") + .IsUnique(); + + b.HasIndex("OrganizationId", "Status"); + + b.HasIndex("OrganizationId", "SupplierId"); + + b.ToTable("supplies", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.SupplyLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RetailPriceManuallyOverridden") + .HasColumnType("boolean"); + + b.Property("RetailPriceOverride") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("SupplyId") + .HasColumnType("uuid"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("SupplyId"); + + b.HasIndex("OrganizationId", "ProductId"); + + b.ToTable("supply_lines", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSale", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CashierUserId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaidCard") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("PaidCash") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Payment") + .HasColumnType("integer"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PostedByUserId") + .HasColumnType("uuid"); + + b.Property("RetailPointId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Total") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("RetailPointId"); + + b.HasIndex("StoreId"); + + b.HasIndex("OrganizationId", "CashierUserId"); + + b.HasIndex("OrganizationId", "Date"); + + b.HasIndex("OrganizationId", "Number") + .IsUnique(); + + b.HasIndex("OrganizationId", "Status"); + + b.ToTable("retail_sales", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSaleLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Discount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RetailSaleId") + .HasColumnType("uuid"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("RetailSaleId"); + + b.HasIndex("OrganizationId", "ProductId"); + + b.ToTable("retail_sale_lines", "public"); + }); + + modelBuilder.Entity("foodmarket.Infrastructure.Identity.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("roles", "public"); + }); + + modelBuilder.Entity("foodmarket.Infrastructure.Identity.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastLoginAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("users", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("foodmarket.Infrastructure.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Counterparty", b => + { + b.HasOne("foodmarket.Domain.Catalog.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Country"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Product", b => + { + b.HasOne("foodmarket.Domain.Catalog.Country", "CountryOfOrigin") + .WithMany() + .HasForeignKey("CountryOfOriginId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.Counterparty", "DefaultSupplier") + .WithMany() + .HasForeignKey("DefaultSupplierId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.ProductGroup", "ProductGroup") + .WithMany() + .HasForeignKey("ProductGroupId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.Currency", "PurchaseCurrency") + .WithMany() + .HasForeignKey("PurchaseCurrencyId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.UnitOfMeasure", "UnitOfMeasure") + .WithMany() + .HasForeignKey("UnitOfMeasureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CountryOfOrigin"); + + b.Navigation("DefaultSupplier"); + + b.Navigation("ProductGroup"); + + b.Navigation("PurchaseCurrency"); + + b.Navigation("UnitOfMeasure"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductBarcode", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany("Barcodes") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductGroup", b => + { + b.HasOne("foodmarket.Domain.Catalog.ProductGroup", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductImage", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany("Images") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductPrice", b => + { + b.HasOne("foodmarket.Domain.Catalog.Currency", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.PriceType", "PriceType") + .WithMany() + .HasForeignKey("PriceTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany("Prices") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + + b.Navigation("PriceType"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.RetailPoint", b => + { + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("foodmarket.Domain.Inventory.Stock", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("foodmarket.Domain.Inventory.StockMovement", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.Supply", b => + { + b.HasOne("foodmarket.Domain.Catalog.Currency", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Counterparty", "Supplier") + .WithMany() + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Currency"); + + b.Navigation("Store"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.SupplyLine", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Purchases.Supply", "Supply") + .WithMany("Lines") + .HasForeignKey("SupplyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Supply"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSale", b => + { + b.HasOne("foodmarket.Domain.Catalog.Currency", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Counterparty", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.RetailPoint", "RetailPoint") + .WithMany() + .HasForeignKey("RetailPointId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Currency"); + + b.Navigation("Customer"); + + b.Navigation("RetailPoint"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSaleLine", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Sales.RetailSale", "RetailSale") + .WithMany("Lines") + .HasForeignKey("RetailSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("RetailSale"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Product", b => + { + b.Navigation("Barcodes"); + + b.Navigation("Images"); + + b.Navigation("Prices"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductGroup", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.Supply", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSale", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.EmployeeRole", b => + { + b.Property("Id").ValueGeneratedOnAdd().HasColumnType("uuid"); + b.Property("OrganizationId").HasColumnType("uuid"); + b.Property("Name").IsRequired().HasMaxLength(100).HasColumnType("character varying(100)"); + b.Property("Description").HasMaxLength(500).HasColumnType("character varying(500)"); + b.Property("IsSystem").HasColumnType("boolean"); + b.Property("SortOrder").HasColumnType("integer"); + b.Property("CreatedAt").HasColumnType("timestamp with time zone"); + b.Property("UpdatedAt").HasColumnType("timestamp with time zone"); + b.HasKey("Id"); + b.HasIndex("OrganizationId", "Name").IsUnique(); + b.ToTable("employee_roles", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.EmployeeRole", b => + { + b.OwnsOne("foodmarket.Domain.Organizations.RolePermissions", "Permissions", o => + { + o.Property("EmployeeRoleId").HasColumnType("uuid"); + o.Property("ProductsView").HasColumnType("boolean"); + o.Property("ProductsEdit").HasColumnType("boolean"); + o.Property("ProductsDelete").HasColumnType("boolean"); + o.Property("ProductGroupsManage").HasColumnType("boolean"); + o.Property("PriceTypesManage").HasColumnType("boolean"); + o.Property("SuppliesView").HasColumnType("boolean"); + o.Property("SuppliesEdit").HasColumnType("boolean"); + o.Property("SuppliesPost").HasColumnType("boolean"); + o.Property("SuppliesDelete").HasColumnType("boolean"); + o.Property("RetailSalesOperate").HasColumnType("boolean"); + o.Property("RetailSalesRefund").HasColumnType("boolean"); + o.Property("CounterpartiesView").HasColumnType("boolean"); + o.Property("CounterpartiesEdit").HasColumnType("boolean"); + o.Property("ReportsView").HasColumnType("boolean"); + o.Property("StocksView").HasColumnType("boolean"); + o.Property("OrgSettingsManage").HasColumnType("boolean"); + o.Property("EmployeesManage").HasColumnType("boolean"); + o.Property("RolesManage").HasColumnType("boolean"); + o.Property("StoresManage").HasColumnType("boolean"); + o.Property("RetailPointsManage").HasColumnType("boolean"); + o.HasKey("EmployeeRoleId"); + o.ToJson("permissions"); + o.WithOwner().HasForeignKey("EmployeeRoleId"); + }); + b.Navigation("Permissions"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.Employee", b => + { + b.Property("Id").ValueGeneratedOnAdd().HasColumnType("uuid"); + b.Property("OrganizationId").HasColumnType("uuid"); + b.Property("UserId").HasColumnType("uuid"); + b.Property("LastName").IsRequired().HasMaxLength(100).HasColumnType("character varying(100)"); + b.Property("FirstName").IsRequired().HasMaxLength(100).HasColumnType("character varying(100)"); + b.Property("MiddleName").HasMaxLength(100).HasColumnType("character varying(100)"); + b.Property("Position").HasMaxLength(150).HasColumnType("character varying(150)"); + b.Property("Email").HasMaxLength(200).HasColumnType("character varying(200)"); + b.Property("Phone").HasMaxLength(50).HasColumnType("character varying(50)"); + b.Property("RoleId").HasColumnType("uuid"); + b.Property("IsActive").HasColumnType("boolean"); + b.Property("FiredAt").HasColumnType("timestamp with time zone"); + b.Property("CreatedAt").HasColumnType("timestamp with time zone"); + b.Property("UpdatedAt").HasColumnType("timestamp with time zone"); + b.HasKey("Id"); + b.HasIndex("RoleId"); + b.HasIndex("OrganizationId", "LastName"); + b.HasIndex("OrganizationId", "UserId").IsUnique().HasFilter("\"UserId\" IS NOT NULL"); + b.ToTable("employees", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.Employee", b => + { + b.HasOne("foodmarket.Domain.Organizations.EmployeeRole", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.Navigation("Role"); + b.Navigation("RetailPointAssignments"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.EmployeeRetailPointAssignment", b => + { + b.Property("Id").ValueGeneratedOnAdd().HasColumnType("uuid"); + b.Property("OrganizationId").HasColumnType("uuid"); + b.Property("EmployeeId").HasColumnType("uuid"); + b.Property("RetailPointId").HasColumnType("uuid"); + b.Property("CreatedAt").HasColumnType("timestamp with time zone"); + b.Property("UpdatedAt").HasColumnType("timestamp with time zone"); + b.HasKey("Id"); + b.HasIndex("EmployeeId", "RetailPointId").IsUnique(); + b.ToTable("employee_retail_point_assignments", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.EmployeeRetailPointAssignment", b => + { + b.HasOne("foodmarket.Domain.Organizations.Employee", "Employee") + .WithMany("RetailPointAssignments") + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Employee"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/food-market.infrastructure/Persistence/Migrations/20260427010000_Phase4_EmployeesAndRoles.cs b/src/food-market.infrastructure/Persistence/Migrations/20260427010000_Phase4_EmployeesAndRoles.cs new file mode 100644 index 0000000..f05b8d0 --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Migrations/20260427010000_Phase4_EmployeesAndRoles.cs @@ -0,0 +1,128 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace foodmarket.Infrastructure.Persistence.Migrations +{ + /// Сотрудники (employees) + роли (employee_roles с JSON-permissions) + /// + назначения сотрудников на кассы (employee_retail_point_assignments). + /// Сидер заведёт системные роли и подвяжет существующего admin'а как + /// сотрудника при первом старте. + public partial class Phase4_EmployeesAndRoles : Migration + { + protected override void Up(MigrationBuilder b) + { + b.CreateTable( + name: "employee_roles", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrganizationId = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Description = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + IsSystem = table.Column(type: "boolean", nullable: false), + SortOrder = table.Column(type: "integer", nullable: false), + permissions = table.Column(type: "jsonb", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + }, + constraints: table => + { + table.PrimaryKey("PK_employee_roles", x => x.Id); + }); + b.CreateIndex( + name: "IX_employee_roles_OrganizationId_Name", + schema: "public", + table: "employee_roles", + columns: new[] { "OrganizationId", "Name" }, + unique: true); + + b.CreateTable( + name: "employees", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrganizationId = table.Column(type: "uuid", nullable: false), + UserId = table.Column(type: "uuid", nullable: true), + LastName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + FirstName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + MiddleName = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Position = table.Column(type: "character varying(150)", maxLength: 150, nullable: true), + Email = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + Phone = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + RoleId = table.Column(type: "uuid", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false), + FiredAt = table.Column(type: "timestamp with time zone", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + }, + constraints: table => + { + table.PrimaryKey("PK_employees", x => x.Id); + table.ForeignKey( + name: "FK_employees_employee_roles_RoleId", + column: x => x.RoleId, + principalSchema: "public", + principalTable: "employee_roles", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + b.CreateIndex( + name: "IX_employees_RoleId", + schema: "public", + table: "employees", + column: "RoleId"); + b.CreateIndex( + name: "IX_employees_OrganizationId_LastName", + schema: "public", + table: "employees", + columns: new[] { "OrganizationId", "LastName" }); + b.CreateIndex( + name: "IX_employees_OrganizationId_UserId", + schema: "public", + table: "employees", + columns: new[] { "OrganizationId", "UserId" }, + unique: true, + filter: "\"UserId\" IS NOT NULL"); + + b.CreateTable( + name: "employee_retail_point_assignments", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + OrganizationId = table.Column(type: "uuid", nullable: false), + EmployeeId = table.Column(type: "uuid", nullable: false), + RetailPointId = table.Column(type: "uuid", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + }, + constraints: table => + { + table.PrimaryKey("PK_employee_retail_point_assignments", x => x.Id); + table.ForeignKey( + name: "FK_employee_retail_point_assignments_employees_EmployeeId", + column: x => x.EmployeeId, + principalSchema: "public", + principalTable: "employees", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + b.CreateIndex( + name: "IX_employee_retail_point_assignments_EmployeeId_RetailPointId", + schema: "public", + table: "employee_retail_point_assignments", + columns: new[] { "EmployeeId", "RetailPointId" }, + unique: true); + } + + protected override void Down(MigrationBuilder b) + { + b.DropTable(name: "employee_retail_point_assignments", schema: "public"); + b.DropTable(name: "employees", schema: "public"); + b.DropTable(name: "employee_roles", schema: "public"); + } + } +} diff --git a/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs b/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs index cf33670..c54ac96 100644 --- a/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs +++ b/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs @@ -1888,6 +1888,110 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("Lines"); }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.EmployeeRole", b => + { + b.Property("Id").ValueGeneratedOnAdd().HasColumnType("uuid"); + b.Property("OrganizationId").HasColumnType("uuid"); + b.Property("Name").IsRequired().HasMaxLength(100).HasColumnType("character varying(100)"); + b.Property("Description").HasMaxLength(500).HasColumnType("character varying(500)"); + b.Property("IsSystem").HasColumnType("boolean"); + b.Property("SortOrder").HasColumnType("integer"); + b.Property("CreatedAt").HasColumnType("timestamp with time zone"); + b.Property("UpdatedAt").HasColumnType("timestamp with time zone"); + b.HasKey("Id"); + b.HasIndex("OrganizationId", "Name").IsUnique(); + b.ToTable("employee_roles", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.EmployeeRole", b => + { + b.OwnsOne("foodmarket.Domain.Organizations.RolePermissions", "Permissions", o => + { + o.Property("EmployeeRoleId").HasColumnType("uuid"); + o.Property("ProductsView").HasColumnType("boolean"); + o.Property("ProductsEdit").HasColumnType("boolean"); + o.Property("ProductsDelete").HasColumnType("boolean"); + o.Property("ProductGroupsManage").HasColumnType("boolean"); + o.Property("PriceTypesManage").HasColumnType("boolean"); + o.Property("SuppliesView").HasColumnType("boolean"); + o.Property("SuppliesEdit").HasColumnType("boolean"); + o.Property("SuppliesPost").HasColumnType("boolean"); + o.Property("SuppliesDelete").HasColumnType("boolean"); + o.Property("RetailSalesOperate").HasColumnType("boolean"); + o.Property("RetailSalesRefund").HasColumnType("boolean"); + o.Property("CounterpartiesView").HasColumnType("boolean"); + o.Property("CounterpartiesEdit").HasColumnType("boolean"); + o.Property("ReportsView").HasColumnType("boolean"); + o.Property("StocksView").HasColumnType("boolean"); + o.Property("OrgSettingsManage").HasColumnType("boolean"); + o.Property("EmployeesManage").HasColumnType("boolean"); + o.Property("RolesManage").HasColumnType("boolean"); + o.Property("StoresManage").HasColumnType("boolean"); + o.Property("RetailPointsManage").HasColumnType("boolean"); + o.HasKey("EmployeeRoleId"); + o.ToJson("permissions"); + o.WithOwner().HasForeignKey("EmployeeRoleId"); + }); + b.Navigation("Permissions"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.Employee", b => + { + b.Property("Id").ValueGeneratedOnAdd().HasColumnType("uuid"); + b.Property("OrganizationId").HasColumnType("uuid"); + b.Property("UserId").HasColumnType("uuid"); + b.Property("LastName").IsRequired().HasMaxLength(100).HasColumnType("character varying(100)"); + b.Property("FirstName").IsRequired().HasMaxLength(100).HasColumnType("character varying(100)"); + b.Property("MiddleName").HasMaxLength(100).HasColumnType("character varying(100)"); + b.Property("Position").HasMaxLength(150).HasColumnType("character varying(150)"); + b.Property("Email").HasMaxLength(200).HasColumnType("character varying(200)"); + b.Property("Phone").HasMaxLength(50).HasColumnType("character varying(50)"); + b.Property("RoleId").HasColumnType("uuid"); + b.Property("IsActive").HasColumnType("boolean"); + b.Property("FiredAt").HasColumnType("timestamp with time zone"); + b.Property("CreatedAt").HasColumnType("timestamp with time zone"); + b.Property("UpdatedAt").HasColumnType("timestamp with time zone"); + b.HasKey("Id"); + b.HasIndex("RoleId"); + b.HasIndex("OrganizationId", "LastName"); + b.HasIndex("OrganizationId", "UserId").IsUnique().HasFilter("\"UserId\" IS NOT NULL"); + b.ToTable("employees", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.Employee", b => + { + b.HasOne("foodmarket.Domain.Organizations.EmployeeRole", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + b.Navigation("Role"); + b.Navigation("RetailPointAssignments"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.EmployeeRetailPointAssignment", b => + { + b.Property("Id").ValueGeneratedOnAdd().HasColumnType("uuid"); + b.Property("OrganizationId").HasColumnType("uuid"); + b.Property("EmployeeId").HasColumnType("uuid"); + b.Property("RetailPointId").HasColumnType("uuid"); + b.Property("CreatedAt").HasColumnType("timestamp with time zone"); + b.Property("UpdatedAt").HasColumnType("timestamp with time zone"); + b.HasKey("Id"); + b.HasIndex("EmployeeId", "RetailPointId").IsUnique(); + b.ToTable("employee_retail_point_assignments", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.EmployeeRetailPointAssignment", b => + { + b.HasOne("foodmarket.Domain.Organizations.Employee", "Employee") + .WithMany("RetailPointAssignments") + .HasForeignKey("EmployeeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + b.Navigation("Employee"); + }); #pragma warning restore 612, 618 } }