213 lines
40 KiB
HTML
213 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Сверка выручки водителей</title>
|
||
<style>
|
||
:root{--primary:#6366f1;--primary-dark:#4f46e5;--primary-light:#a5b4fc;--bg-start:#4f46e5;--bg-end:#7c3aed;--surface:#fff;--surface-alt:#f8fafc;--text:#1e293b;--text-secondary:#64748b;--text-muted:#94a3b8;--border:#e2e8f0;--success:#10b981;--success-bg:#d1fae5;--success-text:#065f46;--warning:#f59e0b;--warning-bg:#fef3c7;--warning-text:#92400e;--danger:#ef4444;--danger-bg:#fee2e2;--danger-text:#991b1b;--radius:16px;--radius-sm:8px;--shadow-md:0 4px 20px rgba(0,0,0,.1);--shadow-lg:0 10px 40px rgba(0,0,0,.12);--transition:.25s cubic-bezier(.4,0,.2,1)}
|
||
[data-theme=dark]{--bg-start:#111827;--bg-end:#1e293b;--surface:#1f2937;--surface-alt:#111827;--text:#f9fafb;--text-secondary:#d1d5db;--text-muted:#9ca3af;--border:#374151;--success-bg:#064e3b;--success-text:#6ee7b7;--warning-bg:#78350f;--warning-text:#fcd34d;--danger-bg:#7f1d1d;--danger-text:#fca5a5}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;background:linear-gradient(160deg,var(--bg-start) 0%,var(--bg-end) 50%,#a855f7 100%);min-height:100vh;color:var(--text);line-height:1.6}
|
||
.container{max-width:1100px;margin:0 auto;padding:2rem 1.5rem 4rem;position:relative;z-index:1}
|
||
.hero{text-align:center;padding:2rem 1rem 3rem;color:#fff}
|
||
.hero-badge{display:inline-flex;align-items:center;gap:.5rem;background:rgba(255,255,255,.15);backdrop-filter:blur(10px);padding:.4rem 1rem;border-radius:20px;font-size:.85rem;font-weight:500;margin-bottom:1.25rem;border:1px solid rgba(255,255,255,.2)}
|
||
.hero h1{font-size:clamp(1.8rem,4vw,2.75rem);font-weight:800;letter-spacing:-.02em;margin-bottom:.75rem;text-shadow:0 2px 10px rgba(0,0,0,.15)}
|
||
.hero-subtitle{font-size:1.05rem;opacity:.9;max-width:650px;margin:0 auto;line-height:1.7}
|
||
.card{background:var(--surface);border-radius:var(--radius);box-shadow:var(--shadow-lg);margin-bottom:1.5rem;overflow:hidden;transition:box-shadow var(--transition)}
|
||
.card:hover{box-shadow:0 15px 50px rgba(0,0,0,.15)}
|
||
.card-head{display:flex;align-items:center;gap:1rem;padding:1.25rem 2rem;border-bottom:1px solid var(--border);background:var(--surface-alt)}
|
||
.step-badge{width:36px;height:36px;border-radius:10px;background:linear-gradient(135deg,var(--primary),var(--bg-end));color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:1rem;flex-shrink:0}
|
||
.card-head h2{font-size:1.25rem;font-weight:700}
|
||
.card-body{padding:2rem}
|
||
.dropzone{border:2px dashed var(--border);border-radius:12px;padding:3rem 2rem;text-align:center;cursor:pointer;transition:all var(--transition);background:var(--surface-alt)}
|
||
.dropzone:hover,.dropzone.dragover{border-color:var(--primary);background:#eef2ff;transform:scale(1.01)}
|
||
[data-theme=dark] .dropzone:hover,[data-theme=dark] .dropzone.dragover{background:#1e1b4b}
|
||
.dropzone.dragover{box-shadow:0 0 0 4px rgba(99,102,241,.15)}
|
||
.dropzone-icon{width:72px;height:72px;margin:0 auto 1.25rem;background:linear-gradient(135deg,#eef2ff,#e0e7ff);border-radius:50%;display:flex;align-items:center;justify-content:center;transition:transform var(--transition)}
|
||
[data-theme=dark] .dropzone-icon{background:linear-gradient(135deg,#312e81,#4338ca)}
|
||
.dropzone:hover .dropzone-icon{transform:scale(1.1)}
|
||
.dropzone-title{font-size:1.1rem;font-weight:600;margin-bottom:.5rem}
|
||
.dropzone-hint{font-size:.9rem;color:var(--text-secondary);max-width:450px;margin:0 auto}
|
||
.file-list{margin-top:1.5rem}
|
||
.file-item{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;background:var(--surface-alt);border-radius:var(--radius-sm);margin-bottom:.5rem;border:1px solid var(--border);animation:slideIn .3s ease}
|
||
@keyframes slideIn{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}
|
||
.file-icon{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:1.1rem;flex-shrink:0}
|
||
.file-icon.vedomost{background:#dbeafe}
|
||
[data-theme=dark] .file-icon.vedomost{background:#1e3a5f}
|
||
.file-icon.transaction{background:#fef3c7}
|
||
[data-theme=dark] .file-icon.transaction{background:#5c3d0e}
|
||
.file-info{flex:1;min-width:0}
|
||
.file-name{font-size:.9rem;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||
.file-type{font-size:.8rem;color:var(--text-muted)}
|
||
.file-remove{width:28px;height:28px;border:none;background:transparent;border-radius:6px;cursor:pointer;color:var(--text-muted);font-size:1.1rem;display:flex;align-items:center;justify-content:center;transition:all var(--transition)}
|
||
.file-remove:hover{background:var(--danger-bg);color:var(--danger)}
|
||
.actions{display:flex;gap:.75rem;margin-top:1.5rem;flex-wrap:wrap}
|
||
.btn{padding:.75rem 1.75rem;border:none;border-radius:10px;font-size:.95rem;font-weight:600;cursor:pointer;transition:all var(--transition);display:inline-flex;align-items:center;gap:.5rem}
|
||
.btn-primary{background:linear-gradient(135deg,var(--primary),var(--bg-end));color:#fff;box-shadow:0 4px 15px rgba(99,102,241,.35)}
|
||
.btn-primary:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 6px 25px rgba(99,102,241,.45)}
|
||
.btn-primary:disabled{opacity:.4;cursor:not-allowed}
|
||
.btn-outline{background:transparent;color:var(--text-secondary);border:1.5px solid var(--border)}
|
||
.btn-outline:hover:not(:disabled){background:var(--surface-alt);border-color:var(--text-muted)}
|
||
.btn-success{background:linear-gradient(135deg,#10b981,#059669);color:#fff;box-shadow:0 4px 15px rgba(16,185,129,.3)}
|
||
.btn-success:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(16,185,129,.4)}
|
||
.btn-export{background:linear-gradient(135deg,#f59e0b,#d97706);color:#fff}
|
||
.btn-export:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(245,158,11,.4)}
|
||
.btn-theme{background:linear-gradient(135deg,#94a3b8,#64748b);color:#fff}
|
||
.btn-theme:hover{transform:translateY(-2px)}
|
||
.progress-bar{width:100%;height:4px;background:var(--border);border-radius:2px;overflow:hidden;margin-top:1rem;display:none}
|
||
.progress-bar.active{display:block}
|
||
.progress-fill{height:100%;background:linear-gradient(90deg,var(--primary),var(--bg-end));border-radius:2px;transition:width .3s ease;width:0%}
|
||
.table-wrap{overflow-x:auto;border-radius:var(--radius-sm);border:1px solid var(--border)}
|
||
table{width:100%;border-collapse:collapse;font-size:.875rem}
|
||
thead{background:var(--surface-alt)}th,td{padding:.75rem 1rem;border-bottom:1px solid var(--border)}
|
||
th{font-weight:600;color:var(--text-secondary);white-space:nowrap;cursor:pointer}
|
||
tbody tr:hover{background:#f8fafc}[data-theme=dark] tbody tr:hover{background:var(--surface-alt)}
|
||
tbody tr:last-child td{border-bottom:none}
|
||
.badge{display:inline-flex;align-items:center;gap:.3rem;padding:.2rem .65rem;border-radius:6px;font-size:.8rem;font-weight:600}
|
||
.badge-ok{background:var(--success-bg);color:var(--success-text)}
|
||
.badge-warn{background:var(--warning-bg);color:var(--warning-text)}
|
||
.badge-err{background:var(--danger-bg);color:var(--danger-text)}
|
||
.tabs-wrapper{border-bottom:2px solid var(--border);margin-bottom:1.5rem;display:flex;gap:.25rem;overflow-x:auto;scrollbar-width:none}
|
||
.tabs-wrapper::-webkit-scrollbar{display:none}
|
||
.tab-btn{padding:.75rem 1.25rem;background:none;border:none;border-bottom:2.5px solid transparent;cursor:pointer;font-size:.9rem;font-weight:600;color:var(--text-muted);white-space:nowrap;transition:all var(--transition);margin-bottom:-2px}
|
||
.tab-btn:hover{color:var(--primary)}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}
|
||
.tab-btn .count{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;background:var(--primary-light);color:var(--primary-dark);border-radius:10px;font-size:.75rem;padding:0 6px;margin-left:.35rem}
|
||
.tab-panel{display:none;animation:fadeUp .3s ease}
|
||
.tab-panel.active{display:block}
|
||
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
||
.empty-state{text-align:center;padding:3rem 1rem}
|
||
.empty-state-icon{font-size:4rem;margin-bottom:1rem;opacity:.3}
|
||
.empty-state p{color:var(--text-muted)}
|
||
.info-card{background:var(--surface);border-radius:var(--radius);box-shadow:var(--shadow-md);overflow:hidden}
|
||
.info-head{padding:1.25rem 2rem;border-bottom:1px solid var(--border);background:var(--surface-alt);display:flex;align-items:center;gap:.75rem}
|
||
.info-head h2{font-size:1.25rem;font-weight:700}
|
||
.info-grid{padding:1.5rem 2rem;display:grid;gap:1.25rem}
|
||
.info-block{display:flex;gap:1rem;padding:1.25rem;background:var(--surface-alt);border-radius:12px;border:1px solid var(--border);transition:border-color var(--transition)}
|
||
.info-block:hover{border-color:var(--primary-light)}
|
||
.info-block-icon{width:42px;height:42px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:1.25rem;flex-shrink:0}
|
||
.info-block-icon.blue{background:#dbeafe}[data-theme=dark] .info-block-icon.blue{background:#1e3a5f}
|
||
.info-block-icon.green{background:#d1fae5}[data-theme=dark] .info-block-icon.green{background:#064e3b}
|
||
.info-block-icon.amber{background:#fef3c7}[data-theme=dark] .info-block-icon.amber{background:#78350f}
|
||
.info-block-icon.purple{background:#ede9fe}[data-theme=dark] .info-block-icon.purple{background:#2e1065}
|
||
.info-block-icon.rose{background:#ffe4e6}[data-theme=dark] .info-block-icon.rose{background:#4c0519}
|
||
.info-block-title{font-weight:700;font-size:.95rem;margin-bottom:.35rem}
|
||
.info-block-text{font-size:.9rem;color:var(--text-secondary);line-height:1.65}
|
||
.footer{text-align:center;padding:2rem 1rem;color:rgba(255,255,255,.6);font-size:.85rem}
|
||
@media(max-width:768px){.container{padding:1rem .75rem 3rem}.hero{padding:1.5rem .5rem 2rem}.card-head{padding:1rem 1.25rem}.card-body{padding:1.25rem}.dropzone{padding:2rem 1rem}.actions{flex-direction:column}.btn{width:100%;justify-content:center}.info-grid{padding:1rem}.info-block{flex-direction:column}th,td{padding:.5rem .75rem}}
|
||
code{font-family:'SF Mono','Fira Code','Consolas',monospace;font-size:.85em;background:var(--surface-alt);padding:.1em .3em;border-radius:4px;border:1px solid var(--border)}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<header class="hero">
|
||
<div class="hero-badge">🔒 Работает офлайн — файлы не отправляются на сервер</div>
|
||
<h1>Сверка выручки водителей</h1>
|
||
<p class="hero-subtitle">Загрузите ведомости приёма наличных и выгрузку транзакций — получите сводную таблицу, расхождения и готовый Excel-отчёт</p>
|
||
</header>
|
||
<section class="card">
|
||
<div class="card-head"><div class="step-badge">1</div><h2>Файлы</h2></div>
|
||
<div class="card-body">
|
||
<div class="dropzone" id="dropzone">
|
||
<div class="dropzone-icon"><svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#6366f1" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg></div>
|
||
<div class="dropzone-title">Перетащите файлы сюда или нажмите для выбора</div>
|
||
<div class="dropzone-hint">Любое число ведомостей (<strong>.xlsx</strong>) + выгрузка <strong>TRANSACTIONS_EXT</strong>. Тип определяется автоматически.</div>
|
||
</div>
|
||
<input type="file" id="fileInput" multiple accept=".xlsx,.xls" style="display:none">
|
||
<div class="file-list" id="fileList"></div>
|
||
<div class="actions">
|
||
<button class="btn btn-primary" id="btnReconcile" disabled>✓ Провести сверку</button>
|
||
<button class="btn btn-outline" id="btnClear" disabled>✕ Очистить</button>
|
||
<button class="btn btn-export" id="btnExportCsv" disabled>⬇ Экспорт CSV</button>
|
||
<button class="btn btn-theme" id="btnToggleTheme">🌙 Тёмная тема</button>
|
||
</div>
|
||
<div class="progress-bar" id="progressBar"><div class="progress-fill" id="progressFill"></div></div>
|
||
</div>
|
||
</section>
|
||
<section class="card" id="resultCard">
|
||
<div class="card-head"><div class="step-badge">2</div><h2>Результат</h2></div>
|
||
<div class="card-body">
|
||
<div id="emptyState" class="empty-state"><div class="empty-state-icon">📋</div><p>Загрузите файлы и нажмите «Провести сверку»</p></div>
|
||
<div id="resultActions" style="display:none;margin-bottom:1.5rem"><button class="btn btn-success" id="btnDownload">📥 Скачать Excel-отчёт</button></div>
|
||
<div id="tabsContainer" style="display:none">
|
||
<div class="tabs-wrapper">
|
||
<button class="tab-btn active" data-tab="discrepancies">Расхождения <span class="count" id="countDisc">0</span></button>
|
||
<button class="tab-btn" data-tab="all">Все водители <span class="count" id="countAll">0</span></button>
|
||
<button class="tab-btn" data-tab="cashiers">По кассирам</button>
|
||
<button class="tab-btn" data-tab="routes">По маршрутам</button>
|
||
<button class="tab-btn" data-tab="conclusions">Выводы</button>
|
||
</div>
|
||
<div class="tab-panel active" id="panel-discrepancies"><div class="table-wrap"><table><thead><tr><th data-key="driver">Водитель</th><th data-key="car">Гос. номер</th><th data-key="given">Сдано (₽)</th><th data-key="collected">Собрано (₽)</th><th data-key="diff">Расхождение</th><th>Статус</th></tr></thead><tbody id="tableDiscrepancies"></tbody></table></div></div>
|
||
<div class="tab-panel" id="panel-all"><div class="table-wrap"><table><thead><tr><th data-key="date">Дата</th><th data-key="driver">Водитель</th><th data-key="route">Маршрут</th><th data-key="given">Сдано (₽)</th><th data-key="collected">Собрано (₽)</th><th data-key="transactions">Σ Транзакций</th></tr></thead><tbody id="tableAll"></tbody></table></div></div>
|
||
<div class="tab-panel" id="panel-cashiers"><div class="table-wrap"><table><thead><tr><th data-key="cashier">Кассир</th><th>Ведомостей</th><th data-key="given">Сдано (₽)</th><th data-key="collected">Собрано (₽)</th><th data-key="diff">Расхождение</th></tr></thead><tbody id="tableCashiers"></tbody></table></div></div>
|
||
<div class="tab-panel" id="panel-routes"><div class="table-wrap"><table><thead><tr><th data-key="route">Маршрут</th><th>Тип</th><th>Водителей</th><th data-key="sum">Сумма (₽)</th></tr></thead><tbody id="tableRoutes"></tbody></table></div></div>
|
||
<div class="tab-panel" id="panel-conclusions"><div id="conclusionsContent"></div></div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section class="info-card">
|
||
<div class="info-head"><span style="font-size:1.4rem">💡</span><h2>Как это работает</h2></div>
|
||
<div class="info-grid">
|
||
<div class="info-block"><div class="info-block-icon blue">📄</div><div><div class="info-block-title">Определение типа файла</div><div class="info-block-text">Ведомость по заголовку «ВЕДОМОСТЬ ПРИЕМА НАЛИЧНЫХ СРЕДСТВ». Выгрузка — CONDUCTOR, DATE, TARIF_SUM.</div></div></div>
|
||
<div class="info-block"><div class="info-block-icon green">📅</div><div><div class="info-block-title">Период</div><div class="info-block-text">Только даты из ведомостей.</div></div></div>
|
||
<div class="info-block"><div class="info-block-icon amber">🔍</div><div><div class="info-block-title">Сопоставление</div><div class="info-block-text">ФИО + гос. номер. Нормализация, нечёткое сравнение ≥ 88%.</div></div></div>
|
||
<div class="info-block"><div class="info-block-icon purple">💰</div><div><div class="info-block-title">Суммы</div><div class="info-block-text">«Сдано» — ведомость, «Собрано» — TARIF_SUM÷100.</div></div></div>
|
||
<div class="info-block"><div class="info-block-icon rose">🔒</div><div><div class="info-block-title">Приватность</div><div class="info-block-text">Файлы никуда не отправляются — всё в браузере.</div></div></div>
|
||
</div>
|
||
</section>
|
||
<div class="footer">Сверка выручки водителей — всё локально в вашем браузере</div>
|
||
</div>
|
||
<script>
|
||
/* ================================================================
|
||
Встроенный XLSX (минимальный, только чтение .xlsx через zip)
|
||
================================================================ */
|
||
(function(){if(typeof XLSX!=='undefined')return;var g=typeof window!=='undefined'?window:{};g.XLSX={};var W=g.XLSX;W.utils={};W.read=function(data){var zip;try{zip=new JSZip(data)}catch(e){throw new Error('Не удалось прочитать .xlsx')}var result=[];var ssFile=zip.file('xl/sharedStrings.xml');var ss=[];if(ssFile){var doc=W._parseXml(ssFile.asText());var sis=doc.getElementsByTagName('si');for(var i=0;i<sis.length;i++){var t=sis[i].getElementsByTagName('t');ss.push(t.length>0?t[0].textContent||'':'')}}var wbXml=zip.file('xl/workbook.xml');var sheetCount=1;if(wbXml){var wbDoc=W._parseXml(wbXml.asText());var wbSheets=wbDoc.getElementsByTagName('sheet');sheetCount=wbSheets.length}for(var si=0;si<sheetCount;si++){var sXml=zip.file('xl/worksheets/sheet'+(si+1)+'.xml');if(!sXml&&si===0)sXml=zip.file('xl/worksheets/sheet1.xml');var data2d=[];if(sXml){var sDoc=W._parseXml(sXml.asText());var rows=sDoc.getElementsByTagName('row');for(var ri=0;ri<rows.length;ri++){var row=rows[ri];var cells=row.getElementsByTagName('c');var rowData=[];var maxCol=0;for(var ci=0;ci<cells.length;ci++){var cell=cells[ci];var r=cell.getAttribute('r');var colNum=W._colToNum(r);var v=cell.getElementsByTagName('v');var t=cell.getAttribute('t');var val='';if(v.length>0)val=v[0].textContent||'';if(t==='s'){var idx=parseInt(val);val=ss[idx]||val}rowData[colNum]=val}for(var j=0;j<rowData.length;j++){if(rowData[j]===undefined)rowData[j]=''}rowData.length=rowData.length||0;data2d.push(rowData)}}result.push({name:'Sheet'+(si+1),data:data2d})}return result};W._parseXml=function(text){return new DOMParser().parseFromString(text,'text/xml')};W._colToNum=function(ref){var match=ref.match(/^([A-Z]+)(\d+)$/);if(!match)return 0;var col=0;for(var i=0;i<match[1].length;i++){col=col*26+(match[1].charCodeAt(i)-64)}return col-1};W.utils.sheet_to_json=function(sheet,opts){opts=opts||{};var data=sheet.data||sheet;if(!data||!data.length)return[];var header=data[0]||[];var result=[];for(var i=1;i<data.length;i++){var row=data[i];if(!row)continue;var obj={};for(var j=0;j<header.length;j++){var h=header[j];obj[h]=(row[j]!==undefined?row[j]:'')}if(opts.defval!==undefined){for(var j=0;j<header.length;j++){if(obj[header[j]]==='')obj[header[j]]=opts.defval}}result.push(obj)}return result};W.utils.book_new=function(){return{SheetNames:[],Sheets:{}}};W.utils.json_to_sheet=function(data){var ws={};if(!data||!data.length)return ws;var keys=Object.keys(data[0]);for(var i=0;i<data.length;i++){for(var j=0;j<keys.length;j++){var ref=String.fromCharCode(65+j)+(i+1);ws[ref]={v:data[i][keys[j]]||''}}}return ws};W.utils.book_append_sheet=function(wb,ws,name){wb.SheetNames.push(name);wb.Sheets[name]=ws};W.write=function(wb){var xml='<?xml version="1.0" encoding="UTF-8"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheets>';for(var i=0;i<wb.SheetNames.length;i++){xml+='<sheet name="'+wb.SheetNames[i]+'" sheetId="'+(i+1)+'"/>'}xml+='</sheets></workbook>';var sheetXml='<?xml version="1.0" encoding="UTF-8"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>';var ws=wb.Sheets[wb.SheetNames[0]];if(ws){for(var key in ws){if(key[0]==='!')continue;var match=key.match(/^([A-Z]+)(\d+)$/);if(!match)continue;var val=String(ws[key].v||'');sheetXml+='<row r="'+match[2]+'"><c r="'+key+'" t="inlineStr"><is><t>'+val.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</t></is></c></row>'}}sheetXml+='</sheetData></worksheet>';return new TextEncoder().encode(xml+'\n'+sheetXml)}})();
|
||
/* ================================================================
|
||
Встроенный JSZip (минимальный, распаковка zip)
|
||
================================================================ */
|
||
(function(){if(typeof JSZip!=='undefined')return;var g=typeof window!=='undefined'?window:{};var JSZip=function(data){this.files={};this._load(data)};JSZip.prototype={_load:function(data){var arr=data instanceof ArrayBuffer?new Uint8Array(data):data;var i=0;function rs(n){var s='';for(var k=0;k<n;k++)s+=String.fromCharCode(arr[i+k]);i+=n;return s}function r16(){var v=arr[i]|(arr[i+1]<<8);i+=2;return v}function r32(){var v=arr[i]|(arr[i+1]<<8)|(arr[i+2]<<16)|(arr[i+3]<<24);i+=4;return v}while(i<arr.length-30){var sig=r32();if(sig===0x04034b50){r16();r16();var method=r16();r16();r16();r32();var compSize=r32();var uncompSize=r32();var fnameLen=r16();var extraLen=r16();var fname=rs(fnameLen);i+=extraLen;var content;if(method===0){content=arr.subarray(i,i+uncompSize)}else{content=arr.subarray(i,i+compSize)}i+=compSize;var self=this;(function(n,c){self.files[n]={asText:function(){var dec='';for(var k=0;k<c.length;k++)dec+=String.fromCharCode(c[k]);return decodeURIComponent(escape(dec))},data:c}})(fname,content)}else{i-=4;break}}},file:function(name){return this.files[name]||null}};g.JSZip=JSZip})();
|
||
/* ================================================================
|
||
Основной код приложения
|
||
================================================================ */
|
||
function readExcel(file){return file.arrayBuffer().then(function(arr){return XLSX.read(arr)})}
|
||
function validateFile(file){var ext=file.name.slice(file.name.lastIndexOf('.')).toLowerCase();if(!['.xlsx','.xls'].includes(ext)){alert('❌ Недопустимый тип: '+ext);return false}return true}
|
||
function arrayToCsv(data,headers){return '\uFEFF'+headers.join(',')+'\n'+data.map(function(r){return headers.map(function(h){return '"'+String(r[h]||'').replace(/"/g,'""')+'"'}).join(',')}).join('\n')}
|
||
function downloadFile(content,filename){var blob=new Blob([content],{type:'application/octet-stream'});var url=URL.createObjectURL(blob);var a=document.createElement('a');a.href=url;a.download=filename;document.body.appendChild(a);a.click();document.body.removeChild(a);setTimeout(function(){URL.revokeObjectURL(url)},1000)}
|
||
function formatMoney(n){if(n===null||n===undefined||isNaN(n))return'—';return n.toLocaleString('ru-RU',{minimumFractionDigits:0,maximumFractionDigits:0})}
|
||
function detectFileType(name,data){if(hasHeader(data,'ВЕДОМОСТЬ ПРИЕМА НАЛИЧНЫХ СРЕДСТВ'))return'vedomost';var low=name.toLowerCase();if(low.includes('transaction')||low.includes('transactions')||low.includes('выгрузка'))return'transaction';if(hasTxCols(data))return'transaction';if(hasHeader(data,'ВЕДОМОСТЬ'))return'vedomost';return null}
|
||
function hasHeader(d,h){if(!d||!d.length)return false;for(var i=0;i<Math.min(d.length,20);i++){var r=d[i];if(!r)continue;if(r.filter(function(c){return c!=null}).map(String).join(' ').toUpperCase().includes(h.toUpperCase()))return true}return false}
|
||
function hasTxCols(d){if(!d||!d.length)return false;for(var i=0;i<Math.min(d.length,5);i++){var r=d[i];if(!r)continue;var c=r.map(function(x){return String(x).toUpperCase().trim()});if(c.some(function(x){return x.includes('CONDUCTOR')})&&c.some(function(x){return x.includes('DATE')})&&c.some(function(x){return x.includes('TARIF_SUM')||x.includes('TARIF')}))return true}return false}
|
||
var L2C={A:'А',B:'В',C:'С',E:'Е',H:'Н',K:'К',M:'М',O:'О',P:'Р',T:'Т',X:'Х',Y:'У'};
|
||
function normPlate(p){if(!p)return'';return String(p).toUpperCase().trim().split('').map(function(c){return L2C[c]||c}).join('').replace(/[^А-ЯЁA-Z0-9]/gi,'')}
|
||
function normName(n){if(!n)return'';return String(n).toUpperCase().trim().replace(/Ё/g,'Е').replace(/\s+/g,' ')}
|
||
function sim(a,b){if(!a||!b)return 0;var s1=normName(a),s2=normName(b);if(s1===s2)return 1;if(!s1.length||!s2.length)return 0;var d=lev(s1,s2);return 1-d/Math.max(s1.length,s2.length)}
|
||
function lev(a,b){var m=a.length,n=b.length;var dp=Array(m+1);for(var i=0;i<=m;i++){dp[i]=Array(n+1).fill(0);dp[i][0]=i}for(var j=0;j<=n;j++)dp[0][j]=j;for(var i=1;i<=m;i++){for(var j=1;j<=n;j++){dp[i][j]=a[i-1]===b[j-1]?dp[i-1][j-1]:1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])}}return dp[m][n]}
|
||
function parseVed(d){if(!d||!d.length)return null;var r={docNumber:'',date:'',cashier:'',sections:{city:[],suburb:[]},drivers:[]},cs=null,hf=false,ts=false,te=false;for(var i=0;i<d.length;i++){var row=d[i];if(!row||!row.length)continue;var c=row.map(function(x){return String(x).trim()});if(!hf){if(c.join(' ').toUpperCase().includes('ВЕДОМОСТЬ ПРИЕМА НАЛИЧНЫХ СРЕДСТВ')){hf=true}continue}if(!r.docNumber){var ni=c.findIndex(function(x){return x.toUpperCase().includes('ДОКУМЕНТ')});if(ni>=0){r.docNumber=c[ni+1]||'';var di=c.findIndex(function(x){return/^\d{2}[./-]\d{2}[./-]\d{4}$/.test(x)});if(di>=0)r.date=c[di]}}if(!r.date){var dm=c.find(function(x){return/^\d{2}[./-]\d{2}[./-]\d{4}$/.test(x)});if(dm)r.date=dm}if(!r.cashier){var ci=c.findIndex(function(x){return x.toUpperCase().includes('КАССИР')});if(ci>=0&&ci+1<c.length&&c[ci+1].length>2)r.cashier=c[ci+1]}var jr=c.join(' ').toUpperCase();if(jr.includes('ГОРОД')||jr.includes('ГОР.')||(jr.includes('ГОР')&&!jr.includes('ПРИГОР'))){cs='city';ts=false;continue}if(jr.includes('ПРИГОРОД')||jr.includes('ПРИГ.')){cs='suburb';ts=false;continue}if(c.every(function(x){return x===''})){if(ts)te=true;continue}if(c.join(' ').toUpperCase().includes('ФИО')||c.join(' ').toUpperCase().includes('ВОДИТЕЛЬ')||c.join(' ').toUpperCase().includes('ФАМИЛИЯ')){ts=true;te=false;continue}if(te&&!ts)continue;if(ts&&!te&&cs){var dr=parseDR(c,cs);if(dr){r.drivers.push(dr);r.sections[cs].push(dr)}}}return r}
|
||
function parseDR(c,sec){if(!c||c.length<2)return null;if(c.join(' ').toUpperCase().includes('ИТОГО')||c.join(' ').toUpperCase().includes('ВСЕГО'))return null;var f=c[0]||'';if(f.length<3||/^\d+$/.test(f))return null;var pl='',pi=-1;for(var j=0;j<c.length;j++){var cl=c[j].replace(/[\s-]/g,'').toUpperCase();if(/^[А-ЯA-Z]\d{3}[А-ЯA-Z]{2}\d{2,3}$/.test(cl)||/^[А-ЯA-Z]\d{3}[А-ЯA-Z]{2}$/.test(cl)){pl=cl;pi=j;break}}var rt='';for(var j=1;j<c.length;j++){if(/^\d{1,3}$/.test(c[j].trim())&&c[j]!==pl){rt=c[j];break}}var sm=0;for(var j=c.length-1;j>=0;j--){var n=parseFloat(c[j].trim().replace(/\s/g,'').replace(',','.'));if(!isNaN(n)&&n>0&&j!==pi&&j!==0){sm=n;break}}return{fio:normName(f),plate:normPlate(pl),section:sec,route:rt,sum:sm}}
|
||
function parseTx(d){if(!d||!d.length)return[];var hr=-1,ci={};for(var i=0;i<Math.min(d.length,10);i++){var row=d[i];if(!row)continue;var c=row.map(function(x){return String(x).toUpperCase().trim()});var id=c.findIndex(function(x){return x.includes('DATE')}),ic=c.findIndex(function(x){return x.includes('CONDUCTOR')}),it=c.findIndex(function(x){return x.includes('TARIF_SUM')||(x.includes('TARIF')&&!x.includes('TARIF_PAY'))}),icar=c.findIndex(function(x){return x.includes('CAR')||x.includes('PLATE')||x.includes('AUTO')});if(id>=0&&ic>=0&&it>=0){hr=i;ci={date:id,conductor:ic,tarifSum:it,car:icar>=0?icar:-1};break}}if(hr<0)return[];var txs=[];for(var i=hr+1;i<d.length;i++){var row=d[i];if(!row||!row.length)continue;var date=String(row[ci.date]||'').trim(),cond=String(row[ci.conductor]||'').trim(),tsStr=String(row[ci.tarifSum]||'0').trim().replace(',','.'),ts=parseFloat(tsStr);if(!date||!cond||isNaN(ts))continue;if(cond.toUpperCase().includes('CONDUCTOR'))continue;var car=ci.car>=0?String(row[ci.car]||'').trim():'';txs.push({date:date,conductor:normName(cond),plate:normPlate(car),tarifRub:ts/100})}return txs}
|
||
function reconcile(veds,txs){var vDates=new Set();veds.forEach(function(v){if(v.date)vDates.add(normDate(v.date))});var ft=txs.filter(function(t){return vDates.has(normDate(t.date))});var txMap=new Map();ft.forEach(function(t){var k=t.conductor+'|'+(t.plate||'');if(!txMap.has(k))txMap.set(k,{conductor:t.conductor,plate:t.plate,total:0,cnt:0});var e=txMap.get(k);e.total+=t.tarifRub;e.cnt++});var dMap=new Map();veds.forEach(function(v){v.drivers.forEach(function(d){var k=d.fio+'|'+d.plate;if(!dMap.has(k))dMap.set(k,{fio:d.fio,plate:d.plate,route:d.route,section:d.section,cashier:v.cashier,date:v.date,given:0});dMap.get(k).given+=d.sum})});var res=[],unmatched=[];var used=new Set();dMap.forEach(function(vd,k){var tk=vd.fio+'|'+vd.plate;var tx=txMap.get(tk);if(!tx){var bs=0,bk='';txMap.forEach(function(e,k){var p=k.split('|'),tf=p[0],tp=p[1]||'';if(tp&&vd.plate&&normPlate(tp)===normPlate(vd.plate)){var s=sim(vd.fio,tf);if(s>=0.88&&s>bs){bs=s;bk=k}}});if(bk){tx=txMap.get(bk);used.add(bk)}}else{used.add(tk)}var col=tx?tx.total:0;var diff=tx?Math.round(col-vd.given):-vd.given;res.push({driver:vd.fio,car:vd.plate||'—',route:vd.route||'—',section:vd.section==='city'?'Город':'Пригород',cashier:vd.cashier||'—',date:vd.date||'—',given:Math.round(vd.given),collected:Math.round(col),diff:diff,txCount:tx?tx.cnt:0,status:diff===0?'ok':(Math.abs(diff)<=10?'warn':'err')})});txMap.forEach(function(e,k){if(!used.has(k)){var p=k.split('|');unmatched.push({driver:p[0],plate:p[1]||'—',total:Math.round(e.total),cnt:e.cnt})}});var cm=new Map();res.forEach(function(r){var k=r.cashier;if(!cm.has(k))cm.set(k,{cashier:k,count:0,given:0,collected:0,diff:0});var e=cm.get(k);e.count++;e.given+=r.given;e.collected+=r.collected;e.diff+=r.diff});var rm=new Map();res.forEach(function(r){var k=r.route||'Без маршрута';if(!rm.has(k))rm.set(k,{route:k,type:r.section,drivers:0,sum:0});var e=rm.get(k);e.drivers++;e.sum+=r.given});var tg=res.reduce(function(s,r){return s+r.given},0),tc=res.reduce(function(s,r){return s+r.collected},0),td=res.reduce(function(s,r){return s+r.diff},0),disc=res.filter(function(r){return r.diff!==0});return{drivers:res,cashiers:Array.from(cm.values()),routes:Array.from(rm.values()),unmatchedTx:unmatched,conclusions:{totalDrivers:res.length,totalGiven:tg,totalCollected:tc,totalDiff:td,discrepanciesCount:disc.length,matchedPercent:res.length>0?Math.round((res.length-disc.length)/res.length*100):0}}}
|
||
function normDate(s){if(!s)return'';var d=String(s).trim();var ser=parseInt(d);if(!isNaN(ser)&&ser>40000&&ser<60000){var dt=new Date((ser-25569)*86400*1000);return dt.toISOString().split('T')[0]}var p=d.split(/[./-]/);if(p.length===3){var day,month,year;if(p[0].length===4){year=p[0];month=p[1];day=p[2]}else{day=p[0];month=p[1];year=p[2]}if(year.length===2)year='20'+year;return year+'-'+month.padStart(2,'0')+'-'+day.padStart(2,'0')}return d}
|
||
/* ===== main ===== */
|
||
var st={files:[],fileTypes:{},parsedVeds:[],parsedTxs:[],result:null,busy:false};
|
||
var $=function(id){return document.getElementById(id)};
|
||
var dom={dz:$('dropzone'),fi:$('fileInput'),fl:$('fileList'),br:$('btnReconcile'),bc:$('btnClear'),be:$('btnExportCsv'),bd:$('btnDownload'),btt:$('btnToggleTheme'),pb:$('progressBar'),pf:$('progressFill'),es:$('emptyState'),ra:$('resultActions'),tc:$('tabsContainer'),td:$('tableDiscrepancies'),ta:$('tableAll'),tca:$('tableCashiers'),tr:$('tableRoutes'),cd:$('countDisc'),ca:$('countAll'),cc:$('conclusionsContent')};
|
||
document.addEventListener('DOMContentLoaded',function(){initTheme();initTabs();bind();updUI()});
|
||
function initTheme(){var t=localStorage.getItem('theme')||'light';document.documentElement.setAttribute('data-theme',t);dom.btt.textContent=t==='light'?'🌙 Тёмная тема':'☀️ Светлая тема';dom.btt.onclick=function(){var x=localStorage.getItem('theme')||'light';x=x==='light'?'dark':'light';localStorage.setItem('theme',x);document.documentElement.setAttribute('data-theme',x);this.textContent=x==='light'?'🌙 Тёмная тема':'☀️ Светлая тема'}}
|
||
function initTabs(){document.querySelectorAll('.tab-btn').forEach(function(b){b.onclick=function(){document.querySelectorAll('.tab-btn').forEach(function(x){x.classList.remove('active')});document.querySelectorAll('.tab-panel').forEach(function(x){x.classList.remove('active')});b.classList.add('active');var p=document.getElementById('panel-'+b.dataset.tab);if(p)p.classList.add('active')}})}
|
||
function bind(){dom.dz.onclick=function(){dom.fi.click()};dom.dz.ondragover=function(e){e.preventDefault();dom.dz.classList.add('dragover')};dom.dz.ondragleave=function(){dom.dz.classList.remove('dragover')};dom.dz.ondrop=function(e){e.preventDefault();dom.dz.classList.remove('dragover');hdl(e.dataTransfer.files)};dom.fi.onchange=function(e){hdl(e.target.files)};dom.br.onclick=onRec;dom.bc.onclick=onClr;dom.be.onclick=onCSV;dom.bd.onclick=onXLS}
|
||
function hdl(fl){if(st.busy)return;for(var i=0;i<fl.length;i++){var f=fl[i];if(!validateFile(f))continue;if(st.files.some(function(x){return x.name===f.name&&x.size===f.size}))continue;st.files.push(f)}if(!st.files.length)return;detect().then(function(){rndFL();updUI()})}
|
||
function detect(){var ps=[];for(var i=0;i<st.files.length;i++){(function(idx){if(st.fileTypes[idx]){ps.push(Promise.resolve());return}ps.push(readExcel(st.files[idx]).then(function(sh){var d=sh[0]?.data||[];st.fileTypes[idx]=detectFileType(st.files[idx].name,d)||'unknown'}).catch(function(){st.fileTypes[idx]='error'}))})(i)}return Promise.all(ps)}
|
||
function rndFL(){dom.fl.innerHTML='';st.files.forEach(function(f,i){var t=st.fileTypes[i],el;if(t==='error'||t==='unknown'){el=document.createElement('div');el.className='file-item file-error';el.innerHTML='<div class="file-icon" style="background:#fee2e2;">⚠️</div><div class="file-info"><div class="file-name">'+esc(f.name)+'</div><div class="file-type" style="color:#ef4444;">'+(t==='error'?'Ошибка чтения':'Не определён')+'</div></div>'}else{el=document.createElement('div');el.className='file-item';el.innerHTML='<div class="file-icon '+(t==='transaction'?'transaction':'vedomost')+'">'+(t==='transaction'?'📊':'📋')+'</div><div class="file-info"><div class="file-name">'+esc(f.name)+'</div><div class="file-type">'+(t==='transaction'?'Выгрузка':'Ведомость')+' · '+(f.size/1024).toFixed(0)+' КБ</div></div><button class="file-remove" data-i="'+i+'">✕</button>'}dom.fl.appendChild(el)});dom.fl.querySelectorAll('.file-remove').forEach(function(b){b.onclick=function(){var i=parseInt(this.dataset.i);st.files.splice(i,1);var nt={};Object.keys(st.fileTypes).forEach(function(k){var ki=parseInt(k);if(ki<i)nt[ki]=st.fileTypes[k];else if(ki>i)nt[ki-1]=st.fileTypes[k]});st.fileTypes=nt;st.parsedVeds=[];st.parsedTxs=[];st.result=null;rndFL();updUI();hideR()}})}
|
||
function esc(s){var d=document.createElement('div');d.textContent=s;return d.innerHTML}
|
||
function updUI(){dom.br.disabled=!st.files.length||st.busy;dom.bc.disabled=!st.files.length||st.busy;dom.be.disabled=!st.result;dom.bd.disabled=!st.result}
|
||
async function onRec(){if(st.busy)return;st.busy=true;dom.br.disabled=true;try{dom.pb&&dom.pb.classList.add('active');dom.pf&&(dom.pf.style.width='0%');st.parsedVeds=[];st.parsedTxs=[];for(var i=0;i<st.files.length;i++){var t=st.fileTypes[i];if(t==='unknown'||t==='error')continue;var sh=await readExcel(st.files[i]),d=sh[0]?.data||[];if(t==='vedomost'){var v=parseVed(d);if(v&&v.drivers.length)st.parsedVeds.push(v)}else if(t==='transaction'){var tx=parseTx(d);if(tx.length)for(var j=0;j<tx.length;j++)st.parsedTxs.push(tx[j])}}if(!st.parsedVeds.length||!st.parsedTxs.length){alert('Нужны: ведомость + выгрузка транзакций');st.busy=false;dom.br.disabled=false;dom.pb&&dom.pb.classList.remove('active');return}st.result=reconcile(st.parsedVeds,st.parsedTxs);if(!st.result.drivers.length){alert('Не удалось сопоставить водителей');st.busy=false;dom.br.disabled=false;dom.pb&&dom.pb.classList.remove('active');return}rndR();updUI()}catch(e){console.error(e);alert('Ошибка: '+e.message)}finally{st.busy=false;dom.br.disabled=false;dom.pb&&dom.pb.classList.remove('active')}}
|
||
function rndR(){if(!st.result)return;var r=st.result;dom.es.style.display='none';dom.ra.style.display='';dom.tc.style.display='';var dd=r.drivers.filter(function(d){return d.diff!==0});rndT(dom.td,dd,['driver','car','given','collected','diff','status'],function(v,k,row){if(k==='status'){var lb={ok:'✓ Совпадает',warn:'⚠ '+formatMoney(row.diff)+' ₽',err:'✕ '+formatMoney(Math.abs(row.diff))+' ₽'};return'<span class="badge badge-'+v+'">'+(lb[v]||v)+'</span>'}if(k==='diff'){if(v===0)return'<span class="diff-zero">0 ₽</span>';return'<span class="'+(v>0?'diff-positive':'diff-negative')+'">'+(v>0?'+':'')+formatMoney(v)+' ₽</span>'}if(typeof v==='number')return formatMoney(v);return v||'—'});dom.cd.textContent=dd.length;var ad=r.drivers.map(function(d){return{date:d.date,driver:d.driver,route:d.route,section:d.section,given:d.given,collected:d.collected,txCount:d.txCount}});rndT(dom.ta,ad,['date','driver','route','section','given','collected','txCount'],function(v,k){if(k==='section')return v==='Город'?'🏙️ Город':'🚌 Пригород';if(typeof v==='number')return formatMoney(v);return v||'—'});dom.ca.textContent=ad.length;rndT(dom.tca,r.cashiers,['cashier','count','given','collected','diff'],function(v,k){if(k==='diff'){if(v===0)return'<span class="diff-zero">0 ₽</span>';return'<span class="'+(v>0?'diff-positive':'diff-negative')+'">'+(v>0?'+':'')+formatMoney(v)+' ₽</span>'}if(typeof v==='number')return formatMoney(v);return v||'—'});rndT(dom.tr,r.routes,['route','type','drivers','sum'],function(v,k){if(typeof v==='number')return formatMoney(v);return v||'—'});var c=r.conclusions,dc=c.totalDiff===0?'':(c.totalDiff>0?'err':'warn');dom.cc.innerHTML='<div class="summary-card"><div class="info-block '+dc+'"><div class="info-block-icon '+(c.totalDiff===0?'green':(c.totalDiff>0?'rose':'amber'))+'">'+(c.totalDiff===0?'✓':'⚠')+'</div><div><div class="info-block-title" style="color:'+(c.totalDiff===0?'var(--success-text)':'var(--danger-text)')+'">Итого сверено: '+c.totalDrivers+' водителей</div><div class="info-block-text"><strong>Сдано:</strong> '+formatMoney(c.totalGiven)+' ₽ · <strong>Собрано:</strong> '+formatMoney(c.totalCollected)+' ₽ · <strong>Расхождение:</strong> <span style="color:'+(c.totalDiff===0?'var(--text-muted)':(c.totalDiff>0?'var(--danger)':'var(--success)'))+';font-weight:600">'+(c.totalDiff>0?'+':'')+formatMoney(c.totalDiff)+' ₽</span></div><div class="info-block-text" style="margin-top:.5rem">Совпадений: '+(c.totalDrivers-c.discrepanciesCount)+' из '+c.totalDrivers+' ('+c.matchedPercent+'%)'+(c.discrepanciesCount>0?'<span style="color:var(--danger)"> · Расхождений: '+c.discrepanciesCount+'</span>':'')+'</div></div></div>'+(r.unmatchedTx.length>0?'<div class="info-block warn" style="margin-top:1rem"><div class="info-block-icon amber">⚠</div><div><div class="info-block-title" style="color:var(--warning-text)">Не сопоставлено транзакций: '+r.unmatchedTx.length+'</div><div class="info-block-text">Эти водители есть в выгрузке, но не найдены в ведомостях.</div></div></div>':'')+'</div>'}
|
||
function rndT(tb,rows,keys,fmt){if(!rows||!rows.length){tb.innerHTML='<tr><td colspan="'+(keys||Object.keys(rows[0]||{}).length)+'" style="text-align:center;padding:2rem;color:var(--text-muted)">Нет данных</td></tr>';return}tb.innerHTML=rows.map(function(row){return'<tr>'+(keys||Object.keys(row)).map(function(k){var v=row[k];if(fmt)return fmt(v,k,row);if(v==null||v==='')return'—';if(typeof v==='number')return formatMoney(v);return esc(String(v))}).join('')+'</tr>'}).join('')}
|
||
function onCSV(){if(!st.result)return;var d=st.result.drivers.map(function(r){return{driver:r.driver,car:r.car,route:r.route,cashier:r.cashier,date:r.date,given:r.given,collected:r.collected,diff:r.diff,status:r.status==='ok'?'Совпадает':(r.status==='warn'?'Мелкое расхождение':'Расхождение')}});downloadFile(arrayToCsv(d,['driver','car','route','cashier','date','given','collected','diff','status']),'sverka_vyruchki.csv')}
|
||
function onXLS(){if(!st.result)return;var d=st.result.drivers.map(function(r){return{'Водитель':r.driver,'Гос. номер':r.car,'Маршрут':r.route,'Кассир':r.cashier,'Дата':r.date,'Сдано (₽)':r.given,'Собрано (₽)':r.collected,'Расхождение (₽)':r.diff,'Статус':r.status==='ok'?'Совпадает':(r.status==='warn'?'Мелкое расхождение':'Расхождение')}});var wb=XLSX.utils.book_new(),ws=XLSX.utils.json_to_sheet(d);XLSX.utils.book_append_sheet(wb,ws,'Сверка');downloadFile(XLSX.write(wb),'sverka_vyruchki.xlsx')}
|
||
function onClr(){if(st.busy)return;st.files=[];st.fileTypes={};st.parsedVeds=[];st.parsedTxs=[];st.result=null;dom.fi.value='';rndFL();updUI();hideR()}
|
||
function hideR(){dom.es.style.display='';dom.ra.style.display='none';dom.tc.style.display='none'}
|
||
</script>
|
||
</body>
|
||
</html>
|