(()=>{ 'use strict'; if(window.CateriumSingleItemPdf)return; const VERSION='20260922-single-item-pdf-v1'; const $=id=>document.getElementById(id); const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const CATEGORY_NAMES={0:'Боксы',1:'Посуда',2:'Дополнения',3:'Напитки',4:'Доставка',5:'Премиум',6:'Банкетное меню'}; let currentItemId='',lastPdfUrl='',wrappedEditBox=null; function toast(message,type='success'){ try{return window.SunEnterprise?.toast?.(message,type)}catch(_){} if(type==='warn'||type==='error')console.warn(message);else console.log(message); } function catalog(){ try{ const rows=window.CateriumDataV1773?.catalog?.list?.(); if(Array.isArray(rows))return rows; }catch(_){} try{ const rows=JSON.parse(localStorage.getItem('sunBoxes')||'[]'); return Array.isArray(rows)?rows:[]; }catch(_){return[]} } function itemById(id){return catalog().find(x=>String(x?.id)===String(id))||null} function categoryName(item){return String(item?.catalogSection||CATEGORY_NAMES[Number(item?.category||0)]||'Каталог').trim()} function price(item){try{return Number(window.CateriumPricing?.price?.(item)??item?.price??0)||0}catch(_){return Number(item?.price||0)||0}} function money(value){return `${Number(value||0).toLocaleString('ru-RU',{maximumFractionDigits:2})} ₽`} function brand(){ try{ const b=window.CateriumBranding?.identity?.(); if(b)return b; }catch(_){} return {name:'Caterium',logo:'',contacts:''}; } function cleanIngredientName(name){return String(name||'').replace(/\s*[—–-]\s*\d+(?:[.,]\d+)?\s*г(?:\s*\/\s*шт\.?)?\s*$/i,'').trim()} function composition(item){ if(Array.isArray(item?.composition)&&item.composition.some(Boolean))return item.composition.map(x=>String(x||'').trim()).filter(Boolean); if(Array.isArray(item?.ingredients))return item.ingredients.filter(x=>Array.isArray(x)&&x[0]).map(row=>{ const qty=Math.max(0,Number(row[1]||0)),unit=String(row[2]||'').trim(),name=cleanIngredientName(row[0]); if(!qty||(/^поз\.?$/i.test(unit)&&qty<=1))return name; const shown=Number.isInteger(qty)?String(qty):qty.toLocaleString('ru-RU',{maximumFractionDigits:2}); return `${name}${unit?` — ${shown} ${unit}`:''}`; }).filter(Boolean); return []; } function safeImageSrc(src){ const raw=String(src||'').trim();if(!raw)return''; try{return window.SunSafe?.imageAssetSrc?window.SunSafe.imageAssetSrc(raw):raw}catch(_){return raw} } function loadImage(src){ return new Promise(resolve=>{ const raw=safeImageSrc(src);if(!raw)return resolve(null); const img=new Image();let done=false; const finish=v=>{if(done)return;done=true;clearTimeout(timer);resolve(v)}; try{const u=new URL(raw,document.baseURI||location.href);if(/^https?:$/i.test(u.protocol)&&u.origin!==location.origin)img.crossOrigin='anonymous'}catch(_){} img.onload=()=>finish(img);img.onerror=()=>finish(null); const timer=setTimeout(()=>finish(null),15000);img.src=raw; }); } function drawCover(ctx,img,x,y,w,h){ if(!img)return; const iw=img.naturalWidth||img.width,ih=img.naturalHeight||img.height;if(!iw||!ih)return; const scale=Math.max(w/iw,h/ih),sw=w/scale,sh=h/scale,sx=(iw-sw)/2,sy=(ih-sh)/2; ctx.drawImage(img,sx,sy,sw,sh,x,y,w,h); } function drawContain(ctx,img,x,y,w,h){ if(!img)return; const iw=img.naturalWidth||img.width,ih=img.naturalHeight||img.height;if(!iw||!ih)return; const scale=Math.min(w/iw,h/ih),dw=iw*scale,dh=ih*scale; ctx.drawImage(img,x+(w-dw)/2,y+(h-dh)/2,dw,dh); } function wrap(ctx,text,width,maxLines=20){ const words=String(text||'').replace(/\s+/g,' ').trim().split(' ').filter(Boolean),lines=[];let line=''; for(const word of words){ const test=line?`${line} ${word}`:word; if(!line||ctx.measureText(test).width<=width)line=test; else{lines.push(line);line=word;if(lines.length>=maxLines-1)break} } if(line&&lines.length2&&ctx.measureText(last+'…').width>width)last=last.slice(0,-1); lines[maxLines-1]=last.replace(/[\s,.;:-]+$/,'')+'…'; } return lines; } function filename(name){ const base=String(name||'Бокс').trim().replace(/[\\/:*?"<>|]+/g,' ').replace(/\s+/g,' ').slice(0,80)||'Бокс'; return `${base}.pdf`; } function canvasJpeg(canvas){ const data=canvas.toDataURL('image/jpeg',.94),base64=String(data).split(',')[1]||''; if(!base64)throw new Error('Не удалось подготовить страницу PDF.'); const raw=atob(base64),bytes=new Uint8Array(raw.length);for(let i=0;ictx.fillText(line,38,titleY+i*58)); ctx.textAlign='right';ctx.fillStyle='#171719';ctx.font='400 50px Arial,sans-serif';ctx.fillText(money(price(item)),1197,priceY); ctx.strokeStyle='#c94f67';ctx.lineWidth=5;ctx.beginPath();ctx.moveTo(970,1212);ctx.lineTo(1197,1212);ctx.stroke();ctx.textAlign='left'; const metaY=1260; ctx.textAlign='right';ctx.fillStyle='#242426';ctx.font='500 25px Arial,sans-serif'; const pieces=Math.max(0,Math.round(Number(item.pieces||0))); if(pieces)ctx.fillText(`${pieces} шт.`,1197,metaY); if(item.weight)ctx.fillText(`Вес: ${String(item.weight)}`,1197,metaY+(pieces?38:0)); ctx.textAlign='left'; const lines=composition(item); ctx.fillStyle='#55565a';ctx.font='400 24px Arial,sans-serif'; let y=1328,rendered=0; for(const raw of lines){ const parts=wrap(ctx,raw,760,2); for(const part of parts){ if(rendered>=8)break; ctx.fillText(part,38,y);y+=34;rendered++; } if(rendered>=8)break; } if(!rendered){ctx.fillStyle='#83858a';ctx.fillText('Состав не указан.',38,y)} // Company mark in the lower-right, similar to the reference watermark. ctx.textAlign='right';ctx.fillStyle='#76b9b4';ctx.font='500 22px Arial,sans-serif'; ctx.fillText(String(b.name||window.CateriumBranding?.documentName?.()||'Caterium'),1197,1708); ctx.textAlign='left'; return canvasJpeg(canvas); } async function buildItemPdfBlob(id){ const item=itemById(id);if(!item)throw new Error('Позиция не найдена в каталоге.'); if(!window.SunPdfEngine?.fromJpegs)throw new Error('PDF-движок ещё не загружен.'); const page=await renderItemPage(item); return window.SunPdfEngine.fromJpegs([page]); } function loadingPage(win,item){ try{ win.document.open();win.document.write(`${esc(item?.name||'PDF бокса')}
Формируем PDF
Один бокс · одна страница A4
`);win.document.close(); }catch(_){} } async function openItemPdf(id){ const item=itemById(id);if(!item){toast('Позиция не найдена в каталоге.','warn');return} const viewer=window.open('about:blank','_blank');if(viewer)loadingPage(viewer,item); try{ const blob=await buildItemPdfBlob(id); if(lastPdfUrl)URL.revokeObjectURL(lastPdfUrl);lastPdfUrl=URL.createObjectURL(blob); if(viewer)viewer.location.replace(lastPdfUrl); else{ const a=document.createElement('a');a.href=lastPdfUrl;a.download=filename(item.name);a.style.display='none';document.body.appendChild(a);a.click();a.remove(); toast('PDF бокса подготовлен.','success'); } }catch(error){ try{if(viewer)viewer.document.body.innerHTML=`
Не удалось сформировать PDF.

${esc(error?.message||error)}

`}catch(_){} toast(error?.message||'Не удалось сформировать PDF.','warn'); } } function ensureEditorButton(){ const actions=document.querySelector('#editor .dialog .actions');if(!actions)return null; let btn=$('sunSingleItemPdfButton'); if(!btn){ btn=document.createElement('button');btn.id='sunSingleItemPdfButton';btn.type='button';btn.className='outline';btn.textContent='PDF бокса'; btn.title='Открыть одностраничный PDF выбранного бокса'; btn.onclick=()=>{const id=String(btn.dataset.itemId||'');if(id)void openItemPdf(id)}; const danger=actions.querySelector('.danger');if(danger)danger.before(btn);else actions.appendChild(btn); } return btn; } function syncEditorButton(id){ currentItemId=String(id||'');const btn=ensureEditorButton();if(!btn)return; const item=currentItemId?itemById(currentItemId):null; btn.dataset.itemId=currentItemId; btn.hidden=!item;btn.style.display=item?'inline-flex':'none'; btn.textContent=item&&[0,5].includes(Number(item.category||0))?'PDF бокса':'PDF позиции'; } function injectReadOnlyButton(){ const host=document.querySelector('#sunMenuDetailV1762 .sun-menu-readonly');if(!host||!currentItemId||host.querySelector('[data-single-item-pdf]'))return; const item=itemById(currentItemId);if(!item)return; const btn=document.createElement('button');btn.type='button';btn.className='outline';btn.dataset.singleItemPdf='1';btn.textContent=[0,5].includes(Number(item.category||0))?'PDF бокса':'PDF позиции'; btn.style.margin='0 0 14px';btn.onclick=()=>void openItemPdf(currentItemId); host.insertBefore(btn,host.firstElementChild?.nextSibling||host.firstChild); } function wrapEditor(){ if(typeof window.editBox!=='function'||window.editBox===wrappedEditBox)return; const previous=window.editBox; wrappedEditBox=function(id,...args){ currentItemId=String(id||''); const result=previous.call(this,id,...args); setTimeout(()=>syncEditorButton(currentItemId),0); return result; }; wrappedEditBox.__singlePdfWrapped=true;window.editBox=wrappedEditBox; } function boot(){ ensureEditorButton();syncEditorButton(''); wrapEditor(); document.addEventListener('click',event=>{ const row=event.target.closest?.('[data-menu-item-v1762]');if(row){currentItemId=String(row.dataset.menuItemV1762||'');setTimeout(injectReadOnlyButton,0)} },true); new MutationObserver(()=>{if(window.editBox!==wrappedEditBox)wrapEditor();if(document.querySelector('#editor .dialog .actions')&&!$('sunSingleItemPdfButton'))syncEditorButton(currentItemId);injectReadOnlyButton();}).observe(document.documentElement,{childList:true,subtree:true}); } window.CateriumSingleItemPdf=Object.freeze({VERSION,buildItemPdfBlob,openItemPdf,renderItemPage}); window.sunBuildSingleCatalogItemPdfBlob=buildItemPdfBlob; window.sunOpenSingleCatalogItemPdf=openItemPdf; window.sunSyncSingleCatalogPdfButton=syncEditorButton; if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot(); })();