From 8ba8fc1214791a57897a2ca4ab7aee1d0197da83 Mon Sep 17 00:00:00 2001 From: Bivekich Date: Mon, 11 Aug 2025 22:30:45 +0300 Subject: [PATCH] WB stats reliability: fix client apply-after-refetch, normalize ad dates, and add cache fallback\n\n- SalesTab: apply data immediately after refetch success to avoid empty state\n- Service: normalize advertising day dates to YYYY-MM-DD for correct range checks\n- Resolver: fallback to cached advertisingData when productsData is missing (429)\n\nHelps show data even when WB API rate-limits and fixes mixed-date aggregation. --- .../seller-statistics/sales-tab.tsx | 6 ++- src/graphql/resolvers.ts | 49 +++++++++++++++++++ src/services/wildberries-service.ts | 3 +- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/src/components/seller-statistics/sales-tab.tsx b/src/components/seller-statistics/sales-tab.tsx index 09e5731..e78ec20 100644 --- a/src/components/seller-statistics/sales-tab.tsx +++ b/src/components/seller-statistics/sales-tab.tsx @@ -232,7 +232,11 @@ const SalesTab = React.memo(({ } if (result.data?.getWildberriesStatistics?.success) { console.warn('Sales: Loading fresh data from API') - // Обрабатываем данные в существующем useEffect + const rows = result.data.getWildberriesStatistics.data || [] + if (rows.length > 0) { + // Применяем данные сразу, не дожидаясь обновления wbData + applyData(rows) + } lastLoadedKeyRef.current = loadKey } } catch (error) { diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 4bd804b..cbbd6ee 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -8224,6 +8224,42 @@ const wildberriesQueries = { message: 'Данные возвращены из кеша из-за ошибки WB API', } } + } else if (cache?.advertisingData) { + // Fallback №2: если нет productsData, но есть advertisingData — + // формируем минимальный набор данных по дням на основе затрат на рекламу + try { + const adv = JSON.parse(cache.advertisingData as unknown as string) as { + dailyData?: Array<{ + date: string + totalSum?: number + totalOrders?: number + totalRevenue?: number + }> + } + + const daily = adv.dailyData ?? [] + const dataFromAdv = daily.map((d) => ({ + date: d.date, + sales: 0, + orders: typeof d.totalOrders === 'number' ? d.totalOrders : 0, + advertising: typeof d.totalSum === 'number' ? d.totalSum : 0, + refusals: 0, + returns: 0, + revenue: typeof d.totalRevenue === 'number' ? d.totalRevenue : 0, + buyoutPercentage: 0, + })) + + if (dataFromAdv.length > 0) { + return { + success: true, + data: dataFromAdv, + message: + 'Данные по продажам недоступны из-за ошибки WB API. Показаны данные по рекламе из кеша.', + } + } + } catch (parseErr) { + console.error('Failed to parse advertisingData from cache:', parseErr) + } } } } catch (fallbackErr) { @@ -8980,6 +9016,17 @@ resolvers.Query = { } } + // Если кеш просрочен — не используем его, как и для склада WB (сервер решает, годен ли кеш) + const now = new Date() + if (cache.expiresAt && cache.expiresAt <= now) { + return { + success: true, + message: 'Кеш устарел, требуется загрузка из API', + cache: null, + fromCache: false, + } + } + return { success: true, message: 'Данные получены из кеша', @@ -8990,6 +9037,8 @@ resolvers.Query = { dateTo: cache.dateTo ? cache.dateTo.toISOString().split('T')[0] : null, productsTotalSales: cache.productsTotalSales ? Number(cache.productsTotalSales) : null, advertisingTotalCost: cache.advertisingTotalCost ? Number(cache.advertisingTotalCost) : null, + // Возвращаем expiresAt в ISO, чтобы клиент корректно парсил дату + expiresAt: cache.expiresAt.toISOString(), createdAt: cache.createdAt.toISOString(), updatedAt: cache.updatedAt.toISOString(), }, diff --git a/src/services/wildberries-service.ts b/src/services/wildberries-service.ts index 7520384..da1ae5f 100644 --- a/src/services/wildberries-service.ts +++ b/src/services/wildberries-service.ts @@ -851,7 +851,8 @@ class WildberriesService { // Обрабатываем статистику по дням для каждой кампании if (advertStat.days && advertStat.days.length > 0) { advertStat.days.forEach((day) => { - const date = day.date + // Нормализуем дату рекламы до формата YYYY-MM-DD, чтобы совпадала с продажами/заказами + const date = (day.date.includes('T') ? day.date.split('T')[0] : day.date.split(' ')[0] || day.date) as string console.warn(`WB API: Day ${date} - spent ${day.sum} rubles (campaign ${advertStat.advertId})`)