diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ConsumablesBlock.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ConsumablesBlock.tsx
new file mode 100644
index 0000000..a3c8e58
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ConsumablesBlock.tsx
@@ -0,0 +1,307 @@
+// =============================================================================
+// 📦 БЛОК РАСХОДНИКОВ
+// =============================================================================
+// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в монолитной версии!
+// Все gradients, glassmorphism, анимации, индикаторы остатков сохранены
+
+import { Search, Wrench, Package, Plus, Minus } from 'lucide-react'
+import Image from 'next/image'
+
+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 type { FulfillmentConsumableProduct } from '../types'
+
+import type { ConsumablesBlockProps } from './types'
+
+export function ConsumablesBlock({
+ selectedSupplier,
+ products,
+ productsLoading,
+ productSearchQuery,
+ getSelectedQuantity,
+ onProductSearchChange,
+ onUpdateQuantity,
+ formatCurrency,
+}: ConsumablesBlockProps) {
+ return (
+
+
+
+
+
+ Расходники для фулфилмента
+ {selectedSupplier && (
+
+ - {selectedSupplier.name || selectedSupplier.fullName}
+
+ )}
+
+
+ {selectedSupplier && (
+
+
+ onProductSearchChange(e.target.value)}
+ className="bg-white/10 border-white/20 text-white placeholder-white/40 pl-7 h-8 text-sm"
+ />
+
+ )}
+
+
+
+ {!selectedSupplier ? (
+
+
+
Выберите поставщика для просмотра расходников
+
+ ) : productsLoading ? (
+
+ ) : products.length === 0 ? (
+
+
+
Нет доступных расходников
+
+ ) : (
+
+ {products.map((product: FulfillmentConsumableProduct, index: number) => {
+ 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%',
+ }}
+ >
+
+ {/* Изображение товара */}
+
+ {/* 🚫 ОВЕРЛЕЙ НЕДОСТУПНОСТИ */}
+ {(() => {
+ const totalStock = product.stock || (product as any).quantity || 0
+ const orderedStock = (product as any).ordered || 0
+ const availableStock = totalStock - orderedStock
+
+ if (availableStock <= 0) {
+ return (
+
+ )
+ }
+ return null
+ })()}
+ {product.images && product.images.length > 0 && product.images[0] ? (
+
+ ) : product.mainImage ? (
+
+ ) : (
+
+
+
+ )}
+ {selectedQuantity > 0 && (
+
+
+ {selectedQuantity > 999 ? '999+' : selectedQuantity}
+
+
+ )}
+
+
+ {/* Информация о товаре */}
+
+
+ {product.name}
+
+
+ {product.category && (
+
+ {product.category.name.slice(0, 10)}
+
+ )}
+ {/* 🚨 ИНДИКАТОР НИЗКИХ ОСТАТКОВ согласно правилам (раздел 6.3) */}
+ {(() => {
+ const totalStock = product.stock || product.quantity || 0
+ const orderedStock = product.ordered || 0
+ const availableStock = totalStock - orderedStock
+
+ if (availableStock <= 0) {
+ return (
+
+ Нет в наличии
+
+ )
+ } else if (availableStock <= 10) {
+ return (
+
+ Мало остатков
+
+ )
+ }
+ return null
+ })()}
+
+
+
+ {formatCurrency(product.price)}
+
+ {/* 📊 АКТУАЛЬНЫЙ ОСТАТОК согласно правилам (раздел 6.4.2) */}
+
+ {(() => {
+ const totalStock = product.stock || product.quantity || 0
+ const orderedStock = product.ordered || 0
+ const availableStock = totalStock - orderedStock
+
+ return (
+
+
+ Доступно: {availableStock}
+
+ {orderedStock > 0 && (
+ Заказано: {orderedStock}
+ )}
+
+ )
+ })()}
+
+
+
+
+ {/* Управление количеством */}
+
+ {(() => {
+ const totalStock = product.stock || (product as any).quantity || 0
+ const orderedStock = (product as any).ordered || 0
+ const availableStock = totalStock - orderedStock
+
+ return (
+
+
+ onUpdateQuantity(product.id, Math.max(0, selectedQuantity - 1))
+ }
+ className="h-6 w-6 p-0 text-white/60 hover:text-white hover:bg-white/20 rounded-full transition-all duration-300"
+ disabled={selectedQuantity === 0}
+ >
+
+
+
{
+ let inputValue = e.target.value
+
+ // Удаляем все нецифровые символы
+ inputValue = inputValue.replace(/[^0-9]/g, '')
+
+ // Удаляем ведущие нули
+ inputValue = inputValue.replace(/^0+/, '')
+
+ // Если строка пустая после удаления нулей, устанавливаем 0
+ const numericValue = inputValue === '' ? 0 : parseInt(inputValue)
+
+ // Ограничиваем значение максимумом доступного остатка
+ const clampedValue = Math.min(numericValue, availableStock, 99999)
+
+ onUpdateQuantity(product.id, clampedValue)
+ }}
+ onBlur={(e) => {
+ // При потере фокуса, если поле пустое, устанавливаем 0
+ if (e.target.value === '') {
+ onUpdateQuantity(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"
+ />
+
+ onUpdateQuantity(
+ product.id,
+ Math.min(selectedQuantity + 1, availableStock, 99999),
+ )
+ }
+ className={`h-6 w-6 p-0 rounded-full transition-all duration-300 ${
+ selectedQuantity >= availableStock || availableStock <= 0
+ ? 'text-white/30 cursor-not-allowed'
+ : 'text-white/60 hover:text-white hover:bg-white/20'
+ }`}
+ disabled={selectedQuantity >= availableStock || availableStock <= 0}
+ title={
+ availableStock <= 0
+ ? 'Товар отсутствует на складе'
+ : selectedQuantity >= availableStock
+ ? `Максимум доступно: ${availableStock}`
+ : 'Увеличить количество'
+ }
+ >
+
+
+
+ )
+ })()}
+
+ {selectedQuantity > 0 && (
+
+
+ {formatCurrency(product.price * selectedQuantity)}
+
+
+ )}
+
+
+
+ {/* Hover эффект */}
+
+
+ )
+ })}
+
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/PageHeader.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/PageHeader.tsx
new file mode 100644
index 0000000..dc3c502
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/PageHeader.tsx
@@ -0,0 +1,32 @@
+// =============================================================================
+// 📄 БЛОК ЗАГОЛОВКА СТРАНИЦЫ
+// =============================================================================
+// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в монолитной версии!
+
+import { ArrowLeft } from 'lucide-react'
+
+import { Button } from '@/components/ui/button'
+
+import type { PageHeaderProps } from './types'
+
+export function PageHeader({ onBack }: PageHeaderProps) {
+ return (
+
+
+
Создание поставки расходников фулфилмента
+
+ Выберите поставщика и добавьте расходники в заказ для вашего фулфилмент-центра
+
+
+
+
+ Назад
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ShoppingCartBlock.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ShoppingCartBlock.tsx
new file mode 100644
index 0000000..863b359
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/ShoppingCartBlock.tsx
@@ -0,0 +1,143 @@
+// =============================================================================
+// 🛒 БЛОК КОРЗИНЫ
+// =============================================================================
+// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в монолитной версии!
+// Все gradients, glassmorphism, анимации сохранены
+
+import { ShoppingCart } from 'lucide-react'
+
+import { Button } from '@/components/ui/button'
+import { Card } from '@/components/ui/card'
+import { Input } from '@/components/ui/input'
+
+import type { ShoppingCartBlockProps } from './types'
+
+export function ShoppingCartBlock({
+ selectedConsumables,
+ deliveryDate,
+ notes,
+ selectedLogistics,
+ logisticsPartners,
+ isCreatingSupply,
+ getTotalAmount,
+ getTotalItems,
+ formatCurrency,
+ onUpdateQuantity,
+ onSetDeliveryDate,
+ onSetNotes,
+ onSetLogistics,
+ onCreateSupply,
+}: ShoppingCartBlockProps) {
+ return (
+
+
+
+ Корзина ({getTotalItems()} шт)
+
+
+ {selectedConsumables.length === 0 ? (
+
+
+
+
+
Корзина пуста
+
Добавьте расходники для создания поставки
+
+ ) : (
+
+ {selectedConsumables.map((consumable) => (
+
+
+
{consumable.name}
+
+ {formatCurrency(consumable.price)} × {consumable.selectedQuantity}
+
+
+
+
+ {formatCurrency(consumable.price * consumable.selectedQuantity)}
+
+ onUpdateQuantity(consumable.id, 0)}
+ className="h-5 w-5 p-0 text-red-400 hover:text-red-300 hover:bg-red-500/10"
+ >
+ ×
+
+
+
+ ))}
+
+ )}
+
+
+
+ Дата поставки:
+ onSetDeliveryDate(e.target.value)}
+ className="bg-white/10 border-white/20 text-white h-8 text-sm"
+ min={new Date().toISOString().split('T')[0]}
+ required
+ />
+
+
+ {/* Выбор логистики */}
+
+
Логистика (опционально):
+
+
{
+ const logisticsId = e.target.value
+ const logistics = logisticsPartners.find((p: any) => p.id === logisticsId)
+ onSetLogistics(logistics || null)
+ }}
+ className="w-full bg-white/10 border border-white/20 rounded-md px-3 py-2 text-white text-sm focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-transparent appearance-none"
+ >
+
+ Выберите логистику
+
+ {logisticsPartners.map((partner: any) => (
+
+ {partner.name || partner.fullName || partner.inn}
+
+ ))}
+
+
+
+
+
+ {/* Заметки */}
+
+ Заметки (необязательно):
+
+
+
+ Итого:
+ {formatCurrency(getTotalAmount())}
+
+
+ {isCreatingSupply ? 'Создание...' : 'Создать поставку'}
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/SuppliersBlock.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/SuppliersBlock.tsx
new file mode 100644
index 0000000..9b714f2
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/SuppliersBlock.tsx
@@ -0,0 +1,143 @@
+// =============================================================================
+// 🏢 БЛОК ПОСТАВЩИКОВ
+// =============================================================================
+// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в монолитной версии!
+// Все gradients, glassmorphism, анимации сохранены
+
+import { Building2, Search } from 'lucide-react'
+
+import { OrganizationAvatar } from '@/components/market/organization-avatar'
+import { Button } from '@/components/ui/button'
+import { Card } from '@/components/ui/card'
+import { Input } from '@/components/ui/input'
+
+import type { FulfillmentConsumableSupplier } from '../types'
+
+import type { SuppliersBlockProps } from './types'
+
+export function SuppliersBlock({
+ suppliers,
+ filteredSuppliers,
+ selectedSupplier,
+ searchQuery,
+ loading,
+ onSelectSupplier,
+ onSearchChange,
+}: SuppliersBlockProps) {
+ return (
+
+
+
+
+
+ Поставщики расходников
+
+
+
+ onSearchChange(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 && (
+
onSelectSupplier(null)}
+ className="text-white/70 hover:text-white hover:bg-white/20 text-sm h-8 px-3 flex-shrink-0 rounded-full transition-all duration-300 hover:scale-105"
+ >
+ ✕ Сбросить
+
+ )}
+
+
+
+
+ {loading ? (
+
+
+
Загружаем поставщиков...
+
+ ) : filteredSuppliers.length === 0 ? (
+
+
+
+
+
+ {searchQuery ? 'Поставщики не найдены' : 'Добавьте поставщиков'}
+
+
+ ) : (
+
+ {filteredSuppliers.slice(0, 7).map((supplier: FulfillmentConsumableSupplier, index: number) => (
+
onSelectSupplier(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}
+
ещё
+
+ )}
+
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/index.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/index.ts
new file mode 100644
index 0000000..1a7b8de
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/index.ts
@@ -0,0 +1,18 @@
+// =============================================================================
+// 🧩 UI BLOCKS ДЛЯ СИСТЕМЫ СОЗДАНИЯ ПОСТАВОК РАСХОДНИКОВ ФУЛФИЛМЕНТА V2
+// =============================================================================
+// Эти блоки содержат весь UI, экстрагированный из монолитного компонента
+// с сохранением ТОЧНО ТАКОГО ЖЕ визуала в соответствии с модульной архитектурой
+
+export { PageHeader } from './PageHeader'
+export { SuppliersBlock } from './SuppliersBlock'
+export { ConsumablesBlock } from './ConsumablesBlock'
+export { ShoppingCartBlock } from './ShoppingCartBlock'
+
+// 🎯 Экспорт типов для блоков
+export type {
+ PageHeaderProps,
+ SuppliersBlockProps,
+ ConsumablesBlockProps,
+ ShoppingCartBlockProps,
+} from './types'
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/seller-blocks/SellerShoppingCartBlock.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/seller-blocks/SellerShoppingCartBlock.tsx
new file mode 100644
index 0000000..b72cb7b
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/seller-blocks/SellerShoppingCartBlock.tsx
@@ -0,0 +1,168 @@
+// =============================================================================
+// 🛒 БЛОК КОРЗИНЫ ДЛЯ СЕЛЛЕРА
+// =============================================================================
+// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в оригинале!
+// Отличие - показываем выбранный фулфилмент-центр и другие лейблы
+
+import { ShoppingCart, Building2 } from 'lucide-react'
+
+import { Button } from '@/components/ui/button'
+import { Card } from '@/components/ui/card'
+import { Input } from '@/components/ui/input'
+
+import type { SellerShoppingCartBlockProps } from '../types/seller-types'
+
+export function SellerShoppingCartBlock({
+ selectedConsumables,
+ selectedFulfillment,
+ deliveryDate,
+ notes,
+ selectedLogistics,
+ logisticsPartners,
+ isCreatingSupply,
+ getTotalAmount,
+ getTotalItems,
+ formatCurrency,
+ onUpdateQuantity,
+ onSetDeliveryDate,
+ onSetNotes,
+ onSetLogistics,
+ onCreateSupply,
+}: SellerShoppingCartBlockProps) {
+ return (
+
+
+
+ Заказ поставки ({getTotalItems()} шт)
+
+
+ {/* 🏢 ИНФОРМАЦИЯ О ВЫБРАННОМ ФУЛФИЛМЕНТ-ЦЕНТРЕ */}
+ {selectedFulfillment && (
+
+
+
+
+
Доставка в:
+
+ {selectedFulfillment.name || selectedFulfillment.fullName}
+
+
+
+
+ )}
+
+ {selectedConsumables.length === 0 ? (
+
+
+
+
+
Заказ пуст
+
Выберите поставщика и
+
добавьте расходники для заказа
+
+ ) : (
+
+ {selectedConsumables.map((consumable) => (
+
+
+
{consumable.name}
+
+ {formatCurrency(consumable.price)} × {consumable.selectedQuantity}
+
+
+
+
+ {formatCurrency(consumable.price * consumable.selectedQuantity)}
+
+ onUpdateQuantity(consumable.id, 0)}
+ className="h-5 w-5 p-0 text-red-400 hover:text-red-300 hover:bg-red-500/10"
+ >
+ ×
+
+
+
+ ))}
+
+ )}
+
+
+
+ Дата доставки в фулфилмент:
+ onSetDeliveryDate(e.target.value)}
+ className="bg-white/10 border-white/20 text-white h-8 text-sm"
+ min={new Date().toISOString().split('T')[0]}
+ required
+ />
+
+
+ {/* Выбор логистики */}
+
+
Логистика (опционально):
+
+
{
+ const logisticsId = e.target.value
+ const logistics = logisticsPartners.find((p: any) => p.id === logisticsId)
+ onSetLogistics(logistics || null)
+ }}
+ className="w-full bg-white/10 border border-white/20 rounded-md px-3 py-2 text-white text-sm focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-transparent appearance-none"
+ >
+
+ Выберите логистику
+
+ {logisticsPartners.map((partner: any) => (
+
+ {partner.name || partner.fullName || partner.inn}
+
+ ))}
+
+
+
+
+
+ {/* Заметки */}
+
+ Заметки для поставки (необязательно):
+
+
+
+ Итого:
+ {formatCurrency(getTotalAmount())}
+
+
+
+ {isCreatingSupply ? 'Создание заказа...' : 'Заказать поставку'}
+
+
+ {/* Подсказка для селлера */}
+ {selectedConsumables.length > 0 && !selectedFulfillment && (
+
+ Выберите фулфилмент-центр для доставки
+
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/seller-blocks/SellerSuppliersBlock.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/seller-blocks/SellerSuppliersBlock.tsx
new file mode 100644
index 0000000..de54986
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/seller-blocks/SellerSuppliersBlock.tsx
@@ -0,0 +1,181 @@
+// =============================================================================
+// 🏪 БЛОК ВЫБОРА ПОСТАВЩИКОВ И ФУЛФИЛМЕНТ-ЦЕНТРОВ ДЛЯ СЕЛЛЕРА
+// =============================================================================
+// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в оригинале!
+// Отличие только в логике - селлер выбирает и поставщика, и фулфилмент-центр
+
+import { Building2, Search } from 'lucide-react'
+
+import { OrganizationAvatar } from '@/components/market/organization-avatar'
+import { Card } from '@/components/ui/card'
+import { Input } from '@/components/ui/input'
+
+import type { SellerSuppliersBlockProps } from '../types/seller-types'
+
+export function SellerSuppliersBlock({
+ suppliers,
+ fulfillmentCenters,
+ filteredSuppliers,
+ filteredFulfillmentCenters,
+ selectedSupplier,
+ selectedFulfillment,
+ searchQuery,
+ loading,
+ onSelectSupplier,
+ onSelectFulfillment,
+ onSearchChange,
+}: SellerSuppliersBlockProps) {
+
+ return (
+
+
+
+ Партнеры ({suppliers.length + fulfillmentCenters.length})
+
+
+ {/* 🔍 Поиск партнеров */}
+
+
+ onSearchChange(e.target.value)}
+ className="pl-10 bg-white/10 border-white/20 text-white placeholder-white/40 h-8 text-sm"
+ />
+
+
+ {loading ? (
+
+
Загрузка партнеров...
+
+ ) : (
+
+
+ {/* 🏪 СЕКЦИЯ ПОСТАВЩИКОВ */}
+
+
+ Поставщики расходников ({filteredSuppliers.length})
+
+
+
+ {filteredSuppliers.length === 0 ? (
+
+
+
+ {searchQuery ? 'Поставщики не найдены' : 'Нет поставщиков'}
+
+
+ ) : (
+ filteredSuppliers.map((supplier, index) => (
+
onSelectSupplier(supplier)}
+ className={`
+ p-3 rounded-lg cursor-pointer transition-all duration-200 border
+ ${
+ selectedSupplier?.id === supplier.id
+ ? 'bg-gradient-to-br from-blue-500/30 to-purple-500/30 border-blue-400/50 shadow-lg'
+ : 'bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20'
+ }
+ `}
+ style={{
+ animationDelay: `${index * 50}ms`,
+ }}
+ >
+
+
+
+
+ {supplier.name || supplier.fullName || supplier.inn}
+
+
ИНН: {supplier.inn}
+ {supplier.address && (
+
{supplier.address}
+ )}
+
+ {selectedSupplier?.id === supplier.id && (
+
+ )}
+
+
+ ))
+ )}
+
+
+
+ {/* 🏢 СЕКЦИЯ ФУЛФИЛМЕНТ-ЦЕНТРОВ */}
+
+
+ Фулфилмент-центры ({filteredFulfillmentCenters.length})
+
+
+
+ {filteredFulfillmentCenters.length === 0 ? (
+
+
+
+ {searchQuery ? 'Фулфилмент-центры не найдены' : 'Нет фулфилмент-центров'}
+
+
+ ) : (
+ filteredFulfillmentCenters.map((fulfillment, index) => (
+
onSelectFulfillment(fulfillment)}
+ className={`
+ p-3 rounded-lg cursor-pointer transition-all duration-200 border
+ ${
+ selectedFulfillment?.id === fulfillment.id
+ ? 'bg-gradient-to-br from-green-500/30 to-emerald-500/30 border-green-400/50 shadow-lg'
+ : 'bg-white/5 border-white/10 hover:bg-white/10 hover:border-white/20'
+ }
+ `}
+ style={{
+ animationDelay: `${(index + filteredSuppliers.length) * 50}ms`,
+ }}
+ >
+
+
+
+
+ {fulfillment.name || fulfillment.fullName || fulfillment.inn}
+
+
ИНН: {fulfillment.inn}
+ {fulfillment.address && (
+
{fulfillment.address}
+ )}
+
+ {selectedFulfillment?.id === fulfillment.id && (
+
+ )}
+
+
+ ))
+ )}
+
+
+
+ )}
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/seller-blocks/index.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/seller-blocks/index.ts
new file mode 100644
index 0000000..faab97b
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/seller-blocks/index.ts
@@ -0,0 +1,6 @@
+// =============================================================================
+// 📦 ЭКСПОРТ СЕЛЛЕРСКИХ UI БЛОКОВ
+// =============================================================================
+
+export { SellerSuppliersBlock } from './SellerSuppliersBlock'
+export { SellerShoppingCartBlock } from './SellerShoppingCartBlock'
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/types.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/types.ts
new file mode 100644
index 0000000..9b76093
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/blocks/types.ts
@@ -0,0 +1,55 @@
+// =============================================================================
+// 🧩 ТИПЫ ДЛЯ UI BLOCKS
+// =============================================================================
+
+import type {
+ FulfillmentConsumableSupplier,
+ FulfillmentConsumableProduct,
+ SelectedFulfillmentConsumable,
+} from '../types'
+
+// 📄 ТИПЫ ДЛЯ ЗАГОЛОВКА СТРАНИЦЫ
+export interface PageHeaderProps {
+ onBack: () => void
+}
+
+// 🏢 ТИПЫ ДЛЯ БЛОКА ПОСТАВЩИКОВ
+export interface SuppliersBlockProps {
+ suppliers: FulfillmentConsumableSupplier[]
+ filteredSuppliers: FulfillmentConsumableSupplier[]
+ selectedSupplier: FulfillmentConsumableSupplier | null
+ searchQuery: string
+ loading: boolean
+ onSelectSupplier: (supplier: FulfillmentConsumableSupplier | null) => void
+ onSearchChange: (query: string) => void
+}
+
+// 📦 ТИПЫ ДЛЯ БЛОКА РАСХОДНИКОВ
+export interface ConsumablesBlockProps {
+ selectedSupplier: FulfillmentConsumableSupplier | null
+ products: FulfillmentConsumableProduct[]
+ productsLoading: boolean
+ productSearchQuery: string
+ getSelectedQuantity: (productId: string) => number
+ onProductSearchChange: (query: string) => void
+ onUpdateQuantity: (productId: string, quantity: number) => void
+ formatCurrency: (amount: number) => string
+}
+
+// 🛒 ТИПЫ ДЛЯ БЛОКА КОРЗИНЫ
+export interface ShoppingCartBlockProps {
+ selectedConsumables: SelectedFulfillmentConsumable[]
+ deliveryDate: string
+ notes: string
+ selectedLogistics: FulfillmentConsumableSupplier | null
+ logisticsPartners: FulfillmentConsumableSupplier[]
+ isCreatingSupply: boolean
+ getTotalAmount: () => number
+ getTotalItems: () => number
+ formatCurrency: (amount: number) => string
+ onUpdateQuantity: (productId: string, quantity: number) => void
+ onSetDeliveryDate: (date: string) => void
+ onSetNotes: (notes: string) => void
+ onSetLogistics: (logistics: FulfillmentConsumableSupplier | null) => void
+ onCreateSupply: () => void
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/index.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/index.ts
new file mode 100644
index 0000000..2044ab5
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/index.ts
@@ -0,0 +1,22 @@
+// =============================================================================
+// 🔧 BUSINESS HOOKS ДЛЯ СИСТЕМЫ СОЗДАНИЯ ПОСТАВОК РАСХОДНИКОВ ФУЛФИЛМЕНТА V2
+// =============================================================================
+// Эти хуки содержат всю бизнес-логику, экстрагированную из монолитного компонента
+// в соответствии с правилами модульной архитектуры MODULAR_ARCHITECTURE_PATTERN.md
+
+export { useSupplierData } from './useSupplierData'
+export { useProductData } from './useProductData'
+export { useSupplyForm } from './useSupplyForm'
+export { useQuantityManagement } from './useQuantityManagement'
+export { useSupplyCreation } from './useSupplyCreation'
+export { useCurrencyFormatting } from './useCurrencyFormatting'
+export { useStockValidation } from './useStockValidation'
+
+// 🎯 Экспорт типов для хуков
+export type {
+ UseSupplierDataReturn,
+ UseProductDataReturn,
+ UseSupplyFormReturn,
+ UseQuantityManagementReturn,
+ UseSupplyCreationReturn,
+} from './types'
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/index.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/index.ts
new file mode 100644
index 0000000..999eeb2
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/index.ts
@@ -0,0 +1,7 @@
+// =============================================================================
+// 📦 ЭКСПОРТ СЕЛЛЕРСКИХ БИЗНЕС ХУКОВ
+// =============================================================================
+
+export { useSellerSupplyCreation } from './useSellerSupplyCreation'
+export { useSellerSupplyForm } from './useSellerSupplyForm'
+export { useSellerSupplierData } from './useSellerSupplierData'
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/useSellerSupplierData.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/useSellerSupplierData.ts
new file mode 100644
index 0000000..14cc1d2
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/useSellerSupplierData.ts
@@ -0,0 +1,90 @@
+// =============================================================================
+// 🏢 ХУК ДЛЯ ПОЛУЧЕНИЯ ДАННЫХ ПОСТАВЩИКОВ И ФУЛФИЛМЕНТ-ЦЕНТРОВ (ДЛЯ СЕЛЛЕРА)
+// =============================================================================
+
+import { useQuery } from '@apollo/client'
+import { useMemo } from 'react'
+
+import { GET_MY_COUNTERPARTIES } from '@/graphql/queries'
+
+import type {
+ CounterpartiesData,
+ SellerConsumableSupplier,
+ UseSupplierDataReturn,
+} from '../types'
+
+interface UseSellerSupplierDataProps {
+ searchQuery: string
+}
+
+interface UseSellerSupplierDataReturn {
+ // Поставщики (WHOLESALE)
+ suppliers: SellerConsumableSupplier[]
+ filteredSuppliers: SellerConsumableSupplier[]
+
+ // Фулфилмент-центры (FULFILLMENT)
+ fulfillmentCenters: SellerConsumableSupplier[]
+ filteredFulfillmentCenters: SellerConsumableSupplier[]
+
+ // Логистические партнеры (LOGIST)
+ logisticsPartners: SellerConsumableSupplier[]
+
+ // Состояние загрузки и ошибки
+ loading: boolean
+ error: Error | null
+}
+
+export function useSellerSupplierData({
+ searchQuery,
+}: UseSellerSupplierDataProps): UseSellerSupplierDataReturn {
+
+ // 🔄 ЗАГРУЗКА КОНТРАГЕНТОВ
+ const { data, loading, error } = useQuery(GET_MY_COUNTERPARTIES)
+
+ // 📊 МЕМОИЗИРОВАННАЯ ОБРАБОТКА ДАННЫХ
+ const processedData = useMemo(() => {
+ const allPartners = data?.myCounterparties || []
+
+ // 🏪 ПОСТАВЩИКИ (где селлер заказывает товары)
+ const suppliers = allPartners.filter(partner => partner.type === 'WHOLESALE')
+
+ // 🏢 ФУЛФИЛМЕНТ-ЦЕНТРЫ (куда селлер доставляет товары)
+ const fulfillmentCenters = allPartners.filter(partner => partner.type === 'FULFILLMENT')
+
+ // 🚚 ЛОГИСТИЧЕСКИЕ ПАРТНЕРЫ (кто доставляет)
+ const logisticsPartners = allPartners.filter(partner => partner.type === 'LOGIST')
+
+ // 🔍 ФИЛЬТРАЦИЯ ПО ПОИСКУ
+ const searchLower = searchQuery.toLowerCase().trim()
+
+ const filteredSuppliers = searchLower
+ ? suppliers.filter(supplier =>
+ supplier.name?.toLowerCase().includes(searchLower) ||
+ supplier.fullName?.toLowerCase().includes(searchLower) ||
+ supplier.inn.includes(searchLower),
+ )
+ : suppliers
+
+ const filteredFulfillmentCenters = searchLower
+ ? fulfillmentCenters.filter(ff =>
+ ff.name?.toLowerCase().includes(searchLower) ||
+ ff.fullName?.toLowerCase().includes(searchLower) ||
+ ff.inn.includes(searchLower),
+ )
+ : fulfillmentCenters
+
+ return {
+ suppliers,
+ fulfillmentCenters,
+ logisticsPartners,
+ filteredSuppliers,
+ filteredFulfillmentCenters,
+ }
+ }, [data?.myCounterparties, searchQuery])
+
+ return {
+ ...processedData,
+ loading,
+ error: error || null,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/useSellerSupplyCreation.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/useSellerSupplyCreation.ts
new file mode 100644
index 0000000..4ef2451
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/useSellerSupplyCreation.ts
@@ -0,0 +1,124 @@
+// =============================================================================
+// 🚀 ХУК ДЛЯ СОЗДАНИЯ ПОСТАВКИ СЕЛЛЕРА
+// =============================================================================
+
+import { useMutation } from '@apollo/client'
+import { useRouter } from 'next/navigation'
+import { useState, useCallback } from 'react'
+import { toast } from 'sonner'
+
+import {
+ CREATE_SELLER_CONSUMABLE_SUPPLY,
+ GET_MY_SELLER_CONSUMABLE_SUPPLIES,
+} from '@/graphql/queries/seller-consumables-v2'
+
+import type {
+ SellerConsumableSupplier,
+ SelectedSellerConsumable,
+ SellerSupplyCreationInput,
+ CreateSellerSupplyMutationResponse,
+ UseSellerSupplyCreationReturn,
+ UseSellerSupplyCreationProps,
+} from '../types/seller-types'
+
+export function useSellerSupplyCreation({
+ selectedSupplier,
+ selectedFulfillment,
+ selectedConsumables,
+ deliveryDate,
+ notes,
+ resetForm,
+}: UseSellerSupplyCreationProps): UseSellerSupplyCreationReturn {
+ const router = useRouter()
+ const [isCreating, setIsCreating] = useState(false)
+ const [error, setError] = useState(null)
+
+ // Мутация для создания заказа поставки расходников селлера
+ const [createSupplyMutation] = useMutation(CREATE_SELLER_CONSUMABLE_SUPPLY)
+
+ // Функция создания поставки
+ const createSupply = useCallback(async () => {
+ // 🔍 ВАЛИДАЦИЯ ОБЯЗАТЕЛЬНЫХ ПОЛЕЙ ДЛЯ СЕЛЛЕРА
+ if (!selectedSupplier || !selectedFulfillment || selectedConsumables.length === 0 || !deliveryDate) {
+ const errorMessage = 'Заполните все обязательные поля: поставщик, фулфилмент-центр, расходники и дата доставки'
+ setError(errorMessage)
+ toast.error(errorMessage)
+ return
+ }
+
+ setIsCreating(true)
+ setError(null)
+
+ try {
+ // 📊 ПОДГОТОВКА ДАННЫХ ДЛЯ СОЗДАНИЯ ПОСТАВКИ
+ const input: SellerSupplyCreationInput = {
+ fulfillmentCenterId: selectedFulfillment.id, // 🏢 Куда доставлять
+ supplierId: selectedSupplier.id, // 🏪 От кого заказывать
+ requestedDeliveryDate: deliveryDate, // 📅 Когда нужно
+ items: selectedConsumables.map((consumable) => ({
+ productId: consumable.id,
+ requestedQuantity: consumable.selectedQuantity,
+ })),
+ notes: notes || undefined,
+ }
+
+ console.log('🚀 Создание поставки селлера с данными:', input)
+
+ // 🔄 ВЫПОЛНЕНИЕ МУТАЦИИ
+ const result = await createSupplyMutation({
+ variables: { input },
+ refetchQueries: [
+ {
+ query: GET_MY_SELLER_CONSUMABLE_SUPPLIES,
+ },
+ ],
+ awaitRefetchQueries: true,
+ })
+
+ const response = result.data?.createSellerConsumableSupply
+
+ if (response?.success) {
+ // ✅ УСПЕШНОЕ СОЗДАНИЕ
+ toast.success(response.message || 'Поставка успешно создана')
+
+ // Очищаем форму
+ resetForm()
+
+ // Переходим к списку поставок селлера
+ // TODO: Создать маршрут для списка поставок селлера
+ // router.push('/seller-supplies')
+
+ console.log('✅ Поставка селлера создана:', response.supplyOrder)
+ } else {
+ // ❌ ОШИБКА ИЗ СЕРВЕРА
+ const errorMessage = response?.message || 'Ошибка создания поставки'
+ setError(errorMessage)
+ toast.error(errorMessage)
+ }
+ } catch (err: any) {
+ // ❌ ОШИБКА ВЫПОЛНЕНИЯ
+ console.error('❌ Ошибка создания поставки селлера:', err)
+
+ const errorMessage = err?.message || err?.graphQLErrors?.[0]?.message || 'Неизвестная ошибка при создании поставки'
+ setError(errorMessage)
+ toast.error(errorMessage)
+ } finally {
+ setIsCreating(false)
+ }
+ }, [
+ selectedSupplier,
+ selectedFulfillment,
+ selectedConsumables,
+ deliveryDate,
+ notes,
+ createSupplyMutation,
+ resetForm,
+ router,
+ ])
+
+ return {
+ createSupply,
+ isCreating,
+ error,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/useSellerSupplyForm.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/useSellerSupplyForm.ts
new file mode 100644
index 0000000..2bc6d44
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/seller-hooks/useSellerSupplyForm.ts
@@ -0,0 +1,93 @@
+// =============================================================================
+// 📋 ХУК ДЛЯ УПРАВЛЕНИЯ ФОРМОЙ ПОСТАВКИ СЕЛЛЕРА
+// =============================================================================
+
+import { useState, useCallback } from 'react'
+
+import type {
+ SellerConsumableSupplier,
+ SelectedSellerConsumable,
+ UseSellerSupplyFormReturn,
+} from '../types/seller-types'
+
+export function useSellerSupplyForm(): UseSellerSupplyFormReturn {
+ // 🏪 СОСТОЯНИЕ ПОСТАВЩИКА
+ const [selectedSupplier, setSelectedSupplier] = useState(null)
+
+ // 🏢 СОСТОЯНИЕ ФУЛФИЛМЕНТ-ЦЕНТРА (специфично для селлера)
+ const [selectedFulfillment, setSelectedFulfillment] = useState(null)
+
+ // 🚚 СОСТОЯНИЕ ЛОГИСТИКИ
+ const [selectedLogistics, setSelectedLogistics] = useState(null)
+
+ // 📦 СОСТОЯНИЕ ВЫБРАННЫХ РАСХОДНИКОВ
+ const [selectedConsumables, setSelectedConsumables] = useState([])
+
+ // 🔍 СОСТОЯНИЕ ПОИСКА
+ const [searchQuery, setSearchQuery] = useState('')
+ const [productSearchQuery, setProductSearchQuery] = useState('')
+
+ // 📅 СОСТОЯНИЕ ДАТЫ ДОСТАВКИ
+ const [deliveryDate, setDeliveryDate] = useState('')
+
+ // 📝 СОСТОЯНИЕ ЗАМЕТОК
+ const [notes, setNotes] = useState('')
+
+ // 🔄 ФУНКЦИЯ СБРОСА ФОРМЫ
+ const resetForm = useCallback(() => {
+ setSelectedSupplier(null)
+ setSelectedFulfillment(null)
+ setSelectedLogistics(null)
+ setSelectedConsumables([])
+ setSearchQuery('')
+ setProductSearchQuery('')
+ setDeliveryDate('')
+ setNotes('')
+ }, [])
+
+ // 🔧 СПЕЦИАЛЬНЫЕ СЕТТЕРЫ С ОЧИСТКОЙ ЗАВИСИМЫХ ПОЛЕЙ
+
+ const handleSetSelectedSupplier = useCallback((supplier: SellerConsumableSupplier | null) => {
+ setSelectedSupplier(supplier)
+
+ // При смене поставщика очищаем выбранные товары
+ setSelectedConsumables([])
+ setProductSearchQuery('')
+ }, [])
+
+ const handleSetSelectedFulfillment = useCallback((fulfillment: SellerConsumableSupplier | null) => {
+ setSelectedFulfillment(fulfillment)
+
+ // При смене фулфилмента можем очистить логистику если нужно
+ // setSelectedLogistics(null)
+ }, [])
+
+ const handleSetSelectedLogistics = useCallback((logistics: SellerConsumableSupplier | null) => {
+ setSelectedLogistics(logistics)
+ }, [])
+
+ return {
+ // Состояние
+ selectedSupplier,
+ selectedFulfillment,
+ selectedLogistics,
+ selectedConsumables,
+ searchQuery,
+ productSearchQuery,
+ deliveryDate,
+ notes,
+
+ // Сеттеры
+ setSelectedSupplier: handleSetSelectedSupplier,
+ setSelectedFulfillment: handleSetSelectedFulfillment,
+ setSelectedLogistics: handleSetSelectedLogistics,
+ setSelectedConsumables,
+ setSearchQuery,
+ setProductSearchQuery,
+ setDeliveryDate,
+ setNotes,
+
+ // Утилиты
+ resetForm,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/types.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/types.ts
new file mode 100644
index 0000000..b9ad61c
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/types.ts
@@ -0,0 +1,65 @@
+// =============================================================================
+// 🔧 ТИПЫ ДЛЯ BUSINESS HOOKS
+// =============================================================================
+
+import type {
+ FulfillmentConsumableSupplier,
+ FulfillmentConsumableProduct,
+ SelectedFulfillmentConsumable,
+} from '../types'
+
+// 🏢 ТИПЫ ДЛЯ ХУКА ПОСТАВЩИКОВ
+export interface UseSupplierDataReturn {
+ suppliers: FulfillmentConsumableSupplier[]
+ logisticsPartners: FulfillmentConsumableSupplier[]
+ filteredSuppliers: FulfillmentConsumableSupplier[]
+ loading: boolean
+ error: Error | null
+}
+
+// 📦 ТИПЫ ДЛЯ ХУКА ТОВАРОВ
+export interface UseProductDataReturn {
+ products: FulfillmentConsumableProduct[]
+ loading: boolean
+ error: Error | null
+ refetch: () => void
+}
+
+// 📋 ТИПЫ ДЛЯ ХУКА ФОРМЫ
+export interface UseSupplyFormReturn {
+ // Состояние формы
+ selectedSupplier: FulfillmentConsumableSupplier | null
+ selectedLogistics: FulfillmentConsumableSupplier | null
+ selectedConsumables: SelectedFulfillmentConsumable[]
+ searchQuery: string
+ productSearchQuery: string
+ deliveryDate: string
+ notes: string
+
+ // Действия формы
+ setSelectedSupplier: (supplier: FulfillmentConsumableSupplier | null) => void
+ setSelectedLogistics: (logistics: FulfillmentConsumableSupplier | null) => void
+ setSelectedConsumables: (consumables: SelectedFulfillmentConsumable[]) => void
+ setSearchQuery: (query: string) => void
+ setProductSearchQuery: (query: string) => void
+ setDeliveryDate: (date: string) => void
+ setNotes: (notes: string) => void
+
+ // Сброс формы
+ resetForm: () => void
+}
+
+// 📊 ТИПЫ ДЛЯ ХУКА УПРАВЛЕНИЯ КОЛИЧЕСТВОМ
+export interface UseQuantityManagementReturn {
+ updateConsumableQuantity: (productId: string, quantity: number) => void
+ getSelectedQuantity: (productId: string) => number
+ getTotalAmount: () => number
+ getTotalItems: () => number
+}
+
+// 🚀 ТИПЫ ДЛЯ ХУКА СОЗДАНИЯ ПОСТАВКИ
+export interface UseSupplyCreationReturn {
+ createSupply: () => Promise
+ isCreating: boolean
+ error: string | null
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useCurrencyFormatting.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useCurrencyFormatting.ts
new file mode 100644
index 0000000..afbb651
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useCurrencyFormatting.ts
@@ -0,0 +1,20 @@
+// =============================================================================
+// 💰 ХУК ДЛЯ ФОРМАТИРОВАНИЯ ВАЛЮТЫ
+// =============================================================================
+
+import { useCallback } from 'react'
+
+export function useCurrencyFormatting() {
+ // Форматирование валюты в рублях
+ const formatCurrency = useCallback((amount: number): string => {
+ return new Intl.NumberFormat('ru-RU', {
+ style: 'currency',
+ currency: 'RUB',
+ minimumFractionDigits: 0,
+ }).format(amount)
+ }, [])
+
+ return {
+ formatCurrency,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useProductData.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useProductData.ts
new file mode 100644
index 0000000..2aa2dfa
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useProductData.ts
@@ -0,0 +1,65 @@
+// =============================================================================
+// 📦 ХУК ДЛЯ УПРАВЛЕНИЯ ДАННЫМИ ТОВАРОВ
+// =============================================================================
+
+import { useQuery } from '@apollo/client'
+import { useMemo } from 'react'
+
+import { GET_ORGANIZATION_PRODUCTS } from '@/graphql/queries'
+
+import type {
+ FulfillmentConsumableSupplier,
+ ProductsData,
+ UseProductDataReturn,
+ GraphQLQueryVariables,
+} from '../types'
+
+interface UseProductDataProps {
+ selectedSupplier: FulfillmentConsumableSupplier | null
+ productSearchQuery: string
+}
+
+export function useProductData({
+ selectedSupplier,
+ productSearchQuery,
+}: UseProductDataProps): UseProductDataReturn {
+ // Стабилизируем переменные для useQuery
+ const queryVariables = useMemo((): GraphQLQueryVariables => {
+ return {
+ organizationId: selectedSupplier?.id || '',
+ search: productSearchQuery || null,
+ category: null,
+ type: 'CONSUMABLE' as const, // Фильтруем только расходники согласно GraphQL правилам
+ }
+ }, [selectedSupplier?.id, productSearchQuery])
+
+ // Загружаем товары для выбранного поставщика с фильтрацией по типу CONSUMABLE
+ const {
+ data,
+ loading,
+ error,
+ refetch,
+ } = useQuery(GET_ORGANIZATION_PRODUCTS, {
+ skip: !selectedSupplier?.id, // Не запрашиваем без выбранного поставщика
+ variables: queryVariables,
+ onCompleted: (data) => {
+ // Логируем только количество загруженных товаров
+ console.warn(`📦 Загружено товаров: ${data?.organizationProducts?.length || 0}`)
+ },
+ onError: (error) => {
+ console.error('❌ GET_ORGANIZATION_PRODUCTS ERROR:', error)
+ },
+ })
+
+ // Получаем товары поставщика (уже отфильтрованы в GraphQL запросе по типу CONSUMABLE)
+ const products = useMemo(() => {
+ return data?.organizationProducts || []
+ }, [data?.organizationProducts])
+
+ return {
+ products,
+ loading,
+ error: error || null,
+ refetch,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useQuantityManagement.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useQuantityManagement.ts
new file mode 100644
index 0000000..e341bde
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useQuantityManagement.ts
@@ -0,0 +1,96 @@
+// =============================================================================
+// 📊 ХУК ДЛЯ УПРАВЛЕНИЯ КОЛИЧЕСТВОМ ТОВАРОВ
+// =============================================================================
+
+import { useCallback } from 'react'
+import { toast } from 'sonner'
+
+import type {
+ FulfillmentConsumableSupplier,
+ FulfillmentConsumableProduct,
+ SelectedFulfillmentConsumable,
+ UseQuantityManagementReturn,
+} from '../types'
+
+interface UseQuantityManagementProps {
+ selectedSupplier: FulfillmentConsumableSupplier | null
+ selectedConsumables: SelectedFulfillmentConsumable[]
+ products: FulfillmentConsumableProduct[]
+ setSelectedConsumables: (updater: SelectedFulfillmentConsumable[] | ((prev: SelectedFulfillmentConsumable[]) => SelectedFulfillmentConsumable[])) => void
+}
+
+export function useQuantityManagement({
+ selectedSupplier,
+ selectedConsumables,
+ products,
+ setSelectedConsumables,
+}: UseQuantityManagementProps): UseQuantityManagementReturn {
+
+ // Обновление количества выбранного расходника
+ const updateConsumableQuantity = useCallback((productId: string, quantity: number) => {
+ const product = products.find((p: FulfillmentConsumableProduct) => p.id === productId)
+ if (!product || !selectedSupplier) return
+
+ // 🔒 ВАЛИДАЦИЯ ОСТАТКОВ согласно бизнес-правилам (раздел 6.2)
+ if (quantity > 0) {
+ const availableStock = (product.stock || product.quantity || 0) - (product.ordered || 0)
+
+ if (quantity > availableStock) {
+ toast.error(`❌ Недостаточно остатков!\nДоступно: ${availableStock} шт.\nЗапрашивается: ${quantity} шт.`)
+ return
+ }
+ }
+
+ setSelectedConsumables((prev: SelectedFulfillmentConsumable[]) => {
+ const existing = prev.find((p: SelectedFulfillmentConsumable) => p.id === productId)
+
+ if (quantity === 0) {
+ // Удаляем расходник если количество 0
+ return prev.filter((p: SelectedFulfillmentConsumable) => p.id !== productId)
+ }
+
+ if (existing) {
+ // Обновляем количество существующего расходника
+ return prev.map((p: SelectedFulfillmentConsumable) => (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 || 'Поставщик',
+ },
+ ]
+ }
+ })
+ }, [products, selectedSupplier, setSelectedConsumables])
+
+ // Получение выбранного количества для товара
+ const getSelectedQuantity = useCallback((productId: string): number => {
+ const selected = selectedConsumables.find((p) => p.id === productId)
+ return selected ? selected.selectedQuantity : 0
+ }, [selectedConsumables])
+
+ // Расчет общей стоимости
+ const getTotalAmount = useCallback(() => {
+ return selectedConsumables.reduce((sum, consumable) => sum + consumable.price * consumable.selectedQuantity, 0)
+ }, [selectedConsumables])
+
+ // Расчет общего количества товаров
+ const getTotalItems = useCallback(() => {
+ return selectedConsumables.reduce((sum, consumable) => sum + consumable.selectedQuantity, 0)
+ }, [selectedConsumables])
+
+ return {
+ updateConsumableQuantity,
+ getSelectedQuantity,
+ getTotalAmount,
+ getTotalItems,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useStockValidation.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useStockValidation.ts
new file mode 100644
index 0000000..5ad56a0
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useStockValidation.ts
@@ -0,0 +1,76 @@
+// =============================================================================
+// 📊 ХУК ДЛЯ ВАЛИДАЦИИ ОСТАТКОВ
+// =============================================================================
+
+import { useCallback } from 'react'
+
+import type {
+ FulfillmentConsumableProduct,
+ StockCalculation,
+ StockValidationResult,
+} from '../types'
+
+export function useStockValidation() {
+ // Расчет остатков товара
+ const calculateStock = useCallback((product: FulfillmentConsumableProduct): StockCalculation => {
+ const totalStock = product.stock || product.quantity || 0
+ const orderedStock = product.ordered || 0
+ const availableStock = totalStock - orderedStock
+
+ return {
+ totalStock,
+ orderedStock,
+ availableStock,
+ }
+ }, [])
+
+ // Валидация запрашиваемого количества
+ const validateQuantity = useCallback((
+ product: FulfillmentConsumableProduct,
+ requestedQuantity: number,
+ ): StockValidationResult => {
+ const { availableStock } = calculateStock(product)
+
+ if (requestedQuantity <= 0) {
+ return {
+ isValid: true,
+ availableStock,
+ requestedQuantity,
+ }
+ }
+
+ if (requestedQuantity > availableStock) {
+ return {
+ isValid: false,
+ availableStock,
+ requestedQuantity,
+ error: `❌ Недостаточно остатков!\nДоступно: ${availableStock} шт.\nЗапрашивается: ${requestedQuantity} шт.`,
+ }
+ }
+
+ return {
+ isValid: true,
+ availableStock,
+ requestedQuantity,
+ }
+ }, [calculateStock])
+
+ // Определение статуса остатков для UI
+ const getStockStatus = useCallback((product: FulfillmentConsumableProduct): 'out_of_stock' | 'low_stock' | 'in_stock' => {
+ const { availableStock } = calculateStock(product)
+
+ if (availableStock <= 0) {
+ return 'out_of_stock'
+ } else if (availableStock <= 10) {
+ return 'low_stock'
+ }
+
+ return 'in_stock'
+ }, [calculateStock])
+
+ return {
+ calculateStock,
+ validateQuantity,
+ getStockStatus,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useSupplierData.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useSupplierData.ts
new file mode 100644
index 0000000..9667db4
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useSupplierData.ts
@@ -0,0 +1,59 @@
+// =============================================================================
+// 🏢 ХУК ДЛЯ УПРАВЛЕНИЯ ДАННЫМИ ПОСТАВЩИКОВ
+// =============================================================================
+
+import { useQuery } from '@apollo/client'
+import { useMemo } from 'react'
+
+import { GET_MY_COUNTERPARTIES } from '@/graphql/queries'
+
+import type {
+ FulfillmentConsumableSupplier,
+ CounterpartiesData,
+ UseSupplierDataReturn,
+} from '../types'
+
+interface UseSupplierDataProps {
+ searchQuery: string
+}
+
+export function useSupplierData({ searchQuery }: UseSupplierDataProps): UseSupplierDataReturn {
+ // Загружаем контрагентов-поставщиков расходников
+ const { data, loading, error } = useQuery(GET_MY_COUNTERPARTIES)
+
+ // Фильтруем только поставщиков расходников (WHOLESALE)
+ const suppliers = useMemo(() => {
+ return (data?.myCounterparties || []).filter(
+ (org: FulfillmentConsumableSupplier) => org.type === 'WHOLESALE',
+ )
+ }, [data?.myCounterparties])
+
+ // Фильтруем только логистические компании (LOGIST)
+ const logisticsPartners = useMemo(() => {
+ return (data?.myCounterparties || []).filter(
+ (org: FulfillmentConsumableSupplier) => org.type === 'LOGIST',
+ )
+ }, [data?.myCounterparties])
+
+ // Фильтруем поставщиков по поисковому запросу
+ const filteredSuppliers = useMemo(() => {
+ if (!searchQuery.trim()) {
+ return suppliers
+ }
+
+ const query = searchQuery.toLowerCase()
+ return suppliers.filter((supplier: FulfillmentConsumableSupplier) =>
+ supplier.name?.toLowerCase().includes(query) ||
+ supplier.fullName?.toLowerCase().includes(query) ||
+ supplier.inn?.toLowerCase().includes(query),
+ )
+ }, [suppliers, searchQuery])
+
+ return {
+ suppliers,
+ logisticsPartners,
+ filteredSuppliers,
+ loading,
+ error: error || null,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useSupplyCreation.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useSupplyCreation.ts
new file mode 100644
index 0000000..02bdb34
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useSupplyCreation.ts
@@ -0,0 +1,112 @@
+// =============================================================================
+// 🚀 ХУК ДЛЯ СОЗДАНИЯ ПОСТАВКИ
+// =============================================================================
+
+import { useMutation } from '@apollo/client'
+import { useRouter } from 'next/navigation'
+import { useState, useCallback } from 'react'
+import { toast } from 'sonner'
+
+import {
+ CREATE_FULFILLMENT_CONSUMABLE_SUPPLY,
+ GET_MY_FULFILLMENT_CONSUMABLE_SUPPLIES,
+} from '@/graphql/queries/fulfillment-consumables-v2'
+
+import type {
+ FulfillmentConsumableSupplier,
+ SelectedFulfillmentConsumable,
+ SupplyCreationInput,
+ CreateSupplyMutationResponse,
+ UseSupplyCreationReturn,
+} from '../types'
+
+interface UseSupplyCreationProps {
+ selectedSupplier: FulfillmentConsumableSupplier | null
+ selectedConsumables: SelectedFulfillmentConsumable[]
+ selectedLogistics: any | null // Добавляем логистику
+ deliveryDate: string
+ notes: string
+ resetForm: () => void
+}
+
+export function useSupplyCreation({
+ selectedSupplier,
+ selectedConsumables,
+ selectedLogistics,
+ deliveryDate,
+ notes,
+ resetForm,
+}: UseSupplyCreationProps): UseSupplyCreationReturn {
+ const router = useRouter()
+ const [isCreating, setIsCreating] = useState(false)
+ const [error, setError] = useState(null)
+
+ // Мутация для создания заказа поставки расходников v2
+ const [createSupplyMutation] = useMutation(CREATE_FULFILLMENT_CONSUMABLE_SUPPLY)
+
+ // Функция создания поставки
+ const createSupply = useCallback(async () => {
+ // Валидация обязательных полей
+ if (!selectedSupplier || selectedConsumables.length === 0 || !deliveryDate) {
+ const errorMessage = 'Заполните все обязательные поля: поставщик, расходники и дата доставки'
+ setError(errorMessage)
+ toast.error(errorMessage)
+ return
+ }
+
+ setIsCreating(true)
+ setError(null)
+
+ try {
+ // Формируем input для системы v2
+ const input: SupplyCreationInput = {
+ supplierId: selectedSupplier.id,
+ logisticsPartnerId: selectedLogistics?.id || undefined, // Добавляем логистику
+ requestedDeliveryDate: deliveryDate,
+ items: selectedConsumables.map((consumable) => ({
+ productId: consumable.id,
+ requestedQuantity: consumable.selectedQuantity,
+ })),
+ notes: notes || undefined,
+ }
+
+ console.warn('🚀 СОЗДАНИЕ ПОСТАВКИ v2 - INPUT:', input)
+
+ const result = await createSupplyMutation({
+ variables: { input },
+ refetchQueries: [
+ { query: GET_MY_FULFILLMENT_CONSUMABLE_SUPPLIES }, // Обновляем новый v2 запрос
+ ],
+ })
+
+ console.warn('🎯 РЕЗУЛЬТАТ СОЗДАНИЯ ПОСТАВКИ v2:', result)
+
+ if (result.data?.createFulfillmentConsumableSupply?.success) {
+ toast.success('Поставка расходников создана успешно!')
+
+ // Очищаем форму
+ resetForm()
+
+ // Перенаправляем на страницу детальных поставок
+ router.push('/fulfillment-supplies/detailed-supplies')
+ } else {
+ const errorMessage = result.data?.createFulfillmentConsumableSupply?.message || 'Ошибка при создании поставки'
+ setError(errorMessage)
+ toast.error(errorMessage)
+ }
+ } catch (error) {
+ const errorMessage = 'Ошибка при создании поставки расходников'
+ console.error('Error creating fulfillment consumables supply v2:', error)
+ setError(errorMessage)
+ toast.error(errorMessage)
+ } finally {
+ setIsCreating(false)
+ }
+ }, [selectedSupplier, selectedConsumables, selectedLogistics, deliveryDate, notes, resetForm, router, createSupplyMutation])
+
+ return {
+ createSupply,
+ isCreating,
+ error,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useSupplyForm.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useSupplyForm.ts
new file mode 100644
index 0000000..e75a188
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/hooks/useSupplyForm.ts
@@ -0,0 +1,56 @@
+// =============================================================================
+// 📋 ХУК ДЛЯ УПРАВЛЕНИЯ ФОРМОЙ СОЗДАНИЯ ПОСТАВКИ
+// =============================================================================
+
+import { useState, useCallback } from 'react'
+
+import type {
+ FulfillmentConsumableSupplier,
+ SelectedFulfillmentConsumable,
+ UseSupplyFormReturn,
+} from '../types'
+
+export function useSupplyForm(): UseSupplyFormReturn {
+ // Состояние формы
+ const [selectedSupplier, setSelectedSupplier] = useState(null)
+ const [selectedLogistics, setSelectedLogistics] = useState(null)
+ const [selectedConsumables, setSelectedConsumables] = useState([])
+ const [searchQuery, setSearchQuery] = useState('')
+ const [productSearchQuery, setProductSearchQuery] = useState('')
+ const [deliveryDate, setDeliveryDate] = useState('')
+ const [notes, setNotes] = useState('')
+
+ // Функция сброса формы
+ const resetForm = useCallback(() => {
+ setSelectedSupplier(null)
+ setSelectedLogistics(null)
+ setSelectedConsumables([])
+ setSearchQuery('')
+ setProductSearchQuery('')
+ setDeliveryDate('')
+ setNotes('')
+ }, [])
+
+ return {
+ // Состояние формы
+ selectedSupplier,
+ selectedLogistics,
+ selectedConsumables,
+ searchQuery,
+ productSearchQuery,
+ deliveryDate,
+ notes,
+
+ // Действия формы
+ setSelectedSupplier,
+ setSelectedLogistics,
+ setSelectedConsumables,
+ setSearchQuery,
+ setProductSearchQuery,
+ setDeliveryDate,
+ setNotes,
+
+ // Сброс формы
+ resetForm,
+ }
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/index.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/index.tsx
new file mode 100644
index 0000000..5f5cd5c
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/index.tsx
@@ -0,0 +1,59 @@
+'use client'
+
+// =============================================================================
+// 🔄 СИСТЕМА ПЕРЕКЛЮЧЕНИЯ ПО ТИПУ ОРГАНИЗАЦИИ - УНИВЕРСАЛЬНАЯ ПОСТАВКА
+// =============================================================================
+
+import { useAuth } from '@/hooks/useAuth'
+
+import { ModularVersion } from './modular-version' // Фулфилмент версия
+import { MonolithicVersion } from './monolithic-version' // Фолбэк версия
+import { SellerModularVersion } from './seller-modular-version' // 🆕 Селлер версия
+
+// ⚙️ КОНФИГУРАЦИЯ ВЕРСИЙ
+const USE_MODULAR_ARCHITECTURE = true // 👈 ПЕРЕКЛЮЧАТЕЛЬ: true = модульная, false = монолитная
+
+export default function CreateConsumablesSupplyV2Page() {
+ const { user } = useAuth()
+
+ // 🔄 Выбор версии по типу организации
+ if (USE_MODULAR_ARCHITECTURE && user?.organization) {
+ const organizationType = user.organization.type
+
+ // 🏪 СЕЛЛЕР - заказывает расходники у поставщика для доставки в ФФ
+ if (organizationType === 'SELLER') {
+ return
+ }
+
+ // 🏢 ФУЛФИЛМЕНТ - заказывает расходники для собственного использования
+ if (organizationType === 'FULFILLMENT') {
+ return
+ }
+
+ // 🚚 ДРУГИЕ ТИПЫ - пока используют фулфилмент версию
+ // TODO: Добавить специальные версии для WHOLESALE и LOGIST если нужно
+ return
+ }
+
+ // ✅ ФОЛБЭК НА МОНОЛИТНУЮ ВЕРСИЮ
+ return
+}
+
+// =============================================================================
+// 📋 ИНСТРУКЦИИ ПО ПЕРЕКЛЮЧЕНИЮ:
+//
+// 🔄 Переключение на модульную архитектуру:
+// 1. Установить USE_MODULAR_ARCHITECTURE = true
+// 2. Импорт ModularVersion уже активен
+// 3. return уже активен
+//
+// ⬅️ ОТКАТ к монолитной версии:
+// 1. Установить USE_MODULAR_ARCHITECTURE = false
+// 2. Импорт ModularVersion можно оставить
+// 3. return автоматически отключится
+//
+// ⚡ ГОРЯЧИЕ КЛАВИШИ ДЛЯ ПЕРЕКЛЮЧЕНИЯ:
+// - Ctrl+F → "USE_MODULAR_ARCHITECTURE"
+// - Изменить true/false
+// - Сохранить файл
+// =============================================================================
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/modular-version.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/modular-version.tsx
new file mode 100644
index 0000000..394a181
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/modular-version.tsx
@@ -0,0 +1,169 @@
+// =============================================================================
+// 🧩 МОДУЛЬНАЯ ВЕРСИЯ - СОЗДАНИЕ ПОСТАВОК РАСХОДНИКОВ ФУЛФИЛМЕНТА V2
+// =============================================================================
+// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в монолитной версии!
+// Рефакторинг касается только архитектуры - разделение на модули
+
+'use client'
+
+import { useRouter } from 'next/navigation'
+import React from 'react'
+
+import { Sidebar } from '@/components/dashboard/sidebar'
+import { useAuth } from '@/hooks/useAuth'
+import { useSidebar } from '@/hooks/useSidebar'
+
+// 📦 Импорт модульных компонентов
+import {
+ PageHeader,
+ SuppliersBlock,
+ ConsumablesBlock,
+ ShoppingCartBlock,
+} from './blocks'
+
+// 🔧 Импорт бизнес-хуков
+import {
+ useSupplierData,
+ useProductData,
+ useSupplyForm,
+ useQuantityManagement,
+ useSupplyCreation,
+ useCurrencyFormatting,
+} from './hooks'
+
+export function ModularVersion() {
+ const router = useRouter()
+ const { getSidebarMargin } = useSidebar()
+ const { user: _user } = useAuth()
+
+ // 📋 Управление состоянием формы
+ const {
+ selectedSupplier,
+ selectedLogistics,
+ selectedConsumables,
+ searchQuery,
+ productSearchQuery,
+ deliveryDate,
+ notes,
+ setSelectedSupplier,
+ setSelectedLogistics,
+ setSelectedConsumables,
+ setSearchQuery,
+ setProductSearchQuery,
+ setDeliveryDate,
+ setNotes,
+ resetForm,
+ } = useSupplyForm()
+
+ // 🏢 Данные поставщиков
+ const {
+ suppliers,
+ logisticsPartners,
+ filteredSuppliers,
+ loading: suppliersLoading,
+ } = useSupplierData({ searchQuery })
+
+ // 📦 Данные товаров
+ const {
+ products,
+ loading: productsLoading,
+ } = useProductData({
+ selectedSupplier,
+ productSearchQuery,
+ })
+
+ // 📊 Управление количеством
+ const {
+ updateConsumableQuantity,
+ getSelectedQuantity,
+ getTotalAmount,
+ getTotalItems,
+ } = useQuantityManagement({
+ selectedSupplier,
+ selectedConsumables,
+ products,
+ setSelectedConsumables,
+ })
+
+ // 🚀 Создание поставки
+ const {
+ createSupply,
+ isCreating: isCreatingSupply,
+ } = useSupplyCreation({
+ selectedSupplier,
+ selectedConsumables,
+ selectedLogistics,
+ deliveryDate,
+ notes,
+ resetForm,
+ })
+
+ // 💰 Форматирование валюты
+ const { formatCurrency } = useCurrencyFormatting()
+
+ // 🔄 Обработчики событий
+ const handleBack = () => {
+ router.push('/fulfillment-supplies/detailed-supplies')
+ }
+
+ return (
+
+
+
+
+ {/* 📄 Заголовок страницы */}
+
+
+ {/* 🧩 Основной контент с двумя блоками */}
+
+ {/* 📦 Левая колонка - Поставщики и Расходники */}
+
+ {/* 🏢 Блок поставщиков */}
+
+
+ {/* 📦 Блок расходников */}
+
+
+
+ {/* 🛒 Правая колонка - Корзина */}
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/monolithic-version.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/monolithic-version.tsx
new file mode 100644
index 0000000..a98ae56
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/monolithic-version.tsx
@@ -0,0 +1,820 @@
+'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, useMemo } 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 { GET_MY_COUNTERPARTIES, GET_ORGANIZATION_PRODUCTS } from '@/graphql/queries'
+import {
+ CREATE_FULFILLMENT_CONSUMABLE_SUPPLY,
+ GET_MY_FULFILLMENT_CONSUMABLE_SUPPLIES,
+} from '@/graphql/queries/fulfillment-consumables-v2'
+import { useAuth } from '@/hooks/useAuth'
+import { useSidebar } from '@/hooks/useSidebar'
+
+interface FulfillmentConsumableSupplier {
+ 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 FulfillmentConsumableProduct {
+ id: string
+ name: string
+ description?: string
+ price: number
+ type?: 'PRODUCT' | 'CONSUMABLE'
+ category?: { name: string }
+ images: string[]
+ mainImage?: string
+ organization: {
+ id: string
+ name: string
+ }
+ stock?: number
+ unit?: string
+ quantity?: number
+ ordered?: number
+}
+
+interface SelectedFulfillmentConsumable {
+ id: string
+ name: string
+ price: number
+ selectedQuantity: number
+ unit?: string
+ category?: string
+ supplierId: string
+ supplierName: string
+}
+
+export function MonolithicVersion() {
+ const router = useRouter()
+ const { getSidebarMargin } = useSidebar()
+ const { user: _user } = useAuth()
+ const [selectedSupplier, setSelectedSupplier] = useState(null)
+ const [selectedLogistics, setSelectedLogistics] = useState(null)
+ const [selectedConsumables, setSelectedConsumables] = useState([])
+ const [searchQuery, setSearchQuery] = useState('')
+ const [productSearchQuery, setProductSearchQuery] = useState('')
+ const [deliveryDate, setDeliveryDate] = useState('')
+ const [notes, setNotes] = useState('')
+ const [isCreatingSupply, setIsCreatingSupply] = useState(false)
+
+ // Загружаем контрагентов-поставщиков расходников
+ const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
+
+ // Убираем избыточное логирование для предотвращения визуального "бесконечного цикла"
+
+ // Стабилизируем переменные для useQuery
+ const queryVariables = useMemo(() => {
+ return {
+ organizationId: selectedSupplier?.id || '', // Всегда возвращаем объект, но с пустым ID если нет поставщика
+ search: productSearchQuery || null,
+ category: null,
+ type: 'CONSUMABLE' as const, // Фильтруем только расходники согласно rules2.md
+ }
+ }, [selectedSupplier?.id, productSearchQuery])
+
+ // Загружаем товары для выбранного поставщика с фильтрацией по типу CONSUMABLE
+ const {
+ data: productsData,
+ loading: productsLoading,
+ error: _productsError,
+ } = useQuery(GET_ORGANIZATION_PRODUCTS, {
+ skip: !selectedSupplier?.id, // Используем стабильное условие вместо !queryVariables
+ variables: queryVariables,
+ onCompleted: (data) => {
+ // Логируем только количество загруженных товаров
+ console.warn(`📦 Загружено товаров: ${data?.organizationProducts?.length || 0}`)
+ },
+ onError: (error) => {
+ console.error('❌ GET_ORGANIZATION_PRODUCTS ERROR:', error)
+ },
+ })
+
+ // Мутация для создания заказа поставки расходников v2
+ const [createSupply] = useMutation(CREATE_FULFILLMENT_CONSUMABLE_SUPPLY)
+
+ // Фильтруем только поставщиков расходников (поставщиков)
+ const consumableSuppliers = (counterpartiesData?.myCounterparties || []).filter(
+ (org: FulfillmentConsumableSupplier) => org.type === 'WHOLESALE',
+ )
+
+ // Фильтруем только логистические компании
+ const logisticsPartners = (counterpartiesData?.myCounterparties || []).filter(
+ (org: FulfillmentConsumableSupplier) => org.type === 'LOGIST',
+ )
+
+ // Фильтруем поставщиков по поисковому запросу
+ const filteredSuppliers = consumableSuppliers.filter(
+ (supplier: FulfillmentConsumableSupplier) =>
+ supplier.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ supplier.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ supplier.inn?.toLowerCase().includes(searchQuery.toLowerCase()),
+ )
+
+ // Фильтруем товары по выбранному поставщику
+ // 📦 Получаем товары поставщика (уже отфильтрованы в GraphQL запросе по типу CONSUMABLE)
+ const supplierProducts = productsData?.organizationProducts || []
+
+ // Отладочное логирование только при смене поставщика
+ React.useEffect(() => {
+ if (selectedSupplier) {
+ console.warn('🔄 ПОСТАВЩИК ВЫБРАН:', {
+ id: selectedSupplier.id,
+ name: selectedSupplier.name || selectedSupplier.fullName,
+ type: selectedSupplier.type,
+ })
+ }
+ }, [selectedSupplier]) // Включаем весь объект поставщика для корректной работы
+
+ // Логируем результат загрузки товаров только при получении данных
+ React.useEffect(() => {
+ if (productsData && !productsLoading) {
+ console.warn('📦 ТОВАРЫ ЗАГРУЖЕНЫ:', {
+ organizationProductsCount: productsData?.organizationProducts?.length || 0,
+ supplierProductsCount: supplierProducts.length,
+ })
+ }
+ }, [productsData, productsLoading, supplierProducts.length]) // Включаем все зависимости для корректной работы
+
+ 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: FulfillmentConsumableProduct) => p.id === productId)
+ if (!product || !selectedSupplier) return
+
+ // 🔒 ВАЛИДАЦИЯ ОСТАТКОВ согласно правилам (раздел 6.2)
+ if (quantity > 0) {
+ const availableStock = (product.stock || product.quantity || 0) - (product.ordered || 0)
+
+ if (quantity > availableStock) {
+ toast.error(`❌ Недостаточно остатков!\nДоступно: ${availableStock} шт.\nЗапрашивается: ${quantity} шт.`)
+ return
+ }
+ }
+
+ 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
+ }
+
+ setIsCreatingSupply(true)
+
+ try {
+ // Новый формат для системы v2
+ const input = {
+ supplierId: selectedSupplier.id,
+ logisticsPartnerId: selectedLogistics?.id || undefined, // Добавляем логистического партнера
+ requestedDeliveryDate: deliveryDate,
+ items: selectedConsumables.map((consumable) => ({
+ productId: consumable.id,
+ requestedQuantity: consumable.selectedQuantity,
+ })),
+ notes: notes || undefined,
+ }
+
+ console.warn('🚀 СОЗДАНИЕ ПОСТАВКИ v2 - INPUT:', input)
+
+ const result = await createSupply({
+ variables: { input },
+ refetchQueries: [
+ { query: GET_MY_FULFILLMENT_CONSUMABLE_SUPPLIES }, // Обновляем новый v2 запрос
+ ],
+ })
+
+ console.warn('🎯 РЕЗУЛЬТАТ СОЗДАНИЯ ПОСТАВКИ v2:', result)
+
+ if (result.data?.createFulfillmentConsumableSupply?.success) {
+ toast.success('Поставка расходников создана успешно!')
+ // Очищаем форму
+ setSelectedSupplier(null)
+ setSelectedLogistics(null)
+ setSelectedConsumables([])
+ setDeliveryDate('')
+ setProductSearchQuery('')
+ setSearchQuery('')
+ setNotes('')
+
+ // Перенаправляем на страницу детальных поставок
+ router.push('/fulfillment-supplies/detailed-supplies')
+ } else {
+ toast.error(result.data?.createFulfillmentConsumableSupply?.message || 'Ошибка при создании поставки')
+ }
+ } catch (error) {
+ console.error('Error creating fulfillment consumables supply v2:', error)
+ toast.error('Ошибка при создании поставки расходников')
+ } finally {
+ setIsCreatingSupply(false)
+ }
+ }
+
+ return (
+
+
+
+
+ {/* Заголовок */}
+
+
+
Создание поставки расходников фулфилмента
+
+ Выберите поставщика и добавьте расходники в заказ для вашего фулфилмент-центра
+
+
+
router.push('/fulfillment-supplies/detailed-supplies')}
+ className="text-white/60 hover:text-white hover:bg-white/10 text-sm"
+ >
+
+ Назад
+
+
+
+ {/* Основной контент с двумя блоками */}
+
+ {/* Левая колонка - Поставщики и Расходники */}
+
+ {/* Блок "Поставщики" */}
+
+
+
+
+
+ Поставщики расходников
+
+
+
+ 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 && (
+
setSelectedSupplier(null)}
+ className="text-white/70 hover:text-white hover:bg-white/20 text-sm h-8 px-3 flex-shrink-0 rounded-full transition-all duration-300 hover:scale-105"
+ >
+ ✕ Сбросить
+
+ )}
+
+
+
+
+ {counterpartiesLoading ? (
+
+
+
Загружаем поставщиков...
+
+ ) : filteredSuppliers.length === 0 ? (
+
+
+
+
+
+ {searchQuery ? 'Поставщики не найдены' : 'Добавьте поставщиков'}
+
+
+ ) : (
+
+ {filteredSuppliers.slice(0, 7).map((supplier: FulfillmentConsumableSupplier, index: number) => (
+
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: FulfillmentConsumableProduct, index: number) => {
+ 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%',
+ }}
+ >
+
+ {/* Изображение товара */}
+
+ {/* 🚫 ОВЕРЛЕЙ НЕДОСТУПНОСТИ */}
+ {(() => {
+ const totalStock = product.stock || (product as any).quantity || 0
+ const orderedStock = (product as any).ordered || 0
+ const availableStock = totalStock - orderedStock
+
+ if (availableStock <= 0) {
+ return (
+
+ )
+ }
+ return null
+ })()}
+ {product.images && product.images.length > 0 && product.images[0] ? (
+
+ ) : product.mainImage ? (
+
+ ) : (
+
+
+
+ )}
+ {selectedQuantity > 0 && (
+
+
+ {selectedQuantity > 999 ? '999+' : selectedQuantity}
+
+
+ )}
+
+
+ {/* Информация о товаре */}
+
+
+ {product.name}
+
+
+ {product.category && (
+
+ {product.category.name.slice(0, 10)}
+
+ )}
+ {/* 🚨 ИНДИКАТОР НИЗКИХ ОСТАТКОВ согласно правилам (раздел 6.3) */}
+ {(() => {
+ const totalStock = product.stock || product.quantity || 0
+ const orderedStock = product.ordered || 0
+ const availableStock = totalStock - orderedStock
+
+ if (availableStock <= 0) {
+ return (
+
+ Нет в наличии
+
+ )
+ } else if (availableStock <= 10) {
+ return (
+
+ Мало остатков
+
+ )
+ }
+ return null
+ })()}
+
+
+
+ {formatCurrency(product.price)}
+
+ {/* 📊 АКТУАЛЬНЫЙ ОСТАТОК согласно правилам (раздел 6.4.2) */}
+
+ {(() => {
+ const totalStock = product.stock || product.quantity || 0
+ const orderedStock = product.ordered || 0
+ const availableStock = totalStock - orderedStock
+
+ return (
+
+
+ Доступно: {availableStock}
+
+ {orderedStock > 0 && (
+ Заказано: {orderedStock}
+ )}
+
+ )
+ })()}
+
+
+
+
+ {/* Управление количеством */}
+
+ {(() => {
+ const totalStock = product.stock || (product as any).quantity || 0
+ const orderedStock = (product as any).ordered || 0
+ const availableStock = totalStock - orderedStock
+
+ return (
+
+
+ updateConsumableQuantity(product.id, Math.max(0, selectedQuantity - 1))
+ }
+ className="h-6 w-6 p-0 text-white/60 hover:text-white hover:bg-white/20 rounded-full transition-all duration-300"
+ disabled={selectedQuantity === 0}
+ >
+
+
+
{
+ let inputValue = e.target.value
+
+ // Удаляем все нецифровые символы
+ inputValue = inputValue.replace(/[^0-9]/g, '')
+
+ // Удаляем ведущие нули
+ inputValue = inputValue.replace(/^0+/, '')
+
+ // Если строка пустая после удаления нулей, устанавливаем 0
+ const numericValue = inputValue === '' ? 0 : parseInt(inputValue)
+
+ // Ограничиваем значение максимумом доступного остатка
+ const clampedValue = Math.min(numericValue, availableStock, 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"
+ />
+
+ updateConsumableQuantity(
+ product.id,
+ Math.min(selectedQuantity + 1, availableStock, 99999),
+ )
+ }
+ className={`h-6 w-6 p-0 rounded-full transition-all duration-300 ${
+ selectedQuantity >= availableStock || availableStock <= 0
+ ? 'text-white/30 cursor-not-allowed'
+ : 'text-white/60 hover:text-white hover:bg-white/20'
+ }`}
+ disabled={selectedQuantity >= availableStock || availableStock <= 0}
+ title={
+ availableStock <= 0
+ ? 'Товар отсутствует на складе'
+ : selectedQuantity >= availableStock
+ ? `Максимум доступно: ${availableStock}`
+ : 'Увеличить количество'
+ }
+ >
+
+
+
+ )
+ })()}
+
+ {selectedQuantity > 0 && (
+
+
+ {formatCurrency(product.price * selectedQuantity)}
+
+
+ )}
+
+
+
+ {/* Hover эффект */}
+
+
+ )
+ })}
+
+ )}
+
+
+
+
+ {/* Правая колонка - Корзина */}
+
+
+
+
+ Корзина ({getTotalItems()} шт)
+
+
+ {selectedConsumables.length === 0 ? (
+
+
+
+
+
Корзина пуста
+
Добавьте расходники для создания поставки
+
+ ) : (
+
+ {selectedConsumables.map((consumable) => (
+
+
+
{consumable.name}
+
+ {formatCurrency(consumable.price)} × {consumable.selectedQuantity}
+
+
+
+
+ {formatCurrency(consumable.price * consumable.selectedQuantity)}
+
+ updateConsumableQuantity(consumable.id, 0)}
+ className="h-5 w-5 p-0 text-red-400 hover:text-red-300 hover:bg-red-500/10"
+ >
+ ×
+
+
+
+ ))}
+
+ )}
+
+
+
+ Дата поставки:
+ 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
+ />
+
+
+ {/* Выбор логистики */}
+
+
Логистика (опционально):
+
+
{
+ const logisticsId = e.target.value
+ const logistics = logisticsPartners.find((p: any) => p.id === logisticsId)
+ setSelectedLogistics(logistics || null)
+ }}
+ className="w-full bg-white/10 border border-white/20 rounded-md px-3 py-2 text-white text-sm focus:outline-none focus:ring-1 focus:ring-purple-500 focus:border-transparent appearance-none"
+ >
+
+ Выберите логистику
+
+ {logisticsPartners.map((partner: any) => (
+
+ {partner.name || partner.fullName || partner.inn}
+
+ ))}
+
+
+
+
+
+ {/* Заметки */}
+
+ Заметки (необязательно):
+
+
+
+ Итого:
+ {formatCurrency(getTotalAmount())}
+
+
+ {isCreatingSupply ? 'Создание...' : 'Создать поставку'}
+
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/seller-modular-version.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/seller-modular-version.tsx
new file mode 100644
index 0000000..9ca5adf
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/seller-modular-version.tsx
@@ -0,0 +1,183 @@
+// =============================================================================
+// 🧩 МОДУЛЬНАЯ ВЕРСИЯ ДЛЯ СЕЛЛЕРА - СОЗДАНИЕ ПОСТАВОК РАСХОДНИКОВ V2
+// =============================================================================
+// ВНИМАНИЕ: Визуал остается ТОЧНО таким же как в фулфилмент версии!
+// Отличия только в бизнес-логике и данных
+
+'use client'
+
+import { useRouter } from 'next/navigation'
+import React from 'react'
+
+import { Sidebar } from '@/components/dashboard/sidebar'
+import { useAuth } from '@/hooks/useAuth'
+import { useSidebar } from '@/hooks/useSidebar'
+
+// 📦 Импорт селлерских компонентов
+import {
+ PageHeader,
+ ConsumablesBlock, // Используем тот же компонент расходников
+} from './blocks'
+import {
+ SellerSuppliersBlock,
+ SellerShoppingCartBlock,
+} from './blocks/seller-blocks'
+
+// 🔧 Импорт селлерских хуков
+import {
+ useProductData, // Используем тот же hook для товаров
+ useQuantityManagement, // Используем тот же hook для количества
+ useCurrencyFormatting, // Используем тот же hook для валюты
+} from './hooks'
+import {
+ useSellerSupplyForm,
+ useSellerSupplyCreation,
+ useSellerSupplierData,
+} from './hooks/seller-hooks'
+
+export function SellerModularVersion() {
+ const router = useRouter()
+ const { getSidebarMargin } = useSidebar()
+ const { user: _user } = useAuth()
+
+ // 📋 Управление состоянием формы СЕЛЛЕРА
+ const {
+ selectedSupplier,
+ selectedFulfillment, // 🆕 Дополнительное поле для селлера
+ selectedLogistics,
+ selectedConsumables,
+ searchQuery,
+ productSearchQuery,
+ deliveryDate,
+ notes,
+ setSelectedSupplier,
+ setSelectedFulfillment, // 🆕 Дополнительный сеттер
+ setSelectedLogistics,
+ setSelectedConsumables,
+ setSearchQuery,
+ setProductSearchQuery,
+ setDeliveryDate,
+ setNotes,
+ resetForm,
+ } = useSellerSupplyForm()
+
+ // 🏢 Данные партнеров для селлера (поставщики + фулфилмент-центры)
+ const {
+ suppliers,
+ fulfillmentCenters, // 🆕 Фулфилмент-центры для селлера
+ filteredSuppliers,
+ filteredFulfillmentCenters, // 🆕 Фильтрованные ФФ
+ logisticsPartners,
+ loading: suppliersLoading,
+ } = useSellerSupplierData({ searchQuery })
+
+ // 📦 Данные товаров (используем тот же hook что и для фулфилмента)
+ const {
+ products,
+ loading: productsLoading,
+ } = useProductData({
+ selectedSupplier, // 🔄 Товары от выбранного поставщика
+ productSearchQuery,
+ })
+
+ // 📊 Управление количеством (используем тот же hook)
+ const {
+ updateConsumableQuantity,
+ getSelectedQuantity,
+ getTotalAmount,
+ getTotalItems,
+ } = useQuantityManagement({
+ selectedSupplier,
+ selectedConsumables,
+ products,
+ setSelectedConsumables,
+ })
+
+ // 🚀 Создание поставки СЕЛЛЕРА
+ const {
+ createSupply,
+ isCreating: isCreatingSupply,
+ } = useSellerSupplyCreation({
+ selectedSupplier,
+ selectedFulfillment, // 🆕 Передаем фулфилмент-центр
+ selectedConsumables,
+ deliveryDate,
+ notes,
+ resetForm,
+ })
+
+ // 💰 Форматирование валюты (используем тот же hook)
+ const { formatCurrency } = useCurrencyFormatting()
+
+ // 🔄 Обработчики событий
+ const handleBack = () => {
+ // TODO: Создать маршрут для списка поставок селлера
+ router.push('/seller-supplies') // или другой подходящий маршрут
+ }
+
+ return (
+
+
+
+
+ {/* 📄 Заголовок страницы */}
+
+
+ {/* 🧩 Основной контент с двумя блоками */}
+
+ {/* 📦 Левая колонка - Партнеры и Расходники */}
+
+ {/* 🏪 Блок партнеров (поставщики + фулфилмент-центры) */}
+
+
+ {/* 📦 Блок расходников (тот же что у фулфилмента) */}
+
+
+
+ {/* 🛒 Правая колонка - Корзина СЕЛЛЕРА */}
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/types/index.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/types/index.ts
new file mode 100644
index 0000000..2134631
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/types/index.ts
@@ -0,0 +1,252 @@
+// =============================================================================
+// 🔧 ТИПЫ ДЛЯ СИСТЕМЫ СОЗДАНИЯ ПОСТАВОК РАСХОДНИКОВ ФУЛФИЛМЕНТА V2
+// =============================================================================
+// Эти типы экстрагированы из монолитного компонента в соответствии с правилами
+// модульной архитектуры MODULAR_ARCHITECTURE_PATTERN.md
+
+// 🏢 ТИПЫ ПОСТАВЩИКОВ И ОРГАНИЗАЦИЙ
+export interface FulfillmentConsumableSupplier {
+ 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
+}
+
+// 📦 ТИПЫ ТОВАРОВ И РАСХОДНИКОВ
+export interface FulfillmentConsumableProduct {
+ id: string
+ name: string
+ description?: string
+ price: number
+ type?: 'PRODUCT' | 'CONSUMABLE'
+ category?: { name: string }
+ images: string[]
+ mainImage?: string
+ organization: {
+ id: string
+ name: string
+ }
+ stock?: number
+ unit?: string
+ quantity?: number
+ ordered?: number
+}
+
+// 🛒 ТИПЫ КОРЗИНЫ И ВЫБРАННЫХ ТОВАРОВ
+export interface SelectedFulfillmentConsumable {
+ id: string
+ name: string
+ price: number
+ selectedQuantity: number
+ unit?: string
+ category?: string
+ supplierId: string
+ supplierName: string
+}
+
+// 📋 ТИПЫ ФОРМЫ СОЗДАНИЯ ПОСТАВКИ
+export interface CreateSupplyFormData {
+ selectedSupplier: FulfillmentConsumableSupplier | null
+ selectedLogistics: FulfillmentConsumableSupplier | null
+ selectedConsumables: SelectedFulfillmentConsumable[]
+ searchQuery: string
+ productSearchQuery: string
+ deliveryDate: string
+ notes: string
+ isCreatingSupply: boolean
+}
+
+// 🔍 ТИПЫ ПОИСКОВЫХ ПАРАМЕТРОВ
+export interface SupplierSearchParams {
+ searchQuery: string
+ selectedSupplier: FulfillmentConsumableSupplier | null
+}
+
+export interface ProductSearchParams {
+ productSearchQuery: string
+ selectedSupplier: FulfillmentConsumableSupplier | null
+ type: 'CONSUMABLE'
+}
+
+// 📊 ТИПЫ ДАННЫХ ЗАПРОСОВ
+export interface CounterpartiesData {
+ myCounterparties: FulfillmentConsumableSupplier[]
+}
+
+export interface ProductsData {
+ organizationProducts: FulfillmentConsumableProduct[]
+}
+
+// 🎯 ТИПЫ ДЕЙСТВИЙ И ОПЕРАЦИЙ
+export interface QuantityUpdateParams {
+ productId: string
+ quantity: number
+ product: FulfillmentConsumableProduct
+ selectedSupplier: FulfillmentConsumableSupplier
+}
+
+export interface SupplyCreationInput {
+ supplierId: string
+ logisticsPartnerId?: string // Добавляем логистического партнера
+ requestedDeliveryDate: string
+ items: Array<{
+ productId: string
+ requestedQuantity: number
+ }>
+ notes?: string
+}
+
+// 🔧 ТИПЫ УТИЛИТ И ХЕЛПЕРОВ
+export interface StockCalculation {
+ totalStock: number
+ orderedStock: number
+ availableStock: number
+}
+
+export interface CurrencyFormatOptions {
+ style: 'currency'
+ currency: 'RUB'
+ minimumFractionDigits: 0
+}
+
+// 🚦 ТИПЫ СОСТОЯНИЙ ЗАГРУЗКИ
+export interface LoadingStates {
+ counterpartiesLoading: boolean
+ productsLoading: boolean
+ isCreatingSupply: boolean
+}
+
+// ⚠️ ТИПЫ ОШИБОК И ВАЛИДАЦИИ
+export interface ValidationError {
+ field: string
+ message: string
+}
+
+export interface StockValidationResult {
+ isValid: boolean
+ availableStock: number
+ requestedQuantity: number
+ error?: string
+}
+
+// 🎨 ТИПЫ UI КОМПОНЕНТОВ
+export interface SupplierCardProps {
+ supplier: FulfillmentConsumableSupplier
+ isSelected: boolean
+ onSelect: (supplier: FulfillmentConsumableSupplier) => void
+ index: number
+}
+
+export interface ProductCardProps {
+ product: FulfillmentConsumableProduct
+ selectedQuantity: number
+ onUpdateQuantity: (productId: string, quantity: number) => void
+ index: number
+}
+
+export interface ShoppingCartProps {
+ selectedConsumables: SelectedFulfillmentConsumable[]
+ deliveryDate: string
+ notes: string
+ selectedLogistics: FulfillmentConsumableSupplier | null
+ logisticsPartners: FulfillmentConsumableSupplier[]
+ isCreatingSupply: boolean
+ onUpdateQuantity: (productId: string, quantity: number) => void
+ onSetDeliveryDate: (date: string) => void
+ onSetNotes: (notes: string) => void
+ onSetLogistics: (logistics: FulfillmentConsumableSupplier | null) => void
+ onCreateSupply: () => void
+}
+
+// 📈 ТИПЫ ВЫЧИСЛЕНИЙ И АНАЛИТИКИ
+export interface SupplyTotals {
+ totalAmount: number
+ totalItems: number
+ itemsCount: number
+}
+
+// 🎯 ТИПЫ СОБЫТИЙ И КОЛБЭКОВ
+export type SupplierSelectHandler = (supplier: FulfillmentConsumableSupplier) => void
+export type QuantityUpdateHandler = (productId: string, quantity: number) => void
+export type SupplyCreateHandler = () => Promise
+export type SearchUpdateHandler = (query: string) => void
+
+// 💾 ТИПЫ СТОРЕЙДЖА И КЕША
+export interface CachedSupplierData {
+ suppliers: FulfillmentConsumableSupplier[]
+ lastFetch: string
+}
+
+export interface CachedProductData {
+ products: FulfillmentConsumableProduct[]
+ supplierId: string
+ lastFetch: string
+}
+
+// 🌐 ТИПЫ ГРАФК ЗАПРОСОВ
+export interface GraphQLQueryVariables {
+ organizationId?: string
+ search?: string | null
+ category?: string | null
+ type?: 'CONSUMABLE'
+}
+
+export interface CreateSupplyMutationResponse {
+ createFulfillmentConsumableSupply: {
+ success: boolean
+ message?: string
+ }
+}
+
+// 🔄 ЭКСПОРТ ТИПОВ ДЛЯ ХУКОВ
+export interface UseSupplierDataReturn {
+ suppliers: FulfillmentConsumableSupplier[]
+ logisticsPartners: FulfillmentConsumableSupplier[]
+ filteredSuppliers: FulfillmentConsumableSupplier[]
+ loading: boolean
+ error: Error | null
+}
+
+export interface UseProductDataReturn {
+ products: FulfillmentConsumableProduct[]
+ loading: boolean
+ error: Error | null
+ refetch: () => void
+}
+
+export interface UseSupplyFormReturn {
+ selectedSupplier: FulfillmentConsumableSupplier | null
+ selectedLogistics: FulfillmentConsumableSupplier | null
+ selectedConsumables: SelectedFulfillmentConsumable[]
+ searchQuery: string
+ productSearchQuery: string
+ deliveryDate: string
+ notes: string
+ setSelectedSupplier: (supplier: FulfillmentConsumableSupplier | null) => void
+ setSelectedLogistics: (logistics: FulfillmentConsumableSupplier | null) => void
+ setSelectedConsumables: (consumables: SelectedFulfillmentConsumable[]) => void
+ setSearchQuery: (query: string) => void
+ setProductSearchQuery: (query: string) => void
+ setDeliveryDate: (date: string) => void
+ setNotes: (notes: string) => void
+ resetForm: () => void
+}
+
+export interface UseQuantityManagementReturn {
+ updateConsumableQuantity: (productId: string, quantity: number) => void
+ getSelectedQuantity: (productId: string) => number
+ getTotalAmount: () => number
+ getTotalItems: () => number
+}
+
+export interface UseSupplyCreationReturn {
+ createSupply: () => Promise
+ isCreating: boolean
+ error: string | null
+}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/types/seller-types.ts b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/types/seller-types.ts
new file mode 100644
index 0000000..87e6b09
--- /dev/null
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/types/seller-types.ts
@@ -0,0 +1,193 @@
+// =============================================================================
+// 🔧 ТИПЫ ДЛЯ СИСТЕМЫ СОЗДАНИЯ ПОСТАВОК РАСХОДНИКОВ СЕЛЛЕРА V2
+// =============================================================================
+// Адаптированные типы из основных типов для селлерской системы
+
+import type {
+ FulfillmentConsumableSupplier,
+ FulfillmentConsumableProduct,
+ SelectedFulfillmentConsumable,
+ SupplyCreationInput as BaseSupplyCreationInput,
+ CreateSupplyMutationResponse as BaseCreateSupplyMutationResponse,
+} from './index'
+
+// =============================================================================
+// 📦 СЕЛЛЕРСКИЕ ТИПЫ (АДАПТАЦИЯ)
+// =============================================================================
+
+// Селлер использует те же типы поставщиков что и фулфилмент
+export type SellerConsumableSupplier = FulfillmentConsumableSupplier
+export type SellerConsumableProduct = FulfillmentConsumableProduct
+export type SelectedSellerConsumable = SelectedFulfillmentConsumable
+
+// =============================================================================
+// 🆕 СПЕЦИФИЧНЫЕ ТИПЫ ДЛЯ СЕЛЛЕРА
+// =============================================================================
+
+// Статусы поставок селлера (5-статусная система)
+export type SellerSupplyOrderStatus =
+ | 'PENDING' // Ожидает одобрения поставщика
+ | 'APPROVED' // Одобрено поставщиком
+ | 'SHIPPED' // Отгружено
+ | 'DELIVERED' // Доставлено
+ | 'COMPLETED' // Завершено
+ | 'CANCELLED' // Отменено
+
+// Данные для создания поставки селлера
+export interface SellerSupplyCreationInput {
+ fulfillmentCenterId: string // куда доставлять (FULFILLMENT партнер)
+ supplierId: string // от кого заказывать (WHOLESALE партнер)
+ logisticsPartnerId?: string // кто везет (LOGIST партнер, опционально)
+ requestedDeliveryDate: string // когда нужно
+ items: Array<{
+ productId: string
+ requestedQuantity: number
+ }>
+ notes?: string
+}
+
+// Response для создания поставки селлера
+export interface CreateSellerSupplyMutationResponse {
+ createSellerConsumableSupply: {
+ success: boolean
+ message: string
+ supplyOrder?: {
+ id: string
+ status: SellerSupplyOrderStatus
+ createdAt: string
+ }
+ }
+}
+
+// =============================================================================
+// 🎯 ТИПЫ ДЛЯ ХУКОВ (АДАПТАЦИЯ)
+// =============================================================================
+
+export interface UseSellerSupplyCreationProps {
+ selectedSupplier: SellerConsumableSupplier | null
+ selectedFulfillment: SellerConsumableSupplier | null // 🆕 Выбор фулфилмента
+ selectedConsumables: SelectedSellerConsumable[]
+ deliveryDate: string
+ notes: string
+ resetForm: () => void
+}
+
+export interface UseSellerSupplyCreationReturn {
+ createSupply: () => Promise
+ isCreating: boolean
+ error: string | null
+}
+
+export interface UseSellerSupplyFormReturn {
+ selectedSupplier: SellerConsumableSupplier | null
+ selectedFulfillment: SellerConsumableSupplier | null // 🆕 Дополнительное поле
+ selectedLogistics: SellerConsumableSupplier | null
+ selectedConsumables: SelectedSellerConsumable[]
+ searchQuery: string
+ productSearchQuery: string
+ deliveryDate: string
+ notes: string
+ setSelectedSupplier: (supplier: SellerConsumableSupplier | null) => void
+ setSelectedFulfillment: (fulfillment: SellerConsumableSupplier | null) => void // 🆕 Новый сеттер
+ setSelectedLogistics: (logistics: SellerConsumableSupplier | null) => void
+ setSelectedConsumables: (consumables: SelectedSellerConsumable[]) => void
+ setSearchQuery: (query: string) => void
+ setProductSearchQuery: (query: string) => void
+ setDeliveryDate: (date: string) => void
+ setNotes: (notes: string) => void
+ resetForm: () => void
+}
+
+// =============================================================================
+// 🎨 ТИПЫ UI КОМПОНЕНТОВ ДЛЯ СЕЛЛЕРА
+// =============================================================================
+
+export interface SellerSuppliersBlockProps {
+ suppliers: SellerConsumableSupplier[]
+ fulfillmentCenters: SellerConsumableSupplier[] // 🆕 Список фулфилмент-центров
+ filteredSuppliers: SellerConsumableSupplier[]
+ filteredFulfillmentCenters: SellerConsumableSupplier[] // 🆕 Фильтрованные ФФ
+ selectedSupplier: SellerConsumableSupplier | null
+ selectedFulfillment: SellerConsumableSupplier | null // 🆕 Выбранный фулфилмент
+ searchQuery: string
+ loading: boolean
+ onSelectSupplier: (supplier: SellerConsumableSupplier | null) => void
+ onSelectFulfillment: (fulfillment: SellerConsumableSupplier | null) => void // 🆕
+ onSearchChange: (query: string) => void
+}
+
+export interface SellerShoppingCartBlockProps {
+ selectedConsumables: SelectedSellerConsumable[]
+ selectedFulfillment: SellerConsumableSupplier | null // 🆕 Показ выбранного ФФ
+ deliveryDate: string
+ notes: string
+ selectedLogistics: SellerConsumableSupplier | null
+ logisticsPartners: SellerConsumableSupplier[]
+ isCreatingSupply: boolean
+ getTotalAmount: () => number
+ getTotalItems: () => number
+ formatCurrency: (amount: number) => string
+ onUpdateQuantity: (productId: string, quantity: number) => void
+ onSetDeliveryDate: (date: string) => void
+ onSetNotes: (notes: string) => void
+ onSetLogistics: (logistics: SellerConsumableSupplier | null) => void
+ onCreateSupply: () => void
+}
+
+// =============================================================================
+// 🔄 ТИПЫ ДЛЯ ПЕРЕКЛЮЧЕНИЯ СИСТЕМ
+// =============================================================================
+
+export interface SupplySystemConfig {
+ type: 'FULFILLMENT' | 'SELLER'
+ queries: {
+ GET_MY_SUPPLIES: any
+ CREATE_SUPPLY: any
+ GET_COUNTERPARTIES: any
+ GET_ORGANIZATION_PRODUCTS: any
+ }
+ components: {
+ SuppliersBlock: React.ComponentType
+ ShoppingCartBlock: React.ComponentType
+ }
+ hooks: {
+ useSupplyCreation: any
+ useSupplyForm: any
+ }
+}
+
+// =============================================================================
+// 🌐 ТИПЫ ДАННЫХ ЗАПРОСОВ ДЛЯ СЕЛЛЕРА
+// =============================================================================
+
+export interface SellerSuppliesData {
+ mySellerConsumableSupplies: Array<{
+ id: string
+ status: SellerSupplyOrderStatus
+ seller: { id: string; name: string; inn: string }
+ fulfillmentCenter: { id: string; name: string; inn: string }
+ supplier?: { id: string; name: string; inn: string }
+ requestedDeliveryDate: string
+ totalCostWithDelivery?: number
+ items: Array<{
+ id: string
+ product: { id: string; name: string; article: string }
+ requestedQuantity: number
+ unitPrice: number
+ totalPrice: number
+ }>
+ createdAt: string
+ }>
+}
+
+export interface IncomingSellerSuppliesData {
+ incomingSellerSupplies: Array<{
+ id: string
+ status: SellerSupplyOrderStatus
+ seller: { id: string; name: string; inn: string }
+ fulfillmentCenter: { id: string; name: string; inn: string }
+ requestedDeliveryDate: string
+ totalCostWithDelivery?: number
+ createdAt: string
+ }>
+}
\ No newline at end of file