diff --git a/src/food-market.api/Program.cs b/src/food-market.api/Program.cs index 08635de..b9c2358 100644 --- a/src/food-market.api/Program.cs +++ b/src/food-market.api/Program.cs @@ -95,6 +95,7 @@ builder.Services.AddSwaggerGen(); builder.Services.AddHostedService(); + builder.Services.AddHostedService(); builder.Services.AddHostedService(); var app = builder.Build(); diff --git a/src/food-market.api/Seed/DevDataSeeder.cs b/src/food-market.api/Seed/DevDataSeeder.cs index 5c660d4..f059434 100644 --- a/src/food-market.api/Seed/DevDataSeeder.cs +++ b/src/food-market.api/Seed/DevDataSeeder.cs @@ -1,5 +1,6 @@ -using foodmarket.Infrastructure.Identity; +using foodmarket.Domain.Catalog; using foodmarket.Domain.Organizations; +using foodmarket.Infrastructure.Identity; using foodmarket.Infrastructure.Persistence; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; @@ -29,7 +30,7 @@ public async Task StartAsync(CancellationToken ct) var userMgr = scope.ServiceProvider.GetRequiredService>(); var roleMgr = scope.ServiceProvider.GetRequiredService>(); - foreach (var role in new[] { SystemRoles.Admin, SystemRoles.Manager, SystemRoles.Cashier, SystemRoles.Storekeeper }) + foreach (var role in new[] { SystemRoles.SuperAdmin, SystemRoles.Admin, SystemRoles.Manager, SystemRoles.Cashier, SystemRoles.Storekeeper }) { if (!await roleMgr.RoleExistsAsync(role)) { @@ -53,6 +54,8 @@ public async Task StartAsync(CancellationToken ct) await db.SaveChangesAsync(ct); } + await SeedTenantReferencesAsync(db, demoOrg.Id, ct); + const string adminEmail = "admin@food-market.local"; var admin = await userMgr.FindByEmailAsync(adminEmail); if (admin is null) @@ -73,5 +76,68 @@ public async Task StartAsync(CancellationToken ct) } } + private static async Task SeedTenantReferencesAsync(AppDbContext db, Guid orgId, CancellationToken ct) + { + var anyVat = await db.VatRates.IgnoreQueryFilters().AnyAsync(v => v.OrganizationId == orgId, ct); + if (!anyVat) + { + db.VatRates.AddRange( + new VatRate { OrganizationId = orgId, Name = "Без НДС", Percent = 0m, IsDefault = false }, + new VatRate { OrganizationId = orgId, Name = "НДС 12%", Percent = 12m, IsIncludedInPrice = true, IsDefault = true } + ); + } + + var anyUnit = await db.UnitsOfMeasure.IgnoreQueryFilters().AnyAsync(u => u.OrganizationId == orgId, ct); + if (!anyUnit) + { + db.UnitsOfMeasure.AddRange( + new UnitOfMeasure { OrganizationId = orgId, Code = "796", Symbol = "шт", Name = "штука", DecimalPlaces = 0, IsBase = true }, + new UnitOfMeasure { OrganizationId = orgId, Code = "166", Symbol = "кг", Name = "килограмм", DecimalPlaces = 3 }, + new UnitOfMeasure { OrganizationId = orgId, Code = "112", Symbol = "л", Name = "литр", DecimalPlaces = 3 }, + new UnitOfMeasure { OrganizationId = orgId, Code = "006", Symbol = "м", Name = "метр", DecimalPlaces = 3 }, + new UnitOfMeasure { OrganizationId = orgId, Code = "625", Symbol = "уп", Name = "упаковка", DecimalPlaces = 0 } + ); + } + + var anyPriceType = await db.PriceTypes.IgnoreQueryFilters().AnyAsync(p => p.OrganizationId == orgId, ct); + if (!anyPriceType) + { + db.PriceTypes.AddRange( + new PriceType { OrganizationId = orgId, Name = "Розничная", IsDefault = true, IsRetail = true, SortOrder = 1 }, + new PriceType { OrganizationId = orgId, Name = "Оптовая", SortOrder = 2 } + ); + } + + var mainStore = await db.Stores.IgnoreQueryFilters().FirstOrDefaultAsync(s => s.OrganizationId == orgId && s.IsMain, ct); + if (mainStore is null) + { + mainStore = new Store + { + OrganizationId = orgId, + Name = "Основной склад", + Code = "MAIN", + Kind = StoreKind.Warehouse, + IsMain = true, + Address = "Алматы, ул. Пример 1", + }; + db.Stores.Add(mainStore); + await db.SaveChangesAsync(ct); + } + + var anyRetail = await db.RetailPoints.IgnoreQueryFilters().AnyAsync(r => r.OrganizationId == orgId, ct); + if (!anyRetail) + { + db.RetailPoints.Add(new RetailPoint + { + OrganizationId = orgId, + Name = "Касса 1", + Code = "POS-1", + StoreId = mainStore.Id, + }); + } + + await db.SaveChangesAsync(ct); + } + public Task StopAsync(CancellationToken ct) => Task.CompletedTask; } diff --git a/src/food-market.api/Seed/SystemReferenceSeeder.cs b/src/food-market.api/Seed/SystemReferenceSeeder.cs new file mode 100644 index 0000000..48339c8 --- /dev/null +++ b/src/food-market.api/Seed/SystemReferenceSeeder.cs @@ -0,0 +1,78 @@ +using foodmarket.Domain.Catalog; +using foodmarket.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace foodmarket.Api.Seed; + +// Seeds global reference data (countries, currencies). Runs in all environments. +public class SystemReferenceSeeder : IHostedService +{ + private readonly IServiceProvider _services; + + public SystemReferenceSeeder(IServiceProvider services) + { + _services = services; + } + + public async Task StartAsync(CancellationToken ct) + { + using var scope = _services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + await SeedCountriesAsync(db, ct); + await SeedCurrenciesAsync(db, ct); + await db.SaveChangesAsync(ct); + } + + public Task StopAsync(CancellationToken ct) => Task.CompletedTask; + + private static async Task SeedCountriesAsync(AppDbContext db, CancellationToken ct) + { + // Kazakhstan first, then common trade partners. + var wanted = new[] + { + new Country { Code = "KZ", Name = "Казахстан", SortOrder = 1 }, + new Country { Code = "RU", Name = "Россия", SortOrder = 2 }, + new Country { Code = "CN", Name = "Китай", SortOrder = 3 }, + new Country { Code = "TR", Name = "Турция", SortOrder = 4 }, + new Country { Code = "BY", Name = "Беларусь", SortOrder = 5 }, + new Country { Code = "UZ", Name = "Узбекистан", SortOrder = 6 }, + new Country { Code = "KG", Name = "Кыргызстан", SortOrder = 7 }, + new Country { Code = "DE", Name = "Германия", SortOrder = 10 }, + new Country { Code = "US", Name = "США", SortOrder = 11 }, + new Country { Code = "KR", Name = "Южная Корея", SortOrder = 12 }, + new Country { Code = "IT", Name = "Италия", SortOrder = 13 }, + new Country { Code = "PL", Name = "Польша", SortOrder = 14 }, + }; + + var existingCodes = await db.Countries.Select(c => c.Code).ToListAsync(ct); + foreach (var c in wanted) + { + if (!existingCodes.Contains(c.Code)) + { + db.Countries.Add(c); + } + } + } + + private static async Task SeedCurrenciesAsync(AppDbContext db, CancellationToken ct) + { + var wanted = new[] + { + new Currency { Code = "KZT", Name = "Тенге", Symbol = "₸", MinorUnit = 2 }, + new Currency { Code = "RUB", Name = "Российский рубль", Symbol = "₽", MinorUnit = 2 }, + new Currency { Code = "USD", Name = "Доллар США", Symbol = "$", MinorUnit = 2 }, + new Currency { Code = "EUR", Name = "Евро", Symbol = "€", MinorUnit = 2 }, + new Currency { Code = "CNY", Name = "Китайский юань", Symbol = "¥", MinorUnit = 2 }, + }; + + var existingCodes = await db.Currencies.Select(c => c.Code).ToListAsync(ct); + foreach (var c in wanted) + { + if (!existingCodes.Contains(c.Code)) + { + db.Currencies.Add(c); + } + } + } +} diff --git a/src/food-market.domain/Catalog/Counterparty.cs b/src/food-market.domain/Catalog/Counterparty.cs new file mode 100644 index 0000000..db25617 --- /dev/null +++ b/src/food-market.domain/Catalog/Counterparty.cs @@ -0,0 +1,25 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +public class Counterparty : TenantEntity +{ + public string Name { get; set; } = null!; // отображаемое имя + public string? LegalName { get; set; } // полное юридическое имя + public CounterpartyKind Kind { get; set; } // Supplier / Customer / Both + public CounterpartyType Type { get; set; } // Юрлицо / Физлицо + public string? Bin { get; set; } // БИН (для юрлиц РК) + public string? Iin { get; set; } // ИИН (для физлиц РК) + public string? TaxNumber { get; set; } // ИНН (для РФ) или другой идентификатор + public Guid? CountryId { get; set; } + public Country? Country { get; set; } + public string? Address { get; set; } + public string? Phone { get; set; } + public string? Email { get; set; } + public string? BankName { get; set; } + public string? BankAccount { get; set; } + public string? Bik { get; set; } + public string? ContactPerson { get; set; } + public string? Notes { get; set; } + public bool IsActive { get; set; } = true; +} diff --git a/src/food-market.domain/Catalog/Country.cs b/src/food-market.domain/Catalog/Country.cs new file mode 100644 index 0000000..2042c84 --- /dev/null +++ b/src/food-market.domain/Catalog/Country.cs @@ -0,0 +1,11 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +// Global reference (not tenant-scoped). Shared across organizations. +public class Country : Entity +{ + public string Code { get; set; } = null!; // ISO 3166-1 alpha-2, e.g. "KZ" + public string Name { get; set; } = null!; + public int SortOrder { get; set; } +} diff --git a/src/food-market.domain/Catalog/Currency.cs b/src/food-market.domain/Catalog/Currency.cs new file mode 100644 index 0000000..b53ac6f --- /dev/null +++ b/src/food-market.domain/Catalog/Currency.cs @@ -0,0 +1,13 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +// Global reference (not tenant-scoped). +public class Currency : Entity +{ + public string Code { get; set; } = null!; // ISO 4217, e.g. "KZT" + public string Name { get; set; } = null!; + public string Symbol { get; set; } = null!; // "₸" + public int MinorUnit { get; set; } = 2; // 2 = two decimal places + public bool IsActive { get; set; } = true; +} diff --git a/src/food-market.domain/Catalog/Enums.cs b/src/food-market.domain/Catalog/Enums.cs new file mode 100644 index 0000000..7f2a11f --- /dev/null +++ b/src/food-market.domain/Catalog/Enums.cs @@ -0,0 +1,31 @@ +namespace foodmarket.Domain.Catalog; + +public enum CounterpartyKind +{ + Supplier = 1, + Customer = 2, + Both = 3, +} + +public enum CounterpartyType +{ + LegalEntity = 1, + Individual = 2, +} + +public enum StoreKind +{ + Warehouse = 1, + RetailFloor = 2, +} + +public enum BarcodeType +{ + Ean13 = 1, + Ean8 = 2, + Code128 = 3, + Code39 = 4, + Upca = 5, + Upce = 6, + Other = 99, +} diff --git a/src/food-market.domain/Catalog/PriceType.cs b/src/food-market.domain/Catalog/PriceType.cs new file mode 100644 index 0000000..e390120 --- /dev/null +++ b/src/food-market.domain/Catalog/PriceType.cs @@ -0,0 +1,13 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +// Тип цены: Розничная, Оптовая, Минимальная и т.д. По одной организации может быть несколько. +public class PriceType : TenantEntity +{ + public string Name { get; set; } = null!; + public bool IsDefault { get; set; } // цена по умолчанию для новых товаров + public bool IsRetail { get; set; } // используется на кассе + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; +} diff --git a/src/food-market.domain/Catalog/Product.cs b/src/food-market.domain/Catalog/Product.cs new file mode 100644 index 0000000..a0c7c8a --- /dev/null +++ b/src/food-market.domain/Catalog/Product.cs @@ -0,0 +1,44 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +public class Product : TenantEntity +{ + public string Name { get; set; } = null!; + public string? Article { get; set; } // артикул + public string? Description { get; set; } + + public Guid UnitOfMeasureId { get; set; } + public UnitOfMeasure? UnitOfMeasure { get; set; } + + public Guid VatRateId { get; set; } + public VatRate? VatRate { get; set; } + + public Guid? ProductGroupId { get; set; } + public ProductGroup? ProductGroup { get; set; } + + public Guid? DefaultSupplierId { get; set; } // основной поставщик + public Counterparty? DefaultSupplier { get; set; } + + public Guid? CountryOfOriginId { get; set; } // страна происхождения + public Country? CountryOfOrigin { get; set; } + + public bool IsService { get; set; } // услуга, а не физический товар + public bool IsWeighed { get; set; } // весовой (продаётся с весов) + public bool IsAlcohol { get; set; } // алкоголь (подакцизный) + public bool IsMarked { get; set; } // маркируемый (Datamatrix) + + public decimal? MinStock { get; set; } // минимальный остаток (для уведомлений) + public decimal? MaxStock { get; set; } // максимальный остаток (для автозаказа) + + public decimal? PurchasePrice { get; set; } // закупочная цена по умолчанию + public Guid? PurchaseCurrencyId { get; set; } + public Currency? PurchaseCurrency { get; set; } + + public string? ImageUrl { get; set; } // основное изображение (остальные в ProductImage) + public bool IsActive { get; set; } = true; + + public ICollection Prices { get; set; } = []; + public ICollection Barcodes { get; set; } = []; + public ICollection Images { get; set; } = []; +} diff --git a/src/food-market.domain/Catalog/ProductBarcode.cs b/src/food-market.domain/Catalog/ProductBarcode.cs new file mode 100644 index 0000000..cc64711 --- /dev/null +++ b/src/food-market.domain/Catalog/ProductBarcode.cs @@ -0,0 +1,12 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +public class ProductBarcode : TenantEntity +{ + public Guid ProductId { get; set; } + public Product? Product { get; set; } + public string Code { get; set; } = null!; + public BarcodeType Type { get; set; } = BarcodeType.Ean13; + public bool IsPrimary { get; set; } +} diff --git a/src/food-market.domain/Catalog/ProductGroup.cs b/src/food-market.domain/Catalog/ProductGroup.cs new file mode 100644 index 0000000..40972bb --- /dev/null +++ b/src/food-market.domain/Catalog/ProductGroup.cs @@ -0,0 +1,15 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +// Иерархическая группа товаров (категория). Произвольная вложенность через ParentId. +public class ProductGroup : TenantEntity +{ + public string Name { get; set; } = null!; + public Guid? ParentId { get; set; } + public ProductGroup? Parent { get; set; } + public ICollection Children { get; set; } = []; + public string Path { get; set; } = ""; // денормализованный путь "Электроника/Телефоны/Смартфоны" для фильтрации + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; +} diff --git a/src/food-market.domain/Catalog/ProductImage.cs b/src/food-market.domain/Catalog/ProductImage.cs new file mode 100644 index 0000000..8deefba --- /dev/null +++ b/src/food-market.domain/Catalog/ProductImage.cs @@ -0,0 +1,12 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +public class ProductImage : TenantEntity +{ + public Guid ProductId { get; set; } + public Product? Product { get; set; } + public string Url { get; set; } = null!; + public bool IsMain { get; set; } + public int SortOrder { get; set; } +} diff --git a/src/food-market.domain/Catalog/ProductPrice.cs b/src/food-market.domain/Catalog/ProductPrice.cs new file mode 100644 index 0000000..eedab9c --- /dev/null +++ b/src/food-market.domain/Catalog/ProductPrice.cs @@ -0,0 +1,17 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +public class ProductPrice : TenantEntity +{ + public Guid ProductId { get; set; } + public Product? Product { get; set; } + + public Guid PriceTypeId { get; set; } + public PriceType? PriceType { get; set; } + + public decimal Amount { get; set; } + + public Guid CurrencyId { get; set; } + public Currency? Currency { get; set; } +} diff --git a/src/food-market.domain/Catalog/RetailPoint.cs b/src/food-market.domain/Catalog/RetailPoint.cs new file mode 100644 index 0000000..f414436 --- /dev/null +++ b/src/food-market.domain/Catalog/RetailPoint.cs @@ -0,0 +1,17 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +// Точка продаж (магазин/отдел). Привязана к складу, с которого продаёт. +public class RetailPoint : TenantEntity +{ + public string Name { get; set; } = null!; + public string? Code { get; set; } + public Guid StoreId { get; set; } // склад, с которого идут продажи + public Store? Store { get; set; } + public string? Address { get; set; } + public string? Phone { get; set; } + public string? FiscalSerial { get; set; } // серийный номер ККМ (заполнится когда подключим фискализацию) + public string? FiscalRegNumber { get; set; } // регистрационный номер в ОФД + public bool IsActive { get; set; } = true; +} diff --git a/src/food-market.domain/Catalog/Store.cs b/src/food-market.domain/Catalog/Store.cs new file mode 100644 index 0000000..00b20ee --- /dev/null +++ b/src/food-market.domain/Catalog/Store.cs @@ -0,0 +1,16 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +// Склад: физическое место хранения товаров. Может быть чисто склад или торговый зал. +public class Store : TenantEntity +{ + public string Name { get; set; } = null!; + public string? Code { get; set; } // внутренний код склада + public StoreKind Kind { get; set; } = StoreKind.Warehouse; + public string? Address { get; set; } + public string? Phone { get; set; } + public string? ManagerName { get; set; } + public bool IsMain { get; set; } // основной склад организации + public bool IsActive { get; set; } = true; +} diff --git a/src/food-market.domain/Catalog/UnitOfMeasure.cs b/src/food-market.domain/Catalog/UnitOfMeasure.cs new file mode 100644 index 0000000..562ceb9 --- /dev/null +++ b/src/food-market.domain/Catalog/UnitOfMeasure.cs @@ -0,0 +1,14 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +// Tenant-scoped справочник единиц измерения. +public class UnitOfMeasure : TenantEntity +{ + public string Code { get; set; } = null!; // ОКЕИ код: "796" (шт), "166" (кг), "112" (л) + public string Symbol { get; set; } = null!; // "шт", "кг", "л", "м" + public string Name { get; set; } = null!; // "штука", "килограмм", "литр" + public int DecimalPlaces { get; set; } // 0 для шт, 3 для кг/л + public bool IsBase { get; set; } // базовая единица этой организации + public bool IsActive { get; set; } = true; +} diff --git a/src/food-market.domain/Catalog/VatRate.cs b/src/food-market.domain/Catalog/VatRate.cs new file mode 100644 index 0000000..23dfce9 --- /dev/null +++ b/src/food-market.domain/Catalog/VatRate.cs @@ -0,0 +1,13 @@ +using foodmarket.Domain.Common; + +namespace foodmarket.Domain.Catalog; + +// Tenant-scoped: разные организации могут работать в разных режимах (с НДС / упрощёнка). +public class VatRate : TenantEntity +{ + public string Name { get; set; } = null!; // "НДС 12%", "Без НДС" + public decimal Percent { get; set; } // 12.00, 0.00 + public bool IsIncludedInPrice { get; set; } // входит ли в цену или начисляется сверху + public bool IsDefault { get; set; } + public bool IsActive { get; set; } = true; +} diff --git a/src/food-market.infrastructure/Persistence/AppDbContext.cs b/src/food-market.infrastructure/Persistence/AppDbContext.cs index e58f9cb..77c1fae 100644 --- a/src/food-market.infrastructure/Persistence/AppDbContext.cs +++ b/src/food-market.infrastructure/Persistence/AppDbContext.cs @@ -1,10 +1,11 @@ using foodmarket.Application.Common.Tenancy; +using foodmarket.Domain.Catalog; using foodmarket.Domain.Common; using foodmarket.Infrastructure.Identity; using foodmarket.Domain.Organizations; +using foodmarket.Infrastructure.Persistence.Configurations; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.ChangeTracking; namespace foodmarket.Infrastructure.Persistence; @@ -20,6 +21,20 @@ public AppDbContext(DbContextOptions options, ITenantContext tenan public DbSet Organizations => Set(); + public DbSet Countries => Set(); + public DbSet Currencies => Set(); + public DbSet VatRates => Set(); + public DbSet UnitsOfMeasure => Set(); + public DbSet Counterparties => Set(); + public DbSet Stores => Set(); + public DbSet RetailPoints => Set(); + public DbSet ProductGroups => Set(); + public DbSet PriceTypes => Set(); + public DbSet Products => Set(); + public DbSet ProductPrices => Set(); + public DbSet ProductBarcodes => Set(); + public DbSet ProductImages => Set(); + protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); @@ -46,6 +61,8 @@ protected override void OnModelCreating(ModelBuilder builder) b.HasIndex(o => o.Name); }); + builder.ConfigureCatalog(); + // 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/CatalogConfigurations.cs b/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs new file mode 100644 index 0000000..a928ed7 --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs @@ -0,0 +1,170 @@ +using foodmarket.Domain.Catalog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace foodmarket.Infrastructure.Persistence.Configurations; + +internal static class CatalogConfigurations +{ + public static void ConfigureCatalog(this ModelBuilder b) + { + b.Entity(ConfigureCountry); + b.Entity(ConfigureCurrency); + b.Entity(ConfigureVatRate); + b.Entity(ConfigureUnit); + b.Entity(ConfigureCounterparty); + b.Entity(ConfigureStore); + b.Entity(ConfigureRetailPoint); + b.Entity(ConfigureProductGroup); + b.Entity(ConfigurePriceType); + b.Entity(ConfigureProduct); + b.Entity(ConfigureProductPrice); + b.Entity(ConfigureBarcode); + b.Entity(ConfigureImage); + } + + private static void ConfigureCountry(EntityTypeBuilder b) + { + b.ToTable("countries"); + b.Property(x => x.Code).HasMaxLength(2).IsRequired(); + b.Property(x => x.Name).HasMaxLength(100).IsRequired(); + b.HasIndex(x => x.Code).IsUnique(); + } + + private static void ConfigureCurrency(EntityTypeBuilder b) + { + b.ToTable("currencies"); + b.Property(x => x.Code).HasMaxLength(3).IsRequired(); + b.Property(x => x.Name).HasMaxLength(100).IsRequired(); + b.Property(x => x.Symbol).HasMaxLength(5).IsRequired(); + b.HasIndex(x => x.Code).IsUnique(); + } + + private static void ConfigureVatRate(EntityTypeBuilder b) + { + b.ToTable("vat_rates"); + b.Property(x => x.Name).HasMaxLength(100).IsRequired(); + b.Property(x => x.Percent).HasPrecision(5, 2); + b.HasIndex(x => new { x.OrganizationId, x.Name }).IsUnique(); + } + + private static void ConfigureUnit(EntityTypeBuilder b) + { + b.ToTable("units_of_measure"); + b.Property(x => x.Code).HasMaxLength(10).IsRequired(); + b.Property(x => x.Symbol).HasMaxLength(20).IsRequired(); + b.Property(x => x.Name).HasMaxLength(100).IsRequired(); + b.HasIndex(x => new { x.OrganizationId, x.Code }).IsUnique(); + } + + private static void ConfigureCounterparty(EntityTypeBuilder b) + { + b.ToTable("counterparties"); + b.Property(x => x.Name).HasMaxLength(255).IsRequired(); + b.Property(x => x.LegalName).HasMaxLength(500); + b.Property(x => x.Bin).HasMaxLength(20); + b.Property(x => x.Iin).HasMaxLength(20); + b.Property(x => x.TaxNumber).HasMaxLength(20); + b.Property(x => x.Phone).HasMaxLength(50); + b.Property(x => x.Email).HasMaxLength(255); + b.Property(x => x.BankName).HasMaxLength(255); + b.Property(x => x.BankAccount).HasMaxLength(50); + b.Property(x => x.Bik).HasMaxLength(20); + b.Property(x => x.ContactPerson).HasMaxLength(255); + b.HasOne(x => x.Country).WithMany().HasForeignKey(x => x.CountryId).OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => new { x.OrganizationId, x.Name }); + b.HasIndex(x => new { x.OrganizationId, x.Bin }); + b.HasIndex(x => new { x.OrganizationId, x.Kind }); + } + + private static void ConfigureStore(EntityTypeBuilder b) + { + b.ToTable("stores"); + b.Property(x => x.Name).HasMaxLength(200).IsRequired(); + b.Property(x => x.Code).HasMaxLength(50); + b.Property(x => x.Phone).HasMaxLength(50); + b.Property(x => x.ManagerName).HasMaxLength(200); + b.HasIndex(x => new { x.OrganizationId, x.Name }); + } + + private static void ConfigureRetailPoint(EntityTypeBuilder b) + { + b.ToTable("retail_points"); + b.Property(x => x.Name).HasMaxLength(200).IsRequired(); + b.Property(x => x.Code).HasMaxLength(50); + b.Property(x => x.Phone).HasMaxLength(50); + b.Property(x => x.FiscalSerial).HasMaxLength(50); + b.Property(x => x.FiscalRegNumber).HasMaxLength(50); + b.HasOne(x => x.Store).WithMany().HasForeignKey(x => x.StoreId).OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => new { x.OrganizationId, x.Name }); + } + + private static void ConfigureProductGroup(EntityTypeBuilder b) + { + b.ToTable("product_groups"); + b.Property(x => x.Name).HasMaxLength(200).IsRequired(); + b.Property(x => x.Path).HasMaxLength(1000); + b.HasOne(x => x.Parent) + .WithMany(x => x.Children) + .HasForeignKey(x => x.ParentId) + .OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => new { x.OrganizationId, x.ParentId }); + b.HasIndex(x => new { x.OrganizationId, x.Path }); + } + + private static void ConfigurePriceType(EntityTypeBuilder b) + { + b.ToTable("price_types"); + b.Property(x => x.Name).HasMaxLength(100).IsRequired(); + b.HasIndex(x => new { x.OrganizationId, x.Name }).IsUnique(); + } + + private static void ConfigureProduct(EntityTypeBuilder b) + { + b.ToTable("products"); + b.Property(x => x.Name).HasMaxLength(500).IsRequired(); + b.Property(x => x.Article).HasMaxLength(100); + b.Property(x => x.MinStock).HasPrecision(18, 4); + b.Property(x => x.MaxStock).HasPrecision(18, 4); + b.Property(x => x.PurchasePrice).HasPrecision(18, 4); + b.Property(x => x.ImageUrl).HasMaxLength(1000); + + b.HasOne(x => x.UnitOfMeasure).WithMany().HasForeignKey(x => x.UnitOfMeasureId).OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.VatRate).WithMany().HasForeignKey(x => x.VatRateId).OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.ProductGroup).WithMany().HasForeignKey(x => x.ProductGroupId).OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.DefaultSupplier).WithMany().HasForeignKey(x => x.DefaultSupplierId).OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.CountryOfOrigin).WithMany().HasForeignKey(x => x.CountryOfOriginId).OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.PurchaseCurrency).WithMany().HasForeignKey(x => x.PurchaseCurrencyId).OnDelete(DeleteBehavior.Restrict); + + b.HasIndex(x => new { x.OrganizationId, x.Name }); + b.HasIndex(x => new { x.OrganizationId, x.Article }); + b.HasIndex(x => new { x.OrganizationId, x.ProductGroupId }); + b.HasIndex(x => new { x.OrganizationId, x.IsActive }); + } + + private static void ConfigureProductPrice(EntityTypeBuilder b) + { + b.ToTable("product_prices"); + b.Property(x => x.Amount).HasPrecision(18, 4); + b.HasOne(x => x.Product).WithMany(p => p.Prices).HasForeignKey(x => x.ProductId).OnDelete(DeleteBehavior.Cascade); + b.HasOne(x => x.PriceType).WithMany().HasForeignKey(x => x.PriceTypeId).OnDelete(DeleteBehavior.Restrict); + b.HasOne(x => x.Currency).WithMany().HasForeignKey(x => x.CurrencyId).OnDelete(DeleteBehavior.Restrict); + b.HasIndex(x => new { x.ProductId, x.PriceTypeId }).IsUnique(); + } + + private static void ConfigureBarcode(EntityTypeBuilder b) + { + b.ToTable("product_barcodes"); + b.Property(x => x.Code).HasMaxLength(100).IsRequired(); + b.HasOne(x => x.Product).WithMany(p => p.Barcodes).HasForeignKey(x => x.ProductId).OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => new { x.OrganizationId, x.Code }).IsUnique(); + } + + private static void ConfigureImage(EntityTypeBuilder b) + { + b.ToTable("product_images"); + b.Property(x => x.Url).HasMaxLength(1000).IsRequired(); + b.HasOne(x => x.Product).WithMany(p => p.Images).HasForeignKey(x => x.ProductId).OnDelete(DeleteBehavior.Cascade); + b.HasIndex(x => x.ProductId); + } +} diff --git a/src/food-market.infrastructure/Persistence/Migrations/20260421141151_Phase1Catalog.Designer.cs b/src/food-market.infrastructure/Persistence/Migrations/20260421141151_Phase1Catalog.Designer.cs new file mode 100644 index 0000000..bab869f --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Migrations/20260421141151_Phase1Catalog.Designer.cs @@ -0,0 +1,1389 @@ +// +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("20260421141151_Phase1Catalog")] + partial class Phase1Catalog + { + /// + 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("IsActive") + .HasColumnType("boolean"); + + b.Property("Kind") + .HasColumnType("integer"); + + 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", "Kind"); + + 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("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + 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("IsActive") + .HasColumnType("boolean"); + + 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("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsRetail") + .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(100) + .HasColumnType("character varying(100)"); + + 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("IsActive") + .HasColumnType("boolean"); + + b.Property("IsAlcohol") + .HasColumnType("boolean"); + + b.Property("IsMarked") + .HasColumnType("boolean"); + + b.Property("IsService") + .HasColumnType("boolean"); + + b.Property("IsWeighed") + .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("PurchaseCurrencyId") + .HasColumnType("uuid"); + + b.Property("PurchasePrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitOfMeasureId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VatRateId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CountryOfOriginId"); + + b.HasIndex("DefaultSupplierId"); + + b.HasIndex("ProductGroupId"); + + b.HasIndex("PurchaseCurrencyId"); + + b.HasIndex("UnitOfMeasureId"); + + b.HasIndex("VatRateId"); + + b.HasIndex("OrganizationId", "Article"); + + b.HasIndex("OrganizationId", "IsActive"); + + 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(100) + .HasColumnType("character varying(100)"); + + 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("IsActive") + .HasColumnType("boolean"); + + 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("Kind") + .HasColumnType("integer"); + + 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("DecimalPlaces") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsBase") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Symbol") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + 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.Catalog.VatRate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsIncludedInPrice") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Percent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("vat_rates", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasColumnType("text"); + + 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("IsActive") + .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("Name"); + + b.ToTable("organizations", "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.HasOne("foodmarket.Domain.Catalog.VatRate", "VatRate") + .WithMany() + .HasForeignKey("VatRateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CountryOfOrigin"); + + b.Navigation("DefaultSupplier"); + + b.Navigation("ProductGroup"); + + b.Navigation("PurchaseCurrency"); + + b.Navigation("UnitOfMeasure"); + + b.Navigation("VatRate"); + }); + + 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("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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/food-market.infrastructure/Persistence/Migrations/20260421141151_Phase1Catalog.cs b/src/food-market.infrastructure/Persistence/Migrations/20260421141151_Phase1Catalog.cs new file mode 100644 index 0000000..ce01341 --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Migrations/20260421141151_Phase1Catalog.cs @@ -0,0 +1,647 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace foodmarket.Infrastructure.Persistence.Migrations +{ + /// + public partial class Phase1Catalog : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "countries", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(2)", maxLength: 2, nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + SortOrder = table.Column(type: "integer", 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_countries", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "currencies", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(3)", maxLength: 3, nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Symbol = table.Column(type: "character varying(5)", maxLength: 5, nullable: false), + MinorUnit = table.Column(type: "integer", nullable: false), + IsActive = table.Column(type: "boolean", 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_currencies", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "price_types", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + IsDefault = table.Column(type: "boolean", nullable: false), + IsRetail = table.Column(type: "boolean", nullable: false), + SortOrder = table.Column(type: "integer", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_price_types", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "product_groups", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + ParentId = table.Column(type: "uuid", nullable: true), + Path = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + SortOrder = table.Column(type: "integer", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_product_groups", x => x.Id); + table.ForeignKey( + name: "FK_product_groups_product_groups_ParentId", + column: x => x.ParentId, + principalSchema: "public", + principalTable: "product_groups", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "stores", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Kind = table.Column(type: "integer", nullable: false), + Address = table.Column(type: "text", nullable: true), + Phone = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + ManagerName = table.Column(type: "character varying(200)", maxLength: 200, nullable: true), + IsMain = table.Column(type: "boolean", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_stores", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "units_of_measure", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + Symbol = table.Column(type: "character varying(20)", maxLength: 20, nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + DecimalPlaces = table.Column(type: "integer", nullable: false), + IsBase = table.Column(type: "boolean", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_units_of_measure", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "vat_rates", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Percent = table.Column(type: "numeric(5,2)", precision: 5, scale: 2, nullable: false), + IsIncludedInPrice = table.Column(type: "boolean", nullable: false), + IsDefault = table.Column(type: "boolean", nullable: false), + IsActive = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_vat_rates", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "counterparties", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + LegalName = table.Column(type: "character varying(500)", maxLength: 500, nullable: true), + Kind = table.Column(type: "integer", nullable: false), + Type = table.Column(type: "integer", nullable: false), + Bin = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + Iin = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + TaxNumber = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + CountryId = table.Column(type: "uuid", nullable: true), + Address = table.Column(type: "text", nullable: true), + Phone = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Email = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + BankName = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + BankAccount = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + Bik = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + ContactPerson = table.Column(type: "character varying(255)", maxLength: 255, nullable: true), + Notes = table.Column(type: "text", nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_counterparties", x => x.Id); + table.ForeignKey( + name: "FK_counterparties_countries_CountryId", + column: x => x.CountryId, + principalSchema: "public", + principalTable: "countries", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "retail_points", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(200)", maxLength: 200, nullable: false), + Code = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + StoreId = table.Column(type: "uuid", nullable: false), + Address = table.Column(type: "text", nullable: true), + Phone = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + FiscalSerial = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + FiscalRegNumber = table.Column(type: "character varying(50)", maxLength: 50, nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_retail_points", x => x.Id); + table.ForeignKey( + name: "FK_retail_points_stores_StoreId", + column: x => x.StoreId, + principalSchema: "public", + principalTable: "stores", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "products", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(500)", maxLength: 500, nullable: false), + Article = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + Description = table.Column(type: "text", nullable: true), + UnitOfMeasureId = table.Column(type: "uuid", nullable: false), + VatRateId = table.Column(type: "uuid", nullable: false), + ProductGroupId = table.Column(type: "uuid", nullable: true), + DefaultSupplierId = table.Column(type: "uuid", nullable: true), + CountryOfOriginId = table.Column(type: "uuid", nullable: true), + IsService = table.Column(type: "boolean", nullable: false), + IsWeighed = table.Column(type: "boolean", nullable: false), + IsAlcohol = table.Column(type: "boolean", nullable: false), + IsMarked = table.Column(type: "boolean", nullable: false), + MinStock = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true), + MaxStock = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true), + PurchasePrice = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true), + PurchaseCurrencyId = table.Column(type: "uuid", nullable: true), + ImageUrl = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: true), + IsActive = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_products", x => x.Id); + table.ForeignKey( + name: "FK_products_counterparties_DefaultSupplierId", + column: x => x.DefaultSupplierId, + principalSchema: "public", + principalTable: "counterparties", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_products_countries_CountryOfOriginId", + column: x => x.CountryOfOriginId, + principalSchema: "public", + principalTable: "countries", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_products_currencies_PurchaseCurrencyId", + column: x => x.PurchaseCurrencyId, + principalSchema: "public", + principalTable: "currencies", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_products_product_groups_ProductGroupId", + column: x => x.ProductGroupId, + principalSchema: "public", + principalTable: "product_groups", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_products_units_of_measure_UnitOfMeasureId", + column: x => x.UnitOfMeasureId, + principalSchema: "public", + principalTable: "units_of_measure", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_products_vat_rates_VatRateId", + column: x => x.VatRateId, + principalSchema: "public", + principalTable: "vat_rates", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "product_barcodes", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + Code = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + Type = table.Column(type: "integer", nullable: false), + IsPrimary = table.Column(type: "boolean", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_product_barcodes", x => x.Id); + table.ForeignKey( + name: "FK_product_barcodes_products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "product_images", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + Url = table.Column(type: "character varying(1000)", maxLength: 1000, nullable: false), + IsMain = table.Column(type: "boolean", nullable: false), + SortOrder = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_product_images", x => x.Id); + table.ForeignKey( + name: "FK_product_images_products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "product_prices", + schema: "public", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + ProductId = table.Column(type: "uuid", nullable: false), + PriceTypeId = table.Column(type: "uuid", nullable: false), + Amount = table.Column(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false), + CurrencyId = 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), + OrganizationId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_product_prices", x => x.Id); + table.ForeignKey( + name: "FK_product_prices_currencies_CurrencyId", + column: x => x.CurrencyId, + principalSchema: "public", + principalTable: "currencies", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_product_prices_price_types_PriceTypeId", + column: x => x.PriceTypeId, + principalSchema: "public", + principalTable: "price_types", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_product_prices_products_ProductId", + column: x => x.ProductId, + principalSchema: "public", + principalTable: "products", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_counterparties_CountryId", + schema: "public", + table: "counterparties", + column: "CountryId"); + + migrationBuilder.CreateIndex( + name: "IX_counterparties_OrganizationId_Bin", + schema: "public", + table: "counterparties", + columns: new[] { "OrganizationId", "Bin" }); + + migrationBuilder.CreateIndex( + name: "IX_counterparties_OrganizationId_Kind", + schema: "public", + table: "counterparties", + columns: new[] { "OrganizationId", "Kind" }); + + migrationBuilder.CreateIndex( + name: "IX_counterparties_OrganizationId_Name", + schema: "public", + table: "counterparties", + columns: new[] { "OrganizationId", "Name" }); + + migrationBuilder.CreateIndex( + name: "IX_countries_Code", + schema: "public", + table: "countries", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_currencies_Code", + schema: "public", + table: "currencies", + column: "Code", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_price_types_OrganizationId_Name", + schema: "public", + table: "price_types", + columns: new[] { "OrganizationId", "Name" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_product_barcodes_OrganizationId_Code", + schema: "public", + table: "product_barcodes", + columns: new[] { "OrganizationId", "Code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_product_barcodes_ProductId", + schema: "public", + table: "product_barcodes", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_product_groups_OrganizationId_ParentId", + schema: "public", + table: "product_groups", + columns: new[] { "OrganizationId", "ParentId" }); + + migrationBuilder.CreateIndex( + name: "IX_product_groups_OrganizationId_Path", + schema: "public", + table: "product_groups", + columns: new[] { "OrganizationId", "Path" }); + + migrationBuilder.CreateIndex( + name: "IX_product_groups_ParentId", + schema: "public", + table: "product_groups", + column: "ParentId"); + + migrationBuilder.CreateIndex( + name: "IX_product_images_ProductId", + schema: "public", + table: "product_images", + column: "ProductId"); + + migrationBuilder.CreateIndex( + name: "IX_product_prices_CurrencyId", + schema: "public", + table: "product_prices", + column: "CurrencyId"); + + migrationBuilder.CreateIndex( + name: "IX_product_prices_PriceTypeId", + schema: "public", + table: "product_prices", + column: "PriceTypeId"); + + migrationBuilder.CreateIndex( + name: "IX_product_prices_ProductId_PriceTypeId", + schema: "public", + table: "product_prices", + columns: new[] { "ProductId", "PriceTypeId" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_products_CountryOfOriginId", + schema: "public", + table: "products", + column: "CountryOfOriginId"); + + migrationBuilder.CreateIndex( + name: "IX_products_DefaultSupplierId", + schema: "public", + table: "products", + column: "DefaultSupplierId"); + + migrationBuilder.CreateIndex( + name: "IX_products_OrganizationId_Article", + schema: "public", + table: "products", + columns: new[] { "OrganizationId", "Article" }); + + migrationBuilder.CreateIndex( + name: "IX_products_OrganizationId_IsActive", + schema: "public", + table: "products", + columns: new[] { "OrganizationId", "IsActive" }); + + migrationBuilder.CreateIndex( + name: "IX_products_OrganizationId_Name", + schema: "public", + table: "products", + columns: new[] { "OrganizationId", "Name" }); + + migrationBuilder.CreateIndex( + name: "IX_products_OrganizationId_ProductGroupId", + schema: "public", + table: "products", + columns: new[] { "OrganizationId", "ProductGroupId" }); + + migrationBuilder.CreateIndex( + name: "IX_products_ProductGroupId", + schema: "public", + table: "products", + column: "ProductGroupId"); + + migrationBuilder.CreateIndex( + name: "IX_products_PurchaseCurrencyId", + schema: "public", + table: "products", + column: "PurchaseCurrencyId"); + + migrationBuilder.CreateIndex( + name: "IX_products_UnitOfMeasureId", + schema: "public", + table: "products", + column: "UnitOfMeasureId"); + + migrationBuilder.CreateIndex( + name: "IX_products_VatRateId", + schema: "public", + table: "products", + column: "VatRateId"); + + migrationBuilder.CreateIndex( + name: "IX_retail_points_OrganizationId_Name", + schema: "public", + table: "retail_points", + columns: new[] { "OrganizationId", "Name" }); + + migrationBuilder.CreateIndex( + name: "IX_retail_points_StoreId", + schema: "public", + table: "retail_points", + column: "StoreId"); + + migrationBuilder.CreateIndex( + name: "IX_stores_OrganizationId_Name", + schema: "public", + table: "stores", + columns: new[] { "OrganizationId", "Name" }); + + migrationBuilder.CreateIndex( + name: "IX_units_of_measure_OrganizationId_Code", + schema: "public", + table: "units_of_measure", + columns: new[] { "OrganizationId", "Code" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_vat_rates_OrganizationId_Name", + schema: "public", + table: "vat_rates", + columns: new[] { "OrganizationId", "Name" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "product_barcodes", + schema: "public"); + + migrationBuilder.DropTable( + name: "product_images", + schema: "public"); + + migrationBuilder.DropTable( + name: "product_prices", + schema: "public"); + + migrationBuilder.DropTable( + name: "retail_points", + schema: "public"); + + migrationBuilder.DropTable( + name: "price_types", + schema: "public"); + + migrationBuilder.DropTable( + name: "products", + schema: "public"); + + migrationBuilder.DropTable( + name: "stores", + schema: "public"); + + migrationBuilder.DropTable( + name: "counterparties", + schema: "public"); + + migrationBuilder.DropTable( + name: "currencies", + schema: "public"); + + migrationBuilder.DropTable( + name: "product_groups", + schema: "public"); + + migrationBuilder.DropTable( + name: "units_of_measure", + schema: "public"); + + migrationBuilder.DropTable( + name: "vat_rates", + schema: "public"); + + migrationBuilder.DropTable( + name: "countries", + schema: "public"); + } + } +} diff --git a/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs b/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs index 105f9c2..db987af 100644 --- a/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs +++ b/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs @@ -334,6 +334,665 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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("IsActive") + .HasColumnType("boolean"); + + b.Property("Kind") + .HasColumnType("integer"); + + 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", "Kind"); + + 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("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + 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("IsActive") + .HasColumnType("boolean"); + + 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("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsRetail") + .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(100) + .HasColumnType("character varying(100)"); + + 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("IsActive") + .HasColumnType("boolean"); + + b.Property("IsAlcohol") + .HasColumnType("boolean"); + + b.Property("IsMarked") + .HasColumnType("boolean"); + + b.Property("IsService") + .HasColumnType("boolean"); + + b.Property("IsWeighed") + .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("PurchaseCurrencyId") + .HasColumnType("uuid"); + + b.Property("PurchasePrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UnitOfMeasureId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VatRateId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("CountryOfOriginId"); + + b.HasIndex("DefaultSupplierId"); + + b.HasIndex("ProductGroupId"); + + b.HasIndex("PurchaseCurrencyId"); + + b.HasIndex("UnitOfMeasureId"); + + b.HasIndex("VatRateId"); + + b.HasIndex("OrganizationId", "Article"); + + b.HasIndex("OrganizationId", "IsActive"); + + 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(100) + .HasColumnType("character varying(100)"); + + 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("IsActive") + .HasColumnType("boolean"); + + 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("Kind") + .HasColumnType("integer"); + + 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("DecimalPlaces") + .HasColumnType("integer"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsBase") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Symbol") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + 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.Catalog.VatRate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsIncludedInPrice") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Percent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("vat_rates", "public"); + }); + modelBuilder.Entity("foodmarket.Domain.Organizations.Organization", b => { b.Property("Id") @@ -569,6 +1228,133 @@ protected override void BuildModel(ModelBuilder modelBuilder) 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.HasOne("foodmarket.Domain.Catalog.VatRate", "VatRate") + .WithMany() + .HasForeignKey("VatRateId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CountryOfOrigin"); + + b.Navigation("DefaultSupplier"); + + b.Navigation("ProductGroup"); + + b.Navigation("PurchaseCurrency"); + + b.Navigation("UnitOfMeasure"); + + b.Navigation("VatRate"); + }); + + 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("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => { b.Navigation("Authorizations"); @@ -580,6 +1366,20 @@ protected override void BuildModel(ModelBuilder modelBuilder) { 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"); + }); #pragma warning restore 612, 618 } }