'use client' import { useQuery, useMutation } from '@apollo/client' import { ArrowLeft, Building2, Search, Package, Plus, Minus, ShoppingCart, Wrench, } from 'lucide-react' import Image from 'next/image' import { useRouter } from 'next/navigation' import React, { useState } from 'react' import { toast } from 'sonner' import { Sidebar } from '@/components/dashboard/sidebar' import { OrganizationAvatar } from '@/components/market/organization-avatar' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Card } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { CREATE_SUPPLY_ORDER } from '@/graphql/mutations' import { GET_MY_COUNTERPARTIES, GET_ORGANIZATION_PRODUCTS, GET_SUPPLY_ORDERS, GET_MY_SUPPLIES } from '@/graphql/queries' import { useAuth } from '@/hooks/useAuth' import { useSidebar } from '@/hooks/useSidebar' interface ConsumableSupplier { id: string inn: string name?: string fullName?: string type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE' address?: string phones?: Array<{ value: string }> emails?: Array<{ value: string }> users?: Array<{ id: string; avatar?: string; managerName?: string }> createdAt: string } interface ConsumableProduct { id: string name: string description?: string price: number category?: { name: string } images: string[] mainImage?: string organization: { id: string name: string } stock?: number unit?: string } interface SelectedConsumable { id: string name: string price: number selectedQuantity: number unit?: string category?: string supplierId: string supplierName: string } export function CreateConsumablesSupplyPage() { const router = useRouter() const { user } = useAuth() const { getSidebarMargin } = useSidebar() const [selectedSupplier, setSelectedSupplier] = useState(null) const [selectedConsumables, setSelectedConsumables] = useState([]) const [searchQuery, setSearchQuery] = useState('') const [productSearchQuery, setProductSearchQuery] = useState('') const [deliveryDate, setDeliveryDate] = useState('') const [selectedFulfillmentCenter, setSelectedFulfillmentCenter] = useState(null) const [selectedLogistics, setSelectedLogistics] = useState(null) const [isCreatingSupply, setIsCreatingSupply] = useState(false) // Загружаем контрагентов-поставщиков расходников const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES) // Загружаем товары для выбранного поставщика с фильтрацией по типу CONSUMABLE const { data: productsData, loading: productsLoading } = useQuery(GET_ORGANIZATION_PRODUCTS, { skip: !selectedSupplier, variables: { organizationId: selectedSupplier.id, search: productSearchQuery || null, category: null, type: 'CONSUMABLE', // Фильтруем только расходники согласно rules2.md }, }) // Мутация для создания заказа поставки расходников const [createSupplyOrder] = useMutation(CREATE_SUPPLY_ORDER) // Фильтруем только поставщиков расходников (поставщиков) const consumableSuppliers = (counterpartiesData?.myCounterparties || []).filter( (org: ConsumableSupplier) => org.type === 'WHOLESALE', ) // Фильтруем фулфилмент-центры const fulfillmentCenters = (counterpartiesData?.myCounterparties || []).filter( (org: ConsumableSupplier) => org.type === 'FULFILLMENT', ) // Фильтруем логистические компании const logisticsPartners = (counterpartiesData?.myCounterparties || []).filter( (org: ConsumableSupplier) => org.type === 'LOGIST', ) // Фильтруем поставщиков по поисковому запросу const filteredSuppliers = consumableSuppliers.filter( (supplier: ConsumableSupplier) => supplier.name?.toLowerCase().includes(searchQuery.toLowerCase()) || supplier.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) || supplier.inn?.toLowerCase().includes(searchQuery.toLowerCase()), ) // Получаем товары поставщика (уже отфильтрованы в GraphQL запросе) const supplierProducts = productsData?.organizationProducts || [] const formatCurrency = (amount: number) => { return new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', minimumFractionDigits: 0, }).format(amount) } const updateConsumableQuantity = (productId: string, quantity: number) => { const product = supplierProducts.find((p: ConsumableProduct) => p.id === productId) if (!product || !selectedSupplier) return // ✅ ПРОВЕРКА ОСТАТКОВ согласно rules2.md раздел 9.4.5 if (quantity > 0) { // Проверяем доступность на складе if (product.stock !== undefined && quantity > product.stock) { toast.error(`Недостаточно товара на складе. Доступно: ${product.stock} ${product.unit || 'шт'}`) return } // Логируем попытку добавления для аудита console.warn('📊 Stock check:', { action: 'add_to_cart', productId: product.id, productName: product.name, requested: quantity, available: product.stock || 'unlimited', result: quantity <= (product.stock || Infinity) ? 'allowed' : 'blocked', userId: selectedSupplier.id, timestamp: new Date().toISOString(), }) } setSelectedConsumables((prev) => { const existing = prev.find((p) => p.id === productId) if (quantity === 0) { // Удаляем расходник если количество 0 return prev.filter((p) => p.id !== productId) } if (existing) { // Обновляем количество существующего расходника return prev.map((p) => (p.id === productId ? { ...p, selectedQuantity: quantity } : p)) } else { // Добавляем новый расходник return [ ...prev, { id: product.id, name: product.name, price: product.price, selectedQuantity: quantity, unit: product.unit || 'шт', category: product.category?.name || 'Расходники', supplierId: selectedSupplier.id, supplierName: selectedSupplier.name || selectedSupplier.fullName || 'Поставщик', }, ] } }) } const getSelectedQuantity = (productId: string): number => { const selected = selectedConsumables.find((p) => p.id === productId) return selected ? selected.selectedQuantity : 0 } const getTotalAmount = () => { return selectedConsumables.reduce((sum, consumable) => sum + consumable.price * consumable.selectedQuantity, 0) } const getTotalItems = () => { return selectedConsumables.reduce((sum, consumable) => sum + consumable.selectedQuantity, 0) } const handleCreateSupply = async () => { if (!selectedSupplier || selectedConsumables.length === 0 || !deliveryDate) { toast.error('Заполните все обязательные поля: поставщик, расходники и дата доставки') return } // ✅ ФИНАЛЬНАЯ ПРОВЕРКА ОСТАТКОВ перед созданием заказа for (const consumable of selectedConsumables) { const product = supplierProducts.find((p) => p.id === consumable.id) if (product?.stock !== undefined && consumable.selectedQuantity > product.stock) { toast.error( `Товар "${consumable.name}" недоступен в количестве ${consumable.selectedQuantity}. ` + `Доступно: ${product.stock} ${product.unit || 'шт'}`, ) return } } // Для селлеров требуется выбор фулфилмент-центра if (!selectedFulfillmentCenter) { toast.error('Выберите фулфилмент-центр для доставки') return } // Логистика опциональна - может выбрать селлер или оставить фулфилменту if (selectedLogistics && !selectedLogistics.id) { toast.error('Некорректно выбрана логистическая компания') return } // Дополнительные проверки if (!selectedFulfillmentCenter.id) { toast.error('ID фулфилмент-центра не найден') return } if (!selectedSupplier.id) { toast.error('ID поставщика не найден') return } if (selectedConsumables.length === 0) { toast.error('Не выбраны расходники') return } // ✅ ПРОВЕРКА ДАТЫ согласно rules2.md - запрет прошедших дат const deliveryDateObj = new Date(deliveryDate) const today = new Date() today.setHours(0, 0, 0, 0) if (isNaN(deliveryDateObj.getTime())) { toast.error('Некорректная дата поставки') return } if (deliveryDateObj < today) { toast.error('Нельзя выбрать прошедшую дату поставки') return } setIsCreatingSupply(true) // 🔍 ОТЛАДКА: проверяем текущего пользователя console.warn('👤 Текущий пользователь:', { userId: user?.id, phone: user?.phone, organizationId: user?.organization?.id, organizationType: user?.organization?.type, organizationName: user?.organization?.name || user?.organization?.fullName, }) console.warn('🚀 Создаем поставку с данными:', { partnerId: selectedSupplier.id, deliveryDate: deliveryDate, fulfillmentCenterId: selectedFulfillmentCenter.id, logisticsPartnerId: selectedLogistics?.id, hasLogistics: !!selectedLogistics?.id, consumableType: 'SELLER_CONSUMABLES', itemsCount: selectedConsumables.length, mutationInput: { partnerId: selectedSupplier.id, deliveryDate: deliveryDate, fulfillmentCenterId: selectedFulfillmentCenter.id, ...(selectedLogistics?.id ? { logisticsPartnerId: selectedLogistics.id } : {}), consumableType: 'SELLER_CONSUMABLES', items: selectedConsumables.map((consumable) => ({ productId: consumable.id, quantity: consumable.selectedQuantity, })), }, }) try { const result = await createSupplyOrder({ variables: { input: { partnerId: selectedSupplier.id, deliveryDate: deliveryDate, fulfillmentCenterId: selectedFulfillmentCenter.id, // 🔄 ЛОГИСТИКА ОПЦИОНАЛЬНА: селлер может выбрать или оставить фулфилменту ...(selectedLogistics?.id ? { logisticsPartnerId: selectedLogistics.id } : {}), // 🏷️ КЛАССИФИКАЦИЯ согласно правилам (раздел 2.2) consumableType: 'SELLER_CONSUMABLES', // Расходники селлеров items: selectedConsumables.map((consumable) => ({ productId: consumable.id, quantity: consumable.selectedQuantity, })), }, }, refetchQueries: [ { query: GET_SUPPLY_ORDERS }, // Обновляем заказы поставок { query: GET_MY_SUPPLIES }, // Обновляем расходники фулфилмента ], }) if (result.data?.createSupplyOrder?.success) { toast.success('Заказ поставки расходников создан успешно!') // Очищаем форму setSelectedSupplier(null) setSelectedFulfillmentCenter(null) setSelectedConsumables([]) setDeliveryDate('') setProductSearchQuery('') setSearchQuery('') // Перенаправляем на страницу поставок селлера с открытой вкладкой "Расходники" router.push('/supplies?tab=consumables') } else { toast.error(result.data?.createSupplyOrder?.message || 'Ошибка при создании заказа поставки') } } catch (error) { console.error('Error creating consumables supply:', error) // Детальная диагностика ошибки if (error instanceof Error) { console.error('Error details:', { message: error.message, stack: error.stack, name: error.name, }) // Показываем конкретную ошибку пользователю toast.error(`Ошибка: ${error.message}`) } else { console.error('Unknown error:', error) toast.error('Ошибка при создании поставки расходников') } } finally { setIsCreatingSupply(false) } } return (
{/* Заголовок */}

Создание поставки расходников

Выберите поставщика и добавьте расходники в заказ

{/* Основной контент с двумя блоками */}
{/* Левая колонка - Поставщики и Расходники */}
{/* Блок "Поставщики" */}

Поставщики

setSearchQuery(e.target.value)} className="bg-white/20 backdrop-blur border-white/30 text-white placeholder-white/50 pl-10 h-8 text-sm rounded-full shadow-inner focus:ring-2 focus:ring-purple-400/50 focus:border-purple-400/50 transition-all duration-300" />
{selectedSupplier && ( )}
{counterpartiesLoading ? (

Загружаем поставщиков...

) : filteredSuppliers.length === 0 ? (

{searchQuery ? 'Поставщики не найдены' : 'Добавьте поставщиков'}

) : (
{filteredSuppliers.slice(0, 7).map((supplier: ConsumableSupplier, index) => ( setSelectedSupplier(supplier)} >
({ id: user.id, avatar: user.avatar, })), }} size="sm" /> {selectedSupplier?.id === supplier.id && (
)}

{(supplier.name || supplier.fullName || 'Поставщик').slice(0, 10)}

4.5
{/* Hover эффект */}
))} {filteredSuppliers.length > 7 && (
+{filteredSuppliers.length - 7}
ещё
)}
)}
{/* Блок "Расходники" */}

