99 lines
12 KiB
JavaScript
99 lines
12 KiB
JavaScript
(()=>{
|
||
'use strict';
|
||
if(window.CateriumTrialDemo)return;
|
||
const KEY='sunTrialDemoV1',BATCH='trial-demo-v1';
|
||
const $=id=>document.getElementById(id);
|
||
const esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||
const money=x=>new Intl.NumberFormat('ru-RU',{maximumFractionDigits:2}).format(Number(x)||0)+' ₽';
|
||
const num=x=>new Intl.NumberFormat('ru-RU',{maximumFractionDigits:3}).format(Number(x)||0);
|
||
const read=(key,fallback)=>{try{return JSON.parse(localStorage.getItem(key))??fallback}catch(_){return fallback}};
|
||
const cloud=()=>window.SunCloudV2;
|
||
const data=()=>window.CateriumDataV1773;
|
||
const catalog=()=>data()?.catalog.list()||read('sunBoxes',[]);
|
||
const installed=()=>read(KEY,{})?.version===1;
|
||
const signedIn=()=>Boolean(cloud()?.getSession?.()?.user&&cloud()?.getWorkspace?.()?.id);
|
||
const writable=()=>signedIn()&&!cloud()?.isSupportMode?.()&&cloud()?.hasPermission?.('orders.create')&&window.SunSaaSV16?.isWritable?.()!==false;
|
||
let status=null,statusWorkspace='',pending=null,epoch=0,busy=false;
|
||
function navigate(id){const label={stock:'Склад',shopping:'Закупки'}[id];const nav=[...document.querySelectorAll('header nav button')].find(b=>String(b.dataset.navLabel||b.textContent||'').trim()===label);if(nav)nav.click();else window.show?.(id);}
|
||
function message(text){const el=$('ctDemoMessage');if(el)el.textContent=text;}
|
||
async function refreshStatus(){
|
||
const c=cloud(),ws=c?.getWorkspace?.()?.id;
|
||
if(!signedIn()||c?.isSupportMode?.()){status=null;statusWorkspace='';render();return;}
|
||
if(statusWorkspace===ws||pending)return;
|
||
const ticket=epoch;pending=ws;
|
||
try{const result=await c.getClient?.()?.rpc('caterium_trial_demo_status',{p_workspace:ws});
|
||
if(ticket!==epoch||c.getWorkspace?.()?.id!==ws)return;
|
||
if(result?.error)throw result.error;
|
||
status=result?.data||null;statusWorkspace=ws;
|
||
}catch(_){status=null;}finally{if(ticket===epoch){pending=null;render();}}
|
||
}
|
||
function render(){
|
||
const host=$('new');if(!host)return;
|
||
const items=catalog().filter(b=>b.demo&&b.ttk),active=installed()&&items.length>0;
|
||
if(!signedIn()||cloud()?.isSupportMode?.()||(!active&&!status?.canInstall)){$('ctTrialDemo')?.remove();return;}
|
||
let card=$('ctTrialDemo');if(!card){card=document.createElement('section');card.id='ctTrialDemo';card.className='card';host.prepend(card);}
|
||
const signature=JSON.stringify({active,items:items.map(x=>[x.id,x.name]),can:writable()});
|
||
if(card.dataset.signature===signature)return;card.dataset.signature=signature;
|
||
card.innerHTML=active?`<div class="ct-demo-heading"><div><strong>Демонстрационная база</strong><p>${items.length} боксов с фото и ТТК · учебные остатки и поставщики</p></div><span class="ct-demo-badge">Можно пробовать</span></div>
|
||
<p>Создайте пробный заказ, посмотрите нехватку в закупках, оформите приход на складе и спишите продукты по заказу. Все данные можно редактировать.</p>
|
||
<div class="ct-demo-actions"><button class="primary" data-demo-order ${writable()?'':'disabled'}>Создать пробный заказ</button><button class="outline" data-demo-go="stock">Склад</button><button class="outline" data-demo-go="shopping">Закупки</button></div>
|
||
<details><summary>Технологические карты боксов</summary><div class="ct-demo-actions"><label>Бокс<select id="ctDemoBox">${items.map(b=>`<option value="${esc(b.id)}">${esc(b.name)}</option>`).join('')}</select></label><button class="outline" data-demo-ttk>Открыть ТТК</button></div></details><p id="ctDemoMessage" role="status" aria-live="polite"></p>`:
|
||
`<div class="ct-demo-heading"><div><strong>Попробуйте приложение на готовых боксах</strong><p>10 боксов с фото и ТТК, 43 продукта, 5 учебных поставщиков и начальные остатки.</p></div></div><button class="primary" data-demo-install>Загрузить демо</button><p id="ctDemoMessage" role="status" aria-live="polite"></p>`;
|
||
card.querySelector('[data-demo-install]')?.addEventListener('click',install);
|
||
card.querySelector('[data-demo-order]')?.addEventListener('click',createOrder);
|
||
card.querySelector('[data-demo-ttk]')?.addEventListener('click',()=>showTTK($('ctDemoBox').value));
|
||
card.querySelectorAll('[data-demo-go]').forEach(b=>b.addEventListener('click',()=>navigate(b.dataset.demoGo)));
|
||
}
|
||
async function install(){
|
||
if(busy)return;busy=true;const btn=$('ctTrialDemo')?.querySelector('[data-demo-install]');if(btn)btn.disabled=true;
|
||
message('Загружаю демонстрационную базу…');
|
||
try{await cloud().installTrialDemo();status=null;statusWorkspace='';render();message('Готово. Создайте пробный заказ или выберите боксы самостоятельно.');await refreshStatus();}
|
||
catch(error){message(error?.message||'Не удалось загрузить демо. Повторите попытку.');}
|
||
finally{busy=false;if(btn?.isConnected)btn.disabled=false;}
|
||
}
|
||
function createOrder(){
|
||
if(!writable()||!installed())return;
|
||
const repo=data()?.orders;if(!repo){message('Приложение ещё загружается. Повторите через несколько секунд.');return;}
|
||
const all=repo.list(),existing=all.find(o=>o.demoBatch===BATCH);
|
||
if(existing){message(`Пробный заказ №${existing.id} уже создан. Его можно открыть во вкладке «Заказы».`);return;}
|
||
const spec=read(KEY,{}).scenario?.lines,items=catalog();
|
||
if(!Array.isArray(spec)||spec.length!==3){message('Сценарий недоступен. Добавьте боксы в заказ самостоятельно.');return;}
|
||
const lines=spec.map(l=>{const box=items.find(b=>String(b.id)===String(l.id));return box?{id:box.id,name:box.name,qty:l.qty,price:Number(box.price)||0}:null});
|
||
if(lines.some(l=>!l)){message('Некоторые демо-боксы удалены. Добавьте оставшиеся в заказ самостоятельно.');return;}
|
||
const total=lines.reduce((s,l)=>s+l.price*l.qty,0),day=new Date();day.setDate(day.getDate()+1);
|
||
const date=`${day.getFullYear()}-${String(day.getMonth()+1).padStart(2,'0')}-${String(day.getDate()).padStart(2,'0')}`;
|
||
const id=Math.max(0,...all.map(o=>Number(o.id)||0))+1;
|
||
repo.replace([...all,{id,event:'Демо: фуршет',date,time:'12:00',contact:'',phone:'',address:'',note:'Учебный заказ для проверки склада и закупки. Автозавершение отключено.',status:'Новый',lines,total,prepayment:0,balance:total,demo:true,demoBatch:BATCH,autoCompletionDisabled:true}],{reason:'trial-demo.order'});
|
||
window.renderOrders?.();window.sunRefreshStock?.();
|
||
message(`Заказ №${id} на ${money(total)} создан на завтра. Откройте «Закупки», чтобы увидеть нехватку продуктов.`);
|
||
}
|
||
function showTTK(id){
|
||
const box=catalog().find(b=>String(b.id)===String(id));if(!box?.ttk)return;
|
||
$('ctDemoTtk')?.remove();const t=box.ttk,dialog=document.createElement('dialog');dialog.id='ctDemoTtk';
|
||
const changed=JSON.stringify(box.ingredients)!==JSON.stringify(t.rows.map(r=>[r.name,r.gross,r.unit]));
|
||
dialog.innerHTML=`<div class="ct-demo-heading"><div><small>${esc(t.number)} · ${esc(t.basis)}</small><h2>${esc(box.name)}</h2></div><button class="outline" data-ttk-close aria-label="Закрыть ТТК">×</button></div>
|
||
<p>${changed?'Исходный выход':'Выход'}: <b>${num(t.outputGrams)} г${t.pieces?` · ${num(t.pieces)} шт.`:''}</b> · Цена бокса: <b>${money(box.price)}</b></p>
|
||
${changed?'<p class="ct-demo-notice">Состав изменён. Закупка и списание используют текущий состав из редактора. Ниже показана исходная учебная ТТК.</p>':''}
|
||
<div class="ct-demo-table"><table><thead><tr><th>Продукт / упаковка</th><th>Ед.</th><th>Брутто</th><th>Нетто</th><th>Цена за ед.</th><th>Сумма</th></tr></thead><tbody>${t.rows.map(r=>`<tr><td>${esc(r.name)}</td><td>${esc(r.unit)}</td><td>${num(r.gross)}</td><td>${num(r.net)}</td><td>${money(r.unitCost)}</td><td>${money(r.gross*r.unitCost)}</td></tr>`).join('')}</tbody></table></div>
|
||
<p><b>Продукты и упаковка: ${money(t.ingredientCost)}</b></p><p class="hint">Учебные закупочные цены. Работа, доставка и накладные расходы не включены. Расход со склада считается по брутто.</p>
|
||
<h3>Приготовление и сборка</h3><ol>${t.steps.map(s=>`<li>${esc(s)}</li>`).join('')}</ol><p><b>Аллергены:</b> ${t.allergens.map(esc).join(', ')}.</p><p class="hint">${esc(t.note)}</p>
|
||
<div class="ct-demo-actions"><button class="outline" data-ttk-edit>Изменить состав и цену</button><button class="primary" data-ttk-close>Закрыть</button></div>`;
|
||
document.body.append(dialog);dialog.querySelectorAll('[data-ttk-close]').forEach(b=>b.onclick=()=>dialog.close());
|
||
dialog.querySelector('[data-ttk-edit]').onclick=()=>{dialog.close();window.editBox?.(box.id)};
|
||
dialog.addEventListener('close',()=>dialog.remove(),{once:true});dialog.showModal();
|
||
}
|
||
function reset(){epoch++;pending=null;status=null;statusWorkspace='';$('ctTrialDemo')?.remove();$('ctDemoTtk')?.remove();}
|
||
function boot(){
|
||
const style=document.createElement('style');style.textContent=`#ctTrialDemo{margin:0 0 18px;padding:18px 22px}#ctTrialDemo p{font-size:13px;line-height:1.5;margin:6px 0 12px;max-width:900px}.ct-demo-heading{display:flex;justify-content:space-between;gap:14px;align-items:flex-start}.ct-demo-heading strong{font-size:18px}.ct-demo-heading h2{font-size:22px;margin:8px 0}.ct-demo-badge{border:1px solid #c3d4bd;border-radius:18px;padding:5px 10px;font-size:12px;white-space:nowrap}.ct-demo-actions{display:flex;gap:10px;align-items:end;flex-wrap:wrap;margin:12px 0}.ct-demo-actions label{min-width:0;max-width:100%;flex:1}.ct-demo-actions select{width:100%;font:inherit;padding:9px}.ct-demo-actions button{max-width:100%}#ctTrialDemo details{font-size:13px;margin-top:14px}#ctTrialDemo summary{cursor:pointer}#ctDemoMessage:empty{display:none}#ctDemoTtk{width:min(860px,calc(100vw - 24px));max-height:90dvh;box-sizing:border-box;overflow:auto;border:1px solid #d4c8b0;border-radius:18px;padding:24px;background:var(--card,#fff);color:var(--text,#27241e)}#ctDemoTtk::backdrop{background:#0008}.ct-demo-table{overflow-x:auto}.ct-demo-table table{border-collapse:collapse;min-width:580px;width:100%;font-size:13px}.ct-demo-table td,.ct-demo-table th{text-align:right;padding:9px 7px;border-bottom:1px solid #ddd}.ct-demo-table th:first-child,.ct-demo-table td:first-child{text-align:left}#ctDemoTtk li{margin:8px 0;line-height:1.5}.ct-demo-notice{background:#fff3d6;color:#604918;padding:12px;border-radius:8px}@media(max-width:600px){#ctTrialDemo{padding:14px}.ct-demo-heading{flex-wrap:wrap}.ct-demo-actions>button{flex:1 1 auto}.ct-demo-actions label{flex-basis:100%}#ctDemoTtk{padding:16px}}@media print{#ctTrialDemo{display:none!important}}`;
|
||
document.head.append(style);
|
||
window.addEventListener('sun:cloud-tenant-changing',reset);
|
||
window.addEventListener('sun:cloud-permissions-changed',()=>{render();refreshStatus()});
|
||
window.addEventListener('sun:cloud-state-applied',()=>{render();refreshStatus()});
|
||
window.addEventListener('sun:subscription-changed',()=>{statusWorkspace='';render();refreshStatus()});
|
||
document.addEventListener('click',e=>{if(e.target.closest('header nav button')){render();refreshStatus()}});
|
||
data()?.subscribe('catalog',render);setTimeout(()=>{render();refreshStatus()},1400);
|
||
}
|
||
window.CateriumTrialDemo={render,refreshStatus,showTTK};
|
||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
|
||
})();
|