fix(admin-ui): запись не сохранялась при пустом optional x-localized поле

Симптом: «Сохранить» (create и edit) ничего не делал, ошибок не видно.

Причина: x-localized поле рендерит Controller на каждую локаль
(data.<field>.<locale>). RHF при сабмите реконструирует values из имён
зарегистрированных полей и материализует родительский объект даже когда
исходное значение было null/отсутствовало → optional поле (например
mission) превращалось в { "ru-RU": undefined, "en-US": undefined }.

AJV отвергал undefined-локали ("must be string"), submit() делал return
до onSubmit — запрос не уходил. Ошибка ложилась через setError на
data.<field>.<locale>, а FieldError читает data.<field> → невидима.

Фикс: pruneEmpty() в submit() рекурсивно убирает undefined и опустевшие
объекты из data перед AJV-валидацией и отправкой — нетронутое
optional-поле снова валидируется как «отсутствует». Не трогает
null/''/0/false.

Откат временной диагностики [ordinis-diag] из MR !251.
Regression-тест падает без фикса, проходит с фиксом.
This commit is contained in:
Zimin A.N.
2026-05-21 15:34:18 +03:00
parent ffffacb538
commit dcb5612c94
3 changed files with 95 additions and 56 deletions
@@ -578,12 +578,6 @@ function DictionaryDetail() {
const rawRecordQuery = useRecordRaw(name, editingKey)
const handleSubmit = (req: CreateRecordRequest) => {
// [ordinis-diag] DIAGNOSTIC BUILD — удалить после диагностики.
console.warn('[ordinis-diag] route.handleSubmit(): получен req от формы', {
editKind: edit.kind,
approvalRequired,
businessKey: req.businessKey,
})
// Fill validFrom at submit (review #5): defaultValues в RHF берётся
// ТОЛЬКО при mount. Если user открыл modal в 12:00 и submit в 12:05 —
// `validFrom` в req это значение из 12:00. Backend check `validFrom >
@@ -637,20 +631,14 @@ function DictionaryDetail() {
)
return
}
console.warn('[ordinis-diag] route: вызываю createMut.mutate() — POST /records')
createMut.mutate(
{ payload: submitReq, idempotencyKey: recordIdempotencyKey },
{
onSuccess: () => {
console.warn('[ordinis-diag] createMut onSuccess — запись создана')
setEdit({ kind: 'closed' })
resetRecordIdempotencyKey()
},
onError: (err) => {
console.warn('[ordinis-diag] createMut onError', {
status: (err as { response?: { status?: number } })?.response?.status,
data: (err as { response?: { data?: unknown } })?.response?.data,
})
// Create rarely conflicts (no prior record to lock), но если backend
// вернёт 409 по уникальному ключу — businessKey возьмём из payload.
handleSaveError(err, submitReq.businessKey ?? '')
@@ -681,7 +669,6 @@ function DictionaryDetail() {
)
return
}
console.warn('[ordinis-diag] route: вызываю updateMut.mutate() — PUT /records/' + edit.record.businessKey)
updateMut.mutate(
{
businessKey: edit.record.businessKey,
@@ -690,17 +677,10 @@ function DictionaryDetail() {
},
{
onSuccess: () => {
console.warn('[ordinis-diag] updateMut onSuccess — запись обновлена')
setEdit({ kind: 'closed' })
resetRecordIdempotencyKey()
},
onError: (err) => {
console.warn('[ordinis-diag] updateMut onError', {
status: (err as { response?: { status?: number } })?.response?.status,
data: (err as { response?: { data?: unknown } })?.response?.data,
})
handleSaveError(err, edit.record.businessKey)
},
onError: (err) => handleSaveError(err, edit.record.businessKey),
},
)
}