diff --git a/src/food-market.api/Controllers/Catalog/CounterpartiesController.cs b/src/food-market.api/Controllers/Catalog/CounterpartiesController.cs index 6ca8c87..4ef9ade 100644 --- a/src/food-market.api/Controllers/Catalog/CounterpartiesController.cs +++ b/src/food-market.api/Controllers/Catalog/CounterpartiesController.cs @@ -103,6 +103,23 @@ public async Task Delete(Guid id, CancellationToken ct) { var e = await _db.Counterparties.FirstOrDefaultAsync(x => x.Id == id, ct); if (e is null) return NotFound(); + + // FK-guard: counterparty используется как Supplier в supplies / customer + // в retail-sales / default supplier у products. Без явного чека EF + // отдаёт 500 «DbUpdateException 23503 violates foreign key constraint» + // вместо понятного 409 — пользователь не понимает что чинить. + var usedAsSupplier = await _db.Supplies.AnyAsync(s => s.SupplierId == id, ct); + var usedAsCustomer = await _db.RetailSales.AnyAsync(s => s.CustomerId == id, ct); + var usedAsDefault = await _db.Products.AnyAsync(p => p.DefaultSupplierId == id, ct); + if (usedAsSupplier || usedAsCustomer || usedAsDefault) + { + return Conflict(new + { + error = "Нельзя удалить контрагента: он используется в документах или товарах.", + usedAsSupplier, usedAsCustomer, usedAsDefault, + }); + } + _db.Counterparties.Remove(e); 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 acdf2ee..bfdf088 100644 --- a/src/food-market.api/Controllers/Catalog/ProductsController.cs +++ b/src/food-market.api/Controllers/Catalog/ProductsController.cs @@ -198,6 +198,8 @@ public async Task> Get(Guid id, CancellationToken ct) [HttpPost, Authorize(Roles = "Admin,Storekeeper")] public async Task> Create([FromBody] ProductInput input, CancellationToken ct) { + if (string.IsNullOrWhiteSpace(input.Name)) + return BadRequest(new { error = "Название товара обязательно.", field = nameof(input.Name) }); if (RequiredGuid.FirstMissing( (nameof(input.UnitOfMeasureId), input.UnitOfMeasureId), (nameof(input.ProductGroupId), input.ProductGroupId)) is { } missingFk) diff --git a/src/food-market.application/Catalog/CatalogDtos.cs b/src/food-market.application/Catalog/CatalogDtos.cs index 23debef..2e3088f 100644 --- a/src/food-market.application/Catalog/CatalogDtos.cs +++ b/src/food-market.application/Catalog/CatalogDtos.cs @@ -85,12 +85,14 @@ public record CounterpartyInput( 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( - string Name, string? Article, string? Description, + [Required, MinLength(1), StringLength(500)] string Name, + [StringLength(500)] string? Article, + string? Description, Guid UnitOfMeasureId, [Range(0, 100)] decimal? Vat, bool VatEnabled, Guid ProductGroupId, Guid? DefaultSupplierId, Guid? CountryOfOriginId, 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, + [StringLength(1000)] string? ImageUrl = null, IReadOnlyList? Prices = null, IReadOnlyList? Barcodes = null);