diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx index 8559f65..81d0f13 100644 --- a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx +++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx @@ -33,6 +33,7 @@ import { CREATE_SUPPLY_ORDER } from "@/graphql/mutations"; import { OrganizationAvatar } from "@/components/market/organization-avatar"; import { toast } from "sonner"; import Image from "next/image"; +import { useAuth } from "@/hooks/useAuth"; interface FulfillmentConsumableSupplier { id: string; @@ -77,6 +78,7 @@ interface SelectedFulfillmentConsumable { export function CreateFulfillmentConsumablesSupplyPage() { const router = useRouter(); const { getSidebarMargin } = useSidebar(); + const { user } = useAuth(); const [selectedSupplier, setSelectedSupplier] = useState(null); const [selectedConsumables, setSelectedConsumables] = useState< @@ -222,7 +224,8 @@ export function CreateFulfillmentConsumablesSupplyPage() { input: { partnerId: selectedSupplier.id, deliveryDate: deliveryDate, - // Для фулфилмента не требуется выбор фулфилмент-центра, поставка идет на свой склад + // Для фулфилмента указываем себя как получателя (поставка на свой склад) + fulfillmentCenterId: user?.organization?.id, items: selectedConsumables.map((consumable) => ({ productId: consumable.id, quantity: consumable.selectedQuantity, diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx index 021ea40..7f7d8b4 100644 --- a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx +++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx @@ -39,6 +39,7 @@ interface SupplyOrderItem { interface SupplyOrder { id: string; + organizationId: string; deliveryDate: string; status: string; totalAmount: number; @@ -74,16 +75,21 @@ export function FulfillmentConsumablesOrdersTab() { const [expandedOrders, setExpandedOrders] = useState>(new Set()); const { user } = useAuth(); - const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS); + const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS, { + fetchPolicy: 'cache-and-network', // Принудительно проверяем сервер + notifyOnNetworkStatusChange: true + }); // Получаем ID текущей организации (фулфилмент-центра) const currentOrganizationId = user?.organization?.id; - // Фильтруем заказы где текущая организация является получателем + // Фильтруем заказы где текущая организация является получателем (заказы ОТ селлеров) const incomingSupplyOrders: SupplyOrder[] = (data?.supplyOrders || []).filter( (order: SupplyOrder) => { - // Показываем заказы где текущий фулфилмент-центр указан как получатель - return order.fulfillmentCenterId === currentOrganizationId; + // Показываем заказы где текущий фулфилмент-центр указан как получатель + // И заказчик НЕ является самим фулфилмент-центром (исключаем наши собственные заказы) + return order.fulfillmentCenterId === currentOrganizationId && + order.organizationId !== currentOrganizationId; } ); diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-detailed-supplies-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-detailed-supplies-tab.tsx index e3d43a1..a24293f 100644 --- a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-detailed-supplies-tab.tsx +++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-detailed-supplies-tab.tsx @@ -7,335 +7,128 @@ import { Badge } from "@/components/ui/badge"; import { StatsCard } from "../../supplies/ui/stats-card"; import { StatsGrid } from "../../supplies/ui/stats-grid"; import { useRouter } from "next/navigation"; +import { useQuery } from "@apollo/client"; +import { GET_SUPPLY_ORDERS } from "@/graphql/queries"; +import { useAuth } from "@/hooks/useAuth"; import { Calendar, - MapPin, Building2, TrendingUp, - AlertTriangle, DollarSign, Wrench, - Box, Package2, - Tags, Plus, + ChevronDown, + ChevronRight, } from "lucide-react"; -// Типы данных для расходников ФФ детально -interface ConsumableParameter { +// Интерфейс для заказа +interface SupplyOrder { id: string; - name: string; - value: string; - unit?: string; -} - -interface Consumable { - id: string; - name: string; - sku: string; - category: string; - type: "packaging" | "labels" | "protective" | "tools" | "other"; - plannedQty: number; - actualQty: number; - defectQty: number; - unitPrice: number; - parameters: ConsumableParameter[]; -} - -interface ConsumableSupplier { - id: string; - name: string; - inn: string; - contact: string; - address: string; - consumables: Consumable[]; - totalAmount: number; -} - -interface ConsumableRoute { - id: string; - from: string; - fromAddress: string; - to: string; - toAddress: string; - suppliers: ConsumableSupplier[]; - totalConsumablesPrice: number; - logisticsPrice: number; - totalAmount: number; -} - -interface FulfillmentConsumableSupply { - id: string; - number: number; + organizationId: string; deliveryDate: string; - createdDate: string; - routes: ConsumableRoute[]; - plannedTotal: number; - actualTotal: number; - defectTotal: number; - totalConsumablesPrice: number; - totalLogisticsPrice: number; - grandTotal: number; - status: "planned" | "in-transit" | "delivered" | "completed"; + createdAt: string; + totalItems: number; + totalAmount: number; + status: string; + items: { + id: string; + quantity: number; + price: number; + totalPrice: number; + product: { + name: string; + article: string; + category?: { + name: string; + }; + }; + }[]; } -// Моковые данные для расходников ФФ детально -const mockFulfillmentConsumablesDetailed: FulfillmentConsumableSupply[] = [ - { - id: "ffcd1", - number: 2001, - deliveryDate: "2024-01-18", - createdDate: "2024-01-14", - status: "delivered", - plannedTotal: 5000, - actualTotal: 4950, - defectTotal: 50, - totalConsumablesPrice: 125000, - totalLogisticsPrice: 8000, - grandTotal: 133000, - routes: [ - { - id: "ffcdr1", - from: "Склад расходников ФФ", - fromAddress: "Москва, ул. Промышленная, 12", - to: "SFERAV Logistics ФФ", - toAddress: "Москва, ул. Складская, 15", - totalConsumablesPrice: 125000, - logisticsPrice: 8000, - totalAmount: 133000, - suppliers: [ - { - id: "ffcds1", - name: 'ООО "УпакСервис ФФ Детально"', - inn: "7703456789", - contact: "+7 (495) 777-88-99", - address: "Москва, ул. Упаковочная, 5", - totalAmount: 75000, - consumables: [ - { - id: "ffcdcons1", - name: "Коробки для ФФ детально 40x30x15", - sku: "BOX-FFD-403015", - category: "Упаковка ФФ детально", - type: "packaging", - plannedQty: 2000, - actualQty: 1980, - defectQty: 20, - unitPrice: 45, - parameters: [ - { - id: "ffcdp1", - name: "Размер", - value: "40x30x15", - unit: "см", - }, - { - id: "ffcdp2", - name: "Материал", - value: "Гофрокартон усиленный ФФ", - }, - { - id: "ffcdp3", - name: "Плотность", - value: "5", - unit: "слоев", - }, - { id: "ffcdp4", name: "Сертификация ФФ", value: "Пройдена" }, - ], - }, - ], - }, - ], - }, - ], - }, - { - id: "ffcd2", - number: 2002, - deliveryDate: "2024-01-22", - createdDate: "2024-01-16", - status: "in-transit", - plannedTotal: 3000, - actualTotal: 3000, - defectTotal: 0, - totalConsumablesPrice: 85000, - totalLogisticsPrice: 5500, - grandTotal: 90500, - routes: [ - { - id: "ffcdr2", - from: "Склад расходников ФФ", - fromAddress: "Москва, ул. Промышленная, 12", - to: "WB Подольск ФФ", - toAddress: "Подольск, ул. Складская, 25", - totalConsumablesPrice: 85000, - logisticsPrice: 5500, - totalAmount: 90500, - suppliers: [ - { - id: "ffcds2", - name: 'ООО "ЭтикеткаПро ФФ"', - inn: "7704567890", - contact: "+7 (495) 888-99-00", - address: "Москва, ул. Этикеточная, 3", - totalAmount: 85000, - consumables: [ - { - id: "ffcdcons2", - name: "Этикетки самоклеящиеся ФФ 10x5", - sku: "LBL-FFD-105", - category: "Этикетки ФФ детально", - type: "labels", - plannedQty: 3000, - actualQty: 3000, - defectQty: 0, - unitPrice: 28, - parameters: [ - { - id: "ffcdp5", - name: "Размер", - value: "10x5", - unit: "см", - }, - { - id: "ffcdp6", - name: "Материал", - value: "Бумага самоклеящаяся ФФ", - }, - { id: "ffcdp7", name: "Клей", value: "Акриловый" }, - { id: "ffcdp8", name: "Качество ФФ", value: "Премиум" }, - ], - }, - ], - }, - ], - }, - ], - }, -]; +// Функция для форматирования валюты +const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("ru-RU", { + style: "currency", + currency: "RUB", + minimumFractionDigits: 0, + }).format(amount); +}; + +// Функция для форматирования даты +const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString("ru-RU"); +}; + +// Функция для отображения статуса +const getStatusBadge = (status: string) => { + const statusConfig = { + PENDING: { label: "Ожидает", color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30" }, + CONFIRMED: { label: "Подтверждён", color: "bg-blue-500/20 text-blue-300 border-blue-500/30" }, + IN_PROGRESS: { label: "В работе", color: "bg-purple-500/20 text-purple-300 border-purple-500/30" }, + SHIPPED: { label: "Отправлен", color: "bg-orange-500/20 text-orange-300 border-orange-500/30" }, + DELIVERED: { label: "Доставлен", color: "bg-green-500/20 text-green-300 border-green-500/30" }, + CANCELLED: { label: "Отменён", color: "bg-red-500/20 text-red-300 border-red-500/30" }, + }; + + const config = statusConfig[status as keyof typeof statusConfig] || statusConfig.PENDING; + + return ( + + {config.label} + + ); +}; export function FulfillmentDetailedSuppliesTab() { const router = useRouter(); - const [expandedSupplies, setExpandedSupplies] = useState>( - new Set() - ); - const [expandedRoutes, setExpandedRoutes] = useState>(new Set()); - const [expandedSuppliers, setExpandedSuppliers] = useState>( - new Set() - ); - const [expandedConsumables, setExpandedConsumables] = useState>( - new Set() + const { user } = useAuth(); + const [expandedOrders, setExpandedOrders] = useState>(new Set()); + + // Загружаем реальные данные заказов расходников + const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS, { + fetchPolicy: 'cache-and-network', // Принудительно проверяем сервер + notifyOnNetworkStatusChange: true + }); + + // Получаем ID текущей организации (фулфилмент-центра) + const currentOrganizationId = user?.organization?.id; + + // Фильтруем заказы созданные текущей организацией (наши расходники) + const ourSupplyOrders: SupplyOrder[] = (data?.supplyOrders || []).filter( + (order: SupplyOrder) => order.organizationId === currentOrganizationId ); - const toggleSupplyExpansion = (supplyId: string) => { - const newExpanded = new Set(expandedSupplies); - if (newExpanded.has(supplyId)) { - newExpanded.delete(supplyId); + const toggleOrderExpansion = (orderId: string) => { + const newExpanded = new Set(expandedOrders); + if (newExpanded.has(orderId)) { + newExpanded.delete(orderId); } else { - newExpanded.add(supplyId); + newExpanded.add(orderId); } - setExpandedSupplies(newExpanded); + setExpandedOrders(newExpanded); }; - const toggleRouteExpansion = (routeId: string) => { - const newExpanded = new Set(expandedRoutes); - if (newExpanded.has(routeId)) { - newExpanded.delete(routeId); - } else { - newExpanded.add(routeId); - } - setExpandedRoutes(newExpanded); - }; + if (loading) { + return ( +
+
+ Загрузка наших расходников... +
+ ); + } - const toggleSupplierExpansion = (supplierId: string) => { - const newExpanded = new Set(expandedSuppliers); - if (newExpanded.has(supplierId)) { - newExpanded.delete(supplierId); - } else { - newExpanded.add(supplierId); - } - setExpandedSuppliers(newExpanded); - }; - - const toggleConsumableExpansion = (consumableId: string) => { - const newExpanded = new Set(expandedConsumables); - if (newExpanded.has(consumableId)) { - newExpanded.delete(consumableId); - } else { - newExpanded.add(consumableId); - } - setExpandedConsumables(newExpanded); - }; - - const getStatusBadge = (status: FulfillmentConsumableSupply["status"]) => { - const statusMap = { - planned: { - label: "Запланирована", - color: "bg-blue-500/20 text-blue-300 border-blue-500/30", - }, - "in-transit": { - label: "В пути", - color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30", - }, - delivered: { - label: "Доставлена", - color: "bg-green-500/20 text-green-300 border-green-500/30", - }, - completed: { - label: "Завершена", - color: "bg-purple-500/20 text-purple-300 border-purple-500/30", - }, - }; - const { label, color } = statusMap[status]; - return {label}; - }; - - const getTypeBadge = (type: Consumable["type"]) => { - const typeMap = { - packaging: { - label: "Упаковка", - color: "bg-blue-500/20 text-blue-300 border-blue-500/30", - }, - labels: { - label: "Этикетки", - color: "bg-green-500/20 text-green-300 border-green-500/30", - }, - protective: { - label: "Защитная", - color: "bg-orange-500/20 text-orange-300 border-orange-500/30", - }, - tools: { - label: "Инструменты", - color: "bg-purple-500/20 text-purple-300 border-purple-500/30", - }, - other: { - label: "Прочее", - color: "bg-gray-500/20 text-gray-300 border-gray-500/30", - }, - }; - const { label, color } = typeMap[type]; - return {label}; - }; - - const formatCurrency = (amount: number) => { - return new Intl.NumberFormat("ru-RU", { - style: "currency", - currency: "RUB", - minimumFractionDigits: 0, - }).format(amount); - }; - - const formatDate = (dateString: string) => { - return new Date(dateString).toLocaleDateString("ru-RU", { - day: "2-digit", - month: "2-digit", - year: "numeric", - }); - }; - - const calculateConsumableTotal = (consumable: Consumable) => { - return consumable.actualQty * consumable.unitPrice; - }; + if (error) { + return ( +
+
+ +

Ошибка загрузки расходников

+

{error.message}

+
+
+ ); + } return (
@@ -358,477 +151,224 @@ export function FulfillmentDetailedSuppliesTab() {
- {/* Статистика расходников ФФ детально */} + {/* Статистика наших расходников */} sum + supply.grandTotal, + ourSupplyOrders.reduce( + (sum: number, order: SupplyOrder) => sum + order.totalAmount, 0 ) )} icon={TrendingUp} iconColor="text-green-400" iconBg="bg-green-500/20" - trend={{ value: 15, isPositive: true }} - subtitle="Общая стоимость ФФ" + subtitle="Стоимость заказов" /> supply.status === "in-transit" - ).length - } - icon={Calendar} - iconColor="text-yellow-400" - iconBg="bg-yellow-500/20" - subtitle="Активные поставки ФФ" - /> - - supply.status === "delivered" - ).length - } - icon={Calendar} + title="Всего единиц" + value={ourSupplyOrders.reduce((sum: number, order: SupplyOrder) => sum + order.totalItems, 0)} + icon={Wrench} iconColor="text-blue-400" iconBg="bg-blue-500/20" - trend={{ value: 3, isPositive: true }} - subtitle="Завершенные поставки" + subtitle="Количество расходников" + /> + + order.status === "DELIVERED" + ).length + } + icon={Calendar} + iconColor="text-purple-400" + iconBg="bg-purple-500/20" + subtitle="Доставленные заказы" /> - {/* Таблица поставок расходников ФФ детально */} - -
- - - - - - - - - - - - - - - - {mockFulfillmentConsumablesDetailed.map((supply) => { - const isSupplyExpanded = expandedSupplies.has(supply.id); + {/* Таблица наших расходников */} + {ourSupplyOrders.length === 0 ? ( + +
+ +

+ Пока нет заказов расходников +

+

+ Создайте первый заказ расходников через кнопку "Создать поставку" +

+
+
+ ) : ( + +
+
- Дата поставки - - Дата создания - ПланФакт - Цена расходников - - Логистика - - Итого сумма - - Статус -
+ + + + + + + + + + + + + + + {ourSupplyOrders.map((order: SupplyOrder) => { + const isOrderExpanded = expandedOrders.has(order.id); - return ( - - {/* Основная строка поставки расходников ФФ детально */} - toggleSupplyExpansion(supply.id)} - > - toggleOrderExpansion(order.id)} + > + + + - + - - - - - - + - - + + + + + + - {/* Развернутые уровни для расходников ФФ */} - {isSupplyExpanded && - supply.routes.map((route) => { - const isRouteExpanded = expandedRoutes.has(route.id); - return ( - - toggleRouteExpansion(route.id)} - > - - - - - - - - - - - - {/* Поставщики расходников */} - {isRouteExpanded && - route.suppliers.map((supplier) => { - const isSupplierExpanded = - expandedSuppliers.has(supplier.id); - return ( - - - toggleSupplierExpansion(supplier.id) - } + {/* Развернутая информация о заказе */} + {isOrderExpanded && ( + + - - - - - - - - - - - {/* Расходники */} - {isSupplierExpanded && - supplier.consumables.map((consumable) => { - const isConsumableExpanded = - expandedConsumables.has( - consumable.id - ); - return ( - - - toggleConsumableExpansion( - consumable.id - ) - } - > - - - - - - - - - - - - {/* Параметры расходника ФФ */} - {isConsumableExpanded && ( - - - - )} - - ); - })} - - ); - })} - - ); - })} - - ); - })} - -
+ Дата поставки + + Дата создания + ПланФакт + Цена расходников + + Логистика + + Итого сумма + + Статус +
-
- - #{supply.number} + return ( + + {/* Основная строка заказа расходников */} +
+
+ {isOrderExpanded ? ( + + ) : ( + + )} + + #{order.id.slice(-8)} + +
+
+
+ + + {formatDate(order.deliveryDate)} + +
+
+ + {formatDate(order.createdAt)} - - -
- +
- {formatDate(supply.deliveryDate)} + {order.totalItems} - - - - {formatDate(supply.createdDate)} - - - - {supply.plannedTotal} - - - - {supply.actualTotal} - - - - {formatCurrency(supply.totalConsumablesPrice)} - - - - {formatCurrency(supply.totalLogisticsPrice)} - - -
- - - {formatCurrency(supply.grandTotal)} +
+ + {order.totalItems} - - {getStatusBadge(supply.status)}
+ + {formatCurrency(order.totalAmount)} + + + + - + + +
+ + + {formatCurrency(order.totalAmount)} + +
+
{getStatusBadge(order.status)}
-
- - - Маршрут ФФ - -
-
-
-
- - {route.from} - - - - {route.to} - -
-
- {route.fromAddress} → {route.toAddress} -
-
-
- - {route.suppliers.reduce( - (sum, s) => - sum + - s.consumables.reduce( - (cSum, c) => cSum + c.plannedQty, - 0 - ), - 0 - )} - - - - {route.suppliers.reduce( - (sum, s) => - sum + - s.consumables.reduce( - (cSum, c) => cSum + c.actualQty, - 0 - ), - 0 - )} - - - - {route.suppliers.reduce( - (sum, s) => - sum + - s.consumables.reduce( - (cSum, c) => cSum + c.defectQty, - 0 - ), - 0 - )} - - - - {formatCurrency(route.totalConsumablesPrice)} - - - - {formatCurrency(route.logisticsPrice)} - - - - {formatCurrency(route.totalAmount)} - -
+
+
+

+ Состав заказа: +

+
+ {order.items.map((item) => ( + -
-
- - - Поставщик ФФ - +
+
+
+ {item.product.name} +
+

+ Артикул: {item.product.article} +

+ {item.product.category && ( + + {item.product.category.name} + + )}
-
-
-
- {supplier.name} +
+
+

+ Количество: {item.quantity} шт +

+

+ Цена: {formatCurrency(item.price)} +

-
- ИНН: {supplier.inn} -
-
- {supplier.address} -
-
- {supplier.contact} +
+

+ {formatCurrency(item.totalPrice)} +

-
- - {supplier.consumables.reduce( - (sum, c) => sum + c.plannedQty, - 0 - )} - - - - {supplier.consumables.reduce( - (sum, c) => sum + c.actualQty, - 0 - )} - - - - {supplier.consumables.reduce( - (sum, c) => sum + c.defectQty, - 0 - )} - - - - {formatCurrency( - supplier.consumables.reduce( - (sum, c) => - sum + - calculateConsumableTotal(c), - 0 - ) - )} - - - - {formatCurrency(supplier.totalAmount)} - -
-
- - - Расходник ФФ - -
-
-
-
- {consumable.name} -
-
- Артикул: {consumable.sku} -
-
- - {consumable.category} - - {getTypeBadge( - consumable.type - )} -
-
-
- - {consumable.plannedQty} - - - - {consumable.actualQty} - - - 0 - ? "text-red-400" - : "text-white" - }`} - > - {consumable.defectQty} - - -
-
- {formatCurrency( - calculateConsumableTotal( - consumable - ) - )} -
-
- {formatCurrency( - consumable.unitPrice - )}{" "} - за шт. -
-
-
- - {formatCurrency( - calculateConsumableTotal( - consumable - ) - )} - -
-
-
-

- - 📋 Параметры - расходника ФФ: - -

-
- {consumable.parameters.map( - (param) => ( -
-
- {param.name} -
-
- {param.value}{" "} - {param.unit || - ""} -
-
- ) - )} -
-
-
-
-
-
+ + + ))} + + + + + + )} + + ); + })} + + + + + )} ); } diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts index fd2c249..f4b3910 100644 --- a/src/graphql/queries.ts +++ b/src/graphql/queries.ts @@ -759,6 +759,7 @@ export const GET_SUPPLY_ORDERS = gql` query GetSupplyOrders { supplyOrders { id + organizationId deliveryDate status totalAmount diff --git a/src/graphql/typedefs.ts b/src/graphql/typedefs.ts index c2baa1a..14556f4 100644 --- a/src/graphql/typedefs.ts +++ b/src/graphql/typedefs.ts @@ -513,6 +513,7 @@ export const typeDefs = gql` # Типы для заказов поставок расходников type SupplyOrder { id: ID! + organizationId: ID! partnerId: ID! partner: Organization! deliveryDate: DateTime!