Owner AI control center

Upload documents and the system builds the company profile, employees, tasks, KPI, partner search, negotiations and CRM automatically.

`; const w=window.open('','_blank'); if(!w) return; w.document.open(); w.document.write(html); w.document.close(); w.focus(); w.print(); }; })(); window.renderWarehouseHub = function(){ ensureWarehouseTabDom(); const tab=q('#tab-warehouse'); if(!tab) return; const data=warehouseData(); const items=data.items||[]; const docs=(state.documents||[]).filter(d=>String(d.doc_type||'').toLowerCase().includes('warehouse') || String(d.doc_type||'').toLowerCase().includes('склад')); const totalQty=items.reduce((s,x)=>s+Number(x.qty||0),0); const totalValue=items.reduce((s,x)=>s+(Number(x.qty||0)*Number(x.cost||0)),0); const reserved=items.reduce((s,x)=>s+Number(x.reserved||0),0); const shortage=items.filter(x=>Number(x.qty||0)

${pairT('Склад и учёт','Warehouse & accounting')}

${pairT('Таблица остатков, файлы, печать, сверка с бухгалтерией и балансом.','Stock table, files, printing, reconciliation with accounting and balance.')}

${pairT('Товарный контур','Stock register')}

ERP
${pairT('Всего единиц','Total units')}
${esc(String(totalQty))}
${pairT('В резерве','Reserved')}
${esc(String(reserved))}
${pairT('Стоимость склада','Inventory value')}
${money(totalValue)}
${pairT('Дефицитных позиций','Shortage lines')}
${esc(String(shortage))}
${items.map((row,idx)=>``).join('') || ``}
${pairT('Номенклатура','Item')}SKU${pairT('Остаток','Qty')}${pairT('Резерв','Reserved')}Min${pairT('Ед.','Unit')}${pairT('Себестоимость','Cost')}${pairT('Баланс','Balance')}${pairT('Локация','Location')}${pairT('Поставщик','Supplier')}${pairT('Счёт / бух. статья','Account')}
${pairT('Пока нет складских строк.','There are no warehouse rows yet.')}

${pairT('Сверка с финансами и файлами','Reconciliation with finance and files')}

sync
${pairT('Склад по таблице','Warehouse by table')}: ${money(totalValue)}
${pairT('Баланс / inventory','Balance / inventory')}: ${money(financeInv)}
${delta===0?'✓':'!'}
${pairT('Разница','Delta')}: ${money(delta)}
${pairT('Заполняйте таблицу по SKU и остаткам. Поле “Баланс” помогает сверять физический остаток со значением из бухгалтерии. После сохранения Finance учитывает этот контур при анализе.','Fill the table by SKU and on-hand quantity. The “Balance” field helps compare the physical stock with accounting. After saving, Finance takes this contour into account in its analysis.')}
${docs.map(d=>`
${esc(d.original_name||'')}
${esc(d.note||'')}
${pairT('Открыть','Open')}
`).join('') || `
${pairT('Файлы склада ещё не загружены.','No warehouse files uploaded yet.')}
`}
`; }; function ensureMarketingStructure(plan){ const currentView=plan.view||'year'; const map={day:'dayCalendar',week:'weekCalendar',month:'monthCalendar',year:'yearCalendar'}; for(const key of Object.values(map)){ plan[key]=Array.isArray(plan[key])?plan[key]:[]; } const active=map[currentView] || 'yearCalendar'; if(!plan[active].length){ const base=buildMarketingPlan(plan.input||''); Object.assign(plan, base, {whatIf:Object.assign({}, base.whatIf||{}, plan.whatIf||{}), view:currentView}); } return plan; } function calendarKey(){ const v=(getMarketingPlan().view||'year'); return v==='day'?'dayCalendar':v==='week'?'weekCalendar':v==='month'?'monthCalendar':'yearCalendar'; } window.addMarketingCalendarRow = function(){ const plan=ensureMarketingStructure(getMarketingPlan()); const key=calendarKey(); plan[key].push({label:'',owner:'AI',target:80,items:['']}); saveMarketingPlan(plan); renderMarketing(); }; window.updateMarketingCalendarRows = function(){ const plan=ensureMarketingStructure(getMarketingPlan()); const key=calendarKey(); const rows=[]; document.querySelectorAll('.calendar-edit-row').forEach((row,idx)=>{ rows[idx]={label:row.querySelector('[data-cal-label]').value, owner:row.querySelector('[data-cal-owner]').value, target:Number(row.querySelector('[data-cal-target]').value||0), items:(row.querySelector('[data-cal-items]').value||'').split('\n').map(x=>x.trim()).filter(Boolean)}; }); plan[key]=rows; saveMarketingPlan(plan); }; window.removeMarketingCalendarRow = function(idx){ const plan=ensureMarketingStructure(getMarketingPlan()); const key=calendarKey(); plan[key].splice(idx,1); saveMarketingPlan(plan); renderMarketing(); }; function renderCalendarEditor(plan){ const key=calendarKey(); const rows=plan[key]||[]; return `
${pairT('Как заполнять: выбери горизонт, добавь строки, укажи период, ответственного и список действий по строкам. Каждая строка — это рабочая единица календаря.','How to fill it: choose the horizon, add rows, enter the period, owner, and actions line by line. Each row is a working calendar unit.')}
${rows.map((r,idx)=>`
`).join('') || `
${pairT('Календарь ещё не заполнен.','The calendar has not been filled yet.')}
`}
`; } window.renderMarketing = function(){ const plan=ensureMarketingStructure(getMarketingPlan()); const alt=altMarketingSeries(plan); const view=plan.view||'year'; q('#tab-marketing').innerHTML=`

${tt('marketingTitle')}

${pairT('Собери стратегию, затем заполни календарь по дням, неделям, месяцам или году.','Build the strategy, then fill the calendar by day, week, month, or year.')}
${pairT('Strategy engine','Strategy engine')}
AI
${pairT('Стратегия','Strategy')}
${esc(plan.strategy||'')}

${pairT('Execution KPI','Execution KPI')}

KPI
${lineSvg(plan.series||[], true, {w:760,h:280,p:58,xLabel:pairT('Месяц','Month'),yLabel:'KPI %'})}

${pairT('Календарь маркетинга','Marketing calendar')}

${renderCalendarEditor(plan)}

What If

simulator
${plan.whatIf.budget||100}%
${plan.whatIf.conversion||100}%
${plan.whatIf.partners||100}%
${plan.whatIf.delay||0}%
${lineSvg(alt, true, {w:760,h:220,p:58,xLabel:pairT('Месяц','Month'),yLabel:'KPI %'})}

AI report

annual
${(plan.report||[]).map(t=>`
${esc(t)}
`).join('')}
${(plan.tasks||[]).map(t=>``).join('')}
${pairT('Неделя','Week')}${pairT('Канал','Channel')}${pairT('Действие','Action')}KPI${pairT('Ответственный','Owner')}${pairT('Статус','Status')}
${esc(String(t.week||t.period||''))}${esc(t.channel||'')}${esc(t.action||'')}${esc(t.kpi||'')}${esc(t.owner||'')}${esc(t.status||'')}
`; }; window.sendCurrentRoomMessage = async function(){ if(!currentRoomId) return; const body=q('#meetingText')?.value?.trim(); if(!body) return; const source_lang=q('#meetingSourceLang')?.value || roomMessageLang || 'ru'; const target_lang=q('#meetingTargetLang')?.value || roomTranslateLang || 'en'; roomMessageLang=source_lang; roomTranslateLang=target_lang; await api(`/api/meeting-rooms/${currentRoomId}/messages`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({sender_company_id:state.company.id,sender_name:state.company.owner_name,company_name:state.company.company_name,sender_role:'Owner',body,source_lang,target_lang,participant_key:`owner:${state.company.id}`})}); q('#meetingText').value=''; await bootstrap(); window._lastTab='meeting'; showTab('meeting'); toast(pairT('Сообщение отправлено.','Message sent.')); }; window.startVoiceInput = function(roomId){ const SR=window.SpeechRecognition||window.webkitSpeechRecognition; if(!SR){ toast(pairT('Голосовой ввод не поддерживается браузером.','Voice input is not supported by this browser.'),'bad'); return; } const source=q('#meetingSourceLang')?.value || roomMessageLang || 'ru'; const target=q('#meetingTargetLang')?.value || roomTranslateLang || 'en'; const langMap={ru:'ru-RU',en:'en-US',de:'de-DE',es:'es-ES'}; const rec=new SR(); rec.lang=langMap[source]||'ru-RU'; rec.interimResults=false; rec.maxAlternatives=1; rec.onresult=async e=>{ if(q('#meetingText')) q('#meetingText').value=e.results[0][0].transcript; roomMessageLang=source; roomTranslateLang=target; await sendCurrentRoomMessage(); }; rec.start(); }; const oldRenderMeetingRoom = window.renderMeetingRoom; window.renderMeetingRoom = function(){ if(typeof oldRenderMeetingRoom==='function') oldRenderMeetingRoom(); const holder=q('#meetingParticipantList'); if(!holder) return; holder.querySelectorAll('.participant-card').forEach(card=>{ const txt=card.textContent||''; if(/сотрудник|employee/i.test(txt)) card.remove(); }); }; window.pullPublicPartners = async function(silent=false){ syncPartnerFilters && syncPartnerFilters(); const pf=getPartnerFilterState ? getPartnerFilterState() : {query:'',country:'',region:'',city:'',radius:1200}; const params=new URLSearchParams({q:pf.query||'',country:pf.country||'',region:pf.region||'',city:pf.city||'',radius:String(pf.radius||1200)}); const items=await api('/api/partners/public-search?'+params.toString()); state.currentPartners=items.filter(x=>Number(x.is_public||0)===1 || String(x.id||'').startsWith('osm:')); renderPartnerList(state.currentPartners); if(partnerMode==='2d') initMap(state.currentPartners); else initPartnerGlobe(); if(!silent) toast(pairT('Загружены реальные компании из открытого доступа.','Loaded real companies from public sources.')); }; window.renderPartners = function(){ const pf=getPartnerFilterState(); const geo=partnerGeoSelections(pf); const matches=Array.isArray(state.currentPartners) ? state.currentPartners : []; q('#tab-partners').innerHTML=`

${pairT('Карта партнёров','Partner map')}

${pf.radius||1200} km

${pairT('Потенциальные партнёры','Potential partners')}

${matches.length?pairT(`Найдено: ${matches.length}`,`Found: ${matches.length}`):pairT('Пусто','Empty')}
${renderQueuePanel()}

${partnerMode==='3d'?'3D globe':pairT('Карта','Map')}

${partnerMode==='3d'?'live geo':'OSM'}
${partnerMode==='2d'?`
${pairT('Ваша компания','Your company')}
${pairT('Сильное совпадение','Strong match')}
${pairT('Публичный партнёр','Public partner')}
`:`
`}
`; renderPartnerList(matches); q('#partnerDetailPane').innerHTML=''; if(partnerMode==='2d') initMap(matches); else initPartnerGlobe(); q('#partnerCountry')?.addEventListener('change', ()=>{ syncPartnerFilters(); state.partnerFilters.region=''; state.partnerFilters.city=''; renderPartners(); }); q('#partnerRegion')?.addEventListener('change', ()=>{ syncPartnerFilters(); state.partnerFilters.city=''; renderPartners(); }); q('#partnerCity')?.addEventListener('change', ()=>{ syncPartnerFilters(); syncPartnerGlobeFromFilters(true); }); if(!matches.length) setTimeout(()=>pullPublicPartners(true), 120); setTimeout(()=>syncPartnerGlobeFromFilters(true), 250); }; window.renderCompany = function(){ const c=state.company||{}; const deps=departments(); const active=currentCompanyDept||deps[0]||''; const deptEmployees=(state.employees||[]).filter(e=>(e.department||'')===active); q('#tab-company').innerHTML=`

${pairT('Карточка компании','Company profile')}

${pairT('Сначала заполни стандартные данные, потом тонкости — через конструктор направлений.','First fill the standard data, then the specifics through the direction builder.')}

${pairT('Стандартные данные','Standard data')}

core

${pairT('AI анализ компании','AI company analysis')}

AI
${(state.company_insights?.insights||[]).map(t=>`
${esc(t)}
`).join('')}${(state.company_insights?.risks||[]).map(t=>`
!
${esc(t)}
`).join('')}

${pairT('Структура отделов','Department structure')}

${pairT('сотрудники','employees')}
${deps.map(d=>``).join('')}
${deptEmployees.length?deptEmployees.map(e=>`
${esc(e.full_name)}
${esc(e.position||'')}
`).join(''):`
${pairT('В этом отделе пока нет сотрудников.','No employees in this department yet.')}
`}

${pairT('Конструктор направлений','Direction builder')}

${pairT('без лишних ячеек','constructor')}
${pairT('Заполни основной вид деятельности, затем добавляй «дополнительно». Если появляется новое направление — нажми «Добавить другое направление».','Fill the main activity first, then add “additional” steps. If a new business line appears, press “Add another direction”.')}
${directionCardsHtml()}
`; }; setTimeout(()=>{ ensureWarehouseTabDom(); if(typeof bootstrap==='function') bootstrap(); }, 0); })();