E-Ticaret Panel
Mağazalar
Mağaza Düzenle
Mağaza Adı
XML Linki
Shopify Store Name
.myshopify.com
Access Token
API Version
2025-07 (En Güncel - Önerilen)
2025-04
2025-01
2024-10
2024-07
2024-04 (Legacy)
2024-01 (Legacy)
Mevcut: 2025-07
Kontrol Süresi
Saniye
Dakika
Saat
Gün
Product Path (Opsiyonel)
XML'de ürünlerin bulunduğu path
🏷️ Vendor (Marka) Filtreleme
📦 Tüm markalar işleniyor
← Ana sayfadan "Filtre Ekle" butonuyla vendor seçimi yapabilirsiniz
Converter Kodunuz
// JSCO Retail XML (Shopify Export) -> Shopify CSV Converter // XML: https://patronundan.com/jsco-retail/xml module.exports = function convertJSCO(item, utils = {}) { // ============ HELPER FUNCTIONS ============ const trim = v => (v == null ? '' : String(v).trim()); const isUrl = u => /^https?:\/\//i.test(String(u || '')); const toNum = v => { if (v == null || v === '') return null; const n = Number(String(v).replace(',', '.')); return Number.isFinite(n) ? n : null; }; const money = v => { const n = toNum(v); return n == null ? '0.00' : n.toFixed(2); }; const uniq = arr => Array.from(new Set((arr || []).filter(Boolean))); const slugify = (s = '') => (utils.slugify ? utils.slugify(s) : String(s).toLowerCase().normalize('NFKD') .replace(/[\u0300-\u036f]/g, '') .replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '') ); const minifyHTML = (s = '') => String(s) .replace(/\s+/g, ' ') .replace(/\s*(>|\<)\s*/g, '$1') .trim(); // xml2js array unwrapper const val = v => { if (Array.isArray(v)) return val(v[0]); if (v && typeof v === 'object' && '_' in v) return val(v._); return v; }; const toArray = v => { if (v == null) return []; return Array.isArray(v) ? v : [v]; }; // Beden sıralaması const SIZE_ORDER = ['XS', 'S', 'M', 'L', 'XL', 'XXL', 'XXXL', '3XL', '4XL']; const sizeRank = s => { const u = String(s || '').toUpperCase().trim(); const i = SIZE_ORDER.indexOf(u); return i === -1 ? 999 : i; }; // ============ MAIN CONVERTER ============ // Bu XML Shopify'dan export edilmiş format // Root key kontrolü if (item.catalog) { // catalog.product array'ini işle const products = toArray(item.catalog.product); return products.flatMap(p => convertJSCO(p, utils)); } // Tek ürün işleme const productId = trim(val(item.id)); const rawHandle = trim(val(item.handle)); const rawTitle = trim(val(item.title)); const description = trim(val(item.description)); const brand = trim(val(item.brand)) || ''; const category = trim(val(item.category)) || ''; const status = trim(val(item.status)); const totalInventory = parseInt(trim(val(item.total_inventory)) || '0', 10); // ✅ KURAL: "-R" sonekini kaldır let cleanTitle = rawTitle; let cleanHandle = rawHandle; // Title'dan -R kaldır if (cleanTitle.endsWith(' -R')) { cleanTitle = cleanTitle.substring(0, cleanTitle.length - 3).trim(); } else if (cleanTitle.endsWith('-R')) { cleanTitle = cleanTitle.substring(0, cleanTitle.length - 2).trim(); } // Handle'dan -r kaldır if (cleanHandle.endsWith('-r')) { cleanHandle = cleanHandle.substring(0, cleanHandle.length - 2); } // ✅ KURAL: Stok <= 10 veya görselsiz -> DRAFT // Active olması için: stok > 10 VE görsel olmalı // Handle explicitArray: true - featured_image is [{url: [...]}] const featuredImageObj = val(item.featured_image); const featuredImageUrl = trim(val(featuredImageObj?.url)); const hasImage = isUrl(featuredImageUrl); const shouldArchive = totalInventory <= 10 || !hasImage; // Status belirleme let productStatus = 'active'; if (shouldArchive) { productStatus = 'archived'; } // Tags - array or string const tagsNode = item.tags?.tag; const tagsArray = toArray(tagsNode); const tags = uniq([brand, category, ...tagsArray.map(t => trim(val(t)))]).join(', '); // Price range const minPrice = money(val(item.price_range?.min_price)); const maxPrice = money(val(item.price_range?.max_price)); // Featured image const productImage = hasImage ? featuredImageUrl : ''; // SEO const seoTitle = trim(val(item.seo?.title)) || cleanTitle; const seoDesc = trim(val(item.seo?.description)) || ''; // Product Options (Size, Color, etc.) // Handle both explicitArray: true and false let optionsNode; if (Array.isArray(item.product_options)) { // explicitArray: true - product_options is [{option: [...]}] optionsNode = item.product_options[0]?.option; } else { // explicitArray: false - product_options is {option: [...]} optionsNode = item.product_options?.option; } const optionsArray = toArray(optionsNode); // Option names const optionNames = optionsArray.map(opt => trim(val(opt.name))); // Variants - Handle both explicitArray: true and false let variantsNode; if (Array.isArray(item.variants)) { // explicitArray: true - variants is an array variantsNode = item.variants[0]?.variant; } else { // explicitArray: false - variants is an object variantsNode = item.variants?.variant; } const variantsArray = toArray(variantsNode); if (variantsArray.length === 0) { console.warn(`⚠️ No variants for product ${productId}`); return []; } // ============ CSV ROWS ============ const rows = []; // Sort variants by options (Size first, then Color) const sortedVariants = variantsArray.sort((a, b) => { // Handle both explicitArray: true and false for variant options const aOptsNode = Array.isArray(a.options) ? a.options[0]?.option : a.options?.option; const bOptsNode = Array.isArray(b.options) ? b.options[0]?.option : b.options?.option; const aOpts = toArray(aOptsNode); const bOpts = toArray(bOptsNode); // Size karşılaştırması const aSize = aOpts.find(o => val(o.$?.name) === 'Size'); const bSize = bOpts.find(o => val(o.$?.name) === 'Size'); if (aSize && bSize) { const aSizeVal = val(aSize._ || aSize); const bSizeVal = val(bSize._ || bSize); const sizeCompare = sizeRank(aSizeVal) - sizeRank(bSizeVal); if (sizeCompare !== 0) return sizeCompare; } return 0; }); // Base row template const baseRowFull = { __PRODUCT_KEY: productId, Handle: cleanHandle, Title: cleanTitle, 'Body (HTML)': minifyHTML(description), Vendor: brand, Type: category, Tags: tags, Published: 'TRUE', 'Option1 Name': '', 'Option2 Name': '', 'Option3 Name': '', 'Variant Inventory Tracker': 'shopify', 'Variant Inventory Policy': 'deny', 'Variant Fulfillment Service': 'manual', 'Variant Requires Shipping': 'TRUE', 'Variant Taxable': 'TRUE', 'Gift Card': 'FALSE', 'SEO Title': seoTitle, 'SEO Description': seoDesc, Status: productStatus }; const baseRowLite = { __PRODUCT_KEY: productId, Handle: cleanHandle, Title: '', 'Body (HTML)': '', Vendor: '', Type: '', Tags: '', Published: '', 'Option1 Name': '', 'Option2 Name': '', 'Option3 Name': '', 'Variant Inventory Tracker': 'shopify', 'Variant Inventory Policy': 'deny', 'Variant Fulfillment Service': 'manual', 'Variant Requires Shipping': 'TRUE', 'Variant Taxable': 'TRUE', 'Gift Card': 'FALSE', 'SEO Title': '', 'SEO Description': '', Status: '' }; // Variant satırları sortedVariants.forEach((variant, vIndex) => { const isFirst = vIndex === 0; const row = { ...(isFirst ? baseRowFull : baseRowLite) }; // SKU, Barcode, Stock const sku = trim(val(variant.sku)); const barcode = trim(val(variant.barcode)); const stock = parseInt(trim(val(variant.stock)) || '0', 10); // Price const variantPrice = money(toNum(val(variant.price)) * 1.20); // Options - Handle both explicitArray: true and false let vOptionsNode; if (Array.isArray(variant.options)) { // explicitArray: true - variant.options is [{option: [...]}] vOptionsNode = variant.options[0]?.option; } else { // explicitArray: false - variant.options is {option: [...]} vOptionsNode = variant.options?.option; } const vOptions = toArray(vOptionsNode); // Option names (sadece ilk satırda) if (isFirst && optionNames.length > 0) { if (optionNames[0]) row['Option1 Name'] = optionNames[0]; if (optionNames[1]) row['Option2 Name'] = optionNames[1]; if (optionNames[2]) row['Option3 Name'] = optionNames[2]; } // Option values vOptions.forEach((opt, idx) => { const optValue = val(opt._ || opt); if (idx === 0) row['Option1 Value'] = optValue || ''; if (idx === 1) row['Option2 Value'] = optValue || ''; if (idx === 2) row['Option3 Value'] = optValue || ''; }); // Variant fields row['Variant SKU'] = sku; row['Variant Barcode'] = barcode; row['Variant Price'] = variantPrice; row['Variant Compare At Price'] = ''; row['Variant Inventory Qty'] = String(stock); // ✅ GÖRSEL EŞLEŞMESİ: Her variant'ın kendi görseli var! // explicitArray: true -> variant.image is [{url: [...]}] // First unwrap the image array, then access url const imageObj = val(variant.image); const variantImageUrl = trim(val(imageObj?.url)); const hasVariantImage = isUrl(variantImageUrl); if (hasVariantImage) { row['Variant Image'] = variantImageUrl; } // İlk satıra ürün ana görseli ekle if (isFirst && productImage) { row['Image Src'] = productImage; row['Image Position'] = '1'; row['Image Alt Text'] = cleanTitle; } rows.push(row); }); // ✅ EKSTRA GÖRSELLER: Product images array'den diğer görselleri ekle const productImagesNode = item.images?.image; if (productImagesNode) { const imagesArray = toArray(productImagesNode); // İlk görseli zaten ekledik, diğerlerini ekleyelim imagesArray.slice(1).forEach((img, idx) => { const imgUrl = trim(val(img.url)); const imgAlt = trim(val(img.alt)) || cleanTitle; const imgPosition = val(img.$?.position) || String(idx + 2); if (isUrl(imgUrl)) { rows.push({ __PRODUCT_KEY: productId, Handle: cleanHandle, 'Image Src': imgUrl, 'Image Position': imgPosition, 'Image Alt Text': imgAlt }); } }); } // ============ DEBUG LOGGING ============ if (shouldArchive) { const reason = !hasImage ? 'NO IMAGE' : `LOW STOCK (${totalInventory})`; console.log(`📦 DRAFT: ${cleanHandle} - ${reason}`); } return rows; };
Güncelle
İptal
İstatistikler
Toplam Çalışma
3047
Başarı Oranı
100%
İşlenen Ürün
0
Push Edilen
0