BUG-024: 500 при удалении ингредиента-полуфабриката с техкартой

Описание

При попытке удалить ингредиент, у которого есть собственная техкарта (ингредиент-полуфабрикат), бэкенд возвращает 500 INTERNAL_ERROR вместо осмысленного ответа.

Причина: Postgres блокирует удаление из-за FK fk_tech_cards_ingredient_id (tech_cards.ingredient_id с ON DELETE RESTRICT). Сервис не обрабатывает ConstraintViolationException и отдаёт общий 500.

Визуально для пользователя: кнопка «Удалить» на карточке ингредиента → INTERNAL_ERROR без объяснений. В UI нет способа понять что именно блокирует удаление (своя техкарта у ингредиента) и нет способа её удалить.

Шаги воспроизведения

  1. Создать ингредиент (например, «Лаваш»)
  2. Для этого ингредиента создать техкарту (полуфабрикат: Лаваш = Мука + Вода + …)
  3. Открыть карточку ингредиента
  4. Нажать «Удалить»

Ожидаемое поведение

Вариант 1 (рекомендуется): удаление ингредиента каскадно удаляет его собственную техкарту и её recipe_items — техкарта принадлежит ингредиенту. Всё в одной транзакции.

Вариант 2: сервис отвечает 409 CONFLICT с понятным сообщением: «У ингредиента есть техкарта “Лаваш-полуфабрикат”. Удалите техкарту сначала.» UI выводит это сообщение и кнопку «Удалить техкарту».

Фактическое поведение

DELETE /api/v1/admin/warehouse/ingredients/{id}
→ 500 Internal Server Error
{"error":{"code":"INTERNAL_ERROR","message":"Internal server error"}}

В логах warehouse-service:

ConstraintViolationException: ERROR: update or delete on table "ingredients"
violates foreign key constraint "fk_tech_cards_ingredient_id" on table "tech_cards"
Detail: Key (id)=(7f0128f1-...) is still referenced from table "tech_cards".

Затронутые сервисы

  • Warehouse ServiceIngredientService.delete() не обрабатывает случай «у ингредиента есть своя техкарта», эксепшен из БД прокидывается наружу как 500.
  • Admin Franchise (web) — карточка ингредиента не знает о связанной техкарте, нет способа её удалить/перенаправить пользователя.

Корневая причина

Семантическая: у ингредиента-полуфабриката есть собственная техкарта (tech_cards.ingredient_id IS NOT NULL AND product_id IS NULL, разрешено check-constraint’ом chk_tech_cards_product_or_ingredient). FK на ingredients стоит с ON DELETE RESTRICT, и в слое Hibernate IngredientService.delete() не проверяет наличие привязанных техкарт перед физическим удалением.

Воркэраунд (сделано вручную)

Удалена цепочка в warehouse_db:

DELETE FROM recipe_items WHERE tech_card_id = 'c8d3349b-2e5d-4ba5-8382-66a3eeb23bba';
DELETE FROM tech_cards WHERE id = 'c8d3349b-2e5d-4ba5-8382-66a3eeb23bba';
DELETE FROM ingredients WHERE id = '7f0128f1-9c9f-4651-96eb-3f010367ad26';