From c6b1b15c80ed1884fdcd88f06495bf5e94c62f9d Mon Sep 17 00:00:00 2001 From: Bivekich Date: Thu, 24 Jul 2025 15:10:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=B5=20fulfillmentCenterId=20=D0=B2=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D1=8C=20SupplyOrder=20=D0=B8=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D1=83=D1=8E?= =?UTF-8?q?=D1=89=D0=B8=D0=B9=20=D1=80=D0=B5=D0=BB=D1=8F=D1=86=D0=B8=D0=BE?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=20fulfillmentCenter=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D1=8B=20FulfillmentSuppliesTab=20=D0=B8=20RealSupplyOrde?= =?UTF-8?q?rsTab=20=D0=B4=D0=BB=D1=8F=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BD=D0=BE=D0=B2=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=D0=B0.=20=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=81=D1=82=D0=B8?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=B8=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82?= =?UTF-8?q?=D1=83=D1=80=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D0=B2=D1=8B=D1=88=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=83=D0=B4=D0=BE=D0=B1=D1=81=D1=82=D0=B2=D0=B0=20=D0=B8?= =?UTF-8?q?=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/schema.prisma | 27 +- .../fulfillment-consumables-orders-tab.tsx | 410 ++++++++++++++++++ .../fulfillment-supplies-tab.tsx | 4 +- .../fulfillment-goods-tab.tsx | 6 +- .../fulfillment-supplies-tab.tsx | 10 +- .../fulfillment-supplies/pvz-returns-tab.tsx | 6 +- .../real-supply-orders-tab.tsx | 6 +- .../supplies/supplies-dashboard.tsx | 4 +- src/graphql/queries.ts | 7 + src/graphql/resolvers.ts | 46 +- src/graphql/typedefs.ts | 2 + 11 files changed, 481 insertions(+), 47 deletions(-) create mode 100644 src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 36f9a8f..1946528 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -99,6 +99,7 @@ model Organization { logistics Logistics[] supplyOrders SupplyOrder[] partnerSupplyOrders SupplyOrder[] @relation("SupplyOrderPartner") + fulfillmentSupplyOrders SupplyOrder[] @relation("SupplyOrderFulfillmentCenter") wildberriesSupplies WildberriesSupply[] @@map("organizations") @@ -446,18 +447,20 @@ model Logistics { } model SupplyOrder { - id String @id @default(cuid()) - partnerId String - deliveryDate DateTime - status SupplyOrderStatus @default(PENDING) - totalAmount Decimal @db.Decimal(12, 2) - totalItems Int - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - organizationId String - items SupplyOrderItem[] - organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) - partner Organization @relation("SupplyOrderPartner", fields: [partnerId], references: [id]) + id String @id @default(cuid()) + partnerId String + deliveryDate DateTime + status SupplyOrderStatus @default(PENDING) + totalAmount Decimal @db.Decimal(12, 2) + totalItems Int + fulfillmentCenterId String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + organizationId String + items SupplyOrderItem[] + organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) + partner Organization @relation("SupplyOrderPartner", fields: [partnerId], references: [id]) + fulfillmentCenter Organization? @relation("SupplyOrderFulfillmentCenter", fields: [fulfillmentCenterId], references: [id]) @@map("supply_orders") } 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 new file mode 100644 index 0000000..021ea40 --- /dev/null +++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx @@ -0,0 +1,410 @@ +"use client"; + +import React, { useState } from "react"; +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { StatsCard } from "../../supplies/ui/stats-card"; +import { StatsGrid } from "../../supplies/ui/stats-grid"; +import { useQuery } from "@apollo/client"; +import { GET_SUPPLY_ORDERS } from "@/graphql/queries"; +import { useAuth } from "@/hooks/useAuth"; +import { + Calendar, + Building2, + TrendingUp, + DollarSign, + Wrench, + Package2, + ChevronDown, + ChevronRight, + User, +} from "lucide-react"; + +interface SupplyOrderItem { + id: string; + quantity: number; + price: number; + totalPrice: number; + product: { + id: string; + name: string; + article: string; + description?: string; + category?: { + id: string; + name: string; + }; + }; +} + +interface SupplyOrder { + id: string; + deliveryDate: string; + status: string; + totalAmount: number; + totalItems: number; + fulfillmentCenterId?: string; + createdAt: string; + updatedAt: string; + partner: { + id: string; + name?: string; + fullName?: string; + inn: string; + address?: string; + phones?: string[]; + emails?: string[]; + }; + organization: { + id: string; + name?: string; + fullName?: string; + type: string; + }; + fulfillmentCenter?: { + id: string; + name?: string; + fullName?: string; + type: string; + }; + items: SupplyOrderItem[]; +} + +export function FulfillmentConsumablesOrdersTab() { + const [expandedOrders, setExpandedOrders] = useState>(new Set()); + const { user } = useAuth(); + + const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS); + + // Получаем ID текущей организации (фулфилмент-центра) + const currentOrganizationId = user?.organization?.id; + + // Фильтруем заказы где текущая организация является получателем + const incomingSupplyOrders: SupplyOrder[] = (data?.supplyOrders || []).filter( + (order: SupplyOrder) => { + // Показываем заказы где текущий фулфилмент-центр указан как получатель + return order.fulfillmentCenterId === currentOrganizationId; + } + ); + + const toggleOrderExpansion = (orderId: string) => { + const newExpanded = new Set(expandedOrders); + if (newExpanded.has(orderId)) { + newExpanded.delete(orderId); + } else { + newExpanded.add(orderId); + } + setExpandedOrders(newExpanded); + }; + + const getStatusBadge = (status: string) => { + const statusMap: Record = { + CREATED: { + label: "Новый заказ", + color: "bg-blue-500/20 text-blue-300 border-blue-500/30", + }, + CONFIRMED: { + label: "Подтвержден", + color: "bg-green-500/20 text-green-300 border-green-500/30", + }, + IN_PROGRESS: { + label: "Обрабатывается", + color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30", + }, + DELIVERED: { + label: "Доставлен", + color: "bg-purple-500/20 text-purple-300 border-purple-500/30", + }, + CANCELLED: { + label: "Отменен", + color: "bg-red-500/20 text-red-300 border-red-500/30", + }, + }; + + const { label, color } = statusMap[status] || { + label: status, + color: "bg-gray-500/20 text-gray-300 border-gray-500/30", + }; + + 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 formatDateTime = (dateString: string) => { + return new Date(dateString).toLocaleString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + }; + + // Статистика для фулфилмент-центра + const totalOrders = incomingSupplyOrders.length; + const totalAmount = incomingSupplyOrders.reduce((sum, order) => sum + order.totalAmount, 0); + const totalItems = incomingSupplyOrders.reduce((sum, order) => sum + order.totalItems, 0); + const newOrders = incomingSupplyOrders.filter(order => order.status === "CREATED").length; + + if (loading) { + return ( +
+
+ Загрузка заказов расходников... +
+ ); + } + + if (error) { + return ( +
+
+ +

Ошибка загрузки заказов

+

{error.message}

+
+
+ ); + } + + return ( +
+ {/* Статистика входящих заказов расходников */} + + + + + + + + + + + {/* Список входящих заказов расходников */} + {incomingSupplyOrders.length === 0 ? ( + +
+ +

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

+

+ Здесь будут отображаться заказы расходников от селлеров +

+
+
+ ) : ( + +
+ + + + + + + + + + + + + + + {incomingSupplyOrders.map((order) => { + const isOrderExpanded = expandedOrders.has(order.id); + + return ( + + {/* Основная строка заказа */} + toggleOrderExpansion(order.id)} + > + + + + + + + + + + + {/* Развернутая информация о заказе */} + {isOrderExpanded && ( + + + + )} + + ); + })} + +
ID + Селлер + + Поставщик + + Дата поставки + + Дата заказа + + Количество + + Сумма + + Статус +
+
+ {isOrderExpanded ? ( + + ) : ( + + )} + + {order.id.slice(-8)} + +
+
+
+
+ + + {order.organization.name || order.organization.fullName || "Селлер"} + +
+

+ Тип: {order.organization.type} +

+
+
+
+
+ + + {order.partner.name || order.partner.fullName || "Поставщик"} + +
+

+ ИНН: {order.partner.inn} +

+
+
+
+ + + {formatDate(order.deliveryDate)} + +
+
+ + {formatDateTime(order.createdAt)} + + + + {order.totalItems} шт + + +
+ + + {formatCurrency(order.totalAmount)} + +
+
{getStatusBadge(order.status)}
+
+
+

+ Состав заказа от селлера: +

+
+ {order.items.map((item) => ( + +
+
+
+ {item.product.name} +
+

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

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

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

+

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

+
+
+

+ {formatCurrency(item.totalPrice)} +

+
+
+
+
+ ))} +
+
+
+
+
+
+ )} +
+ ); +} \ No newline at end of file diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx index a1885fc..38aefd5 100644 --- a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx +++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx @@ -7,7 +7,7 @@ import { Package, Wrench, RotateCcw, Building2 } from "lucide-react"; // Импорты компонентов подкатегорий import { FulfillmentGoodsTab } from "./fulfillment-goods-tab"; import { PvzReturnsTab } from "./pvz-returns-tab"; -import { SuppliesConsumablesTab } from "../../supplies/consumables-supplies/consumables-supplies-tab"; +import { FulfillmentConsumablesOrdersTab } from "./fulfillment-consumables-orders-tab"; // Новые компоненты для детального просмотра (копия из supplies модуля) import { FulfillmentDetailedSuppliesTab } from "./fulfillment-detailed-supplies-tab"; @@ -68,7 +68,7 @@ export function FulfillmentSuppliesTab() {
- +
diff --git a/src/components/supplies/fulfillment-supplies/fulfillment-goods-tab.tsx b/src/components/supplies/fulfillment-supplies/fulfillment-goods-tab.tsx index e8fc7b6..9f51adc 100644 --- a/src/components/supplies/fulfillment-supplies/fulfillment-goods-tab.tsx +++ b/src/components/supplies/fulfillment-supplies/fulfillment-goods-tab.tsx @@ -313,7 +313,7 @@ export function FulfillmentGoodsTab() { }; return ( -
+
{/* Статистика товаров ФФ */} {/* Таблица поставок товаров ФФ */} - -
+ +
diff --git a/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx b/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx index 5bcebc2..de769e6 100644 --- a/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx +++ b/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx @@ -10,11 +10,11 @@ export function FulfillmentSuppliesTab() { const [activeSubTab, setActiveSubTab] = useState("goods"); return ( -
+
{/* Подвкладки для ФФ */} @@ -38,15 +38,15 @@ export function FulfillmentSuppliesTab() { - + - + - + diff --git a/src/components/supplies/fulfillment-supplies/pvz-returns-tab.tsx b/src/components/supplies/fulfillment-supplies/pvz-returns-tab.tsx index 0e63a5e..aa98477 100644 --- a/src/components/supplies/fulfillment-supplies/pvz-returns-tab.tsx +++ b/src/components/supplies/fulfillment-supplies/pvz-returns-tab.tsx @@ -318,7 +318,7 @@ export function PvzReturnsTab() { }; return ( -
+
{/* Статистика возвратов с ПВЗ */} {/* Таблица возвратов с ПВЗ */} - -
+ +
diff --git a/src/components/supplies/fulfillment-supplies/real-supply-orders-tab.tsx b/src/components/supplies/fulfillment-supplies/real-supply-orders-tab.tsx index e03e91f..93ad62d 100644 --- a/src/components/supplies/fulfillment-supplies/real-supply-orders-tab.tsx +++ b/src/components/supplies/fulfillment-supplies/real-supply-orders-tab.tsx @@ -165,7 +165,7 @@ export function RealSupplyOrdersTab() { } return ( -
+
{/* Статистика заказов расходников */} ) : ( - -
+ +
diff --git a/src/components/supplies/supplies-dashboard.tsx b/src/components/supplies/supplies-dashboard.tsx index eb57c36..cab57d1 100644 --- a/src/components/supplies/supplies-dashboard.tsx +++ b/src/components/supplies/supplies-dashboard.tsx @@ -82,11 +82,11 @@ export function SuppliesDashboard() { - + - + diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts index 162b8a5..fd2c249 100644 --- a/src/graphql/queries.ts +++ b/src/graphql/queries.ts @@ -763,6 +763,7 @@ export const GET_SUPPLY_ORDERS = gql` status totalAmount totalItems + fulfillmentCenterId createdAt updatedAt partner { @@ -780,6 +781,12 @@ export const GET_SUPPLY_ORDERS = gql` fullName type } + fulfillmentCenter { + id + name + fullName + type + } items { id quantity diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index fdf4392..0e12197 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -684,12 +684,13 @@ export const resolvers = { throw new GraphQLError("У пользователя нет организации"); } - // Возвращаем заказы где текущая организация является заказчиком или поставщиком + // Возвращаем заказы где текущая организация является заказчиком, поставщиком или получателем return await prisma.supplyOrder.findMany({ where: { OR: [ { organizationId: currentUser.organization.id }, // Заказы созданные организацией { partnerId: currentUser.organization.id }, // Заказы где организация - поставщик + { fulfillmentCenterId: currentUser.organization.id }, // Заказы где организация - получатель (фулфилмент) ], }, include: { @@ -703,6 +704,11 @@ export const resolvers = { users: true, }, }, + fulfillmentCenter: { + include: { + users: true, + }, + }, items: { include: { product: { @@ -3213,33 +3219,39 @@ export const resolvers = { totalAmount: new Prisma.Decimal(totalAmount), totalItems: totalItems, organizationId: currentUser.organization.id, + fulfillmentCenterId: fulfillmentCenterId, status: initialStatus as any, items: { create: orderItems, }, }, - include: { - partner: { - include: { - users: true, - }, + include: { + partner: { + include: { + users: true, }, - organization: { - include: { - users: true, - }, + }, + organization: { + include: { + users: true, }, - items: { - include: { - product: { - include: { - category: true, - organization: true, - }, + }, + fulfillmentCenter: { + include: { + users: true, + }, + }, + items: { + include: { + product: { + include: { + category: true, + organization: true, }, }, }, }, + }, }); // Создаем расходники на основе заказанных товаров diff --git a/src/graphql/typedefs.ts b/src/graphql/typedefs.ts index 5b637ba..c2baa1a 100644 --- a/src/graphql/typedefs.ts +++ b/src/graphql/typedefs.ts @@ -519,6 +519,8 @@ export const typeDefs = gql` status: SupplyOrderStatus! totalAmount: Float! totalItems: Int! + fulfillmentCenterId: ID + fulfillmentCenter: Organization items: [SupplyOrderItem!]! createdAt: DateTime! updatedAt: DateTime!