fix(product-edit): человечная ошибка 400 + блок Save при незаполненных IsRequired ценах

Сервер 400-нул сохранение товара когда обязательная цена пуста, а UI
показывал «Request failed with status code 400» без указания причины.

- onError save mutation: достаём response.data.error из axios-ответа
  и кладём в setError; общий generic message остаётся как fallback.
- canSave дополнен проверкой что у каждого PriceType с IsRequired=true
  есть строка в form.prices с amount > 0 (та же что делает бэкенд,
  чтобы кнопка не отправляла запрос обречённый на 400).
- Под секцией цен — красная подсказка-список незаполненных обязательных
  типов цен («Заполни обязательные цены: «Розничная2» (значение должно
  быть больше 0).»).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-25 23:58:49 +05:00
parent c257ee7e88
commit a8717897b7

View file

@ -166,7 +166,10 @@ export function ProductEditPage() {
qc.invalidateQueries({ queryKey: ['/api/catalog/products'] })
navigate(created ? `/catalog/products/${created.id}` : '/catalog/products')
},
onError: (e: Error) => setError(e.message),
onError: (e: Error) => {
const msg = (e as { response?: { data?: { error?: string } } }).response?.data?.error ?? e.message
setError(msg)
},
})
const remove = useMutation({
@ -190,10 +193,21 @@ export function ProductEditPage() {
const updateBarcode = (i: number, patch: Partial<BarcodeRow>) =>
setForm({ ...form, barcodes: form.barcodes.map((b, ix) => ix === i ? { ...b, ...patch } : b) })
// У каждого PriceType с IsRequired=true должна быть запись в form.prices
// с Amount > 0. Сервер делает ту же проверку (400 иначе), но дублируем
// на фронте чтобы кнопка Save не клатилась с непонятным «Request failed with status 400».
const missingRequiredPrices = (priceTypes.data ?? [])
.filter((pt) => pt.isRequired)
.filter((pt) => {
const row = form.prices.find((p) => p.priceTypeId === pt.id)
return !row || row.amount <= 0
})
const canSave = form.name.trim().length > 0
&& !!form.unitOfMeasureId
&& !!form.productGroupId
&& form.barcodes.length > 0
&& missingRequiredPrices.length === 0
return (
<form onSubmit={onSubmit} className="flex flex-col h-full">
@ -447,6 +461,11 @@ export function ProductEditPage() {
{priceTypes.data?.length === 0 && (
<div className="text-sm text-slate-400 py-2">Нет ни одного типа цен. Создай в «Настройки Типы цен».</div>
)}
{missingRequiredPrices.length > 0 && (
<div className="text-sm text-red-600 mt-2">
Заполни обязательные цены: {missingRequiredPrices.map((pt) => `«${pt.name}»`).join(', ')} (значение должно быть больше 0).
</div>
)}
</div>
</Section>