diff --git a/src/food-market.domain/Organizations/Organization.cs b/src/food-market.domain/Organizations/Organization.cs
index d8353b7..e44689b 100644
--- a/src/food-market.domain/Organizations/Organization.cs
+++ b/src/food-market.domain/Organizations/Organization.cs
@@ -13,6 +13,17 @@ public class Organization : Entity
public string? Email { get; set; }
public bool IsActive { get; set; } = true;
+ /// Архивирована ли организация. Архивные не видны пользователям
+ /// но данные сохраняются. Из архива можно восстановить или (после 30 дней)
+ /// удалить навсегда — этим управляет SuperAdmin.
+ public bool IsArchived { get; set; }
+ public DateTime? ArchivedAt { get; set; }
+
+ /// Account owner (главный владелец, не путать с админами роли).
+ /// Это конкретный AppUser, который считается «хозяином» организации;
+ /// SuperAdmin может сменить через отдельный action c reason в audit-log.
+ public Guid? AccountOwnerUserId { get; set; }
+
/// Персональный API-токен MoySklad. Храним per-organization чтобы
/// пользователю не нужно было вводить его каждый раз при импорте.
public string? MoySkladToken { get; set; }
diff --git a/src/food-market.domain/Organizations/SuperAdminAuditLog.cs b/src/food-market.domain/Organizations/SuperAdminAuditLog.cs
new file mode 100644
index 0000000..636e598
--- /dev/null
+++ b/src/food-market.domain/Organizations/SuperAdminAuditLog.cs
@@ -0,0 +1,20 @@
+using foodmarket.Domain.Common;
+
+namespace foodmarket.Domain.Organizations;
+
+/// Журнал действий SuperAdmin'а: создание/правка/архивирование
+/// организаций, смена аккаунт-владельца, правки в режиме «войти как».
+/// Не tenant-scoped — лог общий для всей системы.
+public class SuperAdminAuditLog : Entity
+{
+ public Guid SuperAdminUserId { get; set; }
+ public string ActionType { get; set; } = "";
+ public Guid? OrganizationId { get; set; }
+ public string? EntityType { get; set; }
+ public Guid? EntityId { get; set; }
+ public string? Description { get; set; }
+ public string? Reason { get; set; }
+ /// JSON с diff'ом before/after или другим payload'ом действия.
+ public string ChangesJson { get; set; } = "{}";
+ public string IpAddress { get; set; } = "";
+}
diff --git a/src/food-market.infrastructure/Persistence/AppDbContext.cs b/src/food-market.infrastructure/Persistence/AppDbContext.cs
index 0c531e2..a62633b 100644
--- a/src/food-market.infrastructure/Persistence/AppDbContext.cs
+++ b/src/food-market.infrastructure/Persistence/AppDbContext.cs
@@ -49,6 +49,7 @@ public AppDbContext(DbContextOptions options, ITenantContext tenan
public DbSet EmployeeRoles => Set();
public DbSet Employees => Set();
public DbSet EmployeeRetailPointAssignments => Set();
+ public DbSet SuperAdminAuditLogs => Set();
protected override void OnModelCreating(ModelBuilder builder)
{
@@ -76,6 +77,21 @@ protected override void OnModelCreating(ModelBuilder builder)
b.Property(o => o.MoySkladToken).HasMaxLength(200);
b.HasOne(o => o.DefaultCurrency).WithMany().HasForeignKey(o => o.DefaultCurrencyId).OnDelete(DeleteBehavior.Restrict);
b.HasIndex(o => o.Name);
+ b.HasIndex(o => o.IsArchived);
+ });
+
+ builder.Entity(b =>
+ {
+ b.ToTable("super_admin_audit_log");
+ b.Property(x => x.ActionType).HasMaxLength(50).IsRequired();
+ b.Property(x => x.EntityType).HasMaxLength(100);
+ b.Property(x => x.Description).HasMaxLength(500);
+ b.Property(x => x.Reason).HasMaxLength(1000);
+ b.Property(x => x.ChangesJson).HasColumnType("jsonb");
+ b.Property(x => x.IpAddress).HasMaxLength(45);
+ b.HasIndex(x => x.CreatedAt);
+ b.HasIndex(x => new { x.SuperAdminUserId, x.CreatedAt });
+ b.HasIndex(x => new { x.OrganizationId, x.CreatedAt });
});
builder.ConfigureCatalog();
diff --git a/src/food-market.infrastructure/Persistence/Migrations/20260427040000_Phase4_SuperAdminConsole.Designer.cs b/src/food-market.infrastructure/Persistence/Migrations/20260427040000_Phase4_SuperAdminConsole.Designer.cs
new file mode 100644
index 0000000..de3b1b8
--- /dev/null
+++ b/src/food-market.infrastructure/Persistence/Migrations/20260427040000_Phase4_SuperAdminConsole.Designer.cs
@@ -0,0 +1,2048 @@
+//
+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("20260427040000_Phase4_SuperAdminConsole")]
+ partial class Phase4_SuperAdminConsole
+ {
+ ///
+ 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("IsArchived")
+ .HasColumnType("boolean");
+
+ b.Property("ArchivedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("AccountOwnerUserId")
+ .HasColumnType("uuid");
+
+ 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