Files
sfera-new/src/graphql/resolvers/domains/wildberries.ts

825 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { GraphQLError } from 'graphql'
import { prisma } from '../../../lib/prisma'
import { MarketplaceService } from '../../../services/marketplace-service'
import { WildberriesService } from '../../../services/wildberries-service'
import { Context } from '../../context'
import { DomainResolvers } from '../shared/types'
// Wildberries & Marketplace Domain Resolvers - управление интеграцией с маркетплейсами
// =============================================================================
// 🔐 AUTHENTICATION HELPERS
// =============================================================================
const withAuth = (resolver: any) => {
return async (parent: any, args: any, context: Context) => {
console.log('🔐 WILDBERRIES DOMAIN AUTH CHECK:', {
hasUser: !!context.user,
userId: context.user?.id,
organizationId: context.user?.organizationId,
})
if (!context.user) {
console.error('❌ AUTH FAILED: No user in context')
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
console.log('✅ AUTH PASSED: Calling resolver')
try {
const result = await resolver(parent, args, context)
console.log('🎯 RESOLVER RESULT TYPE:', typeof result, result === null ? 'NULL RESULT!' : 'Has result')
return result
} catch (error) {
console.error('💥 RESOLVER ERROR:', error)
throw error
}
}
}
// Сервисы
const marketplaceService = new MarketplaceService()
// WildberriesService требует API ключ в конструкторе, создадим экземпляры в резолверах
// =============================================================================
// 🛍️ WILDBERRIES & MARKETPLACE DOMAIN RESOLVERS
// =============================================================================
export const wildberriesResolvers: DomainResolvers = {
Query: {
// Мои поставки Wildberries
myWildberriesSupplies: withAuth(async (_: unknown, __: unknown, context: Context) => {
console.log('🔍 MY_WILDBERRIES_SUPPLIES DOMAIN QUERY STARTED:', { userId: context.user?.id })
try {
const currentUser = await prisma.user.findUnique({
where: { id: context.user!.id },
include: { organization: true },
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
const supplies = await prisma.wildberriesSupply.findMany({
where: { organizationId: currentUser.organization.id },
include: {
organization: true,
cards: true,
},
orderBy: { createdAt: 'desc' },
})
console.log('✅ MY_WILDBERRIES_SUPPLIES DOMAIN SUCCESS:', { count: supplies.length })
return supplies
} catch (error) {
console.error('❌ MY_WILDBERRIES_SUPPLIES DOMAIN ERROR:', error)
return []
}
}),
// Отладка рекламных кампаний Wildberries
debugWildberriesAdverts: withAuth(async (_: unknown, __: unknown, context: Context) => {
console.log('🔍 DEBUG_WILDBERRIES_ADVERTS DOMAIN QUERY STARTED:', { userId: context.user?.id })
try {
const user = await prisma.user.findUnique({
where: { id: context.user!.id },
include: {
organization: {
include: { apiKeys: true },
},
},
})
if (!user?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
const wbApiKey = user.organization.apiKeys.find(key => key.marketplace === 'WILDBERRIES')
if (!wbApiKey) {
throw new GraphQLError('API ключ Wildberries не найден')
}
console.log('🚀 FETCHING WB ADVERTS WITH API KEY:', {
organizationId: user.organization.id,
hasApiKey: !!wbApiKey.apiKey,
})
const campaigns = await wildberriesService.getAdvertCampaigns(wbApiKey.apiKey)
console.log('✅ DEBUG_WILDBERRIES_ADVERTS SUCCESS:', { campaignsCount: campaigns.length })
return {
success: true,
message: 'Кампании получены успешно',
campaignsCount: campaigns.length,
campaigns: campaigns.slice(0, 5), // Первые 5 для отладки
}
} catch (error) {
console.error('❌ DEBUG_WILDBERRIES_ADVERTS ERROR:', error)
return {
success: false,
message: `Ошибка получения кампаний: ${error instanceof Error ? error.message : 'Unknown error'}`,
campaignsCount: 0,
campaigns: [],
}
}
}),
// Получение статистики Wildberries
getWildberriesStatistics: withAuth(async (
_: unknown,
args: { period: string; startDate: string; endDate: string },
context: Context,
) => {
console.log('🔍 GET_WILDBERRIES_STATISTICS DOMAIN QUERY STARTED:', {
userId: context.user?.id,
period: args.period,
startDate: args.startDate,
endDate: args.endDate,
})
try {
const user = await prisma.user.findUnique({
where: { id: context.user!.id },
include: {
organization: {
include: { apiKeys: true },
},
},
})
if (!user?.organization) {
throw new GraphQLError('Пользователь не привязан к организации')
}
const apiKey = user.organization.apiKeys.find(
key => key.marketplace === 'WILDBERRIES' && key.isActive,
)
if (!apiKey) {
return {
success: false,
message: 'API ключ Wildberries не найден',
data: [],
}
}
const wbService = new WildberriesService(apiKey.apiKey)
console.log('🔑 API Key length:', apiKey.apiKey?.length)
console.log('📅 Date params:', { startDate: args.startDate, endDate: args.endDate, period: args.period })
// Если нет конкретных дат, генерируем их на основе периода
let startDate = args.startDate
let endDate = args.endDate
if (!startDate || !endDate) {
const now = new Date()
const today = now.toISOString().split('T')[0] // YYYY-MM-DD
switch (args.period) {
case 'week':
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
startDate = weekAgo.toISOString().split('T')[0]
endDate = today
break
case 'month':
const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
startDate = monthAgo.toISOString().split('T')[0]
endDate = today
break
default:
// По умолчанию неделя
const defaultWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
startDate = defaultWeekAgo.toISOString().split('T')[0]
endDate = today
}
}
console.log('📅 Calculated dates:', { startDate, endDate })
const statistics = await wbService.getStatistics(startDate, endDate)
console.log('📊 Statistics result:', {
type: typeof statistics,
isArray: Array.isArray(statistics),
length: statistics?.length,
firstItem: statistics?.[0] ? Object.keys(statistics[0]) : null
})
console.log('✅ GET_WILDBERRIES_STATISTICS DOMAIN SUCCESS')
return {
success: true,
message: 'Статистика получена',
data: statistics,
}
} catch (error: any) {
console.error('❌ GET_WILDBERRIES_STATISTICS DOMAIN ERROR:', error)
return {
success: false,
message: error.message || 'Ошибка при получении статистики',
data: [],
}
}
}),
// Получение статистики кампаний Wildberries
getWildberriesCampaignStats: withAuth(async (
_: unknown,
args: { input: { campaigns: { id: number; dates?: string[]; interval?: { begin: string; end: string } }[] } },
context: Context,
) => {
console.log('🔍 GET_WILDBERRIES_CAMPAIGN_STATS DOMAIN QUERY STARTED:', {
userId: context.user?.id,
campaigns: args.input.campaigns.map(c => c.id),
})
try {
const user = await prisma.user.findUnique({
where: { id: context.user!.id },
include: {
organization: {
include: { apiKeys: true },
},
},
})
if (!user?.organization) {
throw new GraphQLError('Пользователь не привязан к организации')
}
const apiKey = user.organization.apiKeys.find(
key => key.marketplace === 'WILDBERRIES' && key.isActive,
)
if (!apiKey) {
return {
success: false,
message: 'API ключ Wildberries не найден',
data: [],
}
}
const wbService = new WildberriesService(apiKey.apiKey)
const stats = await wbService.getCampaignStats(args.input.campaigns)
console.log('✅ GET_WILDBERRIES_CAMPAIGN_STATS DOMAIN SUCCESS')
return {
success: true,
message: 'Статистика кампаний получена',
data: stats,
}
} catch (error: any) {
console.error('❌ GET_WILDBERRIES_CAMPAIGN_STATS DOMAIN ERROR:', error)
return {
success: false,
message: error.message || 'Ошибка при получении статистики кампаний',
data: [],
}
}
}),
// Получение списка кампаний Wildberries
getWildberriesCampaignsList: withAuth(async (_: unknown, __: unknown, context: Context) => {
console.log('🔍 GET_WILDBERRIES_CAMPAIGNS_LIST DOMAIN QUERY STARTED:', { userId: context.user?.id })
try {
const user = await prisma.user.findUnique({
where: { id: context.user!.id },
include: {
organization: {
include: { apiKeys: true },
},
},
})
if (!user?.organization) {
throw new GraphQLError('Пользователь не привязан к организации')
}
const apiKey = user.organization.apiKeys.find(
key => key.marketplace === 'WILDBERRIES' && key.isActive,
)
if (!apiKey) {
return {
success: false,
message: 'API ключ Wildberries не найден',
data: { adverts: [], all: 0 },
}
}
const wbService = new WildberriesService(apiKey.apiKey)
const campaignsList = await wbService.getCampaignsList()
console.log('✅ GET_WILDBERRIES_CAMPAIGNS_LIST DOMAIN SUCCESS')
return {
success: true,
message: 'Список кампаний получен',
data: campaignsList,
}
} catch (error: any) {
console.error('❌ GET_WILDBERRIES_CAMPAIGNS_LIST DOMAIN ERROR:', error)
return {
success: false,
message: error.message || 'Ошибка при получении списка кампаний',
data: { adverts: [], all: 0 },
}
}
}),
// Возвратные претензии Wildberries
wbReturnClaims: withAuth(async (_: unknown, args: { isArchive: boolean; limit?: number; offset?: number }, context: Context) => {
console.log('🔍 WB_RETURN_CLAIMS DOMAIN QUERY STARTED:', {
userId: context.user?.id,
isArchive: args.isArchive,
limit: args.limit,
offset: args.offset,
})
try {
const user = await prisma.user.findUnique({
where: { id: context.user!.id },
include: {
organization: {
include: { apiKeys: true },
},
},
})
if (!user?.organization) {
throw new GraphQLError('Пользователь не привязан к организации')
}
const apiKey = user.organization.apiKeys.find(
key => key.marketplace === 'WILDBERRIES' && key.isActive,
)
if (!apiKey) {
return {
claims: [],
total: 0,
}
}
const wbService = new WildberriesService(apiKey.apiKey)
// TODO: Реализовать метод getReturnClaims в WildberriesService
const claims = []
console.log('✅ WB_RETURN_CLAIMS DOMAIN SUCCESS:', {
claimsCount: claims?.length || 0,
})
return {
claims: claims || [],
total: claims?.length || 0,
}
} catch (error: any) {
console.error('❌ WB_RETURN_CLAIMS DOMAIN ERROR:', error)
return {
claims: [],
total: 0,
}
}
}),
// Получение данных о складах WB
getWBWarehouseData: withAuth(async (_: unknown, __: unknown, context: Context) => {
console.log('🔍 GET_WB_WAREHOUSE_DATA DOMAIN QUERY STARTED:', { userId: context.user?.id })
try {
const user = await prisma.user.findUnique({
where: { id: context.user!.id },
include: {
organization: {
include: { apiKeys: true },
},
},
})
if (!user?.organization) {
throw new GraphQLError('Пользователь не привязан к организации')
}
const apiKey = user.organization.apiKeys.find(
key => key.marketplace === 'WILDBERRIES' && key.isActive,
)
if (!apiKey) {
return {
success: false,
message: 'API ключ Wildberries не найден',
cache: null,
fromCache: false,
}
}
const wbService = new WildberriesService(apiKey.apiKey)
const warehouseData = await wbService.getWarehouses()
console.log('✅ GET_WB_WAREHOUSE_DATA DOMAIN SUCCESS:', {
stocksCount: warehouseData?.stocks?.length || 0,
})
return {
success: true,
message: 'Данные о складах получены',
cache: warehouseData,
fromCache: false,
}
} catch (error: any) {
console.error('❌ GET_WB_WAREHOUSE_DATA DOMAIN ERROR:', error)
return {
success: false,
message: error.message || 'Ошибка при получении данных о складах',
cache: null,
fromCache: false,
}
}
}),
},
Mutation: {
// Добавление API ключа маркетплейса
addMarketplaceApiKey: async (
_: unknown,
args: {
input: {
marketplace: 'WILDBERRIES' | 'OZON'
apiKey: string
clientId?: string
validateOnly?: boolean
}
},
context: Context,
) => {
console.log('🔍 ADD_MARKETPLACE_API_KEY DOMAIN MUTATION STARTED:', {
marketplace: args.input.marketplace,
validateOnly: args.input.validateOnly,
hasUser: !!context.user,
})
// Разрешаем валидацию без авторизации
if (!args.input.validateOnly && !context.user) {
throw new GraphQLError('Требуется авторизация', {
extensions: { code: 'UNAUTHENTICATED' },
})
}
const { marketplace, apiKey, clientId, validateOnly } = args.input
// Только валидация ключа
if (validateOnly) {
try {
const isValid = await marketplaceService.validateApiKey(marketplace, apiKey, clientId)
if (isValid) {
return {
success: true,
message: `API ключ ${marketplace} действителен`,
organization: null,
}
} else {
return {
success: false,
message: `Недействительный API ключ ${marketplace}`,
organization: null,
}
}
} catch (error: any) {
return {
success: false,
message: error.message || 'Ошибка при проверке API ключа',
organization: null,
}
}
}
// Полное добавление ключа (требует авторизацию)
if (!context.user) {
throw new GraphQLError('Требуется авторизация')
}
const user = await prisma.user.findUnique({
where: { id: context.user.id },
include: { organization: true },
})
if (!user?.organization) {
throw new GraphQLError('Пользователь не привязан к организации')
}
try {
// Валидация ключа
const isValid = await marketplaceService.validateApiKey(marketplace, apiKey, clientId)
if (!isValid) {
return {
success: false,
message: `Недействительный API ключ ${marketplace}`,
organization: null,
}
}
// Проверка существующего ключа
const existing = await prisma.apiKey.findUnique({
where: {
organizationId_marketplace: {
organizationId: user.organization.id,
marketplace,
},
},
})
if (existing) {
// Обновляем существующий
await prisma.apiKey.update({
where: {
organizationId_marketplace: {
organizationId: user.organization.id,
marketplace,
},
},
data: {
apiKey: apiKey,
isActive: true,
validationData: clientId ? { clientId, validatedAt: new Date() } : { validatedAt: new Date() },
},
})
} else {
// Создаем новый
await prisma.apiKey.create({
data: {
organizationId: user.organization.id,
marketplace,
apiKey: apiKey,
isActive: true,
validationData: clientId ? { clientId, validatedAt: new Date() } : { validatedAt: new Date() },
},
})
}
const updatedOrganization = await prisma.organization.findUnique({
where: { id: user.organization.id },
include: { apiKeys: true },
})
console.log('✅ ADD_MARKETPLACE_API_KEY DOMAIN SUCCESS:', {
organizationId: user.organization.id,
marketplace,
})
return {
success: true,
message: `API ключ ${marketplace} успешно добавлен`,
organization: updatedOrganization,
}
} catch (error: any) {
console.error('❌ ADD_MARKETPLACE_API_KEY DOMAIN ERROR:', error)
return {
success: false,
message: error.message || 'Ошибка при добавлении API ключа',
organization: null,
}
}
},
// Удаление API ключа маркетплейса
removeMarketplaceApiKey: withAuth(async (
_: unknown,
args: { marketplace: 'WILDBERRIES' | 'OZON' },
context: Context,
) => {
console.log('🔍 REMOVE_MARKETPLACE_API_KEY DOMAIN MUTATION STARTED:', {
userId: context.user?.id,
marketplace: args.marketplace,
})
try {
const user = await prisma.user.findUnique({
where: { id: context.user!.id },
include: { organization: true },
})
if (!user?.organization) {
throw new GraphQLError('Пользователь не привязан к организации')
}
await prisma.apiKey.delete({
where: {
organizationId_marketplace: {
organizationId: user.organization.id,
marketplace: args.marketplace,
},
},
})
const updatedOrganization = await prisma.organization.findUnique({
where: { id: user.organization.id },
include: { apiKeys: true },
})
console.log('✅ REMOVE_MARKETPLACE_API_KEY DOMAIN SUCCESS:', {
organizationId: user.organization.id,
marketplace: args.marketplace,
})
return {
success: true,
message: `API ключ ${args.marketplace} успешно удален`,
organization: updatedOrganization,
}
} catch (error: any) {
console.error('❌ REMOVE_MARKETPLACE_API_KEY DOMAIN ERROR:', error)
return {
success: false,
message: error.message || 'Ошибка при удалении API ключа',
organization: null,
}
}
}),
// Создание поставки на Wildberries
createWildberriesSupply: withAuth(async (
_: unknown,
args: {
input: {
cards: Array<{
price: number
discountedPrice?: number
selectedQuantity: number
selectedServices?: string[]
}>
}
},
context: Context,
) => {
console.log('🔍 CREATE_WILDBERRIES_SUPPLY DOMAIN MUTATION STARTED:', {
userId: context.user?.id,
cardsCount: args.input.cards.length,
})
try {
const currentUser = await prisma.user.findUnique({
where: { id: context.user!.id },
include: {
organization: {
include: { apiKeys: true },
},
},
})
if (!currentUser?.organization) {
throw new GraphQLError('У пользователя нет организации')
}
// Проверка API ключа
const apiKey = currentUser.organization.apiKeys.find(
key => key.marketplace === 'WILDBERRIES' && key.isActive,
)
if (!apiKey) {
return {
success: false,
message: 'Не найден активный API ключ Wildberries',
supply: null,
}
}
// Создаем экземпляр WildberriesService с API ключом
const wbService = new WildberriesService(apiKey.apiKey)
// Получаем каталог с карточками
const catalogData = await wbService.getProducts()
const catalog = catalogData?.data || []
if (catalog.length === 0) {
return {
success: false,
message: 'Не удалось получить каталог товаров',
supply: null,
}
}
// Создаем поставку
const supply = await prisma.wildberriesSupply.create({
data: {
organizationId: currentUser.organization.id,
status: 'DRAFT',
totalCards: args.input.cards.length,
totalQuantity: args.input.cards.reduce((sum, card) => sum + card.selectedQuantity, 0),
cards: {
create: args.input.cards.map((card, index) => ({
vendorCode: catalog[index]?.vendorCode || `ART${index}`,
title: catalog[index]?.title || `Товар ${index + 1}`,
selectedQuantity: card.selectedQuantity,
price: card.price,
discountedPrice: card.discountedPrice || card.price,
quantity: card.selectedQuantity,
warehouseId: catalog[index]?.skus?.[0]?.warehouses?.[0]?.warehouseId || 0,
services: card.selectedServices || [],
})),
},
},
include: {
organization: true,
cards: true,
},
})
// TODO: Интеграция с WB API для создания поставки
// const wbSupplyId = await wbService.createSupply(...)
// Пока просто обновляем статус
await prisma.wildberriesSupply.update({
where: { id: supply.id },
data: {
status: 'CREATED',
},
})
console.log('✅ CREATE_WILDBERRIES_SUPPLY DOMAIN SUCCESS:', {
supplyId: supply.id,
externalId: wbSupplyId,
})
return {
success: true,
message: 'Поставка успешно создана',
supply,
}
} catch (error: any) {
console.error('❌ CREATE_WILDBERRIES_SUPPLY DOMAIN ERROR:', error)
return {
success: false,
message: error.message || 'Ошибка при создании поставки',
supply: null,
}
}
}),
// Сохранение кеша данных складов WB
saveWBWarehouseCache: withAuth(async (
_: unknown,
args: {
input: {
data: string;
totalProducts: number;
totalStocks: number;
totalReserved: number;
}
},
context: Context,
) => {
console.log('🔍 SAVE_WB_WAREHOUSE_CACHE DOMAIN MUTATION STARTED:', {
userId: context.user?.id,
hasData: !!args.input.data,
totalProducts: args.input.totalProducts,
totalStocks: args.input.totalStocks,
})
try {
const user = await prisma.user.findUnique({
where: { id: context.user!.id },
include: { organization: true },
})
if (!user?.organization) {
throw new GraphQLError('Пользователь не привязан к организации')
}
// TODO: Реализовать кеширование в отдельной таблице или через Redis
console.log('✅ SAVE_WB_WAREHOUSE_CACHE DOMAIN SUCCESS:', {
organizationId: user.organization.id,
dataSize: args.input.data.length,
})
return {
success: true,
message: 'Кеш данных складов сохранен',
cache: {
id: `cache_${user.organization.id}_${Date.now()}`,
organizationId: user.organization.id,
cacheDate: new Date().toISOString(),
data: args.input.data,
totalProducts: args.input.totalProducts,
totalStocks: args.input.totalStocks,
totalReserved: args.input.totalReserved,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
fromCache: false,
}
} catch (error: any) {
console.error('❌ SAVE_WB_WAREHOUSE_CACHE DOMAIN ERROR:', error)
return {
success: false,
message: error.message || 'Ошибка при сохранении кеша',
cache: null,
fromCache: false,
}
}
}),
},
}
console.warn('🔥 WILDBERRIES DOMAIN МОДУЛЬ ЭКСПОРТЫ ГОТОВЫ')