UNPKG

iluria-sdk

Version:

SDK oficial do Iluria para integração com lojas e criação de frontends customizados

418 lines (368 loc) 12.7 kB
const GraphQLQueryBuilder = require('./graphql-builder'); const CacheManager = require('./cache'); const { NoOpCache } = require('./cache'); /** * Promotion module - Gerencia promoções via GraphQL * Updated: 2025-09-08 - Fixed GraphQL types for PromotionStatus and PromotionOrderBy */ class Promotion { constructor(httpClient, cacheManager = null) { this.http = httpClient; // Usa o cache manager fornecido ou cria um padrão if (cacheManager === false) { this.cache = new NoOpCache(); } else if (cacheManager) { this.cache = cacheManager; } else { // Mantém compatibilidade: cria cache simples se não fornecido this.cache = new CacheManager({ maxMemorySize: 50, ttl: 60000, storage: false // Apenas memória por padrão }); } // TTL padrão para cache de promoções this.cacheTTL = 300000; // 5 minutos cache para promoções } /** * Lista promoções com filtros e campos customizados * @param {object} options - Opções de filtro (limit, offset, status, orderBy) * @param {Array<string>} fields - Campos a buscar para cada promoção * @returns {object} - Lista de promoções com paginação */ async list(options = {}, fields = null) { // Validação de parâmetros para evitar erros const safeOptions = { limit: Math.max(1, Math.min(100, options.limit || 10)), // Entre 1-100 offset: Math.max(0, options.offset || 0), status: options.status || 'ACTIVE', orderBy: options.orderBy || 'NEWEST' }; // Campos padrão se não especificados if (!fields) { fields = [ 'id', 'name', 'type', 'subType', 'discountType', 'discountValue', 'minRequirementType', 'minValue', 'minQuantity', 'targetConfig', 'targetProduct', 'triggerType', 'scope', 'description', 'startDate', 'defineStartDate', 'defineEndDate', 'endDate', 'active', 'combinePromos', 'firstPurchase', 'createdAt', 'updatedAt' ]; } // Chave de cache baseada nos parâmetros const cacheKey = `promotions_list_${JSON.stringify(safeOptions)}_${JSON.stringify(fields)}`; try { // Verifica cache primeiro const cached = await this.cache.get(cacheKey); if (cached) { return cached; } // Monta query GraphQL const query = ` query ($promotionsLimit: Int, $promotionsOffset: Int, $promotionsStatus: PromotionStatus, $promotionsOrderBy: PromotionOrderBy) { promotions(limit: $promotionsLimit, offset: $promotionsOffset, status: $promotionsStatus, orderBy: $promotionsOrderBy) { items { ${fields.join('\n ')} } totalCount hasMore offset limit } } `; const variables = { promotionsLimit: safeOptions.limit, promotionsOffset: safeOptions.offset, promotionsStatus: safeOptions.status, promotionsOrderBy: safeOptions.orderBy }; // Log para debug - tipos atualizados para PromotionStatus e PromotionOrderBy console.log('🔍 GraphQL Query (updated types):', query); console.log('🔍 GraphQL Variables:', JSON.stringify(variables, null, 2)); const data = await this.http.graphql(query, variables); // Processa e valida dados retornados let result = []; if (data && data.promotions) { if (data.promotions.items) { // Formato com paginação result = { items: this.processPromotions(data.promotions.items), totalCount: data.promotions.totalCount || 0, hasMore: data.promotions.hasMore || false, offset: data.promotions.offset || 0, limit: data.promotions.limit || safeOptions.limit }; } else if (Array.isArray(data.promotions)) { // Fallback para array direto result = this.processPromotions(data.promotions); } else { result = []; } } // Salva no cache await this.cache.set(cacheKey, result, this.cacheTTL); return result; } catch (error) { console.error('Erro ao buscar promoções:', error); // Retorna estrutura padrão em caso de erro if (options.limit !== undefined) { return { items: [], totalCount: 0, hasMore: false, offset: safeOptions.offset, limit: safeOptions.limit, error: 'Erro ao carregar promoções' }; } return []; } } /** * Busca uma promoção específica por ID * @param {string} id - ID da promoção * @param {Array<string>} fields - Campos a buscar * @returns {object} - Dados da promoção */ async get(id, fields = null) { // Validação do ID if (!id || typeof id !== 'string') { throw new Error('ID da promoção é obrigatório e deve ser uma string'); } // Campos padrão se não especificados if (!fields) { fields = [ 'id', 'name', 'type', 'subType', 'discountType', 'discountValue', 'minRequirementType', 'minValue', 'minQuantity', 'targetConfig', 'targetProduct', 'triggerType', 'scope', 'description', 'startDate', 'defineStartDate', 'defineEndDate', 'endDate', 'active', 'combinePromos', 'firstPurchase', 'createdAt', 'updatedAt' ]; } // Chave de cache const cacheKey = `promotion_${id}_${JSON.stringify(fields)}`; try { // Verifica cache primeiro const cached = await this.cache.get(cacheKey); if (cached) { return cached; } // Monta query GraphQL const query = ` query Promotion($id: ID!) { promotion(id: $id) { ${fields.join('\n ')} } } `; const variables = { id }; const data = await this.http.graphql(query, variables); // Processa dados retornados let result = null; if (data && data.promotion) { result = this.processPromotion(data.promotion); } // Salva no cache se encontrou a promoção if (result) { await this.cache.set(cacheKey, result, this.cacheTTL); } return result; } catch (error) { console.error(`Erro ao buscar promoção ${id}:`, error); return null; } } /** * Busca promoções ativas aplicáveis para um produto específico * @param {string} productId - ID do produto * @param {object} options - Opções adicionais * @returns {Array} - Lista de promoções aplicáveis */ async getForProduct(productId, options = {}) { if (!productId) { throw new Error('ID do produto é obrigatório'); } const safeOptions = { limit: options.limit || 10, offset: options.offset || 0 }; try { const query = ` query PromotionsForProduct($productId: ID!, $limit: Int, $offset: Int) { promotionsForProduct(productId: $productId, limit: $limit, offset: $offset) { id name type discountType discountValue description startDate endDate active } } `; const variables = { productId, limit: safeOptions.limit, offset: safeOptions.offset }; const data = await this.http.graphql(query, variables); if (data && data.promotionsForProduct) { return this.processPromotions(data.promotionsForProduct); } return []; } catch (error) { console.error(`Erro ao buscar promoções para produto ${productId}:`, error); return []; } } /** * Processa uma lista de promoções para garantir dados válidos * @param {Array} promotions - Lista de promoções * @returns {Array} - Lista processada */ processPromotions(promotions) { if (!Array.isArray(promotions)) { return []; } return promotions.map(promotion => this.processPromotion(promotion)); } /** * Processa uma promoção individual para garantir dados válidos * @param {object} promotion - Dados da promoção * @returns {object} - Promoção processada */ processPromotion(promotion) { if (!promotion || typeof promotion !== 'object') { return null; } // Garantir que campos obrigatórios existam e tratamento de valores nulos const processed = { id: promotion.id || null, name: promotion.name || 'Promoção sem nome', type: promotion.type || null, subType: promotion.subType || null, discountType: promotion.discountType || null, discountValue: this.safeNumericValue(promotion.discountValue, 0), minRequirementType: promotion.minRequirementType || null, minValue: this.safeNumericValue(promotion.minValue, 0), minQuantity: this.safeNumericValue(promotion.minQuantity, 0), targetConfig: promotion.targetConfig || null, targetProduct: promotion.targetProduct || null, triggerType: promotion.triggerType || null, scope: promotion.scope || null, description: promotion.description || '', startDate: promotion.startDate || null, defineStartDate: promotion.defineStartDate || false, defineEndDate: promotion.defineEndDate || false, endDate: promotion.endDate || null, active: promotion.active !== undefined ? promotion.active : true, combinePromos: promotion.combinePromos !== undefined ? promotion.combinePromos : false, firstPurchase: promotion.firstPurchase !== undefined ? promotion.firstPurchase : false, createdAt: promotion.createdAt || null, updatedAt: promotion.updatedAt || null }; // Adiciona métodos auxiliares processed.isActive = () => { if (!processed.active) return false; const now = new Date(); // Verifica data de início se definida if (processed.defineStartDate && processed.startDate) { const startDate = new Date(processed.startDate); if (now < startDate) return false; } // Verifica data de fim se definida if (processed.defineEndDate && processed.endDate) { const endDate = new Date(processed.endDate); if (now > endDate) return false; } return true; }; processed.getDiscountText = () => { if (!processed.discountType || processed.discountValue === 0) { return 'Sem desconto'; } switch (processed.discountType) { case 'PERCENTAGE': return `${processed.discountValue}% OFF`; case 'FIXED_AMOUNT': return `R$ ${this.formatPrice(processed.discountValue)} OFF`; case 'FREE_SHIPPING': return 'Frete Grátis'; default: return 'Desconto especial'; } }; return processed; } /** * Converte valor para número seguro, tratando null/undefined * @param {any} value - Valor a converter * @param {number} defaultValue - Valor padrão se conversão falhar * @returns {number} - Valor numérico seguro */ safeNumericValue(value, defaultValue = 0) { if (value === null || value === undefined) { return defaultValue; } const num = parseFloat(value); return isNaN(num) ? defaultValue : num; } /** * Formata preço para exibição * @param {number} price - Preço a formatar * @returns {string} - Preço formatado */ formatPrice(price) { if (!price || isNaN(price)) return '0,00'; return price.toFixed(2).replace('.', ','); } /** * Limpa cache de promoções */ async clearCache() { try { await this.cache.clear(); } catch (error) { console.error('Erro ao limpar cache de promoções:', error); } } } module.exports = Promotion;