diff --git a/package.json b/package.json index 6040cb8..cd3cbfe 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "caterium-app", "private": true, - "version": "17.6.3", + "version": "17.6.4", "type": "module", "scripts": { - "check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/performance.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/pdf-engine.js", + "check:syntax": "node --check public/app-runtime.js && node --check public/service-worker.js && node --check public/legacy/bootstrap.js && node --check public/core/sun-safe.js && node --check public/core/performance.js && node --check public/core/hotfix-v1763.js && node --check public/core/ops-ux-v1762.js && node --check public/core/ux-fixes-v1764.js && node --check public/core/pdf-engine.js", "test:static": "node tests/static-security.mjs", "check:release": "node tests/release-check.mjs", "check:deploy": "npm run check:syntax && npm run test:static && npm run check:release", diff --git a/public/core/performance.js b/public/core/performance.js index ab7af74..444ee69 100644 --- a/public/core/performance.js +++ b/public/core/performance.js @@ -1,5 +1,7 @@ (()=>{ 'use strict'; + const VERSION='17.6.4'; + const RELEASE='20260907-v17-6-4-settings-orders-pdf-menu'; const critical=img=>img.closest('header,.brand,#sunCloudAuthGate,.sun-auth-gate')||img.id==='sunLoginLogo'||img.classList.contains('sun-live-catalog-logo'); const tune=img=>{ if(!(img instanceof HTMLImageElement)||critical(img))return; @@ -53,39 +55,60 @@ const input=e.target;if(!(input instanceof HTMLInputElement)||input.id!=='sunChatFilesV29'||guardedInputs.has(input)||typeof DataTransfer==='undefined')return; e.preventDefault();e.stopImmediatePropagation();guardChatFiles(input).catch(err=>{console.error('[Caterium photo compression]',err);guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);}); },true); - window.SunAttachmentGuard={VERSION:'17.6.3',MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment}; + window.SunAttachmentGuard={VERSION,MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment}; - let memoryData=null,memoryAt=0,memoryLoading=false; + let memoryData=null,memoryAt=0,memoryPromise=null,memoryTimer=0; + const MEMORY_CACHE_MS=15000,MEMORY_REFRESH_MS=30000,MEMORY_TIMEOUT_MS=8000; const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??''); - async function loadMemory(){ - const now=Date.now();if(memoryLoading)return memoryData;if(memoryData&&now-memoryAt<5000)return memoryData; - const c=window.SunCloudV2?.getClient?.();if(!c)return null;memoryLoading=true; - try{const r=await c.rpc('sun_dev_dashboard');if(r.error)throw r.error;memoryData=r.data||null;memoryAt=Date.now();return memoryData;}catch(_){return null}finally{memoryLoading=false;} + const developerVisible=()=>document.getElementById('sun-developer-console-v22')?.classList.contains('on'); + async function loadMemory({force=false}={}){ + const now=Date.now();if(memoryPromise)return memoryPromise;if(!force&&memoryData&&now-memoryAt{ + let timer=0; + try{ + const timeout=new Promise((_,reject)=>{timer=setTimeout(()=>reject(new Error('Developer memory timeout')),MEMORY_TIMEOUT_MS)}); + const rpc=Promise.resolve(c.rpc('sun_dev_dashboard')); + const r=await Promise.race([rpc,timeout]); + if(r?.error)throw r.error;memoryData=r?.data||null;memoryAt=Date.now();return memoryData; + }catch(error){console.warn('[Caterium developer memory]',error?.message||error);return memoryData} + finally{clearTimeout(timer);memoryPromise=null} + })(); + return memoryPromise; } - async function enhanceDeveloperMemory(){ - const view=document.getElementById('sun-developer-console-v22');if(!view?.classList.contains('on'))return; - const body=document.getElementById('sunDevBody');if(!body)return; - const version=document.getElementById('sunDevReleaseVersion');if(version)version.textContent='17.6.3'; - const d=await loadMemory();if(!d||!view.classList.contains('on'))return; + async function enhanceDeveloperMemory({force=false}={}){ + const view=document.getElementById('sun-developer-console-v22');if(!view?.classList.contains('on'))return null; + const body=document.getElementById('sunDevBody');if(!body)return null; + const version=document.getElementById('sunDevReleaseVersion');if(version)version.textContent=VERSION; + const d=await loadMemory({force});if(!d||!developerVisible())return d; let box=document.getElementById('sunDevMemoryV1761');if(!box){box=document.createElement('div');box.id='sunDevMemoryV1761';box.className='sun-dev-grid';box.style.marginBottom='12px';body.prepend(box);} - box.innerHTML=`
Память сервера${esc(d.server_size||d.database_size||'—')}
База PostgreSQL${esc(d.database_size||'—')}
Файлы Storage${esc(d.storage_size||'—')}
Объектов Storage${Number(d.storage_objects||0)}
`; + const html=`
Память сервера${esc(d.server_size||d.database_size||'—')}
База PostgreSQL${esc(d.database_size||'—')}
Файлы Storage${esc(d.storage_size||'—')}
Объектов Storage${Number(d.storage_objects||0)}
`; + if(box.innerHTML!==html)box.innerHTML=html; + return d; } + function scheduleMemoryRefresh(delay=80,force=false){setTimeout(()=>{if(developerVisible())enhanceDeveloperMemory({force}).catch(()=>{})},delay)} + function startMemoryTimer(){if(memoryTimer)return;memoryTimer=setInterval(()=>{if(!document.hidden&&developerVisible())enhanceDeveloperMemory({force:true}).catch(()=>{})},MEMORY_REFRESH_MS)} + function loadHotfix(){ if(window.SunHotfixV1763||document.getElementById('sunHotfixV1763Script'))return; - const script=document.createElement('script');script.id='sunHotfixV1763Script';script.src='core/hotfix-v1763.js?v=20260907-v17-6-3-developer-hotfix';script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль hotfix-v1763.js');document.head.appendChild(script); + const script=document.createElement('script');script.id='sunHotfixV1763Script';script.src=`core/hotfix-v1763.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль hotfix-v1763.js');document.head.appendChild(script); } function loadOpsUX(){ if(window.SunOpsUXV1762||document.getElementById('sunOpsUXV1762Script'))return; - const script=document.createElement('script');script.id='sunOpsUXV1762Script';script.src='core/ops-ux-v1762.js?v=20260907-v17-6-3-developer-hotfix';script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ops-ux-v1762.js');document.head.appendChild(script); + const script=document.createElement('script');script.id='sunOpsUXV1762Script';script.src=`core/ops-ux-v1762.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ops-ux-v1762.js');document.head.appendChild(script); + } + function loadUXFix(){ + if(window.SunUXFixV1764||document.getElementById('sunUXFixV1764Script'))return; + const script=document.createElement('script');script.id='sunUXFixV1764Script';script.src=`core/ux-fixes-v1764.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль ux-fixes-v1764.js');document.head.appendChild(script); } const start=()=>{ - loadHotfix();loadOpsUX();scan(document); - const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory();}); + loadHotfix();loadOpsUX();loadUXFix();scan(document);startMemoryTimer(); + const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));}); mo.observe(document.documentElement,{childList:true,subtree:true}); - document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))setTimeout(enhanceDeveloperMemory,100)},true); - window.addEventListener('sun:cloud-state-applied',()=>setTimeout(enhanceDeveloperMemory,150)); - setInterval(()=>{if(document.getElementById('sun-developer-console-v22')?.classList.contains('on'))enhanceDeveloperMemory();},5000); - window.SunPerformance={scanImages:()=>scan(document),refreshDeveloperMemory:enhanceDeveloperMemory,loadHotfix,loadOpsUX,disconnect:()=>mo.disconnect()}; + document.addEventListener('click',e=>{if(e.target.closest('#sunDeveloperNavV22,[data-dev-tab],#sunDevRefresh'))scheduleMemoryRefresh(100,true)},true); + window.addEventListener('sun:cloud-state-applied',()=>scheduleMemoryRefresh(180,true)); + document.addEventListener('visibilitychange',()=>{if(!document.hidden&&developerVisible())scheduleMemoryRefresh(50,false)}); + window.SunPerformance={VERSION,scanImages:()=>scan(document),refreshDeveloperMemory:(force=true)=>enhanceDeveloperMemory({force}),loadHotfix,loadOpsUX,loadUXFix,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0;}}}; }; if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start(); })(); diff --git a/public/core/ux-fixes-v1764.js b/public/core/ux-fixes-v1764.js new file mode 100644 index 0000000..c32158d --- /dev/null +++ b/public/core/ux-fixes-v1764.js @@ -0,0 +1,224 @@ +(()=>{ + 'use strict'; + if(window.SunUXFixV1764)return; + + const VERSION='17.6.4'; + const RELEASE_DAY='2026-09-07'; + const AUTO_DELAY_MS=60*1000; + const AUTO_POLL_MS=15000; + const TEMPLATE_IDS=new Set(['light','editorial-grid','midnight-glass','emerald-gold']); + const $=id=>document.getElementById(id); + const qa=(s,r=document)=>[...r.querySelectorAll(s)]; + const esc=v=>window.SunSafe?.escapeHTML?window.SunSafe.escapeHTML(String(v??'')):String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); + const toast=(text,type='info',ms=4200)=>{try{return window.SunEnterprise?.toast?.(text,type,ms)}catch(_){};}; + const clone=value=>{try{return structuredClone(value)}catch(_){return JSON.parse(JSON.stringify(value))}}; + + function runtimeOrders(){ + try{if(typeof orders!=='undefined'&&Array.isArray(orders))return orders}catch(_){} + return null; + } + function readOrders(){ + const live=runtimeOrders();if(live)return live; + try{const value=JSON.parse(localStorage.getItem('sunOrders')||'[]');return Array.isArray(value)?value:[]}catch(_){return[]} + } + function persistOrders(list){ + try{ + const live=runtimeOrders(); + if(live&&live!==list){live.length=0;live.push(...list)} + if(typeof persist==='function')persist(); + else localStorage.setItem('sunOrders',JSON.stringify(list)); + return true; + }catch(error){ + try{localStorage.setItem('sunOrders',JSON.stringify(list));return true}catch(_){console.error('[Caterium v17.6.4] order persist failed',error);return false} + } + } + function orderById(id){return readOrders().find(o=>String(o?.id)===String(id))||null} + function currentDraftId(){try{return draft?.id??null}catch(_){return null}} + function isSupportReadOnly(){ + try{if(window.SunCloudV2?.getSupportMode?.()||window.SunCloudV2?.isSupportMode?.())return true}catch(_){} + return false; + } + function canAutoWrite(){ + if(isSupportReadOnly())return false; + const cloud=window.SunCloudV2; + try{ + const signed=Boolean(cloud?.getSession?.()?.user); + if(!signed)return true; + if(typeof cloud?.hasPermission==='function')return cloud.hasPermission('orders.edit')||cloud.hasPermission('app.write'); + }catch(_){} + return true; + } + function orderTotal(order){ + try{const value=Number(window.orderTotalValue?.(order));if(Number.isFinite(value))return Math.max(0,value)}catch(_){} + const direct=Number(order?.total);return Number.isFinite(direct)?Math.max(0,direct):0; + } + function dueAt(order){ + const date=String(order?.date||''),time=String(order?.time||''); + if(!/^\d{4}-\d{2}-\d{2}$/.test(date)||!/^\d{2}:\d{2}$/.test(time)||date=today)return '0001-01-01'; + return date; + } + function cardDateParts(raw){ + const d=raw?new Date(`${raw}T12:00:00`):null; + if(!d||Number.isNaN(d.getTime()))return {day:'—',month:'',weekday:'Дата'}; + return {day:String(d.getDate()).padStart(2,'0'),month:d.toLocaleDateString('ru-RU',{month:'short'}).replace('.',''),weekday:d.toLocaleDateString('ru-RU',{weekday:'short'}).replace('.','')}; + } + function restoreRenderedOrderDates(){ + const map=new Map(readOrders().map(o=>[String(o?.id),o])); + qa('#sunOrderCards .sun-order-card').forEach(card=>{ + const order=map.get(String(card.dataset.orderId||''));if(!order||!isAutoCompleted(order))return; + const tile=card.querySelector('.sun-order-card-date'),parts=cardDateParts(order.date);if(!tile)return; + const day=tile.querySelector('strong'),month=tile.querySelector('span'),small=tile.querySelector('small'); + if(day)day.textContent=parts.day;if(month)month.textContent=parts.month;if(small)small.textContent=order.time||parts.weekday; + card.dataset.sunAutoCompleted='1'; + }); + qa('#ordersList .row').forEach(row=>{ + const id=String(row.querySelector('span')?.textContent||'').replace(/\D/g,'');const order=map.get(id);if(!order||!isAutoCompleted(order))return; + if(row.children[2])row.children[2].textContent=[order.date,order.time].filter(Boolean).join(' · '); + row.dataset.sunAutoCompleted='1'; + }); + } + function patchOrdersRender(){ + const current=window.renderOrders;if(typeof current!=='function'||current.__sunV1764)return false; + const wrapped=function(...args){ + const live=runtimeOrders(),changed=[];const today=localToday(); + if(live){for(const order of live){const fake=classificationDate(order,today);if(fake&&fake!==order.date){changed.push([order,order.date]);order.date=fake}}} + let result; + try{result=current.apply(this,args)}finally{for(const [order,date] of changed)order.date=date} + restoreRenderedOrderDates();setTimeout(restoreRenderedOrderDates,0);return result; + }; + wrapped.__sunV1764=true;wrapped.__sunV1764Base=current;window.renderOrders=wrapped;return true; + } + + let activeOfferOrderId=null,offerApiPatched=false,originalOfferGet=null; + function templateList(){try{return window.SunOfferTemplate?.list?.()||[]}catch(_){return[]}} + function validTemplate(id){return TEMPLATE_IDS.has(String(id||''))} + function storedTemplateId(order){ + const id=order?.clientOfferSnapshot?.offerTemplateId||order?.clientOfferTemplateId||'';return validTemplate(id)?String(id):''; + } + function globalTemplateId(){ + try{const state=originalOfferGet?originalOfferGet():window.SunOfferTemplate?.get?.();return validTemplate(state?.id)?state.id:'light'}catch(_){return'light'} + } + function activeTemplateId(){return storedTemplateId(orderById(activeOfferOrderId))||globalTemplateId()} + function patchOfferTemplateApi(){ + const api=window.SunOfferTemplate;if(!api||offerApiPatched||typeof api.get!=='function')return false; + originalOfferGet=api.get.bind(api); + api.get=()=>{ + const base=originalOfferGet(); + const modal=$('sunClientOfferModal');if(!activeOfferOrderId||!modal?.classList.contains('on'))return base; + const id=activeTemplateId(),template=templateList().find(x=>x.id===id)||base?.template; + return {...base,id,template:template?{...template}:template}; + }; + offerApiPatched=true;return true; + } + function persistOfferTemplate(orderId,id){ + if(!orderId||!validTemplate(id))return false; + const list=readOrders(),order=list.find(o=>String(o?.id)===String(orderId));if(!order)return false; + order.clientOfferTemplateId=id; + if(order.clientOfferSnapshot&&typeof order.clientOfferSnapshot==='object')order.clientOfferSnapshot.offerTemplateId=id; + try{if(typeof draft!=='undefined'&&draft&&String(draft.id)===String(orderId)){draft.clientOfferTemplateId=id;if(draft.clientOfferSnapshot)draft.clientOfferSnapshot.offerTemplateId=id}}catch(_){} + const ok=persistOrders(list);if(ok)try{window.dispatchEvent(new CustomEvent('sunoffertemplatechange',{detail:{id,orderId:String(orderId),perOffer:true}}))}catch(_){} + return ok; + } + function ensureOfferTemplateStored(){ + const order=orderById(activeOfferOrderId);if(!order||storedTemplateId(order))return storedTemplateId(order); + const id=globalTemplateId();persistOfferTemplate(activeOfferOrderId,id);return id; + } + function pickerHtml(){ + const current=activeTemplateId();return `
Оформление PDFШаблон закрепляется только за этим предложением.
${templateList().map(t=>``).join('')}
`; + } + function ensureOfferPicker(){ + patchOfferTemplateApi();const modal=$('sunClientOfferModal'),dialog=modal?.querySelector('.dialog');if(!dialog||!activeOfferOrderId)return false; + ensureOfferTemplateStored();let box=$('sunOfferTemplateV1764'); + if(!box){box=document.createElement('section');box.id='sunOfferTemplateV1764';box.className='sun-v1764-offer-template';const head=dialog.querySelector('.sun-offer-modal-head');if(head)head.insertAdjacentElement('afterend',box);else dialog.prepend(box);box.addEventListener('click',e=>{const b=e.target.closest('[data-v1764-offer-template]');if(!b)return;const id=b.dataset.v1764OfferTemplate;if(!persistOfferTemplate(activeOfferOrderId,id))return;renderOfferPicker();toast(`Оформление «${templateList().find(x=>x.id===id)?.name||id}» сохранено для этого предложения.`,'success',3200)});} + renderOfferPicker();return true; + } + function renderOfferPicker(){const box=$('sunOfferTemplateV1764');if(box)box.innerHTML=pickerHtml()} + function setActiveOffer(id){if(id==null||id==='')return;activeOfferOrderId=String(id);setTimeout(ensureOfferPicker,0)} + function clearActiveOffer(){activeOfferOrderId=null} + function patchOfferOpenExports(){ + const one=window.sunOpenClientOffer;if(typeof one==='function'&&!one.__sunV1764){const w=function(id,...rest){setActiveOffer(id);return one.call(this,id,...rest)};w.__sunV1764=true;window.sunOpenClientOffer=w} + const cur=window.sunOpenCurrentClientOffer;if(typeof cur==='function'&&!cur.__sunV1764){const w=function(...args){setActiveOffer(currentDraftId());return cur.apply(this,args)};w.__sunV1764=true;window.sunOpenCurrentClientOffer=w} + } + + const menuSvg=''; + function installStyles(){ + if($('sunV1764Style'))return;const style=document.createElement('style');style.id='sunV1764Style';style.textContent=` + header nav .nav-menu{display:inline-flex!important;align-items:center;gap:7px}.sun-v1764-menu-icon{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto} + #new .catalog-head .sun-v1764-menu-action{display:inline-flex;align-items:center;gap:7px}.sun-v1764-menu-action .sun-v1764-menu-icon{width:17px;height:17px} + .sun-v1764-offer-template{padding:12px 16px;border-bottom:1px solid #e7e9ec;background:#fbfbfc}.sun-v1764-offer-template-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:9px}.sun-v1764-offer-template-head b{color:#273746}.sun-v1764-offer-template-head small{display:block;color:#7a838c;margin-top:3px}.sun-v1764-offer-template-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px}.sun-v1764-offer-template-grid button{border:1px solid #dfe3e7;background:#fff;border-radius:11px;padding:6px;text-align:left;color:#3f4d59}.sun-v1764-offer-template-grid button.on{border-color:var(--g);box-shadow:0 0 0 2px color-mix(in srgb,var(--g) 20%,transparent)}.sun-v1764-offer-template-grid img{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:7px;background:#eef1f3}.sun-v1764-offer-template-grid span{display:block;padding:6px 3px 2px;font-size:11px;font-weight:800;line-height:1.2} + #sunOrderCards .sun-order-card[data-sun-auto-completed="1"]{opacity:.88}#sunOrderCards .sun-order-card[data-sun-auto-completed="1"] .sun-order-card-date{filter:saturate(.8)} + @media(max-width:760px){.sun-v1764-offer-template-grid{grid-template-columns:1fr 1fr}} + `;document.head.appendChild(style); + } + function decorateMenuIcon(){ + let changed=false; + qa('header nav button.nav-menu,header nav button[data-nav-label="Меню"]').forEach(button=>{if(button.dataset.sunMenuIconV1764==='1')return;button.dataset.sunMenuIconV1764='1';button.innerHTML=`${menuSvg}Меню`;changed=true}); + qa('#new .catalog-head button').filter(b=>String(b.textContent||'').trim()==='Меню').forEach(button=>{if(button.dataset.sunMenuIconV1764==='1')return;button.dataset.sunMenuIconV1764='1';button.classList.add('sun-v1764-menu-action');button.innerHTML=`${menuSvg}Меню`;changed=true}); + return changed; + } + + let autoTimer=0,maintainTimer=0; + function maintain(){patchOrdersRender();patchOfferTemplateApi();patchOfferOpenExports();decorateMenuIcon();if(activeOfferOrderId)$('sunClientOfferModal')?.classList.contains('on')?ensureOfferPicker():clearActiveOffer()} + function startTimers(){ + if(!autoTimer)autoTimer=setInterval(()=>{if(!document.hidden)autoCompleteOrders(Date.now(),{silent:false})},AUTO_POLL_MS); + if(!maintainTimer)maintainTimer=setInterval(()=>{if(!document.hidden)maintain()},2000); + } + function onClickCapture(e){ + const direct=e.target?.closest?.('[data-sun-offer-id]');if(direct?.dataset?.sunOfferId)setActiveOffer(direct.dataset.sunOfferId); + else if(e.target?.closest?.('#sunClientOfferButton'))setActiveOffer(currentDraftId()); + if(e.target?.closest?.('#sunOfferClose'))setTimeout(clearActiveOffer,0); + } + function boot(){ + installStyles();patchOrdersRender();patchOfferTemplateApi();patchOfferOpenExports();decorateMenuIcon();autoCompleteOrders(Date.now(),{silent:true});startTimers(); + document.addEventListener('click',onClickCapture,true); + document.addEventListener('visibilitychange',()=>{if(!document.hidden){maintain();autoCompleteOrders(Date.now(),{silent:false})}}); + window.addEventListener('focus',()=>autoCompleteOrders(Date.now(),{silent:false})); + window.addEventListener('sun:cloud-state-applied',()=>setTimeout(()=>{maintain();autoCompleteOrders(Date.now(),{silent:true})},80)); + } + if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot(); + + window.SunUXFixV1764={ + VERSION,AUTO_DELAY_MS,RELEASE_DAY, + autoCompleteOrders,dueAt,isAutoCompleted,classificationDate, + patchOrdersRender,restoreRenderedOrderDates, + activeTemplateId,persistOfferTemplate,ensureOfferPicker, + decorateMenuIcon, + checks:()=>({version:VERSION,autoDelay:AUTO_DELAY_MS,menuIcon:Boolean(document.querySelector('.sun-v1764-menu-icon')),offerPicker:Boolean($('sunOfferTemplateV1764')),renderPatched:Boolean(window.renderOrders?.__sunV1764)}) + }; +})(); diff --git a/public/service-worker.js b/public/service-worker.js index 80beccd..de00dda 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,8 +1,8 @@ -const CACHE='sun-catering-pwa-v68-20260907-v17-6-3-developer-hotfix'; -const VERSION='20260907-v17-6-3-developer-hotfix'; +const CACHE='sun-catering-pwa-v69-20260907-v17-6-4-settings-orders-pdf-menu'; +const VERSION='20260907-v17-6-4-settings-orders-pdf-menu'; const CORE=[ './','./index.html', - `./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`, + `./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/hotfix-v1763.js?v=${VERSION}`,`./core/ops-ux-v1762.js?v=${VERSION}`,`./core/ux-fixes-v1764.js?v=${VERSION}`,`./core/pdf-engine.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`, './offer-gallery/001.jpg','./offer-gallery/002.jpg', './catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg', './sun-logo.png','./caterium-login-logo.png','./pwa-icon-192.png','./pwa-icon-512.png','./manifest.webmanifest', diff --git a/tests/app.spec.mjs b/tests/app.spec.mjs index 08e22e1..33b21f7 100644 --- a/tests/app.spec.mjs +++ b/tests/app.spec.mjs @@ -7,6 +7,11 @@ async function loadModule(page,file,globalName){ await page.addScriptTag({content}); expect(await page.evaluate(name=>Boolean(window[name]),globalName)).toBeTruthy(); } +async function injectCore(page,file,globalName){ + const content=fs.readFileSync(path.join(process.cwd(),'public','core',file),'utf8'); + await page.addScriptTag({content}); + if(globalName)expect(await page.evaluate(name=>Boolean(window[name]),globalName)).toBeTruthy(); +} test('legacy localStorage payload cannot execute XSS', async ({ page }) => { const pageErrors=[]; page.on('pageerror',e=>pageErrors.push(String(e))); await page.addInitScript(() => { @@ -91,6 +96,67 @@ test('v17.6.3 developer gate bypasses workspace loading and SaaS click is safe', const args=await page.evaluate(()=>window.__devOpenArgs); expect(args).toEqual(['null','null']); }); +test('v17.6.4 auto-completes and fully pays an order one minute after scheduled time', async ({ page }) => { + await page.addInitScript(()=>{ + localStorage.setItem('sunOrders',JSON.stringify([{id:764,event:'Тест авто-завершения',date:'2099-09-07',time:'12:00',status:'Новый',prepayment:100,total:1500,lines:[]}])) + }); + await page.goto('/index.html',{waitUntil:'domcontentloaded'}); + await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764'); + const result=await page.evaluate(()=>{ + window.orderTotalValue=()=>1500; + const run=window.SunUXFixV1764.autoCompleteOrders(new Date('2099-09-07T12:01:01').getTime(),{silent:true}); + const order=JSON.parse(localStorage.getItem('sunOrders')||'[]').find(x=>String(x.id)==='764'); + return {run,prepayment:order?.prepayment,balance:order?.balance,status:order?.status,auto:Boolean(order?.sunAutoCompletedAt),classDate:window.SunUXFixV1764.classificationDate(order,'2099-09-07')}; + }); + expect(result.run.changed).toBe(1); + expect(result.prepayment).toBe(1500); + expect(result.balance).toBe(0); + expect(result.status).toBe('Отдан заказчику'); + expect(result.auto).toBeTruthy(); + expect(result.classDate).toBe('0001-01-01'); +}); +test('v17.6.4 keeps proposal designs per order and renders a styled menu icon', async ({ page }) => { + await page.addInitScript(()=>{ + localStorage.setItem('sunOrders',JSON.stringify([ + {id:801,event:'Editorial',date:'2099-09-08',time:'12:00',clientOfferSnapshot:{version:8,offerTemplateId:'editorial-grid'}}, + {id:802,event:'Emerald',date:'2099-09-08',time:'13:00',clientOfferSnapshot:{version:8,offerTemplateId:'emerald-gold'}} + ])); + }); + await page.goto('/index.html',{waitUntil:'domcontentloaded'}); + await page.evaluate(()=>{ + let nav=document.querySelector('header nav'); + if(!nav){const header=document.createElement('header');nav=document.createElement('nav');header.appendChild(nav);document.body.prepend(header)} + let menu=nav.querySelector('.nav-menu');if(!menu){menu=document.createElement('button');menu.type='button';menu.className='sun-nav-button nav-menu';menu.dataset.navLabel='Меню';menu.textContent='Меню';nav.appendChild(menu)} + }); + await injectCore(page,'ux-fixes-v1764.js','SunUXFixV1764'); + await page.waitForFunction(()=>Boolean(document.querySelector('header nav .nav-menu .sun-v1764-menu-icon')),null,{timeout:5000}); + const result=await page.evaluate(()=>{ + window.SunUXFixV1764.persistOfferTemplate('801','midnight-glass'); + const list=JSON.parse(localStorage.getItem('sunOrders')||'[]'); + const one=list.find(x=>String(x.id)==='801'),two=list.find(x=>String(x.id)==='802'); + return {one:one?.clientOfferTemplateId,snapOne:one?.clientOfferSnapshot?.offerTemplateId,two:two?.clientOfferSnapshot?.offerTemplateId,icon:Boolean(document.querySelector('header nav .nav-menu .sun-v1764-menu-icon'))}; + }); + expect(result.one).toBe('midnight-glass'); + expect(result.snapOne).toBe('midnight-glass'); + expect(result.two).toBe('emerald-gold'); + expect(result.icon).toBeTruthy(); +}); +test('Developer Console memory refresh is bounded and does not react to its own DOM update', async ({ page }) => { + await page.goto('/index.html',{waitUntil:'domcontentloaded'}); + const performance=fs.readFileSync(path.join(process.cwd(),'public','core','performance.js'),'utf8'); + await page.evaluate(()=>{ + document.body.innerHTML='
'; + window.__rpcCount=0; + window.SunCloudV2={getClient:()=>({rpc:async()=>{window.__rpcCount++;return {data:{server_size:'35 MB',database_size:'21 MB',storage_size:'14 MB',storage_objects:12}}}})}; + }); + await page.addScriptTag({content:performance}); + await page.waitForFunction(()=>Boolean(window.SunPerformance),null,{timeout:5000}); + await page.evaluate(async()=>{await window.SunPerformance.refreshDeveloperMemory(true);await new Promise(r=>setTimeout(r,250));}); + const result=await page.evaluate(()=>({count:window.__rpcCount,box:document.querySelectorAll('#sunDevMemoryV1761').length,version:document.getElementById('sunDevReleaseVersion')?.textContent})); + expect(result.count).toBeLessThanOrEqual(2); + expect(result.box).toBe(1); + expect(result.version).toBe('17.6.4'); +}); test('mobile body does not overflow viewport', async ({ page }, testInfo) => { test.skip(testInfo.project.name!=='mobile-390'); await page.goto('/index.html', { waitUntil:'domcontentloaded' }); await page.waitForTimeout(500); const dims=await page.evaluate(()=>({innerWidth,scrollWidth:document.documentElement.scrollWidth,bodyWidth:document.body.scrollWidth})); diff --git a/tests/release-check.mjs b/tests/release-check.mjs index aeb2fed..bb91604 100644 --- a/tests/release-check.mjs +++ b/tests/release-check.mjs @@ -3,16 +3,19 @@ import path from 'node:path'; const root=process.cwd(), pub=path.join(root,'public'); const read=p=>fs.readFileSync(path.join(pub,p),'utf8'); let bad=0;const check=(v,m)=>{console.log(`${v?'OK':'FAIL'}: ${m}`);if(!v)bad++}; -const index=read('index.html'),runtime=read('app-runtime.js'),sw=read('service-worker.js'),css=read('core/stability-v1760.css'),performance=read('core/performance.js'),ops=read('core/ops-ux-v1762.js'),hotfix=read('core/hotfix-v1763.js'); +const index=read('index.html'),runtime=read('app-runtime.js'),sw=read('service-worker.js'),css=read('core/stability-v1760.css'),performance=read('core/performance.js'),ops=read('core/ops-ux-v1762.js'),hotfix=read('core/hotfix-v1763.js'),ux=read('core/ux-fixes-v1764.js'); check(index.includes('core/stability-v1760.css'),'mobile stability stylesheet loaded'); check(css.includes('overflow-x:hidden')&&css.includes('.cats'),'mobile overflow guard present'); check(!index.includes('offer-gallery-data.js'),'blocking Base64 gallery absent'); check((runtime.match(/\/Type \/Catalog/g)||[]).length===0,'runtime contains no PDF binary writer'); check(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox'); check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4'); -check(sw.includes('v17-6-3-developer-hotfix')&&sw.includes('hotfix-v1763.js'),'service worker cache is v17.6.3'); +check(sw.includes('v17-6-4-settings-orders-pdf-menu')&&sw.includes('ux-fixes-v1764.js'),'service worker cache is v17.6.4'); check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned'); check(performance.includes("rpc('sun_dev_dashboard')")&&performance.includes('server_size')&&performance.includes('storage_size'),'Developer Console server/storage counters are versioned'); +check(performance.includes('MEMORY_REFRESH_MS=30000')&&performance.includes('MEMORY_TIMEOUT_MS=8000')&&performance.includes('memoryPromise'),'Developer Console memory refresh is bounded'); +check(!performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'),'Developer Console refresh is not mutation-driven'); +check(performance.includes('ux-fixes-v1764.js')&&performance.includes('SunUXFixV1764'),'v17.6.4 UX module is loaded'); check(performance.includes('hotfix-v1763.js')&&performance.includes('SunHotfixV1763'),'developer/SaaS hotfix is loaded'); check(hotfix.includes('patchDeveloperOpen')&&hotfix.includes('enhanceDeveloperGate')&&hotfix.includes('data-saas-admin'),'developer gate and SaaS click hotfix is versioned'); check(hotfix.includes('source instanceof HTMLElement')&&hotfix.includes('stopImmediatePropagation'),'SaaS event object cannot reach Developer Console as a nav button'); @@ -22,6 +25,10 @@ check(ops.includes('sun-menu-editor-v1762')&&ops.includes('Премиум бок check(ops.includes('showCalendarDay')&&ops.includes('.cal-more'),'calendar overflow panel is versioned'); check(ops.includes("ROUTE_BASE_KEY='sunRouteBaseV1'")&&ops.includes('saveRouteStart'),'configurable route origin is versioned'); check(ops.includes('showRouteOrder')&&ops.includes('data-route-open'),'route order modal is versioned'); +check(ux.includes("AUTO_DELAY_MS=60*1000")&&ux.includes("order.status='Отдан заказчику'")&&ux.includes('order.prepayment=total'),'orders auto-complete and auto-pay one minute after due time'); +check(ux.includes('classificationDate')&&ux.includes('sunAutoCompletedAt'),'auto-completed orders move to completed list without losing original date'); +check(ux.includes('persistOfferTemplate')&&ux.includes('clientOfferTemplateId')&&ux.includes('offerTemplateId'),'client proposal template is stored per offer'); +check(ux.includes('sun-v1764-menu-icon')&&ux.includes('fs.readFileSync(path.join(pub,p),'utf8'); const readRoot=p=>fs.readFileSync(path.join(root,p),'utf8'); const fail=m=>{console.error('FAIL:',m);process.exitCode=1}; const ok=m=>console.log('OK:',m); -const html=readPub('index.html'),legacy=readPub('legacy/bootstrap.js'),runtime=readPub('app-runtime.js'),safe=readPub('core/sun-safe.js'),sw=readPub('service-worker.js'),performance=readPub('core/performance.js'),ops=readPub('core/ops-ux-v1762.js'),hotfix=readPub('core/hotfix-v1763.js'); +const html=readPub('index.html'),legacy=readPub('legacy/bootstrap.js'),runtime=readPub('app-runtime.js'),safe=readPub('core/sun-safe.js'),sw=readPub('service-worker.js'),performance=readPub('core/performance.js'),ops=readPub('core/ops-ux-v1762.js'),hotfix=readPub('core/hotfix-v1763.js'),ux=readPub('core/ux-fixes-v1764.js'); if(!html.includes('core/sun-safe.js'))fail('SunSafe must load before legacy modules');else ok('shared SunSafe loaded'); if(html.includes('offer-gallery-data.js')||fs.existsSync(path.join(pub,'offer-gallery-data.js')))fail('blocking offer-gallery-data.js still present');else ok('base64 gallery removed'); for(const raw of ['${b.name}','${o.event}','${o.address||','value="${x[0]}"','value="${x[2]}"']) if(legacy.includes(raw)) fail(`legacy bootstrap contains raw HTML interpolation ${raw}`); @@ -27,12 +27,16 @@ if(current!==113)fail(`current catalog photo count ${current}, expected 113`);el if(legacyCount!==60)fail(`legacy catalog photo count ${legacyCount}, expected 60`);else ok('60 legacy catalog photos'); const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x)); if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed'); -if(!sw.includes('v17-6-3-developer-hotfix')||!sw.includes('hotfix-v1763.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated'); +if(!sw.includes('v17-6-4-settings-orders-pdf-menu')||!sw.includes('ux-fixes-v1764.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated'); if(!performance.includes('SunAttachmentGuard')||!performance.includes('MAX_SIDE=2048'))fail('chat photo compression guard missing');else ok('chat photo compression guard present'); if(!performance.includes("rpc('sun_dev_dashboard')")||!performance.includes('storage_size')||!performance.includes('server_size'))fail('Developer Console memory counters missing');else ok('Developer Console memory counters present'); +if(performance.includes('records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)scan(n)}));enhanceDeveloperMemory()'))fail('Developer Console memory refresh is still coupled to MutationObserver');else ok('Developer Console memory refresh loop removed'); +if(!performance.includes('MEMORY_REFRESH_MS=30000')||!performance.includes('MEMORY_TIMEOUT_MS=8000')||!performance.includes('memoryPromise'))fail('Developer Console bounded refresh controls missing');else ok('Developer Console bounded refresh controls present'); +if(!performance.includes('ux-fixes-v1764.js')||!performance.includes('SunUXFixV1764'))fail('v17.6.4 UX fix loader missing');else ok('v17.6.4 UX fix loader present'); if(!performance.includes('hotfix-v1763.js')||!performance.includes('SunHotfixV1763'))fail('v17.6.3 hotfix loader missing');else ok('v17.6.3 hotfix loader present'); if(!performance.includes('ops-ux-v1762.js')||!performance.includes('SunOpsUXV1762'))fail('ops UX loader missing');else ok('ops UX loader present'); for(const marker of ['patchDeveloperOpen','enhanceDeveloperGate','data-saas-admin','stopImmediatePropagation','instanceof HTMLElement']) if(!hotfix.includes(marker))fail(`developer/SaaS hotfix marker missing: ${marker}`);else ok(`developer/SaaS hotfix marker: ${marker}`); for(const marker of ['SUPPORT_POLL_MS=12000','supportReadPermission','sun-menu-editor-v1762','showCalendarDay',"ROUTE_BASE_KEY='sunRouteBaseV1'",'showRouteOrder','routeOpenYandex']) if(!ops.includes(marker)) fail(`ops UX marker missing: ${marker}`); else ok(`ops UX marker: ${marker}`); -if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops+hotfix)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops+hotfix))fail('possible secret in ops/hotfix module');else ok('ops/hotfix modules have no hard-coded secret'); +for(const marker of ["AUTO_DELAY_MS=60*1000","order.prepayment=total","order.status='Отдан заказчику'",'sunAutoCompletedAt','classificationDate','persistOfferTemplate','clientOfferTemplateId','offerTemplateId','sun-v1764-menu-icon']) if(!ux.includes(marker))fail(`v17.6.4 UX marker missing: ${marker}`);else ok(`v17.6.4 UX marker: ${marker}`); +if(/service_role\s*[:=]\s*['"][A-Za-z0-9._-]{20,}/i.test(ops+hotfix+ux)||/eyJ[a-zA-Z0-9_-]{30,}/.test(ops+hotfix+ux))fail('possible secret in ops/hotfix/ux module');else ok('ops/hotfix/ux modules have no hard-coded secret'); if(process.exitCode)process.exit(process.exitCode);