Files
sfera-new/src/components/supplies/create-suppliers/hooks/useSupplyCart.ts
Veronika Smirnova 9988e55126 refactor(supplies): extract custom hooks for supply creation logic
ЭТАП 1.2: Безопасное выделение бизнес-логики в хуки

- Create useSupplierSelection.ts: управление выбором поставщиков и поиском
- Create useProductCatalog.ts: загрузка и управление каталогом товаров
- Create useSupplyCart.ts: логика корзины и создания поставки
- Create useRecipeBuilder.ts: построение рецептур товаров (услуги + расходники)

Каждый хук инкапсулирует отдельную область ответственности:
- Состояние и действия изолированы
- GraphQL запросы сгруппированы по функциональности
- Бизнес-логика отделена от UI компонентов
- Полная типизация с TypeScript

No functional changes - pure logic extraction for better maintainability.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-12 19:52:44 +03:00

228 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ХУКА ДЛЯ ЛОГИКИ КОРЗИНЫ ПОСТАВОК
*
* Выделена из create-suppliers-supply-page.tsx
* Управляет корзиной товаров и настройками поставки
*/
import { useMutation } from '@apollo/client'
import { useRouter } from 'next/navigation'
import { useState, useMemo } from 'react'
import { toast } from 'sonner'
import { CREATE_SUPPLY_ORDER } from '@/graphql/mutations'
import type {
SelectedGoodsItem,
GoodsSupplier,
GoodsProduct,
ProductRecipe,
SupplyCreationFormData,
} from '../types/supply-creation.types'
interface UseSupplyCartProps {
selectedSupplier: GoodsSupplier | null
allCounterparties: GoodsSupplier[]
productRecipes: Record<string, ProductRecipe>
}
export function useSupplyCart({ selectedSupplier, allCounterparties, productRecipes }: UseSupplyCartProps) {
const router = useRouter()
// Состояния корзины и настроек
const [selectedGoods, setSelectedGoods] = useState<SelectedGoodsItem[]>([])
const [deliveryDate, setDeliveryDate] = useState('')
const [selectedLogistics, setSelectedLogistics] = useState<string>('auto')
const [selectedFulfillment, setSelectedFulfillment] = useState<string>('')
const [isCreatingSupply, setIsCreatingSupply] = useState(false)
// Мутация создания поставки
const [createSupplyOrder] = useMutation(CREATE_SUPPLY_ORDER)
// Получаем логистические компании
const logisticsCompanies = useMemo(() => {
return allCounterparties?.filter((partner) => partner.type === 'LOGIST') || []
}, [allCounterparties])
// Добавление товара в корзину
const addToCart = (
product: GoodsProduct,
quantity: number,
additionalData?: {
completeness?: string
recipe?: string
specialRequirements?: string
parameters?: Array<{ name: string; value: string }>
},
) => {
if (!selectedSupplier) {
toast.error('Сначала выберите поставщика')
return
}
if (quantity <= 0) {
toast.error('Укажите количество товара')
return
}
const existingItemIndex = selectedGoods.findIndex((item) => item.id === product.id)
if (existingItemIndex >= 0) {
// Обновляем существующий товар
setSelectedGoods((prev) => {
const updated = [...prev]
updated[existingItemIndex] = {
...updated[existingItemIndex],
selectedQuantity: quantity,
...additionalData,
}
return updated
})
toast.success('Количество товара обновлено')
} else {
// Добавляем новый товар
const newItem: SelectedGoodsItem = {
id: product.id,
name: product.name,
sku: product.article,
price: product.price,
selectedQuantity: quantity,
unit: product.unit,
category: product.category?.name,
supplierId: selectedSupplier?.id || '',
supplierName: selectedSupplier?.name || selectedSupplier?.fullName || '',
completeness: additionalData?.completeness,
recipe: additionalData?.recipe,
specialRequirements: additionalData?.specialRequirements,
parameters: additionalData?.parameters,
}
setSelectedGoods((prev) => [...prev, newItem])
toast.success('Товар добавлен в корзину')
}
}
// Удаление товара из корзины
const removeFromCart = (itemId: string) => {
setSelectedGoods((prev) => prev.filter((item) => item.id !== itemId))
toast.success('Товар удален из корзины')
}
// Функция расчета полной стоимости товара с рецептурой
const getProductTotalWithRecipe = (productId: string, quantity: number) => {
const product = selectedGoods.find((p) => p.id === productId)
if (!product) return 0
const baseTotal = product.price * quantity
const recipe = productRecipes[productId]
if (!recipe) return baseTotal
// Здесь будет логика расчета стоимости услуг и расходников
// Пока возвращаем базовую стоимость
return baseTotal
}
// Расчеты для корзины
const totalGoodsAmount = useMemo(() => {
return selectedGoods.reduce((sum, item) => {
return sum + getProductTotalWithRecipe(item.id, item.selectedQuantity)
}, 0)
}, [selectedGoods, productRecipes])
const totalQuantity = useMemo(() => {
return selectedGoods.reduce((sum, item) => sum + item.selectedQuantity, 0)
}, [selectedGoods])
// Валидация формы
const hasRequiredServices = useMemo(() => {
return selectedGoods.every((item) => productRecipes[item.id]?.selectedServices?.length > 0)
}, [selectedGoods, productRecipes])
const isFormValid = useMemo(() => {
return selectedSupplier && selectedGoods.length > 0 && deliveryDate && selectedFulfillment && hasRequiredServices
}, [selectedSupplier, selectedGoods.length, deliveryDate, selectedFulfillment, hasRequiredServices])
// Создание поставки
const handleCreateSupply = async () => {
if (!isFormValid) {
if (!hasRequiredServices) {
toast.error('Каждый товар должен иметь минимум 1 услугу фулфилмента')
} else {
toast.error('Заполните все обязательные поля')
}
return
}
if (!selectedSupplier) {
toast.error('Поставщик не выбран')
return
}
setIsCreatingSupply(true)
try {
await createSupplyOrder({
variables: {
supplierId: selectedSupplier?.id || '',
fulfillmentCenterId: selectedFulfillment,
items: selectedGoods.map((item) => ({
productId: item.id,
quantity: item.selectedQuantity,
recipe: productRecipes[item.id] || {
productId: item.id,
selectedServices: [],
selectedFFConsumables: [],
selectedSellerConsumables: [],
},
})),
deliveryDate: deliveryDate,
logistics: selectedLogistics,
specialRequirements: selectedGoods
.map((item) => item.specialRequirements)
.filter(Boolean)
.join('; '),
} satisfies SupplyCreationFormData,
})
toast.success('Поставка успешно создана!')
router.push('/supplies')
} catch (error) {
console.error('❌ Ошибка создания поставки:', error)
toast.error('Ошибка при создании поставки')
} finally {
setIsCreatingSupply(false)
}
}
return {
// Состояние корзины
selectedGoods,
setSelectedGoods,
deliveryDate,
setDeliveryDate,
selectedLogistics,
setSelectedLogistics,
selectedFulfillment,
setSelectedFulfillment,
isCreatingSupply,
// Данные
logisticsCompanies,
// Расчеты
totalGoodsAmount,
totalQuantity,
// Валидация
hasRequiredServices,
isFormValid,
// Функции управления корзиной
addToCart,
removeFromCart,
getProductTotalWithRecipe,
handleCreateSupply,
}
}