) => s.id === supplyId)
+
+ switch (action) {
+ case 'approve':
+ if (isSellerGoods) {
+ await updateSellerGoodsStatus({ variables: { id: supplyId, status: 'APPROVED' } })
+ } else if (isSellerConsumables) {
+ await updateSellerSupplyStatus({ variables: { id: supplyId, status: 'APPROVED' } })
+ }
+ break
+ case 'reject':
+ const reason = prompt('Укажите причину отклонения заявки:')
+ if (reason) {
+ if (isSellerGoods) {
+ await updateSellerGoodsStatus({ variables: { id: supplyId, status: 'CANCELLED', notes: reason } })
+ } else if (isSellerConsumables) {
+ await updateSellerSupplyStatus({ variables: { id: supplyId, status: 'CANCELLED', notes: reason } })
+ }
+ }
+ break
+ case 'ship':
+ if (isSellerGoods) {
+ await updateSellerGoodsStatus({ variables: { id: supplyId, status: 'SHIPPED' } })
+ } else if (isSellerConsumables) {
+ await updateSellerSupplyStatus({ variables: { id: supplyId, status: 'SHIPPED' } })
+ }
+ break
+ default:
+ console.error('Неизвестное действие:', action, supplyId)
+ }
+ } catch (error) {
+ console.error('Ошибка при выполнении действия:', error)
+ toast.error('Ошибка при выполнении действия')
+ }
+ }
+
+ const isLoading = sellerGoodsLoading || sellerConsumablesLoading || fulfillmentConsumablesLoading
+ const hasError = sellerGoodsError || sellerConsumablesError || fulfillmentConsumablesError
+
+ if (isLoading) {
+ return (
+
+ )
+ }
+
+ if (hasError) {
+ return (
+
+
+ Ошибка загрузки заявок: {sellerGoodsError?.message || sellerConsumablesError?.message || fulfillmentConsumablesError?.message}
+
+
+ )
+ }
+
+ // Точно такая же логика фильтрации как в исходном компоненте
+ // Фильтрация (прямой расчет для совместимости хуков)
+ let filteredOrders = supplierOrders
+
+ if (searchQuery) {
+ const query = searchQuery.toLowerCase()
+ filteredOrders = filteredOrders.filter(
+ (order) =>
+ order.id.toLowerCase().includes(query) ||
+ (order.organization.name || '').toLowerCase().includes(query) ||
+ (order.organization.fullName || '').toLowerCase().includes(query) ||
+ (order.organization.inn || '').toLowerCase().includes(query) ||
+ order.items.some((item) =>
+ (item.product.name || '').toLowerCase().includes(query) ||
+ (item.product.article || '').toLowerCase().includes(query),
+ ),
+ )
+ }
+
+ if (activeTab !== 'all') {
+ const statusMap = {
+ new: ['PENDING'],
+ approved: ['APPROVED', 'LOGISTICS_CONFIRMED'],
+ shipping: ['SHIPPED', 'IN_TRANSIT'],
+ completed: ['DELIVERED', 'COMPLETED'],
+ }
+ const targetStatuses = statusMap[activeTab as keyof typeof statusMap] || []
+ filteredOrders = filteredOrders.filter((order) => targetStatuses.includes(order.status))
+ }
+
+ filteredOrders.sort((a, b) => {
+ const aValue = a[sortField as keyof SupplyOrder]
+ const bValue = b[sortField as keyof SupplyOrder]
+
+ if (aValue === bValue) return 0
+ const comparison = aValue > bValue ? 1 : -1
+ return sortDirection === 'asc' ? comparison : -comparison
+ })
+
+ // Статистика (прямой расчет для совместимости хуков)
+ const orderStats = {
+ total: supplierOrders.length,
+ new: supplierOrders.filter(o => ['PENDING'].includes(o.status)).length,
+ approved: supplierOrders.filter(o => ['APPROVED', 'LOGISTICS_CONFIRMED'].includes(o.status)).length,
+ shipping: supplierOrders.filter(o => ['SHIPPED', 'IN_TRANSIT'].includes(o.status)).length,
+ completed: supplierOrders.filter(o => ['DELIVERED', 'COMPLETED'].includes(o.status)).length,
+ totalVolume: supplierOrders.reduce((sum, o) => sum + (o.volume || 0), 0),
+ totalAmount: supplierOrders.reduce((sum, o) => sum + (o.totalAmount || 0), 0),
+ }
+
+ return (
+
+ {/* Точно такие же статистические карточки */}
+
+
+ {/* Точно такие же табы */}
+
+
+
+
+ Новые
+ {orderStats.new > 0 && (
+
+ {orderStats.new}
+
+ )}
+
+
+
+ Одобренные
+ {orderStats.approved > 0 && (
+
+ {orderStats.approved}
+
+ )}
+
+
+
+ Отгрузка
+ {orderStats.shipping > 0 && (
+
+ {orderStats.shipping}
+
+ )}
+
+
+
+ Завершенные
+ {orderStats.completed > 0 && (
+
+ {orderStats.completed}
+
+ )}
+
+
+ Все заявки
+ {orderStats.total > 0 && (
+
+ {orderStats.total}
+
+ )}
+
+
+
+
+ {/* Точно такой же поиск */}
+
+
+ {/* Точно такая же таблица */}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/supplier-orders/supplier-orders-tabs.tsx b/src/components/supplier-orders/supplier-orders-tabs.tsx
deleted file mode 100644
index bfb6583..0000000
--- a/src/components/supplier-orders/supplier-orders-tabs.tsx
+++ /dev/null
@@ -1,610 +0,0 @@
-'use client'
-
-import { useQuery, useMutation } from '@apollo/client'
-import { Clock, CheckCircle, Truck, Package } from 'lucide-react'
-import { useState, useMemo, useCallback, useRef } from 'react'
-import { toast } from 'sonner'
-
-import { MultiLevelSuppliesTable } from '@/components/supplies/multilevel-supplies-table'
-import { Badge } from '@/components/ui/badge'
-import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
-import { SUPPLIER_APPROVE_ORDER, SUPPLIER_REJECT_ORDER, SUPPLIER_SHIP_ORDER, UPDATE_SUPPLY_PARAMETERS } from '@/graphql/mutations'
-import { SUPPLIER_APPROVE_CONSUMABLE_SUPPLY, SUPPLIER_REJECT_CONSUMABLE_SUPPLY, SUPPLIER_SHIP_CONSUMABLE_SUPPLY } from '@/graphql/mutations/fulfillment-consumables-v2'
-import { GET_MY_SUPPLY_ORDERS } from '@/graphql/queries'
-import { GET_MY_SUPPLIER_CONSUMABLE_SUPPLIES } from '@/graphql/queries/fulfillment-consumables-v2'
-import { useAuth } from '@/hooks/useAuth'
-
-
-import { SupplierOrderStats } from './supplier-order-stats'
-import { SupplierOrdersSearch } from './supplier-orders-search'
-
-interface SupplyOrder {
- id: string
- organizationId: string
- partnerId: string
- deliveryDate: string
- status: string
- totalAmount: number
- totalItems: number
- fulfillmentCenterId?: string
- logisticsPartnerId?: string
- packagesCount?: number
- volume?: number
- responsibleEmployee?: string
- notes?: string
- createdAt: string
- updatedAt: string
- partner: {
- id: string
- name?: string
- fullName?: string
- inn: string
- address?: string
- addressFull?: string
- market?: string
- phones?: string[]
- emails?: string[]
- type: string
- }
- organization: {
- id: string
- name?: string
- fullName?: string
- type: string
- market?: string
- }
- fulfillmentCenter?: {
- id: string
- name?: string
- fullName?: string
- address?: string
- addressFull?: string
- type: string
- }
- logisticsPartner?: {
- id: string
- name?: string
- fullName?: string
- type: string
- }
- routes: Array<{
- id: string
- fromLocation: string
- toLocation: string
- fromAddress?: string
- toAddress?: string
- distance?: number
- estimatedTime?: number
- price?: number
- status?: string
- createdDate: string
- }>
- items: Array<{
- id: string
- productId: string
- quantity: number
- price: number
- totalPrice: number
- product: {
- id: string
- name: string
- article: string
- description?: string
- category?: {
- id: string
- name: string
- }
- }
- recipe?: {
- services?: Array<{
- id: string
- name: string
- price: number
- }>
- fulfillmentConsumables?: Array<{
- id: string
- name: string
- price: number
- }>
- sellerConsumables?: Array<{
- id: string
- name: string
- price: number
- }>
- marketplaceCardId?: string
- }
- }>
-}
-
-export function SupplierOrdersTabs() {
- const { user: _user } = useAuth()
- const [activeTab, setActiveTab] = useState('new')
- const [searchQuery, setSearchQuery] = useState('')
- const [dateFilter, setDateFilter] = useState('')
- const [priceRange, setPriceRange] = useState({ min: '', max: '' })
-
- // Загружаем заказы поставок с многоуровневыми данными
- const { data, loading, error } = useQuery(GET_MY_SUPPLY_ORDERS, {
- fetchPolicy: 'cache-and-network',
- })
-
- // Загружаем V2 поставки расходников фулфилмента
- const { data: v2Data, loading: v2Loading, error: v2Error } = useQuery(GET_MY_SUPPLIER_CONSUMABLE_SUPPLIES, {
- fetchPolicy: 'cache-and-network',
- })
-
-
- // Мутации для действий поставщика
- const [supplierApproveOrder] = useMutation(SUPPLIER_APPROVE_ORDER, {
- refetchQueries: [{ query: GET_MY_SUPPLY_ORDERS }],
- onCompleted: (data) => {
- if (data.supplierApproveOrder.success) {
- toast.success(data.supplierApproveOrder.message)
- } else {
- toast.error(data.supplierApproveOrder.message)
- }
- },
- onError: (error) => {
- console.error('Error approving order:', error)
- toast.error('Ошибка при одобрении заказа')
- },
- })
-
- const [supplierRejectOrder] = useMutation(SUPPLIER_REJECT_ORDER, {
- refetchQueries: [{ query: GET_MY_SUPPLY_ORDERS }],
- onCompleted: (data) => {
- if (data.supplierRejectOrder.success) {
- toast.success(data.supplierRejectOrder.message)
- } else {
- toast.error(data.supplierRejectOrder.message)
- }
- },
- onError: (error) => {
- console.error('Error rejecting order:', error)
- toast.error('Ошибка при отклонении заказа')
- },
- })
-
- const [supplierShipOrder] = useMutation(SUPPLIER_SHIP_ORDER, {
- refetchQueries: [{ query: GET_MY_SUPPLY_ORDERS }],
- onCompleted: (data) => {
- if (data.supplierShipOrder.success) {
- toast.success(data.supplierShipOrder.message)
- } else {
- toast.error(data.supplierShipOrder.message)
- }
- },
- onError: (error) => {
- console.error('Error shipping order:', error)
- toast.error('Ошибка при отправке заказа')
- },
- })
-
- // Мутация для обновления параметров поставки (объём и грузовые места)
- const [updateSupplyParameters] = useMutation(UPDATE_SUPPLY_PARAMETERS, {
- update: (cache, { data }) => {
- if (data?.updateSupplyParameters.success) {
- // Обновляем кеш Apollo напрямую
- const existingData = cache.readQuery({ query: GET_MY_SUPPLY_ORDERS })
- if (existingData?.mySupplyOrders) {
- const updatedOrders = existingData.mySupplyOrders.map((order: any) =>
- order.id === data.updateSupplyParameters.order.id
- ? { ...order, ...data.updateSupplyParameters.order }
- : order,
- )
- cache.writeQuery({
- query: GET_MY_SUPPLY_ORDERS,
- data: { mySupplyOrders: updatedOrders },
- })
- }
- }
- },
- onCompleted: (data) => {
- if (data.updateSupplyParameters.success) {
- // Parameters updated successfully
- // Сбрасываем pending состояние для обновленных полей
- const updatedOrder = data.updateSupplyParameters.order
- if ((window as any).__handleUpdateComplete) {
- if (updatedOrder.volume !== null) {
- (window as any).__handleUpdateComplete(updatedOrder.id, 'volume')
- }
- if (updatedOrder.packagesCount !== null) {
- (window as any).__handleUpdateComplete(updatedOrder.id, 'packages')
- }
- }
- } else {
- toast.error(data.updateSupplyParameters.message)
- }
- },
- onError: (error) => {
- console.error('Error updating supply parameters:', error)
- toast.error('Ошибка при обновлении параметров поставки')
- },
- })
-
- // V2 мутации для действий с расходниками фулфилмента
- const [supplierApproveConsumableSupply] = useMutation(SUPPLIER_APPROVE_CONSUMABLE_SUPPLY, {
- refetchQueries: [{ query: GET_MY_SUPPLIER_CONSUMABLE_SUPPLIES }],
- onCompleted: (data) => {
- if (data.supplierApproveConsumableSupply.success) {
- toast.success(data.supplierApproveConsumableSupply.message)
- } else {
- toast.error(data.supplierApproveConsumableSupply.message)
- }
- },
- onError: (error) => {
- console.error('Error approving V2 consumable supply:', error)
- toast.error('Ошибка при одобрении поставки V2')
- },
- })
-
- const [supplierRejectConsumableSupply] = useMutation(SUPPLIER_REJECT_CONSUMABLE_SUPPLY, {
- refetchQueries: [{ query: GET_MY_SUPPLIER_CONSUMABLE_SUPPLIES }],
- onCompleted: (data) => {
- if (data.supplierRejectConsumableSupply.success) {
- toast.success(data.supplierRejectConsumableSupply.message)
- } else {
- toast.error(data.supplierRejectConsumableSupply.message)
- }
- },
- onError: (error) => {
- console.error('Error rejecting V2 consumable supply:', error)
- toast.error('Ошибка при отклонении поставки V2')
- },
- })
-
- const [supplierShipConsumableSupply] = useMutation(SUPPLIER_SHIP_CONSUMABLE_SUPPLY, {
- refetchQueries: [{ query: GET_MY_SUPPLIER_CONSUMABLE_SUPPLIES }],
- onCompleted: (data) => {
- if (data.supplierShipConsumableSupply.success) {
- toast.success(data.supplierShipConsumableSupply.message)
- } else {
- toast.error(data.supplierShipConsumableSupply.message)
- }
- },
- onError: (error) => {
- console.error('Error shipping V2 consumable supply:', error)
- toast.error('Ошибка при отправке поставки V2')
- },
- })
-
- // Debounced обработчики для инпутов с задержкой
- const debounceTimeouts = useRef<{ [key: string]: NodeJS.Timeout }>({})
-
- const handleVolumeChange = useCallback((supplyId: string, volume: number | null) => {
- // Handle volume change with debounce
-
- // Очистить предыдущий таймер для данной поставки
- if (debounceTimeouts.current[`volume-${supplyId}`]) {
- clearTimeout(debounceTimeouts.current[`volume-${supplyId}`])
- }
-
- // Установить новый таймер с задержкой 500ms
- debounceTimeouts.current[`volume-${supplyId}`] = setTimeout(() => {
- // Sending volume update
- updateSupplyParameters({
- variables: {
- id: supplyId,
- volume: volume,
- },
- })
- }, 500)
- }, [updateSupplyParameters])
-
- const handlePackagesChange = useCallback((supplyId: string, packagesCount: number | null) => {
- // Handle packages change with debounce
-
- // Очистить предыдущий таймер для данной поставки
- if (debounceTimeouts.current[`packages-${supplyId}`]) {
- clearTimeout(debounceTimeouts.current[`packages-${supplyId}`])
- }
-
- // Установить новый таймер с задержкой 500ms
- debounceTimeouts.current[`packages-${supplyId}`] = setTimeout(() => {
- // Sending packages update
- updateSupplyParameters({
- variables: {
- id: supplyId,
- packagesCount: packagesCount,
- },
- })
- }, 500)
- }, [updateSupplyParameters])
-
- // Адаптер для преобразования V2 поставок в формат SupplyOrder
- const adaptV2SupplyToSupplyOrder = useCallback((v2Supply: any): SupplyOrder & { isV2?: boolean } => {
- return {
- id: v2Supply.id,
- organizationId: v2Supply.fulfillmentCenterId,
- partnerId: v2Supply.supplierId,
- deliveryDate: v2Supply.requestedDeliveryDate,
- status: v2Supply.status,
- totalAmount: v2Supply.items?.reduce((sum: number, item: any) => sum + (item.totalPrice || 0), 0) || 0,
- totalItems: v2Supply.items?.length || 0,
- fulfillmentCenterId: v2Supply.fulfillmentCenterId,
- logisticsPartnerId: v2Supply.logisticsPartnerId,
- packagesCount: v2Supply.packagesCount,
- volume: v2Supply.estimatedVolume,
- responsibleEmployee: v2Supply.receivedBy?.managerName,
- notes: v2Supply.notes,
- createdAt: v2Supply.createdAt,
- updatedAt: v2Supply.updatedAt,
- isV2: true, // Метка для идентификации V2 поставок
- partner: {
- id: v2Supply.fulfillmentCenter?.id || '',
- name: v2Supply.fulfillmentCenter?.name,
- fullName: v2Supply.fulfillmentCenter?.name,
- inn: v2Supply.fulfillmentCenter?.inn || '',
- type: 'FULFILLMENT',
- },
- organization: {
- id: v2Supply.supplier?.id || '',
- name: v2Supply.supplier?.name,
- fullName: v2Supply.supplier?.name,
- type: 'WHOLESALE',
- },
- fulfillmentCenter: v2Supply.fulfillmentCenter ? {
- id: v2Supply.fulfillmentCenter.id,
- name: v2Supply.fulfillmentCenter.name,
- fullName: v2Supply.fulfillmentCenter.name,
- type: 'FULFILLMENT',
- } : undefined,
- logisticsPartner: v2Supply.logisticsPartner ? {
- id: v2Supply.logisticsPartner.id,
- name: v2Supply.logisticsPartner.name,
- fullName: v2Supply.logisticsPartner.name,
- type: 'LOGISTICS',
- } : undefined,
- routes: [],
- items: v2Supply.items?.map((item: any) => ({
- id: item.id,
- productId: item.productId,
- quantity: item.requestedQuantity,
- price: item.unitPrice,
- totalPrice: item.totalPrice,
- product: {
- id: item.product?.id || item.productId,
- name: item.product?.name || '',
- article: item.product?.article || '',
- description: '',
- },
- })) || [],
- }
- }, [])
-
- // Получаем заказы поставок с многоуровневой структурой + V2 поставки
- const supplierOrders: SupplyOrder[] = useMemo(() => {
- const regularOrders = data?.mySupplyOrders || []
- const v2Orders = (v2Data?.mySupplierConsumableSupplies || []).map(adaptV2SupplyToSupplyOrder)
-
- return [...regularOrders, ...v2Orders]
- }, [data?.mySupplyOrders, v2Data?.mySupplierConsumableSupplies, adaptV2SupplyToSupplyOrder])
-
- // Фильтрация заказов по поисковому запросу
- const filteredOrders = useMemo(() => {
- let filtered = supplierOrders
-
- // Поиск по номеру заявки, заказчику, товарам, ИНН
- if (searchQuery) {
- const query = searchQuery.toLowerCase()
- filtered = filtered.filter(
- (order) =>
- order.id.toLowerCase().includes(query) ||
- (order.organization.name || '').toLowerCase().includes(query) ||
- (order.organization.fullName || '').toLowerCase().includes(query) ||
- (order.organization.inn || '').toLowerCase().includes(query) ||
- order.items.some((item) => item.product.name.toLowerCase().includes(query)),
- )
- }
-
- // Фильтр по диапазону цены
- if (priceRange.min || priceRange.max) {
- filtered = filtered.filter((order) => {
- if (priceRange.min && order.totalAmount < parseFloat(priceRange.min)) return false
- if (priceRange.max && order.totalAmount > parseFloat(priceRange.max)) return false
- return true
- })
- }
-
- return filtered
- }, [supplierOrders, searchQuery, priceRange])
-
- // Разделение заказов по статусам согласно правильной бизнес-логике
- const ordersByStatus = useMemo(() => {
- return {
- new: filteredOrders.filter((order) => order.status === 'PENDING'),
- approved: filteredOrders.filter((order) => order.status === 'SUPPLIER_APPROVED'),
- // inProgress вкладка удалена - она была нелогичной
- shipping: filteredOrders.filter((order) => order.status === 'LOGISTICS_CONFIRMED'), // Готовые к отгрузке
- completed: filteredOrders.filter((order) => ['SHIPPED', 'IN_TRANSIT', 'DELIVERED'].includes(order.status)),
- all: filteredOrders,
- }
- }, [filteredOrders])
-
- const getTabBadgeCount = (tabKey: string) => {
- return ordersByStatus[tabKey as keyof typeof ordersByStatus]?.length || 0
- }
-
- const getCurrentOrders = () => {
- return ordersByStatus[activeTab as keyof typeof ordersByStatus] || []
- }
-
- // Обработчик действий поставщика для многоуровневой таблицы
- const handleSupplierAction = async (supplyId: string, action: string) => {
- try {
- // Находим поставку, чтобы определить её тип
- const allOrders = [...(data?.mySupplyOrders || []), ...(v2Data?.mySupplierConsumableSupplies || []).map(adaptV2SupplyToSupplyOrder)]
- const supply = allOrders.find(order => order.id === supplyId)
- const isV2Supply = (supply as any)?.isV2 === true
-
- switch (action) {
- case 'approve':
- if (isV2Supply) {
- await supplierApproveConsumableSupply({ variables: { id: supplyId } })
- } else {
- await supplierApproveOrder({ variables: { id: supplyId } })
- }
- break
- case 'reject':
- const reason = prompt('Укажите причину отклонения заявки:')
- if (reason) {
- if (isV2Supply) {
- await supplierRejectConsumableSupply({ variables: { id: supplyId, reason } })
- } else {
- await supplierRejectOrder({ variables: { id: supplyId, reason } })
- }
- }
- break
- case 'ship':
- if (isV2Supply) {
- await supplierShipConsumableSupply({ variables: { id: supplyId } })
- } else {
- await supplierShipOrder({ variables: { id: supplyId } })
- }
- break
- case 'cancel':
- // Cancel supply order
- // TODO: Реализовать отмену поставки если нужно
- break
- default:
- console.error('Неизвестное действие:', action, supplyId)
- }
- } catch (error) {
- console.error('Ошибка при выполнении действия:', error)
- toast.error('Ошибка при выполнении действия')
- }
- }
-
- if (loading || v2Loading) {
- return (
-
- )
- }
-
- if (error || v2Error) {
- return (
-
-
- Ошибка загрузки заявок: {error?.message || v2Error?.message}
-
-
- )
- }
-
- return (
-
- {/* Статистика - Модуль 2 согласно правилам */}
-
-
- {/* Блок табов - отдельный блок согласно visual-design-rules.md */}
-
-
-
- {/* Уровень 2: Фильтрация по статусам */}
-
-
- Новые
- {getTabBadgeCount('new') > 0 && (
- {getTabBadgeCount('new')}
- )}
-
-
-
-
- Одобренные
- {getTabBadgeCount('approved') > 0 && (
-
- {getTabBadgeCount('approved')}
-
- )}
-
-
-
-
-
- Отгрузка
- {getTabBadgeCount('shipping') > 0 && (
-
- {getTabBadgeCount('shipping')}
-
- )}
-
-
-
-
- Завершенные
- {getTabBadgeCount('completed') > 0 && (
-
- {getTabBadgeCount('completed')}
-
- )}
-
-
-
- Все заявки
- {getTabBadgeCount('all') > 0 && (
- {getTabBadgeCount('all')}
- )}
-
-
-
-
-
-
- {/* Поиск и фильтры */}
-
-
- {/* Отображение контента */}
-
-
- {getCurrentOrders().length === 0 ? (
-
-
-
- {activeTab === 'new' ? 'Нет новых заявок' : 'Заявки не найдены'}
-
-
- {activeTab === 'new'
- ? 'Новые заявки от заказчиков будут отображаться здесь'
- : 'Попробуйте изменить фильтры поиска'}
-
-
- ) : (
-
- )}
-
-
-
- )
-}
diff --git a/src/components/supplies/create-consumables-supply-page.tsx b/src/components/supplies/create-consumables-supply-page.tsx
index 337ebf9..a89af99 100644
--- a/src/components/supplies/create-consumables-supply-page.tsx
+++ b/src/components/supplies/create-consumables-supply-page.tsx
@@ -13,8 +13,8 @@ import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
-import { CREATE_SUPPLY_ORDER } from '@/graphql/mutations'
-import { GET_MY_COUNTERPARTIES, GET_ORGANIZATION_PRODUCTS, GET_SUPPLY_ORDERS, GET_MY_SUPPLIES } from '@/graphql/queries'
+import { GET_MY_COUNTERPARTIES, GET_ORGANIZATION_PRODUCTS } from '@/graphql/queries'
+import { CREATE_SELLER_CONSUMABLE_SUPPLY, GET_MY_SELLER_CONSUMABLE_SUPPLIES } from '@/graphql/queries/seller-consumables-v2'
import { useAuth } from '@/hooks/useAuth'
import { useSidebar } from '@/hooks/useSidebar'
@@ -86,7 +86,7 @@ export function CreateConsumablesSupplyPage() {
})
// Мутация для создания заказа поставки расходников
- const [createSupplyOrder] = useMutation(CREATE_SUPPLY_ORDER)
+ const [createSupplyOrder] = useMutation(CREATE_SELLER_CONSUMABLE_SUPPLY)
// Фильтруем только поставщиков расходников (поставщиков)
const consumableSuppliers = (counterpartiesData?.myCounterparties || []).filter(
@@ -287,26 +287,23 @@ export function CreateConsumablesSupplyPage() {
const result = await createSupplyOrder({
variables: {
input: {
- partnerId: selectedSupplier.id,
- deliveryDate: deliveryDate,
+ supplierId: selectedSupplier.id,
+ requestedDeliveryDate: deliveryDate,
fulfillmentCenterId: selectedFulfillmentCenter.id,
// 🔄 ЛОГИСТИКА ОПЦИОНАЛЬНА: селлер может выбрать или оставить фулфилменту
...(selectedLogistics?.id ? { logisticsPartnerId: selectedLogistics.id } : {}),
- // 🏷️ КЛАССИФИКАЦИЯ согласно правилам (раздел 2.2)
- consumableType: 'SELLER_CONSUMABLES', // Расходники селлеров
items: selectedConsumables.map((consumable) => ({
productId: consumable.id,
- quantity: consumable.selectedQuantity,
+ requestedQuantity: consumable.selectedQuantity,
})),
},
},
refetchQueries: [
- { query: GET_SUPPLY_ORDERS }, // Обновляем заказы поставок
- { query: GET_MY_SUPPLIES }, // Обновляем расходники фулфилмента
+ { query: GET_MY_SELLER_CONSUMABLE_SUPPLIES }, // Обновляем V2 расходники селлера в dashboard
],
})
- if (result.data?.createSupplyOrder?.success) {
+ if (result.data?.createSellerConsumableSupply?.success) {
toast.success('Заказ поставки расходников создан успешно!')
// Очищаем форму
setSelectedSupplier(null)
@@ -319,7 +316,7 @@ export function CreateConsumablesSupplyPage() {
// Перенаправляем на страницу поставок селлера с открытой вкладкой "Расходники"
router.push('/supplies?tab=consumables')
} else {
- toast.error(result.data?.createSupplyOrder?.message || 'Ошибка при создании заказа поставки')
+ toast.error(result.data?.createSellerConsumableSupply?.message || 'Ошибка при создании заказа поставки')
}
} catch (error) {
console.error('Error creating consumables supply:', error)
diff --git a/src/components/supplies/fulfillment-supplies/seller-supply-orders-tab.tsx b/src/components/supplies/fulfillment-supplies/seller-supply-orders-tab.tsx
index 8b4ea13..3b9a068 100644
--- a/src/components/supplies/fulfillment-supplies/seller-supply-orders-tab.tsx
+++ b/src/components/supplies/fulfillment-supplies/seller-supply-orders-tab.tsx
@@ -17,7 +17,7 @@ import React, { useState } from 'react'
import { Badge } from '@/components/ui/badge'
import { Card } from '@/components/ui/card'
-import { GET_SUPPLY_ORDERS } from '@/graphql/queries'
+import { GET_MY_SELLER_CONSUMABLE_SUPPLIES } from '@/graphql/queries/seller-consumables-v2'
import { useAuth } from '@/hooks/useAuth'
// Типы данных для заказов поставок расходников
@@ -83,7 +83,7 @@ export function SellerSupplyOrdersTab() {
const { user } = useAuth()
// Загружаем заказы поставок
- const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS, {
+ const { data, loading, error } = useQuery(GET_MY_SELLER_CONSUMABLE_SUPPLIES, {
fetchPolicy: 'cache-and-network',
})
@@ -97,11 +97,8 @@ export function SellerSupplyOrdersTab() {
setExpandedOrders(newExpanded)
}
- // Фильтруем заказы созданные текущим селлером И только расходники селлера
- const sellerOrders: SupplyOrder[] = (data?.supplyOrders || []).filter((order: SupplyOrder) => {
- return order.organization.id === user?.organization?.id &&
- order.consumableType === 'SELLER_CONSUMABLES' // Только расходники селлера
- })
+ // V2 система - получаем расходники селлера напрямую
+ const sellerOrders: SupplyOrder[] = data?.mySellerConsumableSupplies || []
const getStatusBadge = (status: SupplyOrder['status']) => {
const statusMap = {
@@ -165,8 +162,8 @@ export function SellerSupplyOrdersTab() {
// Статистика для селлера
const totalOrders = sellerOrders.length
- const totalAmount = sellerOrders.reduce((sum, order) => sum + order.totalAmount, 0)
- const _totalItems = sellerOrders.reduce((sum, order) => sum + order.totalItems, 0)
+ const totalAmount = sellerOrders.reduce((sum, order) => sum + (order.totalCostWithDelivery || 0), 0)
+ const _totalItems = sellerOrders.reduce((sum, order) => sum + (order.items?.length || 0), 0)
const pendingOrders = sellerOrders.filter((order) => order.status === 'PENDING').length
const _approvedOrders = sellerOrders.filter((order) => order.status === 'CONFIRMED').length
const _inTransitOrders = sellerOrders.filter((order) => order.status === 'IN_TRANSIT').length
@@ -299,23 +296,23 @@ export function SellerSupplyOrdersTab() {
- {order.partner.name || order.partner.fullName || 'Не указан'}
+ {order.supplier?.name || order.supplier?.fullName || 'Не указан'}
- {order.partner.inn &&
ИНН: {order.partner.inn}
}
+ {order.supplier?.inn &&
ИНН: {order.supplier.inn}
}
- {formatDate(order.deliveryDate)}
+ {formatDate(order.requestedDeliveryDate)}
|
- {order.totalItems}
+ {order.items?.length || 0}
|
- {formatCurrency(order.totalAmount)}
+ {formatCurrency(order.totalCostWithDelivery || 0)}
|
diff --git a/src/components/supplies/supplies-dashboard.tsx b/src/components/supplies/supplies-dashboard.tsx
index d8ec439..a8b9a95 100644
--- a/src/components/supplies/supplies-dashboard.tsx
+++ b/src/components/supplies/supplies-dashboard.tsx
@@ -7,8 +7,9 @@ import React, { useState, useEffect } from 'react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Alert, AlertDescription } from '@/components/ui/alert'
-import { GET_PENDING_SUPPLIES_COUNT, GET_MY_SUPPLY_ORDERS } from '@/graphql/queries'
import { GET_MY_SELLER_GOODS_SUPPLIES } from '@/graphql/mutations/seller-goods-v2'
+import { GET_PENDING_SUPPLIES_COUNT } from '@/graphql/queries'
+import { GET_MY_SELLER_CONSUMABLE_SUPPLIES } from '@/graphql/queries/seller-consumables-v2'
import { useAuth } from '@/hooks/useAuth'
import { useRealtime } from '@/hooks/useRealtime'
import { useSidebar } from '@/hooks/useSidebar'
@@ -45,53 +46,46 @@ export function SuppliesDashboard() {
errorPolicy: 'ignore',
})
- // 🔧 FEATURE FLAG: Используем V2 систему для товаров
- const USE_V2_GOODS_SYSTEM = process.env.NEXT_PUBLIC_USE_V2_GOODS === 'true'
+ // 🔧 V2 СИСТЕМЫ: Работаем только с V2, без переключений
- // Загружаем поставки селлера для многоуровневой таблицы (V1)
- const { data: mySuppliesData, loading: mySuppliesLoading, refetch: refetchMySupplies } = useQuery(GET_MY_SUPPLY_ORDERS, {
- fetchPolicy: 'cache-and-network',
- errorPolicy: 'all',
- skip: !user || user.organization?.type !== 'SELLER' || (USE_V2_GOODS_SYSTEM && (activeSubTab === 'goods')), // Пропускаем V1 для товаров в V2
- })
+ // Загружаем поставки селлера для многоуровневой таблицы (V1) - УДАЛЯЕТСЯ
+ // const { data: mySuppliesData, loading: mySuppliesLoading, refetch: refetchMySupplies } = useQuery(GET_MY_SUPPLY_ORDERS, {
+ // fetchPolicy: 'cache-and-network',
+ // errorPolicy: 'all',
+ // skip: !user || user.organization?.type !== 'SELLER' || (USE_V2_GOODS_SYSTEM && (activeSubTab === 'goods')), // Пропускаем V1 для товаров в V2
+ // })
// Загружаем V2 товарные поставки селлера
- const { data: myV2GoodsData, loading: myV2GoodsLoading, refetch: refetchMyV2Goods, error: myV2GoodsError } = useQuery(GET_MY_SELLER_GOODS_SUPPLIES, {
+ const { data: myV2GoodsData, loading: myV2GoodsLoading, refetch: refetchMyV2Goods, error: _myV2GoodsError } = useQuery(GET_MY_SELLER_GOODS_SUPPLIES, {
fetchPolicy: 'cache-and-network',
errorPolicy: 'all',
- skip: !user || user.organization?.type !== 'SELLER' || !USE_V2_GOODS_SYSTEM || activeSubTab !== 'goods', // Загружаем только для товаров в V2
+ skip: !user || user.organization?.type !== 'SELLER' || activeSubTab !== 'goods', // Загружаем только для товаров
+ })
+
+ // Загружаем V2 расходники селлера
+ const { data: myV2ConsumablesData, loading: myV2ConsumablesLoading, refetch: refetchMyV2Consumables, error: _myV2ConsumablesError } = useQuery(GET_MY_SELLER_CONSUMABLE_SUPPLIES, {
+ fetchPolicy: 'cache-and-network',
+ errorPolicy: 'all',
+ skip: !user || user.organization?.type !== 'SELLER' || activeSubTab !== 'consumables', // Загружаем только для расходников
})
// Отладка V2 данных
- console.log('🔍 V2 Query Skip Conditions:', {
- USE_V2_GOODS_SYSTEM,
+ console.log('🔍 V2 Query Debug:', {
activeSubTab,
userType: user?.organization?.type,
hasUser: !!user,
- shouldSkip: !user || user.organization?.type !== 'SELLER' || !USE_V2_GOODS_SYSTEM || activeSubTab !== 'goods',
+ goodsLoading: myV2GoodsLoading,
+ consumablesLoading: myV2ConsumablesLoading,
+ goodsData: myV2GoodsData?.mySellerGoodsSupplies?.length || 0,
+ consumablesData: myV2ConsumablesData?.mySellerConsumableSupplies?.length || 0,
})
- if (USE_V2_GOODS_SYSTEM && activeSubTab === 'goods') {
- console.log('🔍 V2 Query Debug:', {
- loading: myV2GoodsLoading,
- error: myV2GoodsError,
- data: myV2GoodsData,
- supplies: myV2GoodsData?.mySellerGoodsSupplies,
- suppliesCount: myV2GoodsData?.mySellerGoodsSupplies?.length || 0,
- })
-
- // Детальная структура первой поставки
- if (myV2GoodsData?.mySellerGoodsSupplies?.length > 0) {
- console.log('🔍 V2 First Supply Structure:', JSON.stringify(myV2GoodsData.mySellerGoodsSupplies[0], null, 2))
- }
- }
-
useRealtime({
onEvent: (evt) => {
if (evt.type === 'supply-order:new' || evt.type === 'supply-order:updated') {
refetchPending()
- refetchMySupplies() // Обновляем V1 поставки селлера при изменениях
- refetchMyV2Goods() // Обновляем V2 поставки селлера при изменениях
+ refetchMyV2Goods() // Обновляем V2 товарные поставки селлера при изменениях
+ refetchMyV2Consumables() // Обновляем V2 расходники селлера при изменениях
}
},
})
@@ -433,28 +427,21 @@ export function SuppliesDashboard() {
{/* ✅ ЕДИНАЯ ЛОГИКА для табов "Карточки" и "Поставщики" согласно rules2.md 9.5.3 */}
{(activeThirdTab === 'cards' || activeThirdTab === 'suppliers') && (
- {/* V2 система индикатор */}
- {USE_V2_GOODS_SYSTEM && (
-
- 🆕 Используется V2 система товарных поставок
-
- )}
+ {/* V2 система - всегда активна */}
+
+ 🆕 V2 система товарных поставок
+
({
- // Адаптируем V2 структуру под V1 формат для таблицы
- ...v2Supply,
- partner: v2Supply.supplier, // supplier → partner для совместимости
- deliveryDate: v2Supply.requestedDeliveryDate, // для совместимости
- items: v2Supply.recipeItems, // recipeItems → items для совместимости
- }))
- : (mySuppliesData?.mySupplyOrders || []).filter((supply: any) =>
- supply.consumableType !== 'SELLER_CONSUMABLES',
- )
- }
- loading={USE_V2_GOODS_SYSTEM ? myV2GoodsLoading : mySuppliesLoading}
+ goodsSupplies={(myV2GoodsData?.mySellerGoodsSupplies || []).map((v2Supply: any) => ({
+ // Адаптируем V2 структуру под V1 формат для таблицы
+ ...v2Supply,
+ partner: v2Supply.supplier, // supplier → partner для совместимости
+ deliveryDate: v2Supply.requestedDeliveryDate, // для совместимости
+ items: v2Supply.recipeItems, // recipeItems → items для совместимости
+ }))}
+ loading={myV2GoodsLoading}
/>
)}
@@ -463,7 +450,14 @@ export function SuppliesDashboard() {
{/* РАСХОДНИКИ СЕЛЛЕРА - сохраняем весь функционал */}
{activeSubTab === 'consumables' && (
- {isWholesale ? : }
+
+ {/* V2 система - всегда активна */}
+
+ 🆕 V2 система расходников селлера
+
+
+ {isWholesale ? : }
+
)}
)}
diff --git a/src/graphql/mutations/seller-goods-v2.ts b/src/graphql/mutations/seller-goods-v2.ts
index 622f3da..5688a3f 100644
--- a/src/graphql/mutations/seller-goods-v2.ts
+++ b/src/graphql/mutations/seller-goods-v2.ts
@@ -94,6 +94,26 @@ export const UPDATE_SELLER_GOODS_SUPPLY_STATUS = gql`
}
`
+export const UPDATE_SUPPLY_VOLUME_V2 = gql`
+ mutation UpdateSupplyVolumeV2($id: ID!, $volume: Float) {
+ updateSellerGoodsSupplyVolume(id: $id, volume: $volume) {
+ id
+ estimatedVolume
+ updatedAt
+ }
+ }
+`
+
+export const UPDATE_SUPPLY_PACKAGES_V2 = gql`
+ mutation UpdateSupplyPackagesV2($id: ID!, $packagesCount: Int) {
+ updateSellerGoodsSupplyPackages(id: $id, packagesCount: $packagesCount) {
+ id
+ packagesCount
+ updatedAt
+ }
+ }
+`
+
export const CANCEL_SELLER_GOODS_SUPPLY = gql`
mutation CancelSellerGoodsSupply($id: ID!) {
cancelSellerGoodsSupply(id: $id) {
@@ -152,6 +172,74 @@ export const GET_MY_SELLER_GOODS_SUPPLIES = gql`
}
`
+export const GET_MY_SELLER_GOODS_SUPPLY_REQUESTS = gql`
+ query GetMySellerGoodsSupplyRequests {
+ mySellerGoodsSupplyRequests {
+ id
+ status
+ sellerId
+ seller {
+ id
+ name
+ inn
+ }
+ fulfillmentCenterId
+ fulfillmentCenter {
+ id
+ name
+ inn
+ }
+ supplierId
+ supplier {
+ id
+ name
+ inn
+ }
+ logisticsPartnerId
+ logisticsPartner {
+ id
+ name
+ inn
+ }
+ requestedDeliveryDate
+ estimatedDeliveryDate
+ shippedAt
+ deliveredAt
+ supplierApprovedAt
+ receivedById
+ receivedBy {
+ id
+ managerName
+ phone
+ }
+ notes
+ supplierNotes
+ receiptNotes
+ totalCostWithDelivery
+ packagesCount
+ estimatedVolume
+ trackingNumber
+
+ recipeItems {
+ id
+ productId
+ quantity
+ recipeType
+ product {
+ id
+ name
+ article
+ price
+ mainImage
+ }
+ }
+
+ createdAt
+ updatedAt
+ }
+ }
+`
+
export const GET_SELLER_GOODS_INVENTORY = gql`
query GetSellerGoodsInventory {
mySellerGoodsInventory {
diff --git a/src/graphql/queries/seller-consumables-v2.ts b/src/graphql/queries/seller-consumables-v2.ts
index effaa14..e9627a6 100644
--- a/src/graphql/queries/seller-consumables-v2.ts
+++ b/src/graphql/queries/seller-consumables-v2.ts
@@ -58,7 +58,7 @@ export const GET_MY_SELLER_CONSUMABLE_SUPPLIES = gql`
trackingNumber
# Данные приемки
- deliveredAt
+ receivedAt
receivedById
receivedBy {
id
@@ -149,7 +149,7 @@ export const GET_SELLER_CONSUMABLE_SUPPLY = gql`
trackingNumber
# Данные приемки
- deliveredAt
+ receivedAt
receivedById
receivedBy {
id
@@ -302,7 +302,7 @@ export const UPDATE_SELLER_SUPPLY_STATUS = gql`
updatedAt
supplierApprovedAt
shippedAt
- deliveredAt
+ receivedAt
supplierNotes
receiptNotes
}
diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts
index 04330e2..603ee31 100644
--- a/src/graphql/resolvers.ts
+++ b/src/graphql/resolvers.ts
@@ -14,6 +14,7 @@ import { fulfillmentConsumableV2Queries as fulfillmentConsumableV2QueriesRestore
import { fulfillmentInventoryV2Queries } from './resolvers/fulfillment-inventory-v2'
import { sellerGoodsQueries, sellerGoodsMutations } from './resolvers/goods-supply-v2'
import { logisticsConsumableV2Queries, logisticsConsumableV2Mutations } from './resolvers/logistics-consumables-v2'
+import { sellerConsumableQueries, sellerConsumableMutations } from './resolvers/seller-consumables'
import { sellerInventoryV2Queries } from './resolvers/seller-inventory-v2'
import { CommercialDataAudit } from './security/commercial-data-audit'
import { createSecurityContext } from './security/index'
@@ -2909,6 +2910,9 @@ export const resolvers = {
// V2 система поставок для логистики
...logisticsConsumableV2Queries,
+ // V2 система поставок расходников селлера
+ ...sellerConsumableQueries,
+
// Новая система складских остатков V2 (заменяет старый myFulfillmentSupplies)
...fulfillmentInventoryV2Queries,
@@ -10303,6 +10307,9 @@ resolvers.Mutation = {
// V2 mutations для логистики
...logisticsConsumableV2Mutations,
+ // V2 mutations для поставок расходников селлера
+ ...sellerConsumableMutations,
+
// V2 mutations для товарных поставок селлера
...sellerGoodsMutations,
}
diff --git a/src/graphql/resolvers/seller-consumables.ts b/src/graphql/resolvers/seller-consumables.ts
index c74ed64..aea5b6d 100644
--- a/src/graphql/resolvers/seller-consumables.ts
+++ b/src/graphql/resolvers/seller-consumables.ts
@@ -241,7 +241,7 @@ export const sellerConsumableMutations = {
const fulfillmentCenter = await prisma.organization.findUnique({
where: { id: fulfillmentCenterId },
include: {
- counterpartiesAsCounterparty: {
+ counterpartyOf: {
where: { organizationId: user.organizationId! },
},
},
@@ -251,7 +251,7 @@ export const sellerConsumableMutations = {
throw new GraphQLError('Фулфилмент-центр не найден или имеет неверный тип')
}
- if (fulfillmentCenter.counterpartiesAsCounterparty.length === 0) {
+ if (fulfillmentCenter.counterpartyOf.length === 0) {
throw new GraphQLError('Нет партнерских отношений с данным фулфилмент-центром')
}
@@ -259,7 +259,7 @@ export const sellerConsumableMutations = {
const supplier = await prisma.organization.findUnique({
where: { id: supplierId },
include: {
- counterpartiesAsCounterparty: {
+ counterpartyOf: {
where: { organizationId: user.organizationId! },
},
},
@@ -269,7 +269,7 @@ export const sellerConsumableMutations = {
throw new GraphQLError('Поставщик не найден или имеет неверный тип')
}
- if (supplier.counterpartiesAsCounterparty.length === 0) {
+ if (supplier.counterpartyOf.length === 0) {
throw new GraphQLError('Нет партнерских отношений с данным поставщиком')
}
|