diff --git a/src/components/wb-warehouse/wb-warehouse-dashboard.tsx b/src/components/wb-warehouse/wb-warehouse-dashboard.tsx index 4ec19cf..ca1fe39 100644 --- a/src/components/wb-warehouse/wb-warehouse-dashboard.tsx +++ b/src/components/wb-warehouse/wb-warehouse-dashboard.tsx @@ -78,6 +78,7 @@ export function WBWarehouseDashboard() { const [totalProducts, setTotalProducts] = useState(0) const [totalStocks, setTotalStocks] = useState(0) const [totalReserved, setTotalReserved] = useState(0) + const [totalFromClient, setTotalFromClient] = useState(0) const [activeWarehouses, setActiveWarehouses] = useState(0) // Загрузка данных @@ -106,53 +107,63 @@ export function WBWarehouseDashboard() { const wbService = new WildberriesService(apiToken) - console.log('WB Warehouse: Starting data load with Analytics API...') + console.log('WB Warehouse: Starting data load...') - // Сначала получаем карточки товаров для передачи в Analytics API - console.log('WB Warehouse: Getting cards for Analytics API...') + // Сначала получаем карточки товаров - это основа для всего + console.log('WB Warehouse: Getting cards...') const cards = await WildberriesService.getAllCards(apiToken).catch(() => []) const nmIds = cards.map(card => card.nmID).filter(id => id > 0) - console.log('WB Warehouse: Found cards for Analytics API:', nmIds) + console.log('WB Warehouse: Found cards:', cards.length) + console.log('WB Warehouse: Card IDs for analytics:', nmIds) - // Загружаем склады, основные данные и Analytics API для движения товаров - const [warehousesData, stocksData, rawAnalyticsData] = await Promise.all([ - wbService.getWarehouses().catch((error) => { - console.error('WB Warehouse: Error loading warehouses:', error) - return [] - }), - wbService.getStocks().catch((error) => { - console.error('WB Warehouse: Error loading stocks:', error) - return [] - }), - wbService.getStocksReportByOffices({ - nmIds: nmIds.length > 0 ? nmIds : undefined, // Передаем ID твоих товаров - stockType: '' // все склады - покажем все данные - }).catch((error) => { - console.error('WB Warehouse: Error loading analytics data:', error) - return [] - }) - ]) + if (cards.length === 0) { + console.log('WB Warehouse: No cards found, cannot proceed with analytics') + setStocks([]) + setWarehouses([]) + return + } - console.log('WB Warehouse: Warehouses loaded:', warehousesData.length) - console.log('WB Warehouse: Basic stocks loaded:', stocksData.length) - console.log('WB Warehouse: Analytics data loaded:', rawAnalyticsData.length) + // Получаем данные по складам для каждого товара отдельно + console.log('WB Warehouse: Getting stocks analytics for each card separately...') + const analyticsResults = [] - setWarehouses(warehousesData) + for (const nmId of nmIds) { + console.log(`WB Warehouse: Getting analytics for nmId: ${nmId}`) + try { + const result = await wbService.getStocksReportByOffices({ + nmIds: [nmId], // Один товар за раз + stockType: '' // все склады + }) + analyticsResults.push({ nmId, data: result }) + console.log(`WB Warehouse: Got analytics for ${nmId}:`, result) + + // Пауза между запросами чтобы не превысить лимиты API + await new Promise(resolve => setTimeout(resolve, 1000)) + } catch (error) { + console.error(`WB Warehouse: Error loading analytics for ${nmId}:`, error) + analyticsResults.push({ nmId, data: { data: { regions: [] } } }) + } + } + + console.log('WB Warehouse: Cards loaded:', cards.length) + console.log('WB Warehouse: Analytics data received') - // Analytics API создает записи с другой структурой - изучаем что пришло - console.log('WB Warehouse: Raw analytics data structure:', rawAnalyticsData) - console.log('WB Warehouse: Sample analytics item:', rawAnalyticsData[0]) + // Объединяем карточки товаров с данными Analytics API + const combinedStocks = combineCardsWithIndividualAnalytics(cards, analyticsResults) + console.log('WB Warehouse: Combined stocks:', combinedStocks.length) // Отключаем общую аналитику - будем показывать детализацию по товарам в карточках setAnalyticsData([]) - // Объединяем основные данные со склады и данные Analytics API по складам WB - const combinedStocks = [...stocksData, ...rawAnalyticsData] - const processedStocks = processStocksData(combinedStocks, warehousesData, rawAnalyticsData) - setStocks(processedStocks) + // Используем объединенные данные + setStocks(combinedStocks) + + // Извлекаем информацию о складах из данных Analytics API + const warehousesFromAnalytics = extractWarehousesFromStocks(combinedStocks) + setWarehouses(warehousesFromAnalytics) // Обновляем статистику - updateStatistics(processedStocks, warehousesData) + updateStatistics(combinedStocks, warehousesFromAnalytics) if (showToast) { toast.success('Данные обновлены') @@ -167,102 +178,175 @@ export function WBWarehouseDashboard() { } } - // Обработка данных остатков с дополнением данными из Analytics API - const processStocksData = (stocksData: unknown[], warehousesData: WBWarehouse[], analyticsData: WBStock[] = []): WBStock[] => { + + + // Объединение карточек товаров с индивидуальными данными Analytics API + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const combineCardsWithIndividualAnalytics = (cards: any[], analyticsResults: any[]): WBStock[] => { const stocksMap = new Map() - // Создаем карту данных Analytics API по складам для быстрого поиска - const analyticsMap = new Map() - analyticsData.forEach(item => { - item.stocks.forEach(stock => { - analyticsMap.set(stock.warehouseId, { - toClientCount: stock.inWayToClient, - fromClientCount: stock.inWayFromClient + console.log('WB Warehouse: Combining cards with individual analytics...') + console.log('WB Warehouse: Cards count:', cards.length) + console.log('WB Warehouse: Analytics results count:', analyticsResults.length) + + // Создаем карту Analytics результатов по nmId + const analyticsMap = new Map() + analyticsResults.forEach(result => { + analyticsMap.set(result.nmId, result.data) + }) + + // Создаем записи для каждой карточки товара + cards.forEach(card => { + const stock: WBStock = { + nmId: card.nmID, + vendorCode: String(card.vendorCode || card.supplierVendorCode || ''), + title: String(card.title || card.object || `Товар ${card.nmID}`), + brand: String(card.brand || ''), + price: 0, // Цена будет из размеров, если есть + stocks: [], // Заполним из Analytics API + totalQuantity: 0, + totalReserved: 0, + photos: Array.isArray(card.photos) ? card.photos : [], + mediaFiles: Array.isArray(card.mediaFiles) ? card.mediaFiles : [], + characteristics: Array.isArray(card.characteristics) ? card.characteristics : [], + subjectName: String(card.subjectName || card.object || ''), + description: String(card.description || '') + } + + // Берем цену из первого размера если есть + if (card.sizes && card.sizes.length > 0) { + stock.price = Number(card.sizes[0].price || card.sizes[0].discountedPrice) || 0 + } + + // Получаем данные Analytics для этого конкретного товара + const analyticsData = analyticsMap.get(card.nmID) + console.log(`WB Warehouse: Processing analytics for card ${card.nmID}:`, analyticsData) + + if (analyticsData?.data?.regions) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + analyticsData.data.regions.forEach((region: any) => { + if (region.offices && region.offices.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + region.offices.forEach((office: any) => { + console.log(`WB Warehouse: Adding office ${office.officeName} for card ${card.nmID}`) + console.log(`WB Warehouse: Office metrics:`, office.metrics) + + stock.stocks.push({ + warehouseId: office.officeID, + warehouseName: office.officeName, + quantity: office.metrics?.stockCount || 0, + quantityFull: office.metrics?.stockCount || 0, + inWayToClient: office.metrics?.toClientCount || 0, + inWayFromClient: office.metrics?.fromClientCount || 0 + }) + + stock.totalQuantity += office.metrics?.stockCount || 0 + stock.totalReserved += office.metrics?.toClientCount || 0 + }) + } }) + } else { + console.log(`WB Warehouse: No analytics data found for card ${card.nmID}`) + } + + stocksMap.set(card.nmID, stock) + }) + + console.log('WB Warehouse: Final stocks after combining:', stocksMap.size) + return Array.from(stocksMap.values()).sort((a, b) => b.totalQuantity - a.totalQuantity) + } + + // Объединение карточек товаров с данными Analytics API (старая функция) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const combineCardsWithAnalytics = (cards: any[], analyticsResponse: any): WBStock[] => { + const stocksMap = new Map() + + console.log('WB Warehouse: Combining cards with analytics...') + console.log('WB Warehouse: Cards count:', cards.length) + console.log('WB Warehouse: Analytics response:', analyticsResponse) + + // Сначала создаем записи для всех карточек товаров + cards.forEach(card => { + const stock: WBStock = { + nmId: card.nmID, + vendorCode: String(card.vendorCode || card.supplierVendorCode || ''), + title: String(card.title || card.object || `Товар ${card.nmID}`), + brand: String(card.brand || ''), + price: 0, // Цена будет из размеров, если есть + stocks: [], // Заполним из Analytics API + totalQuantity: 0, + totalReserved: 0, + photos: Array.isArray(card.photos) ? card.photos : [], + mediaFiles: Array.isArray(card.mediaFiles) ? card.mediaFiles : [], + characteristics: Array.isArray(card.characteristics) ? card.characteristics : [], + subjectName: String(card.subjectName || card.object || ''), + description: String(card.description || '') + } + + // Берем цену из первого размера если есть + if (card.sizes && card.sizes.length > 0) { + stock.price = Number(card.sizes[0].price || card.sizes[0].discountedPrice) || 0 + } + + stocksMap.set(card.nmID, stock) + }) + + console.log('WB Warehouse: Created stocks from cards:', stocksMap.size) + + // Теперь дополняем данными из Analytics API + if (analyticsResponse?.data?.regions) { + console.log('WB Warehouse: Processing analytics regions:', analyticsResponse.data.regions.length) + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + analyticsResponse.data.regions.forEach((region: any) => { + if (region.offices && region.offices.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + region.offices.forEach((office: any) => { + console.log(`WB Warehouse: Processing office ${office.officeName} (${office.officeID})`) + console.log('WB Warehouse: Office metrics:', office.metrics) + + // Пока что добавляем данные склада ко всем товарам + // TODO: нужно понять как Analytics API связывает товары со складами + stocksMap.forEach((stock, nmId) => { + stock.stocks.push({ + warehouseId: office.officeID, + warehouseName: office.officeName, + quantity: office.metrics?.stockCount || 0, + quantityFull: office.metrics?.stockCount || 0, + inWayToClient: office.metrics?.toClientCount || 0, + inWayFromClient: office.metrics?.fromClientCount || 0 + }) + + stock.totalQuantity += office.metrics?.stockCount || 0 + stock.totalReserved += office.metrics?.toClientCount || 0 + }) + }) + } + }) + } + + console.log('WB Warehouse: Final stocks after combining:', stocksMap.size) + return Array.from(stocksMap.values()).sort((a, b) => b.totalQuantity - a.totalQuantity) + } + + // Извлечение информации о складах из данных Analytics API + const extractWarehousesFromStocks = (stocksData: WBStock[]): WBWarehouse[] => { + const warehousesMap = new Map() + + stocksData.forEach(stock => { + stock.stocks.forEach(stockInfo => { + if (!warehousesMap.has(stockInfo.warehouseId)) { + warehousesMap.set(stockInfo.warehouseId, { + id: stockInfo.warehouseId, + name: stockInfo.warehouseName, + cargoType: 1, // По умолчанию + deliveryType: 1 // По умолчанию + }) + } }) }) - console.log('WB Warehouse: Analytics map created with', analyticsMap.size, 'warehouse entries') - - stocksData.forEach((stockItem: unknown) => { - const stock = stockItem as Record - const nmId = Number(stock.nmId) || 0 - - if (!stocksMap.has(nmId)) { - console.log(`WB Warehouse: Processing stock for nmId ${nmId}`) - console.log(`WB Warehouse: Stock item:`, stock) - - stocksMap.set(nmId, { - nmId, - vendorCode: String(stock.vendorCode || stock.supplierArticle || ''), - title: String(stock.title || stock.subject || `Товар ${nmId}`), - brand: String(stock.brand || ''), - price: Number(stock.price || stock.Price) || 0, - stocks: [], - totalQuantity: 0, - totalReserved: 0, - photos: Array.isArray(stock.photos) ? stock.photos as Array<{big?: string; c246x328?: string; c516x688?: string; square?: string; tm?: string}> : [], - mediaFiles: Array.isArray(stock.mediaFiles) ? stock.mediaFiles as string[] : [], - characteristics: Array.isArray(stock.characteristics) ? stock.characteristics as Array<{id: number; name: string; value: string[] | string}> : [], - subjectName: String(stock.subjectName || stock.subject || ''), - description: String(stock.description || '') - }) - } - - const item = stocksMap.get(nmId)! - - // Для Analytics API данных берем warehouseId из первого stock в массиве stocks - let warehouseId = Number(stock.warehouseId || stock.warehouse) || 0 - let warehouseName = String(stock.warehouseName || '') - - // Если это данные Analytics API (есть массив stocks) - if (Array.isArray(stock.stocks) && stock.stocks.length > 0) { - const firstStock = stock.stocks[0] - warehouseId = Number(firstStock.warehouseId) || 0 - warehouseName = String(firstStock.warehouseName || `Склад ${warehouseId}`) - console.log(`WB Warehouse: Analytics stock - warehouseId: ${warehouseId}, name: ${warehouseName}`) - } else { - // Обычные данные - warehouseName = warehouseName || warehousesData.find(w => w.id === warehouseId)?.name || `Склад ${warehouseId}` - } - - let quantity = Number(stock.quantity) || 0 - let quantityFull = Number(stock.quantityFull) || 0 - let inWayToClient = 0 - let inWayFromClient = 0 - - // Если это данные Analytics API - if (Array.isArray(stock.stocks) && stock.stocks.length > 0) { - const firstStock = stock.stocks[0] - quantity = Number(firstStock.quantity) || 0 - quantityFull = Number(firstStock.quantityFull) || 0 - inWayToClient = Number(firstStock.inWayToClient) || 0 - inWayFromClient = Number(firstStock.inWayFromClient) || 0 - } else { - // Обычные данные - используем Analytics API если доступны - const analyticsInfo = analyticsMap.get(warehouseId) - inWayToClient = analyticsInfo?.toClientCount ?? (Number(stock.inWayToClient) || 0) - inWayFromClient = analyticsInfo?.fromClientCount ?? (Number(stock.inWayFromClient) || 0) - } - - const hasAnalytics = Array.isArray(stock.stocks) && stock.stocks.length > 0 - console.log(`WB Warehouse: Warehouse ${warehouseId} - Analytics: ${hasAnalytics ? 'YES' : 'NO'}, toClient: ${inWayToClient}, fromClient: ${inWayFromClient}`) - - const warehouseStock = { - warehouseId, - warehouseName, - quantity, - quantityFull, - inWayToClient, - inWayFromClient - } - - item.stocks.push(warehouseStock) - item.totalQuantity += quantity - item.totalReserved += inWayToClient - }) - - return Array.from(stocksMap.values()).sort((a, b) => b.totalQuantity - a.totalQuantity) + return Array.from(warehousesMap.values()) } // Обновление статистики @@ -271,6 +355,12 @@ export function WBWarehouseDashboard() { setTotalStocks(stocksData.reduce((sum, item) => sum + item.totalQuantity, 0)) setTotalReserved(stocksData.reduce((sum, item) => sum + item.totalReserved, 0)) + // Считаем общее количество товаров в пути от клиента + const totalFromClientCount = stocksData.reduce((sum, item) => + sum + item.stocks.reduce((stockSum, stock) => stockSum + stock.inWayFromClient, 0), 0 + ) + setTotalFromClient(totalFromClientCount) + const warehousesWithStock = new Set( stocksData.flatMap(item => item.stocks.map(s => s.warehouseId)) ) @@ -306,74 +396,88 @@ export function WBWarehouseDashboard() {
- {/* Заголовок */} -
-
-

Склад Wildberries

+ + {/* Результирующие вкладки */} +
+ {/* Товаров */} +
+
+
+
+ + Товаров +
+
+ {loading ?
: totalProducts.toLocaleString()} +
+
+
+
-
- - + {/* Общий остаток */} +
+
+
+
+ + Остаток +
+
+ {loading ?
: totalStocks.toLocaleString()} +
+
+
+
-
- {/* Статистика */} -
- + {/* К клиенту */} +
-
-

Товаров

-
- {loading ? : totalProducts.toLocaleString()} +
+
+ + К клиенту +
+
+ {loading ?
: totalReserved.toLocaleString()}
-
- +
+
- + {/* От клиента */} +
-
-

Общий остаток

-
- {loading ? : totalStocks.toLocaleString()} +
+
+ + От клиента +
+
+ {loading ?
: totalFromClient.toLocaleString()}
-
- +
+
- + {/* Складов */} +
-
-

В пути к клиенту

-
- {loading ? : totalReserved.toLocaleString()} +
+
+ + Складов +
+
+ {loading ?
: activeWarehouses}
-
- - - -
-
-

Активных складов

-
- {loading ? : activeWarehouses} -
-
- -
-
+
+
{/* Аналитика по складам WB */} @@ -404,45 +508,99 @@ export function WBWarehouseDashboard() { )} {/* Фильтры */} - -
-
-
- - setSearchTerm(e.target.value)} - className="glass-input text-white placeholder:text-white/40 pl-10" - /> -
-
-
- +
+
+ + setSearchTerm(e.target.value)} + className="w-full h-12 pl-12 pr-4 rounded-xl bg-white/5 border border-white/10 text-white placeholder:text-white/40 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500/30 transition-all duration-200 hover:bg-white/10" + /> +
+
+ + +
+ + +
- +
{/* Список товаров */}
{loading ? ( -
- {[...Array(5)].map((_, i) => ( - - - - ))} +
+ {/* Заголовки таблицы */} +
+
+
Товар
+
Остаток
+
К клиенту
+
От клиента
+
Складов
+
Характеристики
+
+
+ + {/* Skeleton строки */} +
+ {[...Array(8)].map((_, i) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
) : !hasWBApiKey ? ( @@ -469,10 +627,25 @@ export function WBWarehouseDashboard() {

) : ( -
- {filteredStocks.map((item, index) => ( - - ))} +
+ {/* Заголовки таблицы */} +
+
+
Товар
+
Остаток
+
К клиенту
+
От клиента
+
Складов
+
Характеристики
+
+
+ + {/* Таблица товаров */} +
+ {filteredStocks.map((item, index) => ( + + ))} +
)}
@@ -482,218 +655,381 @@ export function WBWarehouseDashboard() { ) } -// Компонент карточки товара -function StockCard({ item }: { item: WBStock }) { +// Табличная строка товара +function StockTableRow({ item }: { item: WBStock }) { + // Получение изображений карточки через WildberriesService const getCardImages = (item: WBStock): string[] => { - console.log(`WB Warehouse: Getting images for card ${item.nmId}`) - console.log(`WB Warehouse: Photos:`, item.photos) - console.log(`WB Warehouse: MediaFiles:`, item.mediaFiles) - - // Если есть photos в формате WB API if (item.photos && item.photos.length > 0) { const urls = item.photos .map(photo => photo.c246x328 || photo.c516x688 || photo.big) .filter((url): url is string => Boolean(url)) - console.log(`WB Warehouse: URLs from photos:`, urls) return urls } - // Проверяем mediaFiles (как в создании поставки) if (item.mediaFiles && item.mediaFiles.length > 0) { - console.log(`WB Warehouse: URLs from mediaFiles:`, item.mediaFiles) return item.mediaFiles } - // Fallback - генерируем URL по стандартной схеме WB const vol = Math.floor(item.nmId / 100000) const part = Math.floor(item.nmId / 1000) const fallbackUrl = `https://basket-${String(vol).padStart(2, '0')}.wbbasket.ru/vol${vol}/part${part}/${item.nmId}/images/c246x328/1.webp` - console.log(`WB Warehouse: Using fallback URL:`, fallbackUrl) return [fallbackUrl] } const getStockStatus = (quantity: number) => { - if (quantity === 0) return { color: 'bg-red-500/20 text-red-400 border-red-500/30', label: 'Нет в наличии' } - if (quantity < 10) return { color: 'bg-orange-500/20 text-orange-400 border-orange-500/30', label: 'Мало' } - return { color: 'bg-green-500/20 text-green-400 border-green-500/30', label: 'В наличии' } + if (quantity === 0) return { + color: 'text-red-400', + bgColor: 'bg-red-500/10', + label: 'Нет в наличии' + } + if (quantity < 10) return { + color: 'text-orange-400', + bgColor: 'bg-orange-500/10', + label: 'Мало' + } + return { + color: 'text-green-400', + bgColor: 'bg-green-500/10', + label: 'В наличии' + } } const stockStatus = getStockStatus(item.totalQuantity) - // Получаем изображения из данных карточки WB const images = getCardImages(item) const mainImage = images[0] || null + // Отбираем ключевые характеристики для отображения в таблице + const keyCharacteristics = item.characteristics?.slice(0, 3) || [] + return ( - -
-
- {/* Изображение товара */} -
+
+ {/* Основная строка товара */} +
+ {/* Товар (3 колонки) */} +
+
{mainImage ? ( {item.title} ) : (
- +
)} - - {/* Индикатор WB */} -
- - WB - +
+
+
+ + {item.brand || 'Без бренда'} + + #{item.nmId} +
+

+ {item.title} +

+
+ {item.vendorCode}
+
- {/* Информация о товаре */} -
-
-
- {/* Заголовок и бренд */} -
- - {item.brand || 'Без бренда'} - - №{item.nmId} -
- -

- {item.title} -

- - {/* Артикул */} -
- Артикул: {item.vendorCode} -
-
- - - {stockStatus.label} - + {/* Остаток */} +
+
+
+ {item.totalQuantity.toLocaleString()}
- - {/* Общая статистика */} -
-
-

{item.totalQuantity.toLocaleString()}

-

Доступно

-
-
-

{item.stocks.length}

-

Складов

-
+
+ {stockStatus.label}
+
+
- {/* Статистика по движению товаров */} - {(item.stocks.some(s => s.inWayToClient > 0) || item.stocks.some(s => s.inWayFromClient > 0)) && ( -
-
-

- {item.stocks.reduce((sum, s) => sum + s.inWayToClient, 0).toLocaleString()} -

-

К клиенту

-
-
-

- {item.stocks.reduce((sum, s) => sum + s.inWayFromClient, 0).toLocaleString()} -

-

От клиента

-
+ {/* К клиенту */} +
+
+
+ {item.stocks.reduce((sum, s) => sum + s.inWayToClient, 0)} +
+
в пути
+
+
+ + {/* От клиента */} +
+
+
+ {item.stocks.reduce((sum, s) => sum + s.inWayFromClient, 0)} +
+
возвраты
+
+
+ + {/* Складов */} +
+
+
+ {item.stocks.length} +
+
активных
+
+
+ + {/* Характеристики (5 колонок) */} +
+
+ {keyCharacteristics.map((char, index) => ( +
+ {char.name}: + + {Array.isArray(char.value) ? char.value.join(', ') : String(char.value)} + +
+ ))} + {item.subjectName && ( +
+ Категория: + {item.subjectName}
)}
+
- {/* Остатки по складам */} -
-

Остатки по складам:

-
- {item.stocks.map((stock, stockIndex) => ( -
-
-

{stock.warehouseName}

-

ID: {stock.warehouseId}

+ {/* Города в модулях */} +
+
+ {item.stocks.map((stock, stockIndex) => ( +
+ {/* Название города */} +
+ {stock.warehouseName} +
+ + {/* Цифры */} +
+
+
0 ? 'text-green-400' : 'text-white/30'}`}> + {stock.quantity} +
+
остаток
-
+ + {(stock.inWayToClient > 0 || stock.inWayFromClient > 0) && ( + <> +
+ + {stock.inWayToClient > 0 && ( +
+
{stock.inWayToClient}
+
к клиенту
+
+ )} + + {stock.inWayFromClient > 0 && ( +
+
{stock.inWayFromClient}
+
от клиента
+
+ )} + + )} +
+
+ ))} +
+
+
+ ) +} + +// Супер современная карточка товара (СТАРАЯ ВЕРСИЯ - НЕ ИСПОЛЬЗУЕТСЯ) +function StockCard({ item }: { item: WBStock }) { + // Получение изображений карточки через WildberriesService + const getCardImages = (item: WBStock): string[] => { + if (item.photos && item.photos.length > 0) { + const urls = item.photos + .map(photo => photo.c246x328 || photo.c516x688 || photo.big) + .filter((url): url is string => Boolean(url)) + return urls + } + + if (item.mediaFiles && item.mediaFiles.length > 0) { + return item.mediaFiles + } + + const vol = Math.floor(item.nmId / 100000) + const part = Math.floor(item.nmId / 1000) + const fallbackUrl = `https://basket-${String(vol).padStart(2, '0')}.wbbasket.ru/vol${vol}/part${part}/${item.nmId}/images/c246x328/1.webp` + return [fallbackUrl] + } + + const getStockStatus = (quantity: number) => { + if (quantity === 0) return { + color: 'from-red-500/20 to-red-600/5 border-red-500/30', + textColor: 'text-red-400', + label: 'Нет в наличии', + icon: '❌' + } + if (quantity < 10) return { + color: 'from-orange-500/20 to-orange-600/5 border-orange-500/30', + textColor: 'text-orange-400', + label: 'Мало', + icon: '⚠️' + } + return { + color: 'from-green-500/20 to-green-600/5 border-green-500/30', + textColor: 'text-green-400', + label: 'В наличии', + icon: '✅' + } + } + + const stockStatus = getStockStatus(item.totalQuantity) + const images = getCardImages(item) + const mainImage = images[0] || null + + return ( +
+ {/* Градиентный фон при hover */} +
+ +
+ {/* Хедер карточки */} +
+ {/* Изображение товара */} +
+
+ {mainImage ? ( + {item.title} + ) : ( +
+ +
+ )} +
+ {/* WB Badge */} +
+
+ WB +
+
+
+ + {/* Инфо товара */} +
+
+
+
+ + {item.brand || 'Без бренда'} + + #{item.nmId} +
+

+ {item.title} +

+
+ + {/* Статус */} +
+ {stockStatus.icon} + {stockStatus.label} +
+
+ +
+ Артикул: {item.vendorCode} +
+
+
+ + {/* Компактная статистика */} +
+
+
{item.totalQuantity.toLocaleString()}
+
Остаток
+
+
+
{item.stocks.length}
+
Складов
+
+
+
+ {item.stocks.reduce((sum, s) => sum + s.inWayToClient, 0)} +
+
К клиенту
+
+
+
+ {item.stocks.reduce((sum, s) => sum + s.inWayFromClient, 0)} +
+
От клиента
+
+
+ + {/* Склады - компактно */} +
+

+ + Склады +

+
+ {item.stocks.slice(0, 3).map((stock, stockIndex) => ( +
+
+
{stock.warehouseName}
+
ID: {stock.warehouseId}
+
+
-

{stock.quantity}

-

Доступно

+
0 ? 'text-green-400' : 'text-white/30'}`}> + {stock.quantity} +
-

0 ? 'text-blue-400' : 'text-white/30'}`}> +

0 ? 'text-orange-400' : 'text-white/30'}`}> {stock.inWayToClient} -

-

К клиенту

+
-

0 ? 'text-orange-400' : 'text-white/30'}`}> +

0 ? 'text-red-400' : 'text-white/30'}`}> {stock.inWayFromClient} -

-

От клиента

+
))} + {item.stocks.length > 3 && ( +
+ +{item.stocks.length - 3} ещё складов +
+ )}
- {/* Основная информация о товаре */} - {(item.subjectName || item.description) && ( -
-

Информация о товаре:

-
- {item.subjectName && ( -
- Категория: - {item.subjectName} -
- )} - - {item.description && ( -
- Описание: -

{item.description}

-
- )} -
-
- )} - - {/* Характеристики товара */} - {item.characteristics && item.characteristics.length > 0 && ( -
-

Характеристики:

-
- {item.characteristics.map((characteristic, charIndex) => ( -
- - {characteristic.name}: - -
- {Array.isArray(characteristic.value) ? ( - characteristic.value.map((val, valIndex) => ( - - {val} - {valIndex < characteristic.value.length - 1 && ', '} - - )) - ) : ( - - {String(characteristic.value)} - - )} -
-
- ))} + {/* Категория */} + {item.subjectName && ( +
+
+ Категория: {item.subjectName}
)}
- +
) } \ No newline at end of file diff --git a/src/services/wildberries-service.ts b/src/services/wildberries-service.ts index 442b032..3a60533 100644 --- a/src/services/wildberries-service.ts +++ b/src/services/wildberries-service.ts @@ -1168,7 +1168,7 @@ class WildberriesService { dateFrom?: string dateTo?: string stockType?: '' | 'wb' | 'mp' - } = {}): Promise { + } = {}): Promise { try { console.log('WB Analytics API: Getting stocks report by offices...') @@ -1210,137 +1210,53 @@ class WildberriesService { }) console.log('WB Analytics API: Response:', JSON.stringify(response, null, 2)) + + // Детальный анализ структуры ответа + console.log('\n=== ДЕТАЛЬНЫЙ АНАЛИЗ ОТВЕТА API ===') + if (response.data) { + console.log('✅ response.data существует') + if (response.data.regions) { + console.log('✅ response.data.regions существует, длина:', response.data.regions.length) + response.data.regions.forEach((region, regionIndex) => { + console.log(`\n📍 РЕГИОН ${regionIndex + 1}:`) + console.log(' - regionName:', region.regionName) + console.log(' - metrics:', region.metrics) + console.log(' - offices.length:', region.offices?.length || 0) + + if (region.offices && region.offices.length > 0) { + region.offices.forEach((office, officeIndex) => { + console.log(`\n 🏢 СКЛАД ${officeIndex + 1}:`) + console.log(' - officeID:', office.officeID) + console.log(' - officeName:', office.officeName) + console.log(' - metrics:', office.metrics) + + // Проверяем наличие метрик + if (office.metrics) { + console.log(' - stockCount:', office.metrics.stockCount || 0) + console.log(' - toClientCount:', office.metrics.toClientCount || 0) + console.log(' - fromClientCount:', office.metrics.fromClientCount || 0) + } + }) + } else { + console.log(' ⚠️ Нет складов в этом регионе') + } + }) + } else { + console.log('❌ response.data.regions отсутствует') + } + } else { + console.log('❌ response.data отсутствует') + } + console.log('=== КОНЕЦ АНАЛИЗА ===\n') + - console.log('WB Analytics API: Processing response data...') - // Преобразуем данные Analytics API в формат WBStock - const stocks: WBStock[] = [] - - if (response.data?.regions) { - console.log(`WB Analytics API: Found ${response.data.regions.length} regions`) - - // Получаем карточки товаров и остатки для сопоставления - console.log('WB Analytics API: Loading cards and current stocks for matching...') - const [cards, currentStocks] = await Promise.all([ - WildberriesService.getAllCards(this.apiKey).catch(() => []), - this.getStocks().catch(() => []) - ]) - - console.log(`WB Analytics API: Loaded ${cards.length} cards and ${currentStocks.length} stock records`) - - const cardsMap = new Map(cards.map((card: WildberriesCard) => [card.nmID, card])) - - // Создаем карту остатков по складам из текущих данных - const stocksByWarehouse = new Map[]>() - const typedCurrentStocks = currentStocks as Record[] - typedCurrentStocks.forEach((stock: Record) => { - const warehouseId = Number(stock.warehouseId || stock.warehouse) || 0 - if (!stocksByWarehouse.has(warehouseId)) { - stocksByWarehouse.set(warehouseId, []) - } - stocksByWarehouse.get(warehouseId)!.push(stock) - }) - - response.data.regions.forEach(region => { - console.log(`WB Analytics API: Processing region "${region.regionName}" with ${region.offices.length} offices`) - - region.offices.forEach(office => { - console.log(`WB Analytics API: Processing office "${office.officeName}" (ID: ${office.officeID})`) - console.log(`WB Analytics API: Office metrics:`, office.metrics) - - // Получаем товары для этого склада WB - const warehouseStocks = stocksByWarehouse.get(office.officeID) || [] - console.log(`WB Analytics API: Found ${warehouseStocks.length} stock records for warehouse ${office.officeID}`) - - // Создаем записи для каждого товара на этом складе WB - // Если нет конкретных остатков, создаем на основе карточек товаров - if (warehouseStocks.length > 0) { - // Группируем по nmId - const stocksByNmId = new Map[]>() - warehouseStocks.forEach((stock: Record) => { - const nmId = Number(stock.nmId) || 0 - if (nmId > 0) { - if (!stocksByNmId.has(nmId)) { - stocksByNmId.set(nmId, []) - } - stocksByNmId.get(nmId)!.push(stock) - } - }) - - // Создаем записи для каждого товара - stocksByNmId.forEach((stockItems, nmId) => { - const firstStock = stockItems[0] - const card = cardsMap.get(nmId) - - const stock: WBStock = { - nmId, - vendorCode: String(firstStock.vendorCode || firstStock.supplierArticle || ''), - title: String(firstStock.title || firstStock.subject || card?.title || `Товар ${nmId}`), - brand: String(firstStock.brand || card?.brand || ''), - price: Number(firstStock.price || firstStock.Price) || 0, - stocks: [{ - warehouseId: office.officeID, - warehouseName: office.officeName, - quantity: Number(firstStock.quantity) || 0, - quantityFull: Number(firstStock.quantityFull) || 0, - inWayToClient: office.metrics.toClientCount, // Берем из Analytics API - inWayFromClient: office.metrics.fromClientCount // Берем из Analytics API - }], - totalQuantity: Number(firstStock.quantity) || 0, - totalReserved: office.metrics.toClientCount, - photos: Array.isArray(firstStock.photos) ? firstStock.photos : (card?.photos || []), - mediaFiles: Array.isArray(firstStock.mediaFiles) ? firstStock.mediaFiles : [], - characteristics: Array.isArray(firstStock.characteristics) ? firstStock.characteristics : (card?.characteristics || []), - subjectName: String(firstStock.subjectName || firstStock.subject || card?.subjectName || ''), - description: String(firstStock.description || card?.description || '') - } - - stocks.push(stock) - }) - } else { - console.log(`WB Analytics API: No stock records found for warehouse ${office.officeID}, creating entries for each product`) - - // Создаем записи для каждого товара на этом складе WB - // Даже если нет точных остатков, показываем движение товаров - cardsMap.forEach((card, nmId) => { - const stock: WBStock = { - nmId, - vendorCode: String(card.vendorCode || ''), - title: String(card.title || `Товар ${nmId}`), - brand: String(card.brand || ''), - price: 0, // У карточки нет цены, используем 0 - stocks: [{ - warehouseId: office.officeID, - warehouseName: office.officeName, - quantity: office.metrics.stockCount, // Общее количество на складе - quantityFull: office.metrics.stockCount, - inWayToClient: office.metrics.toClientCount, // К клиенту - inWayFromClient: office.metrics.fromClientCount // От клиента - }], - totalQuantity: office.metrics.stockCount, - totalReserved: office.metrics.toClientCount, - photos: Array.isArray(card.photos) ? card.photos : [], - mediaFiles: Array.isArray(card.mediaFiles) ? card.mediaFiles : [], - characteristics: Array.isArray(card.characteristics) ? card.characteristics : [], - subjectName: String(card.subjectName || region.regionName), - description: String(card.description || `Регион: ${region.regionName}, Склад: ${office.officeName}`) - } - - stocks.push(stock) - }) - } - }) - }) - } else { - console.log('WB Analytics API: No regions data in response') - } - - console.log(`WB Analytics API: Processed ${stocks.length} stock records`) - return stocks + console.log(`WB Analytics API: Returning raw response for processing in component`) + return response } catch (error) { console.error('WB Analytics API: Error getting stocks report:', error) - return [] + return { data: { regions: [] } } } }