diff --git a/src/components/fulfillment-warehouse/fulfillment-supplies-page.tsx b/src/components/fulfillment-warehouse/fulfillment-supplies-page.tsx index 27097c9..37f4473 100644 --- a/src/components/fulfillment-warehouse/fulfillment-supplies-page.tsx +++ b/src/components/fulfillment-warehouse/fulfillment-supplies-page.tsx @@ -1,25 +1,34 @@ 'use client' import { useQuery } from '@apollo/client' -import { Package, Wrench, AlertTriangle, CheckCircle, Clock } from 'lucide-react' +import { AlertTriangle, CheckCircle } from 'lucide-react' import React, { useState, useMemo, useCallback } from 'react' import { toast } from 'sonner' import { Sidebar } from '@/components/dashboard/sidebar' import { GET_MY_FULFILLMENT_SUPPLIES } from '@/graphql/queries' +import { GET_MY_FULFILLMENT_CONSUMABLE_SUPPLIES } from '@/graphql/queries/fulfillment-consumables-v2' import { useSidebar } from '@/hooks/useSidebar' -// Новые компоненты import { SuppliesGrid } from './supplies-grid' import { SuppliesHeader } from './supplies-header' import { SuppliesList } from './supplies-list' import { SuppliesStats } from './supplies-stats' - -// Типы import { Supply, FilterState, SortState, ViewMode, GroupBy, StatusConfig } from './types' // Статусы расходников с цветами const STATUS_CONFIG = { + 'На складе': { + label: 'На складе', + color: 'bg-green-500/20 text-green-300', + icon: CheckCircle, + }, + Недоступен: { + label: 'Недоступен', + color: 'bg-red-500/20 text-red-300', + icon: AlertTriangle, + }, + // Обратная совместимость available: { label: 'Доступен', color: 'bg-green-500/20 text-green-300', @@ -52,7 +61,7 @@ export function FulfillmentSuppliesPage() { const [groupBy, setGroupBy] = useState('none') const [expandedSupplies, setExpandedSupplies] = useState>(new Set()) - // Загрузка данных + // Загрузка данных складских остатков const { data: suppliesData, loading, @@ -65,8 +74,51 @@ export function FulfillmentSuppliesPage() { }, }) - const supplies: Supply[] = suppliesData?.myFulfillmentSupplies || [] + // Загрузка истории поставок для детализации + const { data: deliveriesData, loading: deliveriesLoading } = useQuery(GET_MY_FULFILLMENT_CONSUMABLE_SUPPLIES, { + fetchPolicy: 'cache-and-network', + onError: (error) => { + console.warn('Ошибка загрузки истории поставок:', error.message) + }, + }) + const supplies = useMemo(() => suppliesData?.myFulfillmentSupplies || [], [suppliesData]) + const allDeliveries = useMemo(() => deliveriesData?.myFulfillmentConsumableSupplies || [], [deliveriesData]) + + // Функция для корректировки агрегированных данных на основе истории поставок + const getAggregatedSupplyData = useCallback( + (supply: Supply) => { + if (!supply.productId || !allDeliveries.length) return supply + + // Получаем все поставки этого товара + const productDeliveries = allDeliveries.filter((delivery) => + delivery.items?.some((item) => item.productId === supply.productId), + ) + + if (!productDeliveries.length) return supply + + // Вычисляем суммы из истории поставок + let totalReceived = 0 + let totalShipped = 0 + + productDeliveries.forEach((delivery) => { + const item = delivery.items?.find((item) => item.productId === supply.productId) + if (item) { + totalReceived += item.receivedQuantity || 0 + totalShipped += item.shippedQuantity || 0 + } + }) + + // Возвращаем supply с исправленными данными + return { + ...supply, + quantity: totalReceived, // ПОСТАВЛЕНО = сумма всех receivedQuantity + currentStock: Math.max(0, totalReceived - totalShipped), // ОСТАТОК = получено - отгружено + shippedQuantity: totalShipped, // ОТПРАВЛЕНО = сумма всех shippedQuantity + } + }, + [allDeliveries], + ) // Функции const getStatusConfig = useCallback((supply: Supply): StatusConfig => { @@ -75,58 +127,53 @@ export function FulfillmentSuppliesPage() { const getSupplyDeliveries = useCallback( (supply: Supply): Supply[] => { - return supplies.filter((s) => s.name === supply.name && s.category === supply.category) + if (!supply.productId || !allDeliveries.length) return [] + + // Фильтруем поставки по productId товара + return allDeliveries + .filter((delivery) => delivery.items?.some((item) => item.productId === supply.productId)) + .map((delivery) => { + // Преобразуем поставку в формат Supply для DeliveryDetails + const item = delivery.items?.find((item) => item.productId === supply.productId) + if (!item) return null + + return { + id: delivery.id, + productId: supply.productId, + name: supply.name, + description: supply.description, + price: item.unitPrice || 0, + quantity: item.requestedQuantity || 0, + unit: supply.unit, + category: supply.category, + status: delivery.status ? delivery.status.toLowerCase() : 'pending', + date: delivery.createdAt || '', + supplier: delivery.supplier?.name || supply.supplier, + minStock: supply.minStock, + currentStock: item.receivedQuantity || 0, + imageUrl: supply.imageUrl, + createdAt: delivery.createdAt || '', + updatedAt: delivery.updatedAt || '', + shippedQuantity: item.shippedQuantity || 0, + } + }) + .filter(Boolean) as Supply[] }, - [supplies], + [allDeliveries], ) - // Объединение одинаковых расходников + // V2 система уже возвращает агрегированные данные, дополнительная консолидация не нужна const consolidatedSupplies = useMemo(() => { - const grouped = supplies.reduce( - (acc, supply) => { - const key = supply.article // НОВОЕ: группировка по артикулу СФ - // СТАРОЕ - ОТКАТ: const key = `${supply.name}-${supply.category}` - if (!acc[key]) { - acc[key] = { - ...supply, - currentStock: 0, - quantity: 0, // Общее количество поставленного (= заказанному) - shippedQuantity: 0, // Общее отправленное количество - status: 'consolidated', // Не используем статус от отдельной поставки - } - } - - // НОВОЕ: Учитываем принятые поставки (все варианты статусов) - if (supply.status === 'доставлено' || supply.status === 'На складе' || supply.status === 'in-stock') { - // СТАРОЕ - ОТКАТ: if (supply.status === 'in-stock') { - // НОВОЕ: Используем actualQuantity (фактически поставленное) вместо quantity - const actualQuantity = supply.actualQuantity ?? supply.quantity // По умолчанию = заказанному - - acc[key].quantity += actualQuantity - acc[key]!.shippedQuantity! += supply.shippedQuantity || 0 - acc[key]!.currentStock += actualQuantity - (supply.shippedQuantity || 0) - - /* СТАРОЕ - ОТКАТ: - // Суммируем только принятое количество - acc[key].quantity += supply.quantity - // Суммируем отправленное количество - acc[key]!.shippedQuantity! += supply.shippedQuantity || 0 - // Остаток = Принятое - Отправленное - acc[key]!.currentStock += supply.quantity - (supply.shippedQuantity || 0) - */ - } - - - return acc - }, - {} as Record, - ) - - const result = Object.values(grouped) - - - return result - }, [supplies]) + // Корректируем агрегированные данные на основе истории поставок для точности + return supplies.map((supply) => { + const correctedSupply = getAggregatedSupplyData(supply) + return { + ...correctedSupply, + // Переопределяем статус на основе исправленных остатков + status: correctedSupply.currentStock > 0 ? 'На складе' : 'Недоступен', + } + }) + }, [supplies, getAggregatedSupplyData]) // Фильтрация и сортировка const filteredAndSortedSupplies = useMemo(() => { @@ -135,7 +182,11 @@ export function FulfillmentSuppliesPage() { supply.name.toLowerCase().includes(filters.search.toLowerCase()) || supply.description.toLowerCase().includes(filters.search.toLowerCase()) const matchesCategory = !filters.category || supply.category === filters.category - const matchesStatus = !filters.status || + const matchesStatus = + !filters.status || + (filters.status === 'На складе' && supply.currentStock > 0) || + (filters.status === 'Недоступен' && supply.currentStock === 0) || + // Обратная совместимость (filters.status === 'available' && supply.currentStock > 0) || (filters.status === 'unavailable' && supply.currentStock === 0) const matchesSupplier = @@ -147,12 +198,12 @@ export function FulfillmentSuppliesPage() { // Сортировка filtered.sort((a, b) => { - let aValue: any = a[sort.field] - let bValue: any = b[sort.field] + let aValue: string | number = a[sort.field] + let bValue: string | number = b[sort.field] if (typeof aValue === 'string') { aValue = aValue.toLowerCase() - bValue = bValue.toLowerCase() + bValue = (bValue as string).toLowerCase() } if (sort.direction === 'asc') { @@ -173,7 +224,7 @@ export function FulfillmentSuppliesPage() { (acc, supply) => { let key: string if (groupBy === 'status') { - key = supply.currentStock > 0 ? 'Доступен' : 'Недоступен' + key = supply.currentStock > 0 ? 'На складе' : 'Недоступен' } else { key = supply[groupBy] || 'Без категории' } @@ -235,7 +286,7 @@ export function FulfillmentSuppliesPage() { toast.success('Данные обновлены') }, [refetch]) - if (loading) { + if (loading || deliveriesLoading) { return (
diff --git a/src/components/fulfillment-warehouse/supplies-stats.tsx b/src/components/fulfillment-warehouse/supplies-stats.tsx index ba4fc7f..22c6fca 100644 --- a/src/components/fulfillment-warehouse/supplies-stats.tsx +++ b/src/components/fulfillment-warehouse/supplies-stats.tsx @@ -1,6 +1,6 @@ 'use client' -import { Package, AlertTriangle, TrendingUp, TrendingDown, DollarSign, Activity } from 'lucide-react' +import { Package, TrendingUp, TrendingDown, DollarSign, Activity } from 'lucide-react' import React, { useMemo } from 'react' import { Card } from '@/components/ui/card' @@ -74,15 +74,15 @@ export function SuppliesStats({ supplies }: SuppliesStatsProps) {
- {/* Мало на складе */} + {/* Остаток */}
-

Мало на складе

-

{formatNumber(stats.lowStock)}

+

Остаток

+

{formatNumber(stats.totalStock)} шт

-
- +
+
diff --git a/src/components/fulfillment-warehouse/types.ts b/src/components/fulfillment-warehouse/types.ts index ea6e690..6312583 100644 --- a/src/components/fulfillment-warehouse/types.ts +++ b/src/components/fulfillment-warehouse/types.ts @@ -3,6 +3,7 @@ import { LucideIcon } from 'lucide-react' // Основные типы данных export interface Supply { id: string + productId?: string // ID продукта для фильтрации истории поставок name: string description: string price: number