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) const statistics = await wbService.getStatistics(args.startDate, args.endDate) 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 МОДУЛЬ ЭКСПОРТЫ ГОТОВЫ')