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:
parent
c257ee7e88
commit
a8717897b7
|
|
@ -166,7 +166,10 @@ export function ProductEditPage() {
|
||||||
qc.invalidateQueries({ queryKey: ['/api/catalog/products'] })
|
qc.invalidateQueries({ queryKey: ['/api/catalog/products'] })
|
||||||
navigate(created ? `/catalog/products/${created.id}` : '/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({
|
const remove = useMutation({
|
||||||
|
|
@ -190,10 +193,21 @@ export function ProductEditPage() {
|
||||||
const updateBarcode = (i: number, patch: Partial<BarcodeRow>) =>
|
const updateBarcode = (i: number, patch: Partial<BarcodeRow>) =>
|
||||||
setForm({ ...form, barcodes: form.barcodes.map((b, ix) => ix === i ? { ...b, ...patch } : b) })
|
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
|
const canSave = form.name.trim().length > 0
|
||||||
&& !!form.unitOfMeasureId
|
&& !!form.unitOfMeasureId
|
||||||
&& !!form.productGroupId
|
&& !!form.productGroupId
|
||||||
&& form.barcodes.length > 0
|
&& form.barcodes.length > 0
|
||||||
|
&& missingRequiredPrices.length === 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit} className="flex flex-col h-full">
|
<form onSubmit={onSubmit} className="flex flex-col h-full">
|
||||||
|
|
@ -447,6 +461,11 @@ export function ProductEditPage() {
|
||||||
{priceTypes.data?.length === 0 && (
|
{priceTypes.data?.length === 0 && (
|
||||||
<div className="text-sm text-slate-400 py-2">Нет ни одного типа цен. Создай в «Настройки → Типы цен».</div>
|
<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>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue