From 17c929b507db05aac33ba7c2d8d8da5c57c8aee9 Mon Sep 17 00:00:00 2001 From: Bivekich Date: Mon, 28 Jul 2025 10:44:31 +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=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D1=87=D0=B5=D1=82?= =?UTF-8?q?=D1=87=D0=B8=D0=BA=D0=B0=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2?= =?UTF-8?q?=D0=BE=D0=BA,=20=D1=82=D1=80=D0=B5=D0=B1=D1=83=D1=8E=D1=89?= =?UTF-8?q?=D0=B8=D1=85=20=D0=BE=D0=B4=D0=BE=D0=B1=D1=80=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F,=20=D0=B2=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B5=20SuppliesDashboard.=20=D0=A0=D0=B5=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=20GraphQL=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BF=D1=80=D0=BE=D1=81=20=D0=B4=D0=BB=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BEPendingSuppliesCount,=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=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=B5=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D1=8B=20=D0=B8=20=D1=80=D0=B5=D0=B7=D0=BE=D0=BB?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=8B.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=20=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D0=B8=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=B5=20=D0=BE=D0=B6=D0=B8?= =?UTF-8?q?=D0=B4=D0=B0=D1=8E=D1=89=D0=B8=D1=85=20=D0=B7=D0=B0=D0=BA=D0=B0?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=20=D0=B8=20=D0=B7=D0=B0=D1=8F=D0=B2=D0=BE?= =?UTF-8?q?=D0=BA=20=D0=BD=D0=B0=20=D0=BF=D0=B0=D1=80=D1=82=D0=BD=D0=B5?= =?UTF-8?q?=D1=80=D1=81=D1=82=D0=B2=D0=BE.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fulfillment-supplies-tab.tsx | 9 +++- .../supplies/supplies-dashboard.tsx | 38 ++++++++++++++-- src/graphql/queries.ts | 10 +++++ src/graphql/resolvers.ts | 43 +++++++++++++++++++ src/graphql/typedefs.ts | 9 ++++ 5 files changed, 105 insertions(+), 4 deletions(-) diff --git a/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx b/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx index 4c8ec00..80b4b9d 100644 --- a/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx +++ b/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx @@ -10,10 +10,12 @@ import { useAuth } from "@/hooks/useAuth"; interface FulfillmentSuppliesTabProps { defaultSubTab?: string; + pendingSupplyOrders?: number; } export function FulfillmentSuppliesTab({ defaultSubTab, + pendingSupplyOrders = 0, }: FulfillmentSuppliesTabProps) { const [activeSubTab, setActiveSubTab] = useState("goods"); const { user } = useAuth(); @@ -45,9 +47,14 @@ export function FulfillmentSuppliesTab({ 0 ? 'animate-pulse' : ''}`} > Расходники + {pendingSupplyOrders > 0 && ( +
+ {pendingSupplyOrders} +
+ )}
0; + // Автоматически открываем нужную вкладку при загрузке useEffect(() => { const tab = searchParams.get("tab"); @@ -36,6 +49,19 @@ export function SuppliesDashboard() { className={`flex-1 ${getSidebarMargin()} px-2 py-2 overflow-hidden transition-all duration-300`} >
+ {/* Уведомляющий баннер */} + {hasPendingItems && ( + + + + У вас есть {pendingCount.total} элемент{pendingCount.total > 1 ? (pendingCount.total < 5 ? 'а' : 'ов') : ''}, требующ{pendingCount.total > 1 ? 'их' : 'ий'} одобрения: + {pendingCount.supplyOrders > 0 && ` ${pendingCount.supplyOrders} заказ${pendingCount.supplyOrders > 1 ? (pendingCount.supplyOrders < 5 ? 'а' : 'ов') : ''} поставок`} + {pendingCount.incomingRequests > 0 && pendingCount.supplyOrders > 0 && ', '} + {pendingCount.incomingRequests > 0 && ` ${pendingCount.incomingRequests} заявк${pendingCount.incomingRequests > 1 ? (pendingCount.incomingRequests < 5 ? 'и' : '') : 'а'} на партнерство`} + + + )} + {/* Главные вкладки с кнопкой создания */}
- + 0 ? 'animate-pulse' : ''}`} > Поставки на ФФ ФФ + {pendingCount?.supplyOrders > 0 && ( +
+ {pendingCount.supplyOrders} +
+ )}
diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts index a33eac1..e4ef1bc 100644 --- a/src/graphql/queries.ts +++ b/src/graphql/queries.ts @@ -836,4 +836,14 @@ export const GET_SUPPLY_ORDERS = gql` } } } +`; + +export const GET_PENDING_SUPPLIES_COUNT = gql` + query GetPendingSuppliesCount { + pendingSuppliesCount { + supplyOrders + incomingRequests + total + } + } `; \ No newline at end of file diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 374d151..e25bb4c 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -772,6 +772,49 @@ export const resolvers = { }); }, + // Счетчик поставок, требующих одобрения + pendingSuppliesCount: async (_: unknown, __: unknown, context: Context) => { + if (!context.user) { + throw new GraphQLError("Требуется авторизация", { + extensions: { code: "UNAUTHENTICATED" }, + }); + } + + const currentUser = await prisma.user.findUnique({ + where: { id: context.user.id }, + include: { organization: true }, + }); + + if (!currentUser?.organization) { + throw new GraphQLError("У пользователя нет организации"); + } + + // Считаем заказы поставок со статусом PENDING где мы поставщики или получатели + const pendingSupplyOrders = await prisma.supplyOrder.count({ + where: { + status: "PENDING", + OR: [ + { partnerId: currentUser.organization.id }, // Заказы где мы - поставщик (нужно подтвердить) + { fulfillmentCenterId: currentUser.organization.id }, // Заказы где мы - получатель ФФ (нужно подтвердить) + ], + }, + }); + + // Считаем входящие заявки на партнерство со статусом PENDING + const pendingIncomingRequests = await prisma.counterpartyRequest.count({ + where: { + receiverId: currentUser.organization.id, + status: "PENDING", + }, + }); + + return { + supplyOrders: pendingSupplyOrders, + incomingRequests: pendingIncomingRequests, + total: pendingSupplyOrders + pendingIncomingRequests, + }; + }, + // Логистика организации myLogistics: async (_: unknown, __: unknown, context: Context) => { if (!context.user) { diff --git a/src/graphql/typedefs.ts b/src/graphql/typedefs.ts index 0b5e2d4..0411bc1 100644 --- a/src/graphql/typedefs.ts +++ b/src/graphql/typedefs.ts @@ -43,6 +43,9 @@ export const typeDefs = gql` # Заказы поставок расходников supplyOrders: [SupplyOrder!]! + # Счетчик поставок, требующих одобрения + pendingSuppliesCount: PendingSuppliesCount! + # Логистика организации myLogistics: [Logistics!]! @@ -579,6 +582,12 @@ export const typeDefs = gql` quantity: Int! } + type PendingSuppliesCount { + supplyOrders: Int! + incomingRequests: Int! + total: Int! + } + type SupplyOrderProcessInfo { role: String! # Роль организации в процессе (SELLER, FULFILLMENT, LOGIST) supplier: String! # Название поставщика