1

Загрузите файл Excel (выгрузка из 1С)

Перетащите XLS / XLSX файл сюда

или

Поддерживаются: .xls, .xlsx, .csv · Данные обрабатываются локально в браузере

Сохранённые отчёты

Шаг 1 — Оборачиваемость

CSV/XLS с колонками: Товар, Зона
Берём только список товаров и зону.

Шаг 2 — Остатки и цены

XLS/XLSX с колонками: Наименование, Остаток, Закупочная цена, Розничная цена.

Шаг 3 — Беспрайс Каспи

XLSX от Каспи с колонками: Город, Наименование, Цена, Лучшая цена.

Вставьте API-токен для подключения

Kaspi Seller API

Вставьте API-токен продавца — данные заказов загрузятся автоматически

Где взять: kaspi.kz/mcНастройки → Токен API → Сформировать (только руководитель компании)

Загрузите ArchiveOrders.xlsx

Перетащите файл или нажмите · обрабатывается локально в браузере

Выбрать файл
Обрабатываем данные…
_dirFilterAndRenderProd(); }; window.dirAddProdRow = function(){ const newRow = { id:null, name:'', article:'', category:'', active:true, turn_days:null, turn_times:null, turn_zone:'', stock_beg:null, stock_end:null, sales_sum:null, sales_qty:null, kaspi_orders:null, kaspi_revenue:null, kaspi_cancelled:null, price_our:null, price_best:null, price_diff:null, price_status:'', stock_available:null, notes:'' }; prodEditRows.push(newRow); prodSearchQ = ''; prodFilterSrc = ''; document.getElementById('dirProdSearch').value = ''; document.getElementById('dirProdFilterSrc').value = ''; _dirFilterAndRenderProd(); const wrap = document.querySelector('#dir-tab-products [style*="overflow-x"]'); if(wrap) wrap.scrollTop = wrap.scrollHeight; }; window.deleteProdRow = function(idx){ if(!confirm('Удалить товар из справочника?')) return; prodEditRows[idx]._deleted = true; _dirFilterAndRenderProd(); dirUpdateProdBadge(); }; // ═══════════════════════════════════════════════════ // СОХРАНЕНИЕ ТОВАРОВ // ═══════════════════════════════════════════════════ window.dirSaveProd = async function(){ const btn = document.getElementById('dirProdSaveBtn'); if(btn){ btn.disabled=true; btn.innerHTML=' Сохранение…'; } // Синхронизируем prodEditRows → GProductList window.GProductList = prodEditRows.filter(r => !r._deleted); gApplyProductList(window.GProductList); // обновляем Dir и localStorage let saved=0, deleted=0, errors=0; for(const r of prodEditRows){ try{ if(r._deleted){ if(r.id){ await fetch(`tables/${PROD_TABLE}/${r.id}`,{method:'DELETE'}); deleted++; } continue; } const tsToISO = v => v ? new Date(v).toISOString() : null; const payload = { name:r.name||'', article:r.article||'', category:r.category||'', barcode:r.barcode||'', unit:r.unit||'', active: r.active!==false, turn_days: r.turn_days??null, turn_times: r.turn_times??null, turn_zone: r.turn_zone||'', stock_beg: r.stock_beg??null, stock_end: r.stock_end??null, sales_sum: r.sales_sum??null, sales_qty: r.sales_qty??null, kaspi_orders: r.kaspi_orders??null, kaspi_revenue: r.kaspi_revenue??null, kaspi_cancelled: r.kaspi_cancelled??null, price_our: r.price_our??null, price_best: r.price_best??null, price_diff: r.price_diff??null, price_status: r.price_status||'', stock_available: r.stock_available??null, last_turn_update: tsToISO(r.last_turn_update), last_kaspi_update: tsToISO(r.last_kaspi_update), last_price_update: tsToISO(r.last_price_update), notes: r.notes||'', }; if(r.id){ const resp = await fetch(`tables/${PROD_TABLE}/${r.id}`, {method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); if(resp.ok) saved++; else errors++; } else { const resp = await fetch(`tables/${PROD_TABLE}`, {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); if(resp.ok){ const d=await resp.json(); r.id=d.id; saved++; } else errors++; } }catch(e){ console.error(e); errors++; } } prodEditRows = prodEditRows.filter(r=>!r._deleted); window.GProductList = prodEditRows.map(r=>({...r})); localStorage.setItem(PROD_LS_KEY, JSON.stringify(window.GProductList)); gApplyProductList(window.GProductList); if(btn){ btn.disabled=false; btn.innerHTML=' Сохранить справочник'; } dirShowProdStatus(`Сохранено: ${saved}${deleted?' | Удалено: '+deleted:''}${errors?' | Ошибок: '+errors:''}`, errors?'error':'success'); if(typeof showToast==='function') showToast('Справочник товаров сохранён ✓'); }; function dirShowProdStatus(msg, type='success'){ const el = document.getElementById('dirProdStatus'); if(!el) return; const colors = { success:'background:#dcfce7;color:#15803d;border:1px solid #bbf7d0', error: 'background:#fee2e2;color:#b91c1c;border:1px solid #fecaca', warn: 'background:#fef9c3;color:#92400e;border:1px solid #fde68a', }; el.style.cssText = `display:block;margin-bottom:10px;padding:8px 12px;border-radius:7px;font-size:.82rem;${colors[type]||colors.success}`; el.textContent = msg; setTimeout(()=>{ el.style.display='none'; }, 5000); } // ═══════════════════════════════════════════════════ // ВКЛАДКА «НЕ НАЙДЕНО» // ═══════════════════════════════════════════════════ let nfSearchQ = ''; let nfSourceQ = ''; function dirRenderNotFound(){ const allItems = _getNfFiltered(); const tbody = document.getElementById('dirNfTbody'); const countEl = document.getElementById('dirNfCount'); if(!tbody) return; if(!allItems.length){ tbody.innerHTML = ` Все позиции найдены в справочнике! `; if(countEl) countEl.textContent = ''; dirUpdateNotFoundBadge(); return; } const srcLabel = { turn:'⚙️ Оборачив.', kaspi:'🛒 Kaspi', price:'🏷️ Цены' }; tbody.innerHTML = allItems.map((r, i) => ` ${escP(r.name)} ${srcLabel[r.source]||r.source} ${r.label||'—'} `).join(''); if(countEl) countEl.textContent = `Позиций не найдено: ${allItems.length}`; dirUpdateNotFoundBadge(); } function _getNfFiltered(){ let all = []; const src = nfSourceQ; if(!src || src==='turn') all = all.concat(window.GProductNotFound.turn || []); if(!src || src==='kaspi') all = all.concat(window.GProductNotFound.kaspi || []); if(!src || src==='price') all = all.concat(window.GProductNotFound.price || []); // Дедупликация по name const seen = new Set(); all = all.filter(r => { if(seen.has(r.name)) return false; seen.add(r.name); return true; }); if(nfSearchQ) all = all.filter(r => r.name.toLowerCase().includes(nfSearchQ.toLowerCase())); return all; } window.dirFilterNotFound = function(){ nfSearchQ = document.getElementById('dirNfSearch').value; nfSourceQ = document.getElementById('dirNfSource').value; dirRenderNotFound(); }; // Добавить одну позицию в справочник window.dirAddNotFoundItem = function(name){ const key = name.trim().toLowerCase(); if(window.GProductDir[key]){ dirRenderNotFound(); return; // уже есть } const newRow = { id:null, name: name.trim(), article:'', category:'', active:true, turn_days:null, turn_times:null, turn_zone:'', stock_beg:null, stock_end:null, sales_sum:null, sales_qty:null, kaspi_orders:null, kaspi_revenue:null, kaspi_cancelled:null, price_our:null, price_best:null, price_diff:null, price_status:'', stock_available:null, notes:'' }; // Подтягиваем данные из источников const t = (window.GProductNotFound.turn || []).find(r => r.name === name); const k = (window.GProductNotFound.kaspi || []).find(r => r.name === name); const p = (window.GProductNotFound.price || []).find(r => r.name === name); if(t){ newRow.turn_days = t.value; } if(k){ newRow.kaspi_orders = k.qty; newRow.kaspi_revenue = k.revenue; } if(p){ newRow.price_our = p.price; newRow.price_best = p.bestPrice; } window.GProductList.push(newRow); gApplyProductList(window.GProductList); dirRenderProdTable(); dirRenderNotFound(); dirShowProdStatus(`«${name}» добавлен в справочник. Не забудьте сохранить!`, 'success'); // Переключить на вкладку Товары dirSwitchTabByName('products'); }; // Добавить все позиции «не найдено» в справочник window.dirAddAllNotFound = function(){ const items = _getNfFiltered(); if(!items.length) return; if(!confirm(`Добавить все ${items.length} позиций в справочник товаров?`)) return; items.forEach(r => { const key = r.name.trim().toLowerCase(); if(!window.GProductDir[key]) window.dirAddNotFoundItem(r.name); }); }; // ═══════════════════════════════════════════════════ // ВСПОМОГАТЕЛЬНЫЕ // ═══════════════════════════════════════════════════ window.dirSwitchTabByName = function(tabName){ const btn = document.querySelector(`.dir-tab-btn[data-tab="${tabName}"]`); if(btn) dirSwitchTab(btn); }; // Перехватить переключение вкладки для обновления «Не найдено» и «Товары» const _origDirSwitchTab = window.dirSwitchTab; window.dirSwitchTab = function(btn){ _origDirSwitchTab(btn); const tab = btn.dataset.tab; if(tab === 'products'){ dirRenderProdTable(); } if(tab === 'notfound'){ gRecalcNotFound(); dirRenderNotFound(); } }; // Расширяем openDirectoryModal — загружаем товары const _origOpenDir = window.openDirectoryModal; window.openDirectoryModal = async function(){ await _origOpenDir(); await gLoadProductDir(); dirRenderProdTable(); dirRenderNotFound(); }; // При обновлении Kaspi-заказов — обновлять NotFound window.gOnKaspiDataLoaded = function(){ gSyncFromKaspi(); }; // При обновлении оборачиваемости — обновлять NotFound window.gOnTurnoverLoaded = function(){ gSyncFromTurnover(); }; // При обновлении лучшей цены — обновлять NotFound window.gOnBestPriceLoaded = function(){ gSyncFromBestPrice(); }; document.addEventListener('DOMContentLoaded', function(){ // Диагностика загрузки XLSX console.log('🔍 Проверяю доступность XLSX библиотеки...'); console.log('typeof XLSX:', typeof XLSX); if(typeof XLSX !== 'undefined'){ console.log('✅ XLSX успешно загружена! Версия:', XLSX.version || 'неизвестно'); } else { console.error('❌ XLSX НЕ загружена! Проверьте файл js/xlsx.full.min.js'); console.error('Путь к файлу должен быть: js/xlsx.full.min.js'); } // Загружаем справочник товаров gLoadProductDir(); }); })();