fix(price-types): IsRequired применяется сразу, без перезагрузки страницы
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 58s
CI / Web (React + Vite) (push) Successful in 35s
Docker Web / Build + push Web (push) Successful in 25s
Docker Web / Deploy Web on stage (push) Successful in 12s

Баг: переключение «Обязательная» в /catalog/price-types не приводило
к валидации в карточке товара — Save шёл и проходил, не требуя цену.

Корневая причина — два разных queryKey:
- useCatalogMutations в PriceTypesPage инвалидирует
  ['/api/catalog/price-types'] (для самой List-страницы),
- но usePriceTypes-хук, которым пользуется ProductEditPage и
  ProductsPage, живёт под ключом ['lookup:price-types'].
В итоге свежий снапшот справочника не доходит до потребителей.

Фикс:
- useLookups: staleTime=0 + refetchOnMount: 'always' +
  refetchOnWindowFocus=true. Любой переход на ProductEditPage
  делает свежий GET и видит актуальный IsRequired/IsRetail.
- PriceTypesPage save/delete: дополнительно вызывают
  qc.invalidateQueries({ queryKey: ['lookup:price-types'] }) —
  потребители тут же перерендериваются, без необходимости F5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-26 00:38:15 +05:00
parent bcc6976bd0
commit defad7cbb4
2 changed files with 16 additions and 1 deletions

View file

@ -9,7 +9,14 @@ function useLookup<T>(key: string, url: string) {
return useQuery({
queryKey: [`lookup:${key}`],
queryFn: async () => (await api.get<PagedResult<T>>(`${url}?pageSize=500`)).data.items,
staleTime: 5 * 60 * 1000,
// staleTime=0 + refetchOnMount: 'always' гарантируют что любая страница
// (например ProductEditPage), монтирующая usePriceTypes / useUnits / etc,
// сразу подтянет свежий снапшот справочника после правки в его UI —
// без необходимости явно прокидывать invalidate-pair (lookup-ключ ↔
// URL-ключ useCatalogMutations).
staleTime: 0,
refetchOnMount: 'always',
refetchOnWindowFocus: true,
})
}

View file

@ -8,6 +8,7 @@ import { Button } from '@/components/Button'
import { Modal } from '@/components/Modal'
import { Field, TextInput, Checkbox } from '@/components/Field'
import { useCatalogList, useCatalogMutations } from '@/lib/useCatalog'
import { useQueryClient } from '@tanstack/react-query'
import type { PriceType } from '@/lib/types'
const URL = '/api/catalog/price-types'
@ -26,6 +27,7 @@ export function PriceTypesPage() {
const { data, isLoading, page, setPage, search, setSearch, sortKey, sortOrder, setSort } = useCatalogList<PriceType>(URL)
const { create, update, remove } = useCatalogMutations(URL, URL)
const [form, setForm] = useState<Form | null>(null)
const qc = useQueryClient()
const save = async () => {
if (!form) return
@ -33,6 +35,11 @@ export function PriceTypesPage() {
void _omit
if (id) await update.mutateAsync({ id, input: payload })
else await create.mutateAsync(payload)
// useCatalogMutations инвалидирует ['/api/catalog/price-types'] (List на этой
// странице), но usePriceTypes-хук живёт под ключом 'lookup:price-types' —
// явно тригерим перефетч чтобы карточка товара сразу увидела новый
// IsRequired/IsRetail без перезагрузки страницы.
await qc.invalidateQueries({ queryKey: ['lookup:price-types'] })
setForm(null)
}
@ -86,6 +93,7 @@ export function PriceTypesPage() {
<Button variant="danger" size="sm" onClick={async () => {
if (confirm('Удалить тип цены?')) {
await remove.mutateAsync(form.id!)
await qc.invalidateQueries({ queryKey: ['lookup:price-types'] })
setForm(null)
}
}}>