diff --git a/src/food-market.api/Controllers/Catalog/CounterpartiesController.cs b/src/food-market.api/Controllers/Catalog/CounterpartiesController.cs index af4e9a6..7c9aa6c 100644 --- a/src/food-market.api/Controllers/Catalog/CounterpartiesController.cs +++ b/src/food-market.api/Controllers/Catalog/CounterpartiesController.cs @@ -44,8 +44,6 @@ public class CounterpartiesController : ControllerBase ("legalName", true) => q.OrderByDescending(c => c.LegalName).ThenBy(c => c.Name), ("phone", false) => q.OrderBy(c => c.Phone).ThenBy(c => c.Name), ("phone", true) => q.OrderByDescending(c => c.Phone).ThenBy(c => c.Name), - ("isActive", false) => q.OrderBy(c => c.IsActive).ThenBy(c => c.Name), - ("isActive", true) => q.OrderByDescending(c => c.IsActive).ThenBy(c => c.Name), ("name", true) => q.OrderByDescending(c => c.Name), _ => q.OrderBy(c => c.Name), }; @@ -55,7 +53,7 @@ public class CounterpartiesController : ControllerBase c.Id, c.Name, c.LegalName, c.Type, c.Bin, c.Iin, c.TaxNumber, c.CountryId, c.Country != null ? c.Country.Name : null, c.Address, c.Phone, c.Email, - c.BankName, c.BankAccount, c.Bik, c.ContactPerson, c.Notes, c.IsActive)) + c.BankName, c.BankAccount, c.Bik, c.ContactPerson, c.Notes)) .ToListAsync(ct); return new PagedResult { Items = items, Total = total, Page = req.Page, PageSize = req.Take }; } @@ -68,7 +66,7 @@ public async Task> Get(Guid id, CancellationToken c.Id, c.Name, c.LegalName, c.Type, c.Bin, c.Iin, c.TaxNumber, c.CountryId, c.Country?.Name, c.Address, c.Phone, c.Email, - c.BankName, c.BankAccount, c.Bik, c.ContactPerson, c.Notes, c.IsActive); + c.BankName, c.BankAccount, c.Bik, c.ContactPerson, c.Notes); } [HttpPost, Authorize(Roles = "Admin,Manager,Storekeeper")] @@ -117,7 +115,6 @@ private static Counterparty Apply(Counterparty e, CounterpartyInput i) e.Bik = i.Bik; e.ContactPerson = i.ContactPerson; e.Notes = i.Notes; - e.IsActive = i.IsActive; return e; } @@ -128,6 +125,6 @@ private async Task ProjectAsync(Guid id, CancellationToken ct) c.Id, c.Name, c.LegalName, c.Type, c.Bin, c.Iin, c.TaxNumber, c.CountryId, c.Country?.Name, c.Address, c.Phone, c.Email, - c.BankName, c.BankAccount, c.Bik, c.ContactPerson, c.Notes, c.IsActive); + c.BankName, c.BankAccount, c.Bik, c.ContactPerson, c.Notes); } } diff --git a/src/food-market.api/Controllers/Catalog/PriceTypesController.cs b/src/food-market.api/Controllers/Catalog/PriceTypesController.cs index 931b041..4874cdf 100644 --- a/src/food-market.api/Controllers/Catalog/PriceTypesController.cs +++ b/src/food-market.api/Controllers/Catalog/PriceTypesController.cs @@ -31,17 +31,13 @@ public async Task>> List([FromQuery] Page { ("name", false) => q.OrderBy(p => p.Name), ("name", true) => q.OrderByDescending(p => p.Name), - ("isDefault", false) => q.OrderBy(p => p.IsDefault).ThenBy(p => p.Name), - ("isDefault", true) => q.OrderByDescending(p => p.IsDefault).ThenBy(p => p.Name), - ("isRetail", false) => q.OrderBy(p => p.IsRetail).ThenBy(p => p.Name), - ("isRetail", true) => q.OrderByDescending(p => p.IsRetail).ThenBy(p => p.Name), - ("isActive", false) => q.OrderBy(p => p.IsActive).ThenBy(p => p.Name), - ("isActive", true) => q.OrderByDescending(p => p.IsActive).ThenBy(p => p.Name), - _ => q.OrderByDescending(p => p.IsDefault).ThenBy(p => p.SortOrder).ThenBy(p => p.Name), + ("isRequired", false) => q.OrderBy(p => p.IsRequired).ThenBy(p => p.Name), + ("isRequired", true) => q.OrderByDescending(p => p.IsRequired).ThenBy(p => p.Name), + _ => q.OrderByDescending(p => p.IsSystem).ThenBy(p => p.SortOrder).ThenBy(p => p.Name), }; var items = await q .Skip(req.Skip).Take(req.Take) - .Select(p => new PriceTypeDto(p.Id, p.Name, p.IsDefault, p.IsRetail, p.SortOrder, p.IsActive)) + .Select(p => new PriceTypeDto(p.Id, p.Name, p.IsRequired, p.IsSystem, p.IsDefault, p.IsRetail, p.SortOrder)) .ToListAsync(ct); return new PagedResult { Items = items, Total = total, Page = req.Page, PageSize = req.Take }; } @@ -50,7 +46,7 @@ public async Task>> List([FromQuery] Page public async Task> Get(Guid id, CancellationToken ct) { var p = await _db.PriceTypes.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, ct); - return p is null ? NotFound() : new PriceTypeDto(p.Id, p.Name, p.IsDefault, p.IsRetail, p.SortOrder, p.IsActive); + return p is null ? NotFound() : new PriceTypeDto(p.Id, p.Name, p.IsRequired, p.IsSystem, p.IsDefault, p.IsRetail, p.SortOrder); } [HttpPost, Authorize(Roles = "Admin,Manager")] @@ -62,13 +58,17 @@ public async Task> Create([FromBody] PriceTypeInput i } var e = new PriceType { - Name = input.Name, IsDefault = input.IsDefault, IsRetail = input.IsRetail, - SortOrder = input.SortOrder, IsActive = input.IsActive, + Name = input.Name, + IsRequired = input.IsRequired, + IsSystem = false, + IsDefault = input.IsDefault, + IsRetail = input.IsRetail, + SortOrder = input.SortOrder, }; _db.PriceTypes.Add(e); await _db.SaveChangesAsync(ct); return CreatedAtAction(nameof(Get), new { id = e.Id }, - new PriceTypeDto(e.Id, e.Name, e.IsDefault, e.IsRetail, e.SortOrder, e.IsActive)); + new PriceTypeDto(e.Id, e.Name, e.IsRequired, e.IsSystem, e.IsDefault, e.IsRetail, e.SortOrder)); } [HttpPut("{id:guid}"), Authorize(Roles = "Admin,Manager")] @@ -84,7 +84,8 @@ public async Task Update(Guid id, [FromBody] PriceTypeInput input e.IsDefault = input.IsDefault; e.IsRetail = input.IsRetail; e.SortOrder = input.SortOrder; - e.IsActive = input.IsActive; + // У системной записи IsRequired всегда true и не меняется. + e.IsRequired = e.IsSystem ? true : input.IsRequired; await _db.SaveChangesAsync(ct); return NoContent(); } @@ -94,6 +95,7 @@ public async Task Delete(Guid id, CancellationToken ct) { var e = await _db.PriceTypes.FirstOrDefaultAsync(x => x.Id == id, ct); if (e is null) return NotFound(); + if (e.IsSystem) return BadRequest(new { error = "Системная запись не может быть удалена." }); _db.PriceTypes.Remove(e); await _db.SaveChangesAsync(ct); return NoContent(); diff --git a/src/food-market.api/Controllers/Catalog/ProductGroupsController.cs b/src/food-market.api/Controllers/Catalog/ProductGroupsController.cs index 46c0c15..9fa83c6 100644 --- a/src/food-market.api/Controllers/Catalog/ProductGroupsController.cs +++ b/src/food-market.api/Controllers/Catalog/ProductGroupsController.cs @@ -40,13 +40,11 @@ public class ProductGroupsController : ControllerBase ("name", true) => q.OrderByDescending(g => g.Name), ("path", false) => q.OrderBy(g => g.Path), ("path", true) => q.OrderByDescending(g => g.Path), - ("isActive", false) => q.OrderBy(g => g.IsActive).ThenBy(g => g.Path), - ("isActive", true) => q.OrderByDescending(g => g.IsActive).ThenBy(g => g.Path), _ => q.OrderBy(g => g.Path).ThenBy(g => g.SortOrder).ThenBy(g => g.Name), }; var items = await q .Skip(req.Skip).Take(req.Take) - .Select(g => new ProductGroupDto(g.Id, g.Name, g.ParentId, g.Path, g.SortOrder, g.IsActive, g.MarkupPercent)) + .Select(g => new ProductGroupDto(g.Id, g.Name, g.ParentId, g.Path, g.SortOrder, g.MarkupPercent)) .ToListAsync(ct); return new PagedResult { Items = items, Total = total, Page = req.Page, PageSize = req.Take }; } @@ -55,7 +53,7 @@ public class ProductGroupsController : ControllerBase public async Task> Get(Guid id, CancellationToken ct) { var g = await _db.ProductGroups.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, ct); - return g is null ? NotFound() : new ProductGroupDto(g.Id, g.Name, g.ParentId, g.Path, g.SortOrder, g.IsActive, g.MarkupPercent); + return g is null ? NotFound() : new ProductGroupDto(g.Id, g.Name, g.ParentId, g.Path, g.SortOrder, g.MarkupPercent); } [HttpPost, Authorize(Roles = "Admin,Manager")] @@ -65,13 +63,13 @@ public async Task> Create([FromBody] ProductGroupI var e = new ProductGroup { Name = input.Name, ParentId = input.ParentId, Path = path, - SortOrder = input.SortOrder, IsActive = input.IsActive, + SortOrder = input.SortOrder, MarkupPercent = input.MarkupPercent, }; _db.ProductGroups.Add(e); await _db.SaveChangesAsync(ct); return CreatedAtAction(nameof(Get), new { id = e.Id }, - new ProductGroupDto(e.Id, e.Name, e.ParentId, e.Path, e.SortOrder, e.IsActive, e.MarkupPercent)); + new ProductGroupDto(e.Id, e.Name, e.ParentId, e.Path, e.SortOrder, e.MarkupPercent)); } [HttpPut("{id:guid}"), Authorize(Roles = "Admin,Manager")] @@ -85,7 +83,6 @@ public async Task Update(Guid id, [FromBody] ProductGroupInput in e.ParentId = input.ParentId; e.Path = await BuildPathAsync(input.ParentId, input.Name, ct); e.SortOrder = input.SortOrder; - e.IsActive = input.IsActive; e.MarkupPercent = input.MarkupPercent; await _db.SaveChangesAsync(ct); return NoContent(); diff --git a/src/food-market.api/Controllers/Catalog/ProductsController.cs b/src/food-market.api/Controllers/Catalog/ProductsController.cs index 1bd9b05..35b7438 100644 --- a/src/food-market.api/Controllers/Catalog/ProductsController.cs +++ b/src/food-market.api/Controllers/Catalog/ProductsController.cs @@ -92,7 +92,6 @@ private async Task ResolveDefaultVatAsync(CancellationToken ct) [FromQuery] bool? isService, [FromQuery] Packaging? packaging, [FromQuery] bool? isMarked, - [FromQuery] bool? isActive, [FromQuery] decimal? purchasePriceFrom, [FromQuery] decimal? purchasePriceTo, CancellationToken ct) @@ -116,7 +115,6 @@ private async Task ResolveDefaultVatAsync(CancellationToken ct) if (isService is not null) q = q.Where(p => p.IsService == isService); if (packaging is not null) q = q.Where(p => p.Packaging == packaging); if (isMarked is not null) q = q.Where(p => p.IsMarked == isMarked); - if (isActive is not null) q = q.Where(p => p.IsActive == isActive); if (purchasePriceFrom is not null) q = q.Where(p => p.ReferencePrice >= purchasePriceFrom); if (purchasePriceTo is not null) q = q.Where(p => p.ReferencePrice <= purchasePriceTo); @@ -144,8 +142,6 @@ private async Task ResolveDefaultVatAsync(CancellationToken ct) ("purchasePrice", true) => q.OrderByDescending(p => p.ReferencePrice).ThenBy(p => p.Name), ("vat", false) => q.OrderBy(p => p.Vat).ThenBy(p => p.Name), ("vat", true) => q.OrderByDescending(p => p.Vat).ThenBy(p => p.Name), - ("isActive", false) => q.OrderBy(p => p.IsActive).ThenBy(p => p.Name), - ("isActive", true) => q.OrderByDescending(p => p.IsActive).ThenBy(p => p.Name), ("name", true) => q.OrderByDescending(p => p.Name), _ => q.OrderBy(p => p.Name), }; @@ -294,14 +290,14 @@ public async Task RecalcRetail(Guid id, CancellationToken ct) : Math.Ceiling(raw); var defaultType = await _db.PriceTypes - .Where(pt => pt.IsActive) - .OrderByDescending(pt => pt.IsDefault) + .OrderByDescending(pt => pt.IsSystem) + .ThenByDescending(pt => pt.IsDefault) .ThenByDescending(pt => pt.IsRetail) .ThenBy(pt => pt.SortOrder) .ThenBy(pt => pt.Name) .FirstOrDefaultAsync(ct); if (defaultType is null) - return BadRequest(new { error = "Нет ни одного активного типа цен. Создайте его в настройках." }); + return BadRequest(new { error = "Нет ни одного типа цен. Создайте его в настройках." }); var fallbackCurrency = await _db.Organizations.Select(o => o.DefaultCurrencyId).FirstOrDefaultAsync(ct) ?? await _db.Currencies.OrderBy(c => c.Code).Select(c => (Guid?)c.Id).FirstOrDefaultAsync(ct); @@ -384,7 +380,7 @@ public async Task>> BarcodeDuplicat p.ReferencePrice, p.ReferencePriceUpdatedAt, p.PurchaseCurrencyId, p.PurchaseCurrency != null ? p.PurchaseCurrency.Code : null, p.Cost, p.LastSupplyAt, - p.ImageUrl, p.IsActive, + p.ImageUrl, p.ShelfLifeDays, p.Prices.Select(pr => new ProductPriceDto(pr.Id, pr.PriceTypeId, pr.PriceType!.Name, pr.Amount, pr.CurrencyId, pr.Currency!.Code)).ToList(), p.Barcodes.Select(b => new ProductBarcodeDto(b.Id, b.Code, b.Type, b.IsPrimary)).ToList()); @@ -414,6 +410,6 @@ private static void Apply(Product e, ProductInput i) } e.PurchaseCurrencyId = i.PurchaseCurrencyId; e.ImageUrl = i.ImageUrl; - e.IsActive = i.IsActive; + e.ShelfLifeDays = i.ShelfLifeDays; } } diff --git a/src/food-market.api/Controllers/Catalog/UnitsOfMeasureController.cs b/src/food-market.api/Controllers/Catalog/UnitsOfMeasureController.cs index f108dc9..58d3dfb 100644 --- a/src/food-market.api/Controllers/Catalog/UnitsOfMeasureController.cs +++ b/src/food-market.api/Controllers/Catalog/UnitsOfMeasureController.cs @@ -31,14 +31,12 @@ public async Task>> List([FromQuery] { ("code", false) => q.OrderBy(u => u.Code), ("code", true) => q.OrderByDescending(u => u.Code), - ("isActive", false) => q.OrderBy(u => u.IsActive).ThenBy(u => u.Name), - ("isActive", true) => q.OrderByDescending(u => u.IsActive).ThenBy(u => u.Name), ("name", true) => q.OrderByDescending(u => u.Name), _ => q.OrderBy(u => u.Name), }; var items = await q .Skip(req.Skip).Take(req.Take) - .Select(u => new UnitOfMeasureDto(u.Id, u.Code, u.Name, u.Description, u.IsActive)) + .Select(u => new UnitOfMeasureDto(u.Id, u.Code, u.Name, u.Description)) .ToListAsync(ct); return new PagedResult { Items = items, Total = total, Page = req.Page, PageSize = req.Take }; } @@ -47,7 +45,7 @@ public async Task>> List([FromQuery] public async Task> Get(Guid id, CancellationToken ct) { var u = await _db.UnitsOfMeasure.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id, ct); - return u is null ? NotFound() : new UnitOfMeasureDto(u.Id, u.Code, u.Name, u.Description, u.IsActive); + return u is null ? NotFound() : new UnitOfMeasureDto(u.Id, u.Code, u.Name, u.Description); } [HttpPost, Authorize(Roles = "Admin,Manager")] @@ -58,12 +56,11 @@ public async Task> Create([FromBody] UnitOfMeasur Code = input.Code, Name = input.Name, Description = input.Description, - IsActive = input.IsActive, }; _db.UnitsOfMeasure.Add(e); await _db.SaveChangesAsync(ct); return CreatedAtAction(nameof(Get), new { id = e.Id }, - new UnitOfMeasureDto(e.Id, e.Code, e.Name, e.Description, e.IsActive)); + new UnitOfMeasureDto(e.Id, e.Code, e.Name, e.Description)); } [HttpPut("{id:guid}"), Authorize(Roles = "Admin,Manager")] @@ -75,7 +72,6 @@ public async Task Update(Guid id, [FromBody] UnitOfMeasureInput i e.Code = input.Code; e.Name = input.Name; e.Description = input.Description; - e.IsActive = input.IsActive; await _db.SaveChangesAsync(ct); return NoContent(); } diff --git a/src/food-market.api/Controllers/Organizations/OrganizationSettingsController.cs b/src/food-market.api/Controllers/Organizations/OrganizationSettingsController.cs index 8222d47..de43a07 100644 --- a/src/food-market.api/Controllers/Organizations/OrganizationSettingsController.cs +++ b/src/food-market.api/Controllers/Organizations/OrganizationSettingsController.cs @@ -35,7 +35,6 @@ public record OrgSettingsDto( bool ShowMarkedOnProduct, bool ShowMinMaxStock, bool AllowFractionalPrices, - bool MultiplePriceTypesEnabled, bool ShowReferencePriceOnProduct); // DefaultCurrencyId не принимается — он read-only, выводится из страны (Country.DefaultCurrencyId). @@ -48,7 +47,6 @@ public record OrgSettingsInput( bool ShowMarkedOnProduct, bool ShowMinMaxStock, bool AllowFractionalPrices, - bool MultiplePriceTypesEnabled, bool ShowReferencePriceOnProduct); [HttpGet("settings")] @@ -85,7 +83,6 @@ public async Task> Update([FromBody] OrgSettingsInp o.ShowMarkedOnProduct = input.ShowMarkedOnProduct; o.ShowMinMaxStock = input.ShowMinMaxStock; o.AllowFractionalPrices = input.AllowFractionalPrices; - o.MultiplePriceTypesEnabled = input.MultiplePriceTypesEnabled; o.ShowReferencePriceOnProduct = input.ShowReferencePriceOnProduct; await _db.SaveChangesAsync(ct); @@ -115,6 +112,5 @@ private async Task ReadVatRateAsync(string countryCode, CancellationTok o.ShowMarkedOnProduct, o.ShowMinMaxStock, o.AllowFractionalPrices, - o.MultiplePriceTypesEnabled, o.ShowReferencePriceOnProduct); } diff --git a/src/food-market.api/Controllers/Purchases/SuppliesController.cs b/src/food-market.api/Controllers/Purchases/SuppliesController.cs index bdad53a..875f985 100644 --- a/src/food-market.api/Controllers/Purchases/SuppliesController.cs +++ b/src/food-market.api/Controllers/Purchases/SuppliesController.cs @@ -301,8 +301,8 @@ public async Task Post(Guid id, CancellationToken ct) private void SetDefaultRetail(foodmarket.Domain.Catalog.Product p, decimal value, Guid fallbackCurrencyId) { var defaultType = _db.PriceTypes - .Where(pt => pt.IsActive) - .OrderByDescending(pt => pt.IsDefault) + .OrderByDescending(pt => pt.IsSystem) + .ThenByDescending(pt => pt.IsDefault) .ThenByDescending(pt => pt.IsRetail) .ThenBy(pt => pt.SortOrder) .ThenBy(pt => pt.Name) diff --git a/src/food-market.api/Seed/DemoCatalogSeeder.cs b/src/food-market.api/Seed/DemoCatalogSeeder.cs index d1361d4..21f61e5 100644 --- a/src/food-market.api/Seed/DemoCatalogSeeder.cs +++ b/src/food-market.api/Seed/DemoCatalogSeeder.cs @@ -66,7 +66,6 @@ Guid AddGroup(string name, Guid? parentId) db.ProductGroups.Add(new ProductGroup { Id = id, OrganizationId = orgId, Name = name, ParentId = parentId, - Path = path, SortOrder = groups.Count, IsActive = true, }); groups[path] = id; return id; @@ -92,7 +91,6 @@ Guid AddGroup(string name, Guid? parentId) Bin = "100140005678", CountryId = kz?.Id, Address = "Алматы, ул. Абая 15", Phone = "+7 (727) 100-00-01", Email = "order@prodtrade.kz", BankName = "Kaspi Bank", BankAccount = "KZ000000000000000001", Bik = "CASPKZKA", - IsActive = true, }; var supplier2 = new Counterparty { @@ -100,7 +98,6 @@ Guid AddGroup(string name, Guid? parentId) Type = CounterpartyType.Individual, Iin = "850101300000", CountryId = kz?.Id, Phone = "+7 (777) 100-00-02", ContactPerson = "Иванов Алексей", - IsActive = true, }; db.Counterparties.AddRange(supplier1, supplier2); @@ -166,7 +163,6 @@ Guid AddGroup(string name, Guid? parentId) ProductGroupId = d.Group, CountryOfOriginId = d.Country, Packaging = d.IsWeighed ? Packaging.Weight : Packaging.Piece, - IsActive = true, ReferencePrice = Math.Round(d.RetailPrice * 0.72m, 2), PurchaseCurrencyId = kzt.Id, Prices = diff --git a/src/food-market.application/Catalog/CatalogDtos.cs b/src/food-market.application/Catalog/CatalogDtos.cs index d411a83..3b672ef 100644 --- a/src/food-market.application/Catalog/CatalogDtos.cs +++ b/src/food-market.application/Catalog/CatalogDtos.cs @@ -12,10 +12,11 @@ public record CountryDto( public record CurrencyDto(Guid Id, string Code, string Name, string Symbol); public record UnitOfMeasureDto( - Guid Id, string Code, string Name, string? Description, bool IsActive); + Guid Id, string Code, string Name, string? Description); public record PriceTypeDto( - Guid Id, string Name, bool IsDefault, bool IsRetail, int SortOrder, bool IsActive); + Guid Id, string Name, bool IsRequired, bool IsSystem, + bool IsDefault, bool IsRetail, int SortOrder); public record StoreDto( Guid Id, string Name, string? Code, string? Address, string? Phone, @@ -26,14 +27,14 @@ public record RetailPointDto( string? Address, string? Phone, string? FiscalSerial, string? FiscalRegNumber, bool IsActive); public record ProductGroupDto( - Guid Id, string Name, Guid? ParentId, string Path, int SortOrder, bool IsActive, + Guid Id, string Name, Guid? ParentId, string Path, int SortOrder, decimal? MarkupPercent); public record CounterpartyDto( Guid Id, string Name, string? LegalName, CounterpartyType Type, string? Bin, string? Iin, string? TaxNumber, Guid? CountryId, string? CountryName, string? Address, string? Phone, string? Email, - string? BankName, string? BankAccount, string? Bik, string? ContactPerson, string? Notes, bool IsActive); + string? BankName, string? BankAccount, string? Bik, string? ContactPerson, string? Notes); public record ProductBarcodeDto(Guid Id, string Code, BarcodeType Type, bool IsPrimary); @@ -51,7 +52,7 @@ public record ProductDto( decimal? ReferencePrice, DateTime? ReferencePriceUpdatedAt, Guid? PurchaseCurrencyId, string? PurchaseCurrencyCode, decimal Cost, DateTime? LastSupplyAt, - string? ImageUrl, bool IsActive, + string? ImageUrl, int? ShelfLifeDays, IReadOnlyList Prices, IReadOnlyList Barcodes); @@ -60,8 +61,10 @@ public record CountryInput( string Code, string Name, Guid? DefaultCurrencyId = null, decimal VatRate = 0m); public record CurrencyInput(string Code, string Name, string Symbol); -public record UnitOfMeasureInput(string Code, string Name, string? Description = null, bool IsActive = true); -public record PriceTypeInput(string Name, bool IsDefault = false, bool IsRetail = false, int SortOrder = 0, bool IsActive = true); +public record UnitOfMeasureInput(string Code, string Name, string? Description = null); +public record PriceTypeInput( + string Name, bool IsRequired = false, + bool IsDefault = false, bool IsRetail = false, int SortOrder = 0); public record StoreInput( string Name, string? Code, string? Address = null, string? Phone = null, string? ManagerName = null, @@ -71,13 +74,13 @@ public record RetailPointInput( string? Address = null, string? Phone = null, string? FiscalSerial = null, string? FiscalRegNumber = null, bool IsActive = true); public record ProductGroupInput( - string Name, Guid? ParentId, int SortOrder = 0, bool IsActive = true, + string Name, Guid? ParentId, int SortOrder = 0, [Range(0, 1000)] decimal? MarkupPercent = null); public record CounterpartyInput( string Name, string? LegalName, CounterpartyType Type, string? Bin, string? Iin, string? TaxNumber, Guid? CountryId, string? Address, string? Phone, string? Email, - string? BankName, string? BankAccount, string? Bik, string? ContactPerson, string? Notes, bool IsActive = true); + string? BankName, string? BankAccount, string? Bik, string? ContactPerson, string? Notes); public record ProductBarcodeInput(string Code, BarcodeType Type = BarcodeType.Ean13, bool IsPrimary = false); public record ProductPriceInput(Guid PriceTypeId, [Range(0, 1e10)] decimal Amount, Guid CurrencyId); public record ProductInput( @@ -87,6 +90,6 @@ public record ProductInput( bool IsService = false, Packaging Packaging = Packaging.Piece, bool IsMarked = false, [Range(0, 1e10)] decimal? MinStock = null, [Range(0, 1e10)] decimal? MaxStock = null, [Range(0, 1e10)] decimal? ReferencePrice = null, Guid? PurchaseCurrencyId = null, - string? ImageUrl = null, bool IsActive = true, + string? ImageUrl = null, [Range(0, 100000)] int? ShelfLifeDays = null, IReadOnlyList? Prices = null, IReadOnlyList? Barcodes = null); diff --git a/src/food-market.domain/Catalog/Counterparty.cs b/src/food-market.domain/Catalog/Counterparty.cs index 0549580..47b95e8 100644 --- a/src/food-market.domain/Catalog/Counterparty.cs +++ b/src/food-market.domain/Catalog/Counterparty.cs @@ -20,5 +20,4 @@ public class Counterparty : TenantEntity 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/PriceType.cs b/src/food-market.domain/Catalog/PriceType.cs index e390120..a1d23ef 100644 --- a/src/food-market.domain/Catalog/PriceType.cs +++ b/src/food-market.domain/Catalog/PriceType.cs @@ -6,8 +6,12 @@ namespace foodmarket.Domain.Catalog; public class PriceType : TenantEntity { public string Name { get; set; } = null!; + /// true — цена должна быть заполнена у каждого товара (валидация на UI и сервере). + public bool IsRequired { get; set; } + /// true — системная запись «Розничная цена», не удаляется и + /// IsRequired всегда true. Имя можно переименовать. Сидируется при первом старте. + public bool IsSystem { get; set; } 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 index 09f0325..1e25a19 100644 --- a/src/food-market.domain/Catalog/Product.cs +++ b/src/food-market.domain/Catalog/Product.cs @@ -54,7 +54,8 @@ public class Product : TenantEntity public DateTime? LastSupplyAt { get; set; } public string? ImageUrl { get; set; } // основное изображение (остальные в ProductImage) - public bool IsActive { get; set; } = true; + /// Срок годности в днях (для отчётов и фильтрации). Не обязательное. + public int? ShelfLifeDays { get; set; } public ICollection Prices { get; set; } = []; public ICollection Barcodes { get; set; } = []; diff --git a/src/food-market.domain/Catalog/ProductGroup.cs b/src/food-market.domain/Catalog/ProductGroup.cs index 4071373..ad9c8f3 100644 --- a/src/food-market.domain/Catalog/ProductGroup.cs +++ b/src/food-market.domain/Catalog/ProductGroup.cs @@ -11,7 +11,6 @@ public class ProductGroup : TenantEntity public ICollection Children { get; set; } = []; public string Path { get; set; } = ""; // денормализованный путь "Электроника/Телефоны/Смартфоны" для фильтрации public int SortOrder { get; set; } - public bool IsActive { get; set; } = true; /// Процент наценки на себестоимость для автоматического расчёта /// розничной цены при проведении приёмки. NULL = автонаценка отключена. diff --git a/src/food-market.domain/Catalog/UnitOfMeasure.cs b/src/food-market.domain/Catalog/UnitOfMeasure.cs index 06d42d4..115430c 100644 --- a/src/food-market.domain/Catalog/UnitOfMeasure.cs +++ b/src/food-market.domain/Catalog/UnitOfMeasure.cs @@ -8,5 +8,4 @@ public class UnitOfMeasure : TenantEntity public string Code { get; set; } = null!; // ОКЕИ код: "796" (шт), "166" (кг), "112" (л) public string Name { get; set; } = null!; // "штука", "килограмм", "литр" public string? Description { get; set; } - public bool IsActive { get; set; } = true; } diff --git a/src/food-market.domain/Organizations/Organization.cs b/src/food-market.domain/Organizations/Organization.cs index 16e0f3a..2718b4b 100644 --- a/src/food-market.domain/Organizations/Organization.cs +++ b/src/food-market.domain/Organizations/Organization.cs @@ -54,11 +54,6 @@ public class Organization : Entity /// дробное через API. public bool AllowFractionalPrices { get; set; } - /// Если true — в карточке товара рендерится список цен по всем - /// PriceType, есть страница «Настройки → Типы цен». Если false (default) - /// — одно поле «Розничная цена», работающее с дефолтным PriceType. - public bool MultiplePriceTypesEnabled { get; set; } - /// Показывать ли в карточке товара поле «Эталонная цена». /// Default: true. public bool ShowReferencePriceOnProduct { get; set; } = true; diff --git a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs index f6b5730..4d27b04 100644 --- a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs +++ b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladImportService.cs @@ -131,7 +131,6 @@ await foreach (var c in _client.StreamCounterpartiesAsync(token, ct)) entity.Email = Trim(c.Email, 255); entity.Address = Trim(c.ActualAddress ?? c.LegalAddress, 500); entity.Notes = Trim(c.Description, 1000); - entity.IsActive = !c.Archived; } public async Task ImportProductsAsync( @@ -168,7 +167,6 @@ await foreach (var c in _client.StreamCounterpartiesAsync(token, ct)) OrganizationId = orgId, Name = "Продукты питания", Path = "Продукты питания", - IsActive = true, }; _db.ProductGroups.Add(defaultGroup); await _db.SaveChangesAsync(ct); @@ -195,7 +193,6 @@ await foreach (var c in _client.StreamCounterpartiesAsync(token, ct)) OrganizationId = orgId, Name = f.Name, Path = string.IsNullOrEmpty(f.PathName) ? f.Name : $"{f.PathName}/{f.Name}", - IsActive = !f.Archived, }; _db.ProductGroups.Add(g); localGroupByMsId[f.Id] = g.Id; @@ -262,7 +259,6 @@ await foreach (var p in _client.StreamProductsAsync(token, ct)) product.CountryOfOriginId = countryId ?? product.CountryOfOriginId; product.Packaging = p.Weighed ? Packaging.Weight : Packaging.Piece; product.IsMarked = !string.IsNullOrEmpty(p.TrackingType) && p.TrackingType != "NOT_TRACKED"; - product.IsActive = !p.Archived; product.ReferencePrice = p.BuyPrice is null ? product.ReferencePrice : p.BuyPrice.Value / 100m; updated++; if (progress is not null) progress.Updated = updated; @@ -282,7 +278,6 @@ await foreach (var p in _client.StreamProductsAsync(token, ct)) CountryOfOriginId = countryId, Packaging = p.Weighed ? Packaging.Weight : Packaging.Piece, IsMarked = !string.IsNullOrEmpty(p.TrackingType) && p.TrackingType != "NOT_TRACKED", - IsActive = !p.Archived, ReferencePrice = p.BuyPrice is null ? null : p.BuyPrice.Value / 100m, PurchaseCurrencyId = kzt.Id, }; diff --git a/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs b/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs index bae983a..9ad7927 100644 --- a/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs +++ b/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs @@ -133,7 +133,6 @@ private static void ConfigureProduct(EntityTypeBuilder b) 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) diff --git a/src/food-market.infrastructure/Persistence/Migrations/20260425170000_Phase3b_PricingCleanup.Designer.cs b/src/food-market.infrastructure/Persistence/Migrations/20260425170000_Phase3b_PricingCleanup.Designer.cs new file mode 100644 index 0000000..fd75537 --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Migrations/20260425170000_Phase3b_PricingCleanup.Designer.cs @@ -0,0 +1,1904 @@ +// +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("20260425170000_Phase3b_PricingCleanup")] + partial class Phase3b_PricingCleanup + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("public") + .HasAnnotation("ProductVersion", "8.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", "public"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ClientId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ClientSecret") + .HasColumnType("text"); + + b.Property("ClientType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ConsentType") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("JsonWebKeySet") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("PostLogoutRedirectUris") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedirectUris") + .HasColumnType("text"); + + b.Property("Requirements") + .HasColumnType("text"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("OpenIddictApplications", "public"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Scopes") + .HasColumnType("text"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictAuthorizations", "public"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Descriptions") + .HasColumnType("text"); + + b.Property("DisplayName") + .HasColumnType("text"); + + b.Property("DisplayNames") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("Resources") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("OpenIddictScopes", "public"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("text"); + + b.Property("ApplicationId") + .HasColumnType("text"); + + b.Property("AuthorizationId") + .HasColumnType("text"); + + b.Property("ConcurrencyToken") + .IsConcurrencyToken() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Payload") + .HasColumnType("text"); + + b.Property("Properties") + .HasColumnType("text"); + + b.Property("RedemptionDate") + .HasColumnType("timestamp with time zone"); + + b.Property("ReferenceId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Status") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Subject") + .HasMaxLength(400) + .HasColumnType("character varying(400)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.HasIndex("AuthorizationId"); + + b.HasIndex("ReferenceId") + .IsUnique(); + + b.HasIndex("ApplicationId", "Status", "Subject", "Type"); + + b.ToTable("OpenIddictTokens", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Counterparty", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("BankAccount") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BankName") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Bik") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Bin") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("ContactPerson") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("CountryId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Iin") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("LegalName") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Notes") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Phone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TaxNumber") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CountryId"); + + b.HasIndex("OrganizationId", "Bin"); + + b.HasIndex("OrganizationId", "Name"); + + b.ToTable("counterparties", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultCurrencyId") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VatRate") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("DefaultCurrencyId"); + + b.ToTable("countries", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("character varying(3)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MinorUnit") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Symbol") + .IsRequired() + .HasMaxLength(5) + .HasColumnType("character varying(5)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.ToTable("currencies", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.PriceType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsDefault") + .HasColumnType("boolean"); + + b.Property("IsRequired") + .HasColumnType("boolean"); + + b.Property("IsRetail") + .HasColumnType("boolean"); + + b.Property("IsSystem") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name") + .IsUnique(); + + b.ToTable("price_types", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Article") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("CountryOfOriginId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DefaultSupplierId") + .HasColumnType("uuid"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("ImageUrl") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("IsMarked") + .HasColumnType("boolean"); + + b.Property("IsService") + .HasColumnType("boolean"); + + b.Property("Packaging") + .HasColumnType("boolean"); + + b.Property("MaxStock") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("MinStock") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductGroupId") + .HasColumnType("uuid"); + + b.Property("Cost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("LastSupplyAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PurchaseCurrencyId") + .HasColumnType("uuid"); + + b.Property("ReferencePrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReferencePriceUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ShelfLifeDays") + .HasColumnType("integer"); + + b.Property("UnitOfMeasureId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Vat") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.Property("VatEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.HasKey("Id"); + + b.HasIndex("CountryOfOriginId"); + + b.HasIndex("DefaultSupplierId"); + + b.HasIndex("ProductGroupId"); + + b.HasIndex("PurchaseCurrencyId"); + + b.HasIndex("UnitOfMeasureId"); + + b.HasIndex("OrganizationId", "Article"); + + b.HasIndex("OrganizationId", "Name"); + + b.HasIndex("OrganizationId", "ProductGroupId"); + + b.ToTable("products", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductBarcode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsPrimary") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("OrganizationId", "Code") + .IsUnique(); + + b.ToTable("product_barcodes", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MarkupPercent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ParentId") + .HasColumnType("uuid"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.HasIndex("OrganizationId", "ParentId"); + + b.HasIndex("OrganizationId", "Path"); + + b.ToTable("product_groups", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsMain") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.ToTable("product_images", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Amount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PriceTypeId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.HasIndex("PriceTypeId"); + + b.HasIndex("ProductId", "PriceTypeId") + .IsUnique(); + + b.ToTable("product_prices", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.RetailPoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("Code") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FiscalRegNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("FiscalSerial") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Phone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("StoreId"); + + b.HasIndex("OrganizationId", "Name"); + + b.ToTable("retail_points", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Store", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("Code") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsMain") + .HasColumnType("boolean"); + + b.Property("ManagerName") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Phone") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Name"); + + b.ToTable("stores", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.UnitOfMeasure", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId", "Code") + .IsUnique(); + + b.ToTable("units_of_measure", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Inventory.Stock", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("ReservedQuantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("StoreId"); + + b.HasIndex("OrganizationId", "StoreId"); + + b.HasIndex("OrganizationId", "ProductId", "StoreId") + .IsUnique(); + + b.ToTable("stocks", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Inventory.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedByUserId") + .HasColumnType("uuid"); + + b.Property("DocumentId") + .HasColumnType("uuid"); + + b.Property("DocumentNumber") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("DocumentType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UnitCost") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("StoreId"); + + b.HasIndex("DocumentType", "DocumentId"); + + b.HasIndex("OrganizationId", "ProductId", "OccurredAt"); + + b.HasIndex("OrganizationId", "StoreId", "OccurredAt"); + + b.ToTable("stock_movements", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Organizations.Organization", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Address") + .HasColumnType("text"); + + b.Property("AllowFractionalPrices") + .HasColumnType("boolean"); + + b.Property("Bin") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("CountryCode") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("DefaultCurrencyId") + .HasColumnType("uuid"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("MoySkladToken") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("MultiCurrencyEnabled") + .HasColumnType("boolean"); + + b.Property("ShowMarkedOnProduct") + .HasColumnType("boolean"); + + b.Property("ShowReferencePriceOnProduct") + .HasColumnType("boolean"); + + b.Property("ShowMinMaxStock") + .HasColumnType("boolean"); + + b.Property("ShowServiceOnProduct") + .HasColumnType("boolean"); + + b.Property("ShowVatEnabledOnProduct") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Phone") + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DefaultCurrencyId"); + + b.HasIndex("Name"); + + b.ToTable("organizations", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.Supply", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PostedByUserId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("SupplierId") + .HasColumnType("uuid"); + + b.Property("SupplierInvoiceDate") + .HasColumnType("timestamp with time zone"); + + b.Property("SupplierInvoiceNumber") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Total") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.HasIndex("StoreId"); + + b.HasIndex("SupplierId"); + + b.HasIndex("OrganizationId", "Date"); + + b.HasIndex("OrganizationId", "Number") + .IsUnique(); + + b.HasIndex("OrganizationId", "Status"); + + b.HasIndex("OrganizationId", "SupplierId"); + + b.ToTable("supplies", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.SupplyLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RetailPriceManuallyOverridden") + .HasColumnType("boolean"); + + b.Property("RetailPriceOverride") + .HasPrecision(18, 2) + .HasColumnType("numeric(18,2)"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("SupplyId") + .HasColumnType("uuid"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("SupplyId"); + + b.HasIndex("OrganizationId", "ProductId"); + + b.ToTable("supply_lines", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSale", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CashierUserId") + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CurrencyId") + .HasColumnType("uuid"); + + b.Property("CustomerId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp with time zone"); + + b.Property("DiscountTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Notes") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("Number") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaidCard") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("PaidCash") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Payment") + .HasColumnType("integer"); + + b.Property("PostedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("PostedByUserId") + .HasColumnType("uuid"); + + b.Property("RetailPointId") + .HasColumnType("uuid"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("StoreId") + .HasColumnType("uuid"); + + b.Property("Subtotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("Total") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CurrencyId"); + + b.HasIndex("CustomerId"); + + b.HasIndex("RetailPointId"); + + b.HasIndex("StoreId"); + + b.HasIndex("OrganizationId", "CashierUserId"); + + b.HasIndex("OrganizationId", "Date"); + + b.HasIndex("OrganizationId", "Number") + .IsUnique(); + + b.HasIndex("OrganizationId", "Status"); + + b.ToTable("retail_sales", "public"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSaleLine", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Discount") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("LineTotal") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProductId") + .HasColumnType("uuid"); + + b.Property("Quantity") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("RetailSaleId") + .HasColumnType("uuid"); + + b.Property("SortOrder") + .HasColumnType("integer"); + + b.Property("UnitPrice") + .HasPrecision(18, 4) + .HasColumnType("numeric(18,4)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("VatPercent") + .HasPrecision(5, 2) + .HasColumnType("numeric(5,2)"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("RetailSaleId"); + + b.HasIndex("OrganizationId", "ProductId"); + + b.ToTable("retail_sale_lines", "public"); + }); + + modelBuilder.Entity("foodmarket.Infrastructure.Identity.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("roles", "public"); + }); + + modelBuilder.Entity("foodmarket.Infrastructure.Identity.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("LastLoginAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("users", "public"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("foodmarket.Infrastructure.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("foodmarket.Infrastructure.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Authorizations") + .HasForeignKey("ApplicationId"); + + b.Navigation("Application"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreToken", b => + { + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", "Application") + .WithMany("Tokens") + .HasForeignKey("ApplicationId"); + + b.HasOne("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", "Authorization") + .WithMany("Tokens") + .HasForeignKey("AuthorizationId"); + + b.Navigation("Application"); + + b.Navigation("Authorization"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Counterparty", b => + { + b.HasOne("foodmarket.Domain.Catalog.Country", "Country") + .WithMany() + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Country"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Product", b => + { + b.HasOne("foodmarket.Domain.Catalog.Country", "CountryOfOrigin") + .WithMany() + .HasForeignKey("CountryOfOriginId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.Counterparty", "DefaultSupplier") + .WithMany() + .HasForeignKey("DefaultSupplierId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.ProductGroup", "ProductGroup") + .WithMany() + .HasForeignKey("ProductGroupId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.Currency", "PurchaseCurrency") + .WithMany() + .HasForeignKey("PurchaseCurrencyId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.UnitOfMeasure", "UnitOfMeasure") + .WithMany() + .HasForeignKey("UnitOfMeasureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CountryOfOrigin"); + + b.Navigation("DefaultSupplier"); + + b.Navigation("ProductGroup"); + + b.Navigation("PurchaseCurrency"); + + b.Navigation("UnitOfMeasure"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductBarcode", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany("Barcodes") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductGroup", b => + { + b.HasOne("foodmarket.Domain.Catalog.ProductGroup", "Parent") + .WithMany("Children") + .HasForeignKey("ParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductImage", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany("Images") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductPrice", b => + { + b.HasOne("foodmarket.Domain.Catalog.Currency", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.PriceType", "PriceType") + .WithMany() + .HasForeignKey("PriceTypeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany("Prices") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Currency"); + + b.Navigation("PriceType"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.RetailPoint", b => + { + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("foodmarket.Domain.Inventory.Stock", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("foodmarket.Domain.Inventory.StockMovement", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.Supply", b => + { + b.HasOne("foodmarket.Domain.Catalog.Currency", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Counterparty", "Supplier") + .WithMany() + .HasForeignKey("SupplierId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Currency"); + + b.Navigation("Store"); + + b.Navigation("Supplier"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.SupplyLine", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Purchases.Supply", "Supply") + .WithMany("Lines") + .HasForeignKey("SupplyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Supply"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSale", b => + { + b.HasOne("foodmarket.Domain.Catalog.Currency", "Currency") + .WithMany() + .HasForeignKey("CurrencyId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Catalog.Counterparty", "Customer") + .WithMany() + .HasForeignKey("CustomerId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.RetailPoint", "RetailPoint") + .WithMany() + .HasForeignKey("RetailPointId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("foodmarket.Domain.Catalog.Store", "Store") + .WithMany() + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Currency"); + + b.Navigation("Customer"); + + b.Navigation("RetailPoint"); + + b.Navigation("Store"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSaleLine", b => + { + b.HasOne("foodmarket.Domain.Catalog.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("foodmarket.Domain.Sales.RetailSale", "RetailSale") + .WithMany("Lines") + .HasForeignKey("RetailSaleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("RetailSale"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreApplication", b => + { + b.Navigation("Authorizations"); + + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("OpenIddict.EntityFrameworkCore.Models.OpenIddictEntityFrameworkCoreAuthorization", b => + { + b.Navigation("Tokens"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.Product", b => + { + b.Navigation("Barcodes"); + + b.Navigation("Images"); + + b.Navigation("Prices"); + }); + + modelBuilder.Entity("foodmarket.Domain.Catalog.ProductGroup", b => + { + b.Navigation("Children"); + }); + + modelBuilder.Entity("foodmarket.Domain.Purchases.Supply", b => + { + b.Navigation("Lines"); + }); + + modelBuilder.Entity("foodmarket.Domain.Sales.RetailSale", b => + { + b.Navigation("Lines"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/food-market.infrastructure/Persistence/Migrations/20260425170000_Phase3b_PricingCleanup.cs b/src/food-market.infrastructure/Persistence/Migrations/20260425170000_Phase3b_PricingCleanup.cs new file mode 100644 index 0000000..c5b3096 --- /dev/null +++ b/src/food-market.infrastructure/Persistence/Migrations/20260425170000_Phase3b_PricingCleanup.cs @@ -0,0 +1,87 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace foodmarket.Infrastructure.Persistence.Migrations +{ + /// Phase3b — рационализация модели: + /// - DROP IsActive у products / product_groups / units_of_measure / counterparties / price_types. + /// - DROP organizations.MultiplePriceTypesEnabled (раздел «Типы цен» теперь всегда виден). + /// - DROP price_types.IsActive (системная запись неудаляема, остальные удаляются физически). + /// - ADD price_types.IsRequired bool default false. + /// - ADD price_types.IsSystem bool default false. + /// - ADD products.ShelfLifeDays integer NULL. + /// - Гарантирует существование одной системной записи «Розничная цена» + /// (IsSystem=true, IsRequired=true) в каждой организации. + public partial class Phase3b_PricingCleanup : Migration + { + protected override void Up(MigrationBuilder b) + { + // На products был индекс (OrganizationId, IsActive) — удалим до dropколонки. + b.Sql("DROP INDEX IF EXISTS public.\"IX_products_OrganizationId_IsActive\";"); + + b.DropColumn(name: "IsActive", schema: "public", table: "products"); + b.DropColumn(name: "IsActive", schema: "public", table: "product_groups"); + b.DropColumn(name: "IsActive", schema: "public", table: "units_of_measure"); + b.DropColumn(name: "IsActive", schema: "public", table: "counterparties"); + b.DropColumn(name: "IsActive", schema: "public", table: "price_types"); + b.DropColumn(name: "MultiplePriceTypesEnabled", schema: "public", table: "organizations"); + + b.AddColumn( + name: "IsRequired", schema: "public", table: "price_types", + type: "boolean", nullable: false, defaultValue: false); + b.AddColumn( + name: "IsSystem", schema: "public", table: "price_types", + type: "boolean", nullable: false, defaultValue: false); + + b.AddColumn( + name: "ShelfLifeDays", schema: "public", table: "products", + type: "integer", nullable: true); + + // Гарантируем системную «Розничная цена» в каждой организации. + b.Sql(""" + INSERT INTO public.price_types + ("Id", "OrganizationId", "Name", "IsRequired", "IsSystem", "IsDefault", "IsRetail", "SortOrder", "CreatedAt") + SELECT gen_random_uuid(), o."Id", 'Розничная цена', true, true, true, true, 0, now() AT TIME ZONE 'UTC' + FROM public.organizations o + WHERE NOT EXISTS ( + SELECT 1 FROM public.price_types pt + WHERE pt."OrganizationId" = o."Id" AND pt."IsSystem" = true + ); + """); + + // Если в организации есть «Розничная цена», но она не помечена системной — пометим её. + b.Sql(""" + UPDATE public.price_types + SET "IsSystem" = true, "IsRequired" = true + WHERE "Name" = 'Розничная цена' + AND "IsSystem" = false + AND "OrganizationId" IN ( + SELECT "OrganizationId" FROM public.price_types + GROUP BY "OrganizationId" + HAVING SUM(CASE WHEN "IsSystem" THEN 1 ELSE 0 END) = 0 + ); + """); + } + + protected override void Down(MigrationBuilder b) + { + b.DropColumn(name: "ShelfLifeDays", schema: "public", table: "products"); + b.DropColumn(name: "IsSystem", schema: "public", table: "price_types"); + b.DropColumn(name: "IsRequired", schema: "public", table: "price_types"); + + b.AddColumn(name: "MultiplePriceTypesEnabled", schema: "public", table: "organizations", + type: "boolean", nullable: false, defaultValue: false); + b.AddColumn(name: "IsActive", schema: "public", table: "price_types", + type: "boolean", nullable: false, defaultValue: true); + b.AddColumn(name: "IsActive", schema: "public", table: "counterparties", + type: "boolean", nullable: false, defaultValue: true); + b.AddColumn(name: "IsActive", schema: "public", table: "units_of_measure", + type: "boolean", nullable: false, defaultValue: true); + b.AddColumn(name: "IsActive", schema: "public", table: "product_groups", + type: "boolean", nullable: false, defaultValue: true); + b.AddColumn(name: "IsActive", schema: "public", table: "products", + type: "boolean", nullable: false, defaultValue: true); + } + } +} diff --git a/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs b/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs index 9ba519a..4e4c36a 100644 --- a/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs +++ b/src/food-market.infrastructure/Persistence/Migrations/AppDbContextModelSnapshot.cs @@ -377,10 +377,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(20) .HasColumnType("character varying(20)"); - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("LegalName") + b.Property("LegalName") .HasMaxLength(500) .HasColumnType("character varying(500)"); @@ -506,15 +503,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("IsActive") - .HasColumnType("boolean"); - b.Property("IsDefault") .HasColumnType("boolean"); + b.Property("IsRequired") + .HasColumnType("boolean"); + b.Property("IsRetail") .HasColumnType("boolean"); + b.Property("IsSystem") + .HasColumnType("boolean"); + b.Property("Name") .IsRequired() .HasMaxLength(100) @@ -563,10 +563,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(1000) .HasColumnType("character varying(1000)"); - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("IsMarked") + b.Property("IsMarked") .HasColumnType("boolean"); b.Property("IsService") @@ -611,6 +608,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("ReferencePriceUpdatedAt") .HasColumnType("timestamp with time zone"); + b.Property("ShelfLifeDays") + .HasColumnType("integer"); + b.Property("UnitOfMeasureId") .HasColumnType("uuid"); @@ -640,8 +640,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("OrganizationId", "Article"); - b.HasIndex("OrganizationId", "IsActive"); - b.HasIndex("OrganizationId", "Name"); b.HasIndex("OrganizationId", "ProductGroupId"); @@ -697,10 +695,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("CreatedAt") .HasColumnType("timestamp with time zone"); - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("MarkupPercent") + b.Property("MarkupPercent") .HasPrecision(5, 2) .HasColumnType("numeric(5,2)"); @@ -933,10 +928,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasMaxLength(500) .HasColumnType("character varying(500)"); - b.Property("IsActive") - .HasColumnType("boolean"); - - b.Property("Name") + b.Property("Name") .IsRequired() .HasMaxLength(100) .HasColumnType("character varying(100)"); @@ -1107,9 +1099,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("MultiCurrencyEnabled") .HasColumnType("boolean"); - b.Property("MultiplePriceTypesEnabled") - .HasColumnType("boolean"); - b.Property("ShowMarkedOnProduct") .HasColumnType("boolean"); diff --git a/src/food-market.web/src/App.tsx b/src/food-market.web/src/App.tsx index 6250830..031938d 100644 --- a/src/food-market.web/src/App.tsx +++ b/src/food-market.web/src/App.tsx @@ -6,7 +6,6 @@ import { CountriesPage } from '@/pages/CountriesPage' import { CurrenciesPage } from '@/pages/CurrenciesPage' import { UnitsOfMeasurePage } from '@/pages/UnitsOfMeasurePage' import { PriceTypesPage } from '@/pages/PriceTypesPage' -import { GroupMarkupsPage } from '@/pages/GroupMarkupsPage' import { StoresPage } from '@/pages/StoresPage' import { RetailPointsPage } from '@/pages/RetailPointsPage' import { ProductGroupsPage } from '@/pages/ProductGroupsPage' @@ -48,7 +47,6 @@ export default function App() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/src/food-market.web/src/components/AppLayout.tsx b/src/food-market.web/src/components/AppLayout.tsx index d457e5a..587a007 100644 --- a/src/food-market.web/src/components/AppLayout.tsx +++ b/src/food-market.web/src/components/AppLayout.tsx @@ -10,8 +10,6 @@ import { Boxes, History, TruckIcon, ShoppingCart, Settings, Menu, X, } from 'lucide-react' import { Logo } from './Logo' -import { Percent } from 'lucide-react' -import { useOrgSettings } from '@/lib/useOrgSettings' interface MeResponse { sub: string @@ -24,13 +22,13 @@ interface MeResponse { type NavItem = { to: string; icon: typeof LayoutDashboard; label: string; end?: boolean } type NavSection = { group: string; items: NavItem[] } -function buildNav(showPriceTypes: boolean): NavSection[] { +function buildNav(): NavSection[] { const catalog: NavItem[] = [ { to: '/catalog/products', icon: Package, label: 'Товары' }, { to: '/catalog/product-groups', icon: FolderTree, label: 'Группы' }, { to: '/catalog/units', icon: Ruler, label: 'Ед. измерения' }, + { to: '/catalog/price-types', icon: Tag, label: 'Типы цен' }, ] - if (showPriceTypes) catalog.push({ to: '/catalog/price-types', icon: Tag, label: 'Типы цен' }) return [ { group: 'Главное', items: [ { to: '/', icon: LayoutDashboard, label: 'Dashboard', end: true }, @@ -62,7 +60,6 @@ function buildNav(showPriceTypes: boolean): NavSection[] { ]}, { group: 'Настройки', items: [ { to: '/settings/organization', icon: Settings, label: 'Организация' }, - { to: '/settings/group-markups', icon: Percent, label: 'Наценки по группам' }, ]}, ] } @@ -74,8 +71,7 @@ export function AppLayout() { staleTime: 5 * 60 * 1000, }) - const org = useOrgSettings() - const nav = buildNav(org.data?.multiplePriceTypesEnabled ?? false) + const nav = buildNav() const [drawerOpen, setDrawerOpen] = useState(false) const location = useLocation() diff --git a/src/food-market.web/src/lib/types.ts b/src/food-market.web/src/lib/types.ts index 1792c01..93eace8 100644 --- a/src/food-market.web/src/lib/types.ts +++ b/src/food-market.web/src/lib/types.ts @@ -28,8 +28,8 @@ export interface Country { vatRate: number } export interface Currency { id: string; code: string; name: string; symbol: string } -export interface UnitOfMeasure { id: string; code: string; name: string; description: string | null; isActive: boolean } -export interface PriceType { id: string; name: string; isDefault: boolean; isRetail: boolean; sortOrder: number; isActive: boolean } +export interface UnitOfMeasure { id: string; code: string; name: string; description: string | null } +export interface PriceType { id: string; name: string; isRequired: boolean; isSystem: boolean; isDefault: boolean; isRetail: boolean; sortOrder: number } export interface Store { id: string; name: string; code: string | null; address: string | null; phone: string | null; managerName: string | null; isMain: boolean; isActive: boolean @@ -38,13 +38,13 @@ export interface RetailPoint { id: string; name: string; code: string | null; storeId: string; storeName: string | null; address: string | null; phone: string | null; fiscalSerial: string | null; fiscalRegNumber: string | null; isActive: boolean } -export interface ProductGroup { id: string; name: string; parentId: string | null; path: string; sortOrder: number; isActive: boolean; markupPercent: number | null } +export interface ProductGroup { id: string; name: string; parentId: string | null; path: string; sortOrder: number; markupPercent: number | null } export interface Counterparty { id: string; name: string; legalName: string | null; type: CounterpartyType; bin: string | null; iin: string | null; taxNumber: string | null; countryId: string | null; countryName: string | null; address: string | null; phone: string | null; email: string | null; bankName: string | null; bankAccount: string | null; bik: string | null; - contactPerson: string | null; notes: string | null; isActive: boolean + contactPerson: string | null; notes: string | null } export interface ProductBarcode { id: string; code: string; type: BarcodeType; isPrimary: boolean } export interface ProductPrice { id: string; priceTypeId: string; priceTypeName: string; amount: number; currencyId: string; currencyCode: string } @@ -60,7 +60,7 @@ export interface Product { referencePrice: number | null; referencePriceUpdatedAt: string | null; purchaseCurrencyId: string | null; purchaseCurrencyCode: string | null; cost: number; lastSupplyAt: string | null; - imageUrl: string | null; isActive: boolean; + imageUrl: string | null; shelfLifeDays: number | null; prices: ProductPrice[]; barcodes: ProductBarcode[] } diff --git a/src/food-market.web/src/lib/useOrgSettings.ts b/src/food-market.web/src/lib/useOrgSettings.ts index 03f0df3..f97e001 100644 --- a/src/food-market.web/src/lib/useOrgSettings.ts +++ b/src/food-market.web/src/lib/useOrgSettings.ts @@ -15,7 +15,6 @@ export interface OrgSettings { showMarkedOnProduct: boolean showMinMaxStock: boolean allowFractionalPrices: boolean - multiplePriceTypesEnabled: boolean showReferencePriceOnProduct: boolean } diff --git a/src/food-market.web/src/pages/CounterpartiesPage.tsx b/src/food-market.web/src/pages/CounterpartiesPage.tsx index e6431c0..bbe627c 100644 --- a/src/food-market.web/src/pages/CounterpartiesPage.tsx +++ b/src/food-market.web/src/pages/CounterpartiesPage.tsx @@ -8,7 +8,7 @@ import { Pagination } from '@/components/Pagination' import { SearchBar } from '@/components/SearchBar' import { Button } from '@/components/Button' import { Modal } from '@/components/Modal' -import { Field, TextInput, TextArea, Select, Checkbox } from '@/components/Field' +import { Field, TextInput, TextArea, Select } from '@/components/Field' import { useCatalogList, useCatalogMutations } from '@/lib/useCatalog' import { type Counterparty, type Country, type PagedResult, CounterpartyType } from '@/lib/types' @@ -31,7 +31,6 @@ interface Form { bik: string contactPerson: string notes: string - isActive: boolean } const blankForm: Form = { @@ -39,7 +38,7 @@ const blankForm: Form = { bin: '', iin: '', taxNumber: '', countryId: '', address: '', phone: '', email: '', bankName: '', bankAccount: '', bik: '', - contactPerson: '', notes: '', isActive: true, + contactPerson: '', notes: '', } const typeLabel: Record = { @@ -94,7 +93,7 @@ export function CounterpartiesPage() { bin: r.bin ?? '', iin: r.iin ?? '', taxNumber: r.taxNumber ?? '', countryId: r.countryId ?? '', address: r.address ?? '', phone: r.phone ?? '', email: r.email ?? '', bankName: r.bankName ?? '', bankAccount: r.bankAccount ?? '', bik: r.bik ?? '', - contactPerson: r.contactPerson ?? '', notes: r.notes ?? '', isActive: r.isActive, + contactPerson: r.contactPerson ?? '', notes: r.notes ?? '', })} columns={[ { header: 'Название', sortKey: 'name', cell: (r) => r.name }, @@ -102,7 +101,6 @@ export function CounterpartiesPage() { { header: 'БИН/ИИН', width: '140px', cell: (r) => {r.bin ?? r.iin ?? '—'} }, { header: 'Телефон', width: '160px', sortKey: 'phone', cell: (r) => r.phone ?? '—' }, { header: 'Страна', width: '120px', sortKey: 'country', cell: (r) => r.countryName ?? '—' }, - { header: 'Активен', width: '100px', sortKey: 'isActive', cell: (r) => r.isActive ? '✓' : '—' }, ]} /> @@ -183,7 +181,6 @@ export function CounterpartiesPage() {