Расходники {selectedSupplier && ( - {selectedSupplier.name || selectedSupplier.fullName} )}

{selectedSupplier && (
setProductSearchQuery(e.target.value)} className="bg-white/10 border-white/20 text-white placeholder-white/40 pl-7 h-8 text-sm" />
)}
{!selectedSupplier ? (

Выберите поставщика для просмотра расходников

) : productsLoading ? (

Загрузка...

) : supplierProducts.length === 0 ? (

Нет доступных расходников

) : (
{supplierProducts.map((product: ConsumableProduct, index) => { const selectedQuantity = getSelectedQuantity(product.id) return ( 0 ? 'ring-2 ring-green-400/50 bg-gradient-to-br from-green-500/20 via-green-400/10 to-green-500/20' : 'hover:from-white/20 hover:via-white/10 hover:to-white/20 hover:border-white/40' }`} style={{ animationDelay: `${index * 50}ms`, minHeight: '200px', width: '100%', }} >
{/* Изображение товара */}
{product.images && product.images.length > 0 && product.images[0] ? ( {product.name} ) : product.mainImage ? ( {product.name} ) : (
)} {selectedQuantity > 0 && (
{selectedQuantity > 999 ? '999+' : selectedQuantity}
)}
{/* Информация о товаре */}

{product.name}

{product.category && ( {product.category.name.slice(0, 10)} )}
{formatCurrency(product.price)} {product.stock && {product.stock}}
{/* Управление количеством */}
{ let inputValue = e.target.value // Удаляем все нецифровые символы inputValue = inputValue.replace(/[^0-9]/g, '') // Удаляем ведущие нули inputValue = inputValue.replace(/^0+/, '') // Если строка пустая после удаления нулей, устанавливаем 0 const numericValue = inputValue === '' ? 0 : parseInt(inputValue) // Ограничиваем значение максимумом 99999 const clampedValue = Math.min(numericValue, 99999) updateConsumableQuantity(product.id, clampedValue) }} onBlur={(e) => { // При потере фокуса, если поле пустое, устанавливаем 0 if (e.target.value === '') { updateConsumableQuantity(product.id, 0) } }} className="w-16 h-7 text-center text-sm bg-white/10 border-white/20 text-white rounded px-1 focus:ring-2 focus:ring-purple-400/50 focus:border-purple-400/50" placeholder="0" />
{selectedQuantity > 0 && (
{formatCurrency(product.price * selectedQuantity)}
)}
{/* Hover эффект */}
) })}
)}
{/* Правая колонка - Корзина */}

Корзина ({getTotalItems()} шт)

{selectedConsumables.length === 0 ? (

Корзина пуста

Добавьте расходники для создания поставки

{selectedFulfillmentCenter && (

Доставка в:

{selectedFulfillmentCenter.name || selectedFulfillmentCenter.fullName}

)}
) : (
{selectedConsumables.map((consumable) => (

{consumable.name}

{formatCurrency(consumable.price)} × {consumable.selectedQuantity}

{formatCurrency(consumable.price * consumable.selectedQuantity)}
))}
)}
{/* БЛОК ВЫБОРА ЛОГИСТИЧЕСКОЙ КОМПАНИИ */}
{ const selectedDate = new Date(e.target.value) const today = new Date() today.setHours(0, 0, 0, 0) if (selectedDate < today) { toast.error('Нельзя выбрать прошедшую дату поставки') return } setDeliveryDate(e.target.value) }} className="bg-white/10 border-white/20 text-white h-8 text-sm" min={new Date().toISOString().split('T')[0]} required />
Итого: {formatCurrency(getTotalAmount())}
) }