refactor(retail-points): rename «Точка продаж» → «Касса» + перенос
складов и касс в раздел «Настройки организации»; useOrgInfra хук UI-переименование: - RetailPointsPage: title «Кассы», description обновлён, лейблы «Новая касса» / «Удалить кассу?»; доменная сущность RetailPoint и URL /api/catalog/retail-points сохранены — DTO/БД не трогаем. - В сайдбаре пункты «Склады» и «Кассы» перенесены из бывшей группы «Склады» в группу «Настройки организации» (рядом с «Общие»). Старые пункты верхнего уровня убраны. useOrgInfra() — общий хук: - возвращает stores, cashRegisters, defaultStoreId, defaultCashId - showStorePicker / showCashPicker = length > 1 (умное скрытие селекторов в формах документов когда инфра одна). В SupplyEditPage скрытие склада уже работало через (stores.data?.length ?? 0) > 1 — оставил как есть, новый хук для будущих документов (продажи, инвентаризации). Сидер default Store + RetailPoint per Organization уже есть в DevDataSeeder.cs (Основной склад MAIN + Касса 1 POS-1) — дополнять не нужно. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dc4b5360b9
commit
cec76ecaaf
|
|
@ -37,10 +37,6 @@ function buildNav(): NavSection[] {
|
||||||
{ group: 'Контрагенты', items: [
|
{ group: 'Контрагенты', items: [
|
||||||
{ to: '/catalog/counterparties', icon: Users, label: 'Контрагенты' },
|
{ to: '/catalog/counterparties', icon: Users, label: 'Контрагенты' },
|
||||||
]},
|
]},
|
||||||
{ group: 'Склады', items: [
|
|
||||||
{ to: '/catalog/stores', icon: Warehouse, label: 'Склады' },
|
|
||||||
{ to: '/catalog/retail-points', icon: StoreIcon, label: 'Точки продаж' },
|
|
||||||
]},
|
|
||||||
{ group: 'Остатки', items: [
|
{ group: 'Остатки', items: [
|
||||||
{ to: '/inventory/stock', icon: Boxes, label: 'Остатки' },
|
{ to: '/inventory/stock', icon: Boxes, label: 'Остатки' },
|
||||||
{ to: '/inventory/movements', icon: History, label: 'Движения' },
|
{ to: '/inventory/movements', icon: History, label: 'Движения' },
|
||||||
|
|
@ -57,8 +53,10 @@ function buildNav(): NavSection[] {
|
||||||
{ group: 'Импорт', items: [
|
{ group: 'Импорт', items: [
|
||||||
{ to: '/admin/import/moysklad', icon: Download, label: 'МойСклад' },
|
{ to: '/admin/import/moysklad', icon: Download, label: 'МойСклад' },
|
||||||
]},
|
]},
|
||||||
{ group: 'Настройки', items: [
|
{ group: 'Настройки организации', items: [
|
||||||
{ to: '/settings/organization', icon: Settings, label: 'Организация' },
|
{ to: '/settings/organization', icon: Settings, label: 'Общие' },
|
||||||
|
{ to: '/catalog/stores', icon: Warehouse, label: 'Склады' },
|
||||||
|
{ to: '/catalog/retail-points', icon: StoreIcon, label: 'Кассы' },
|
||||||
]},
|
]},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
src/food-market.web/src/lib/useOrgInfra.ts
Normal file
34
src/food-market.web/src/lib/useOrgInfra.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { useStores } from '@/lib/useLookups'
|
||||||
|
import { useQuery } from '@tanstack/react-query'
|
||||||
|
import { api } from '@/lib/api'
|
||||||
|
import type { PagedResult, RetailPoint } from '@/lib/types'
|
||||||
|
|
||||||
|
/** Инфраструктура организации (склады, кассы) для умного скрытия селекторов
|
||||||
|
* в формах документов: если у организации только 1 склад/касса — выбор не
|
||||||
|
* показывается, форма автоматически подставляет единственное значение.
|
||||||
|
* Появится 2-й — пикер вернётся. */
|
||||||
|
export function useOrgInfra() {
|
||||||
|
const stores = useStores()
|
||||||
|
const cashRegisters = useQuery({
|
||||||
|
queryKey: ['lookup:retail-points'],
|
||||||
|
queryFn: async () =>
|
||||||
|
(await api.get<PagedResult<RetailPoint>>('/api/catalog/retail-points?pageSize=500')).data.items,
|
||||||
|
staleTime: 0,
|
||||||
|
refetchOnMount: 'always',
|
||||||
|
refetchOnWindowFocus: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const storesList = stores.data ?? []
|
||||||
|
const cashList = cashRegisters.data ?? []
|
||||||
|
const mainStore = storesList.find((s) => s.isMain) ?? storesList[0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
stores: storesList,
|
||||||
|
cashRegisters: cashList,
|
||||||
|
isLoading: stores.isLoading || cashRegisters.isLoading,
|
||||||
|
showStorePicker: storesList.length > 1,
|
||||||
|
showCashPicker: cashList.length > 1,
|
||||||
|
defaultStoreId: mainStore?.id ?? null,
|
||||||
|
defaultCashId: cashList[0]?.id ?? null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,8 +55,8 @@ export function RetailPointsPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListPageShell
|
<ListPageShell
|
||||||
title="Точки продаж"
|
title="Кассы"
|
||||||
description="Кассовые точки. Привязаны к складу, с которого идут продажи."
|
description="Кассовые точки продаж. Каждая привязана к складу, с которого списывается товар."
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<SearchBar value={search} onChange={setSearch} />
|
<SearchBar value={search} onChange={setSearch} />
|
||||||
|
|
@ -96,12 +96,12 @@ export function RetailPointsPage() {
|
||||||
<Modal
|
<Modal
|
||||||
open={!!form}
|
open={!!form}
|
||||||
onClose={() => setForm(null)}
|
onClose={() => setForm(null)}
|
||||||
title={form?.id ? 'Редактировать точку продаж' : 'Новая точка продаж'}
|
title={form?.id ? 'Редактировать кассу' : 'Новая касса'}
|
||||||
footer={
|
footer={
|
||||||
<>
|
<>
|
||||||
{form?.id && (
|
{form?.id && (
|
||||||
<Button variant="danger" size="sm" onClick={async () => {
|
<Button variant="danger" size="sm" onClick={async () => {
|
||||||
if (confirm('Удалить точку продаж?')) {
|
if (confirm('Удалить кассу?')) {
|
||||||
await remove.mutateAsync(form.id!)
|
await remove.mutateAsync(form.id!)
|
||||||
setForm(null)
|
setForm(null)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue