From 3f759e7454cc4f57c6e61f2801cc2e5eed5db65d Mon Sep 17 00:00:00 2001 From: Veronika Smirnova Date: Fri, 1 Aug 2025 14:08:40 +0300 Subject: [PATCH] =?UTF-8?q?=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=20=D1=80=D0=B5=D0=B7=D0=BE?= =?UTF-8?q?=D0=BB=D0=B2=D0=B5=D1=80=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D0=BA=D0=B8=20=D1=81=D0=BA=D0=BB=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=20=D1=84=D1=83=D0=BB=D1=84=D0=B8=D0=BB=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0:=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=BE=20?= =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=B4=D0=B0,=20=D0=B4?= =?UTF-8?q?=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BE=D1=82=D1=81=D0=BB=D0=B5=D0=B6=D0=B8=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=BE=D1=81=D1=82=D0=B0=D1=82=D0=BA=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B8=20=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7=D0=BE=D0=B2.=20=D0=98?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=83?= =?UTF-8?q?=D1=81=D0=BB=D0=BE=D0=B2=D0=B8=D1=8F=20=D0=B2=D1=8B=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D0=B8=20=D0=B7=D0=B0=D0=BA=D0=B0=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?,=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D1=8F=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=BA=D0=B0=D0=B7=D1=8B=20=D1=81=D0=B0=D0=BC=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D1=84=D1=83=D0=BB=D1=84=D0=B8=D0=BB=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0.=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=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B5=D0=B3=D0=BE=20=D0=BF=D0=BE=D0=BD=D0=B8=D0=BC=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B.=20=D0=A1=D0=B8=D0=BD?= =?UTF-8?q?=D1=85=D1=80=D0=BE=D0=BD=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B0=D1=82=D0=BA=D0=BE=D0=B2=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=B2=D1=89=D0=B8=D0=BA=D0=B0=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B2=D0=BA=D0=BB=D1=8E=D1=87=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/graphql/resolvers.ts | 498 +++++++++++++++++++++++++++++---------- 1 file changed, 367 insertions(+), 131 deletions(-) diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index c07ab38..3efde6d 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -1009,7 +1009,11 @@ export const resolvers = { }, // Статистика склада фулфилмента с изменениями за сутки - fulfillmentWarehouseStats: async (_: unknown, __: unknown, context: Context) => { + fulfillmentWarehouseStats: async ( + _: unknown, + __: unknown, + context: Context + ) => { console.log("🔥 FULFILLMENT WAREHOUSE STATS RESOLVER CALLED"); if (!context.user) { throw new GraphQLError("Требуется авторизация", { @@ -1036,41 +1040,53 @@ export const resolvers = { const oneDayAgo = new Date(); oneDayAgo.setDate(oneDayAgo.getDate() - 1); - console.log(`🏢 Organization ID: ${organizationId}, Date 24h ago: ${oneDayAgo.toISOString()}`); + console.log( + `🏢 Organization ID: ${organizationId}, Date 24h ago: ${oneDayAgo.toISOString()}` + ); // Сначала проверим ВСЕ заказы поставок const allSupplyOrders = await prisma.supplyOrder.findMany({ where: { status: "DELIVERED" }, - include: { + include: { items: { - include: { product: true } + include: { product: true }, }, - organization: { select: { id: true, name: true, type: true } } - } + organization: { select: { id: true, name: true, type: true } }, + }, }); console.log(`📦 ALL DELIVERED ORDERS: ${allSupplyOrders.length}`); - allSupplyOrders.forEach(order => { - console.log(` Order ${order.id}: org=${order.organizationId} (${order.organization?.name}), fulfillment=${order.fulfillmentCenterId}, items=${order.items.length}`); + allSupplyOrders.forEach((order) => { + console.log( + ` Order ${order.id}: org=${order.organizationId} (${order.organization?.name}), fulfillment=${order.fulfillmentCenterId}, items=${order.items.length}` + ); }); // Продукты (товары от селлеров) - заказы К нам, но исключаем расходники фулфилмента - const allDeliveredOrders = await prisma.supplyOrder.findMany({ + const sellerDeliveredOrders = await prisma.supplyOrder.findMany({ where: { fulfillmentCenterId: organizationId, // Доставлено к нам (фулфилменту) - status: "DELIVERED" + organizationId: { not: organizationId }, // ИСПРАВЛЕНО: исключаем заказы самого фулфилмента + status: "DELIVERED", }, - include: { + include: { items: { - include: { product: true } - } - } + include: { product: true }, + }, + }, }); - console.log(`🛒 ALL ORDERS TO FULFILLMENT: ${allDeliveredOrders.length}`); - - const productsCount = sellerDeliveredOrders.reduce((sum, order) => - sum + order.items.reduce((itemSum, item) => - itemSum + (item.product.type === "PRODUCT" ? item.quantity : 0), 0 - ), 0 + console.log( + `🛒 SELLER ORDERS TO FULFILLMENT: ${sellerDeliveredOrders.length}` + ); + + const productsCount = sellerDeliveredOrders.reduce( + (sum, order) => + sum + + order.items.reduce( + (itemSum, item) => + itemSum + (item.product.type === "PRODUCT" ? item.quantity : 0), + 0 + ), + 0 ); // Изменения товаров за сутки (от селлеров) const recentSellerDeliveredOrders = await prisma.supplyOrder.findMany({ @@ -1078,19 +1094,24 @@ export const resolvers = { fulfillmentCenterId: organizationId, // К нам organizationId: { not: organizationId }, // От селлеров status: "DELIVERED", - updatedAt: { gte: oneDayAgo } + updatedAt: { gte: oneDayAgo }, }, - include: { + include: { items: { - include: { product: true } - } - } + include: { product: true }, + }, + }, }); - - const productsChangeToday = recentSellerDeliveredOrders.reduce((sum, order) => - sum + order.items.reduce((itemSum, item) => - itemSum + (item.product.type === "PRODUCT" ? item.quantity : 0), 0 - ), 0 + + const productsChangeToday = recentSellerDeliveredOrders.reduce( + (sum, order) => + sum + + order.items.reduce( + (itemSum, item) => + itemSum + (item.product.type === "PRODUCT" ? item.quantity : 0), + 0 + ), + 0 ); // Товары (готовые товары = все продукты, не расходники) @@ -1105,70 +1126,123 @@ export const resolvers = { const pvzReturnsCount = 0; // TODO: реальные данные о возвратах const pvzReturnsChangeToday = 0; - // Расходники фулфилмента - заказы ОТ фулфилмента К поставщикам + // Расходники фулфилмента - заказы ОТ фулфилмента К поставщикам, НО доставленные на склад фулфилмента + // Согласно правилам: фулфилмент заказывает расходники у поставщиков для своих операционных нужд const fulfillmentSupplyOrders = await prisma.supplyOrder.findMany({ where: { organizationId: organizationId, // Заказчик = фулфилмент - fulfillmentCenterId: null, // Не является доставкой к фулфилменту - status: "DELIVERED" - }, - include: { - items: { - include: { product: true } - } - } - }); - console.log(`🏭 FULFILLMENT SUPPLY ORDERS: ${fulfillmentSupplyOrders.length}`); - const fulfillmentSuppliesCount = fulfillmentSupplyOrders.reduce( - (sum, order) => sum + order.items.reduce((itemSum, item) => - itemSum + (item.product.type === "CONSUMABLE" ? item.quantity : 0), 0 - ), 0 - ); - - console.log(`🔥 FULFILLMENT SUPPLIES DEBUG: organizationId=${organizationId}, totalOrders=${fulfillmentSupplyOrders.length}, totalCount=${fulfillmentSuppliesCount}`); - - // Изменения расходников фулфилмента за сутки - const fulfillmentSuppliesReceivedToday = await prisma.supplyOrder.findMany({ - where: { - organizationId: organizationId, // Заказчик = фулфилмент - fulfillmentCenterId: null, // Не доставка к фулфилменту + fulfillmentCenterId: organizationId, // ИСПРАВЛЕНО: доставлено НА склад фулфилмента status: "DELIVERED", - updatedAt: { gte: oneDayAgo } }, - include: { + include: { items: { - include: { product: true } - } - } + include: { product: true }, + }, + }, }); - const fulfillmentSuppliesChangeToday = fulfillmentSuppliesReceivedToday.reduce( - (sum, order) => sum + order.items.reduce((itemSum, item) => - itemSum + (item.product.type === "CONSUMABLE" ? item.quantity : 0), 0 - ), 0 + + console.log( + `🏭 FULFILLMENT SUPPLY ORDERS: ${fulfillmentSupplyOrders.length}` ); - console.log(`📊 FULFILLMENT SUPPLIES RECEIVED TODAY: ${fulfillmentSuppliesReceivedToday.length} orders, ${fulfillmentSuppliesChangeToday} items`); + // Подсчитываем количество из таблицы Supply (актуальные остатки на складе фулфилмента) + const fulfillmentSuppliesFromWarehouse = await prisma.supply.findMany({ + where: { + organizationId: organizationId, // Склад фулфилмента + }, + }); + + const fulfillmentSuppliesCount = fulfillmentSuppliesFromWarehouse.reduce( + (sum, supply) => sum + (supply.currentStock || 0), + 0 + ); + + console.log( + `🔥 FULFILLMENT SUPPLIES DEBUG: organizationId=${organizationId}, ordersCount=${fulfillmentSupplyOrders.length}, warehouseCount=${fulfillmentSuppliesFromWarehouse.length}, totalStock=${fulfillmentSuppliesCount}` + ); + console.log( + `📦 FULFILLMENT SUPPLIES BREAKDOWN:`, + fulfillmentSuppliesFromWarehouse.map((supply) => ({ + name: supply.name, + currentStock: supply.currentStock, + supplier: supply.supplier, + })) + ); + + // Изменения расходников фулфилмента за сутки (ПРИБЫЛО) + // Ищем заказы фулфилмента, доставленные на его склад за последние сутки + const fulfillmentSuppliesReceivedToday = + await prisma.supplyOrder.findMany({ + where: { + organizationId: organizationId, // Заказчик = фулфилмент + fulfillmentCenterId: organizationId, // ИСПРАВЛЕНО: доставлено НА склад фулфилмента + status: "DELIVERED", + updatedAt: { gte: oneDayAgo }, + }, + include: { + items: { + include: { product: true }, + }, + }, + }); + + const fulfillmentSuppliesChangeToday = + fulfillmentSuppliesReceivedToday.reduce( + (sum, order) => + sum + + order.items.reduce( + (itemSum, item) => + itemSum + + (item.product.type === "CONSUMABLE" ? item.quantity : 0), + 0 + ), + 0 + ); + + console.log( + `📊 FULFILLMENT SUPPLIES RECEIVED TODAY (ПРИБЫЛО): ${fulfillmentSuppliesReceivedToday.length} orders, ${fulfillmentSuppliesChangeToday} items` + ); // Расходники селлеров - получаем из заказов от селлеров (расходники = CONSUMABLE) - const sellerSuppliesCount = sellerDeliveredOrders.reduce((sum, order) => - sum + order.items.reduce((itemSum, item) => - itemSum + (item.product.type === "CONSUMABLE" ? item.quantity : 0), 0 - ), 0 + // Согласно правилам: селлеры заказывают расходники у поставщиков и доставляют на склад фулфилмента + const sellerSuppliesCount = sellerDeliveredOrders.reduce( + (sum, order) => + sum + + order.items.reduce( + (itemSum, item) => + itemSum + + (item.product.type === "CONSUMABLE" ? item.quantity : 0), + 0 + ), + 0 ); - console.log(`💼 SELLER SUPPLIES DEBUG: totalCount=${sellerSuppliesCount} (from delivered orders)`); + console.log( + `💼 SELLER SUPPLIES DEBUG: totalCount=${sellerSuppliesCount} (from delivered orders)` + ); // Изменения расходников селлеров за сутки - используем уже полученные данные - const sellerSuppliesChangeToday = recentSellerDeliveredOrders.reduce((sum, order) => - sum + order.items.reduce((itemSum, item) => - itemSum + (item.product.type === "CONSUMABLE" ? item.quantity : 0), 0 - ), 0 + const sellerSuppliesChangeToday = recentSellerDeliveredOrders.reduce( + (sum, order) => + sum + + order.items.reduce( + (itemSum, item) => + itemSum + + (item.product.type === "CONSUMABLE" ? item.quantity : 0), + 0 + ), + 0 ); - console.log(`📊 SELLER SUPPLIES RECEIVED TODAY: ${recentSellerDeliveredOrders.length} orders, ${sellerSuppliesChangeToday} items`); + console.log( + `📊 SELLER SUPPLIES RECEIVED TODAY: ${recentSellerDeliveredOrders.length} orders, ${sellerSuppliesChangeToday} items` + ); // Вычисляем процентные изменения - const calculatePercentChange = (current: number, change: number): number => { + const calculatePercentChange = ( + current: number, + change: number + ): number => { if (current === 0) return change > 0 ? 100 : 0; return (change / current) * 100; }; @@ -1177,36 +1251,54 @@ export const resolvers = { products: { current: productsCount, change: productsChangeToday, - percentChange: calculatePercentChange(productsCount, productsChangeToday) + percentChange: calculatePercentChange( + productsCount, + productsChangeToday + ), }, goods: { current: goodsCount, change: goodsChangeToday, - percentChange: calculatePercentChange(goodsCount, goodsChangeToday) + percentChange: calculatePercentChange(goodsCount, goodsChangeToday), }, defects: { current: defectsCount, change: defectsChangeToday, - percentChange: calculatePercentChange(defectsCount, defectsChangeToday) + percentChange: calculatePercentChange( + defectsCount, + defectsChangeToday + ), }, pvzReturns: { current: pvzReturnsCount, change: pvzReturnsChangeToday, - percentChange: calculatePercentChange(pvzReturnsCount, pvzReturnsChangeToday) + percentChange: calculatePercentChange( + pvzReturnsCount, + pvzReturnsChangeToday + ), }, fulfillmentSupplies: { current: fulfillmentSuppliesCount, change: fulfillmentSuppliesChangeToday, - percentChange: calculatePercentChange(fulfillmentSuppliesCount, fulfillmentSuppliesChangeToday) + percentChange: calculatePercentChange( + fulfillmentSuppliesCount, + fulfillmentSuppliesChangeToday + ), }, sellerSupplies: { current: sellerSuppliesCount, change: sellerSuppliesChangeToday, - percentChange: calculatePercentChange(sellerSuppliesCount, sellerSuppliesChangeToday) - } + percentChange: calculatePercentChange( + sellerSuppliesCount, + sellerSuppliesChangeToday + ), + }, }; - console.log(`🏁 FINAL WAREHOUSE STATS RESULT:`, JSON.stringify(result, null, 2)); + console.log( + `🏁 FINAL WAREHOUSE STATS RESULT:`, + JSON.stringify(result, null, 2) + ); return result; }, @@ -4037,9 +4129,12 @@ export const resolvers = { }); } - console.log(`📦 Зарезервированы товары для заказа ${supplyOrder.id}:`, - args.input.items.map(item => `${item.productId}: +${item.quantity} шт.`).join(', ')); - + console.log( + `📦 Зарезервированы товары для заказа ${supplyOrder.id}:`, + args.input.items + .map((item) => `${item.productId}: +${item.quantity} шт.`) + .join(", ") + ); // Создаем расходники на основе заказанных товаров // Расходники создаются в организации получателя (фулфилмент-центре) @@ -4482,7 +4577,8 @@ export const resolvers = { } // Проверяем доступность товара - const availableStock = (product.stock || product.quantity) - (product.ordered || 0); + const availableStock = + (product.stock || product.quantity) - (product.ordered || 0); if (availableStock < args.quantity) { return { success: false, @@ -4498,7 +4594,9 @@ export const resolvers = { }, }); - console.log(`📦 Зарезервировано ${args.quantity} единиц товара ${product.name}`); + console.log( + `📦 Зарезервировано ${args.quantity} единиц товара ${product.name}` + ); return { success: true, @@ -4543,7 +4641,7 @@ export const resolvers = { // Освобождаем резерв (уменьшаем поле ordered) const newOrdered = Math.max((product.ordered || 0) - args.quantity, 0); - + const updatedProduct = await prisma.product.update({ where: { id: args.productId }, data: { @@ -4551,7 +4649,9 @@ export const resolvers = { }, }); - console.log(`🔄 Освобожден резерв ${args.quantity} единиц товара ${product.name}`); + console.log( + `🔄 Освобожден резерв ${args.quantity} единиц товара ${product.name}` + ); return { success: true, @@ -4617,7 +4717,9 @@ export const resolvers = { }, }); - console.log(`🚚 Обновлен статус "в пути" для товара ${product.name}: ${args.operation}`); + console.log( + `🚚 Обновлен статус "в пути" для товара ${product.name}: ${args.operation}` + ); return { success: true, @@ -5729,7 +5831,9 @@ export const resolvers = { }, context: Context ) => { - console.log(`[DEBUG] updateSupplyOrderStatus вызван для заказа ${args.id} со статусом ${args.status}`); + console.log( + `[DEBUG] updateSupplyOrderStatus вызван для заказа ${args.id} со статусом ${args.status}` + ); if (!context.user) { throw new GraphQLError("Требуется авторизация", { extensions: { code: "UNAUTHENTICATED" }, @@ -5795,10 +5899,13 @@ export const resolvers = { // ОТКЛЮЧЕНО: Устаревшая логика для обновления расходников // Теперь используются специальные мутации для каждой роли - const targetOrganizationId = existingOrder.fulfillmentCenterId || existingOrder.organizationId; - + const targetOrganizationId = + existingOrder.fulfillmentCenterId || existingOrder.organizationId; + if (args.status === "CONFIRMED") { - console.log(`[WARNING] Попытка использовать устаревший статус CONFIRMED для заказа ${args.id}`); + console.log( + `[WARNING] Попытка использовать устаревший статус CONFIRMED для заказа ${args.id}` + ); // Не обновляем расходники для устаревших статусов // await prisma.supply.updateMany({ // where: { @@ -5812,10 +5919,10 @@ export const resolvers = { // status: "confirmed" // } // }); - + console.log("✅ Статусы расходников обновлены на 'confirmed'"); } - + if (args.status === "IN_TRANSIT") { // При отгрузке - переводим расходники в статус "in-transit" await prisma.supply.updateMany({ @@ -5823,20 +5930,19 @@ export const resolvers = { organizationId: targetOrganizationId, status: "confirmed", name: { - in: existingOrder.items.map(item => item.product.name) - } + in: existingOrder.items.map((item) => item.product.name), + }, }, data: { - status: "in-transit" - } + status: "in-transit", + }, }); - + console.log("✅ Статусы расходников обновлены на 'in-transit'"); } - + // Если статус изменился на DELIVERED, обновляем склад if (args.status === "DELIVERED") { - console.log("🚚 Обновляем склад организации:", { targetOrganizationId, fulfillmentCenterId: existingOrder.fulfillmentCenterId, @@ -5848,21 +5954,34 @@ export const resolvers = { })), }); - // 🔄 СИНХРОНИЗАЦИЯ: Обновляем товары поставщика (переводим из "в пути" в "продано") + // 🔄 СИНХРОНИЗАЦИЯ: Обновляем товары поставщика (переводим из "в пути" в "продано" + обновляем основные остатки) for (const item of existingOrder.items) { const product = await prisma.product.findUnique({ where: { id: item.product.id }, }); if (product) { + // Согласно правилам: Основные значения = Предыдущие остатки + Прибыло - Убыло + const currentStock = product.stock || product.quantity || 0; + const newStock = Math.max(currentStock - item.quantity, 0); + await prisma.product.update({ where: { id: item.product.id }, data: { - inTransit: Math.max((product.inTransit || 0) - item.quantity, 0), + // Обновляем основные остатки (УБЫЛО) + stock: newStock, + quantity: newStock, // Синхронизируем оба поля для совместимости + // Обновляем дополнительные значения + inTransit: Math.max( + (product.inTransit || 0) - item.quantity, + 0 + ), sold: (product.sold || 0) + item.quantity, }, }); - console.log(`✅ Товар поставщика "${product.name}" обновлен: доставлено ${item.quantity} единиц`); + console.log( + `✅ Товар поставщика "${product.name}" обновлен: доставлено ${item.quantity} единиц (остаток: ${currentStock} -> ${newStock})` + ); } } @@ -5992,8 +6111,10 @@ export const resolvers = { }; } - console.log(`[DEBUG] Поставщик ${currentUser.organization.name} одобряет заказ ${args.id}`); - + console.log( + `[DEBUG] Поставщик ${currentUser.organization.name} одобряет заказ ${args.id}` + ); + // 🔄 СИНХРОНИЗАЦИЯ ОСТАТКОВ: Резервируем товары у поставщика const orderWithItems = await prisma.supplyOrder.findUnique({ where: { id: args.id }, @@ -6014,8 +6135,9 @@ export const resolvers = { }); if (product) { - const availableStock = (product.stock || product.quantity) - (product.ordered || 0); - + const availableStock = + (product.stock || product.quantity) - (product.ordered || 0); + if (availableStock < item.quantity) { return { success: false, @@ -6023,14 +6145,32 @@ export const resolvers = { }; } + // Согласно правилам: при одобрении заказа остаток должен уменьшиться + const currentStock = product.stock || product.quantity || 0; + const newStock = Math.max(currentStock - item.quantity, 0); + await prisma.product.update({ where: { id: item.product.id }, data: { + // Уменьшаем основной остаток (товар зарезервирован для заказа) + stock: newStock, + quantity: newStock, // Синхронизируем оба поля для совместимости + // Увеличиваем количество заказанного (для отслеживания) ordered: (product.ordered || 0) + item.quantity, }, }); - console.log(`📦 Зарезервировано ${item.quantity} единиц товара "${product.name}"`); + console.log( + `📦 Товар "${product.name}" зарезервирован: ${item.quantity} единиц` + ); + console.log( + ` 📊 Остаток: ${currentStock} -> ${newStock} (уменьшен на ${item.quantity})` + ); + console.log( + ` 📋 Заказано: ${product.ordered || 0} -> ${ + (product.ordered || 0) + item.quantity + }` + ); } } } @@ -6056,10 +6196,13 @@ export const resolvers = { }, }); - console.log(`[DEBUG] Заказ ${args.id} успешно обновлен до статуса: ${updatedOrder.status}`); + console.log( + `[DEBUG] Заказ ${args.id} успешно обновлен до статуса: ${updatedOrder.status}` + ); return { success: true, - message: "Заказ поставки одобрен поставщиком. Товары зарезервированы.", + message: + "Заказ поставки одобрен поставщиком. Товары зарезервированы, остатки обновлены.", order: updatedOrder, }; } catch (error) { @@ -6129,24 +6272,48 @@ export const resolvers = { }); // 📦 СНИМАЕМ РЕЗЕРВАЦИЮ ПРИ ОТКЛОНЕНИИ - // Уменьшаем поле "ordered" для каждого отклоненного товара + // Восстанавливаем остатки и убираем резервацию для каждого отклоненного товара for (const item of updatedOrder.items) { - await prisma.product.update({ + const product = await prisma.product.findUnique({ where: { id: item.productId }, - data: { - ordered: { - decrement: item.quantity, - }, - }, }); + + if (product) { + // Восстанавливаем основные остатки (на случай, если заказ был одобрен, а затем отклонен) + const currentStock = product.stock || product.quantity || 0; + const restoredStock = currentStock + item.quantity; + + await prisma.product.update({ + where: { id: item.productId }, + data: { + // Восстанавливаем основной остаток + stock: restoredStock, + quantity: restoredStock, + // Уменьшаем количество заказанного + ordered: Math.max((product.ordered || 0) - item.quantity, 0), + }, + }); + + console.log( + `🔄 Восстановлены остатки товара "${ + product.name + }": ${currentStock} -> ${restoredStock}, ordered: ${ + product.ordered + } -> ${Math.max((product.ordered || 0) - item.quantity, 0)}` + ); + } } - console.log(`📦 Снята резервация при отклонении заказа ${updatedOrder.id}:`, - updatedOrder.items.map(item => `${item.productId}: -${item.quantity} шт.`).join(', ')); + console.log( + `📦 Снята резервация при отклонении заказа ${updatedOrder.id}:`, + updatedOrder.items + .map((item) => `${item.productId}: -${item.quantity} шт.`) + .join(", ") + ); return { success: true, - message: args.reason + message: args.reason ? `Заказ отклонен поставщиком. Причина: ${args.reason}` : "Заказ отклонен поставщиком", order: updatedOrder, @@ -6223,7 +6390,9 @@ export const resolvers = { }, }); - console.log(`🚚 Товар "${product.name}" переведен в статус "в пути": ${item.quantity} единиц`); + console.log( + `🚚 Товар "${product.name}" переведен в статус "в пути": ${item.quantity} единиц` + ); } } } @@ -6251,7 +6420,8 @@ export const resolvers = { return { success: true, - message: "Заказ отправлен поставщиком. Товары переведены в статус 'в пути'.", + message: + "Заказ отправлен поставщиком. Товары переведены в статус 'в пути'.", order: updatedOrder, }; } catch (error) { @@ -6295,7 +6465,8 @@ export const resolvers = { if (!existingOrder) { return { success: false, - message: "Заказ не найден или недоступен для подтверждения логистикой", + message: + "Заказ не найден или недоступен для подтверждения логистикой", }; } @@ -6393,7 +6564,7 @@ export const resolvers = { return { success: true, - message: args.reason + message: args.reason ? `Заказ отклонен логистической компанией. Причина: ${args.reason}` : "Заказ отклонен логистической компанией", order: updatedOrder, @@ -6472,7 +6643,54 @@ export const resolvers = { }, }); + // 🔄 СИНХРОНИЗАЦИЯ СКЛАДА ПОСТАВЩИКА: Обновляем остатки поставщика согласно правилам + console.log("🔄 Начинаем синхронизацию остатков поставщика..."); + for (const item of existingOrder.items) { + const product = await prisma.product.findUnique({ + where: { id: item.product.id }, + }); + + if (product) { + // Согласно правилам: Основные значения = Предыдущие остатки + Прибыло - Убыло + const currentStock = product.stock || product.quantity || 0; + const newStock = Math.max(currentStock - item.quantity, 0); + + await prisma.product.update({ + where: { id: item.product.id }, + data: { + // Обновляем основные остатки (УБЫЛО) + stock: newStock, + quantity: newStock, // Синхронизируем оба поля для совместимости + // Обновляем дополнительные значения + inTransit: Math.max( + (product.inTransit || 0) - item.quantity, + 0 + ), + sold: (product.sold || 0) + item.quantity, + }, + }); + console.log( + `✅ Товар поставщика "${product.name}" обновлен: доставлено ${item.quantity} единиц` + ); + console.log( + ` 📊 Остатки: ${currentStock} -> ${newStock} (УБЫЛО: ${item.quantity})` + ); + console.log( + ` 🚚 В пути: ${product.inTransit || 0} -> ${Math.max( + (product.inTransit || 0) - item.quantity, + 0 + )}` + ); + console.log( + ` 💰 Продано: ${product.sold || 0} -> ${ + (product.sold || 0) + item.quantity + }` + ); + } + } + // Обновляем склад фулфилмента + console.log("📦 Обновляем склад фулфилмента..."); for (const item of existingOrder.items) { const existingSupply = await prisma.supply.findFirst({ where: { @@ -6490,11 +6708,20 @@ export const resolvers = { status: "in-stock", }, }); + console.log( + `📈 Обновлен существующий расходник фулфилмента "${ + item.product.name + }": ${existingSupply.currentStock} -> ${ + existingSupply.currentStock + item.quantity + }` + ); } else { await prisma.supply.create({ data: { name: item.product.name, - description: item.product.description || `Расходники от ${updatedOrder.partner.name}`, + description: + item.product.description || + `Расходники от ${updatedOrder.partner.name}`, price: item.price, quantity: item.quantity, currentStock: item.quantity, @@ -6502,16 +6729,25 @@ export const resolvers = { unit: "шт", category: item.product.category?.name || "Расходники", status: "in-stock", - supplier: updatedOrder.partner.name || updatedOrder.partner.fullName || "Поставщик", + supplier: + updatedOrder.partner.name || + updatedOrder.partner.fullName || + "Поставщик", organizationId: currentUser.organization.id, }, }); + console.log( + `➕ Создан новый расходник фулфилмента "${item.product.name}": ${item.quantity} единиц` + ); } } + console.log("🎉 Синхронизация склада завершена успешно!"); + return { success: true, - message: "Заказ принят фулфилментом. Склад обновлен.", + message: + "Заказ принят фулфилментом. Склад обновлен. Остатки поставщика синхронизированы.", order: updatedOrder, }; } catch (error) {