Add order comments, reminders, search and delivery totals
Add per-line comments, day/hour order reminders, catalog search by number/name, and delivery-inclusive financial summary. Includes static regression checks.
This commit is contained in:
parent
dbbc416676
commit
9bf75ccf11
@ -4,8 +4,8 @@
|
||||
"version": "17.7.3",
|
||||
"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/auth-security-v1774.js && node --check public/core/data-layer-v1773.js && node --check public/core/server-automation-v1770.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 && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js",
|
||||
"test:static": "node tests/static-security.mjs && node tests/auth-security-v1774.mjs && node tests/employee-create-v1774.mjs && node tests/html-integrity-v1774.mjs && node tests/edge-security-v1774.mjs && node tests/branding-v1774.mjs",
|
||||
"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/auth-security-v1774.js && node --check public/core/order-enhancements-v1775.js && node --check public/core/data-layer-v1773.js && node --check public/core/server-automation-v1770.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 && node --check public/core/classic-offer-pdf-v1767.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js",
|
||||
"test:static": "node tests/static-security.mjs && node tests/auth-security-v1774.mjs && node tests/employee-create-v1774.mjs && node tests/html-integrity-v1774.mjs && node tests/edge-security-v1774.mjs && node tests/branding-v1774.mjs && node tests/order-enhancements-v1775.mjs",
|
||||
"check:release": "node tests/release-check.mjs",
|
||||
"check:deploy": "npm run check:syntax && npm run test:static && npm run check:release",
|
||||
"test:e2e": "playwright test --config=tests/playwright.config.mjs",
|
||||
|
||||
231
public/core/order-enhancements-v1775.js
Normal file
231
public/core/order-enhancements-v1775.js
Normal file
@ -0,0 +1,231 @@
|
||||
(()=>{
|
||||
'use strict';
|
||||
if(window.__cateriumOrderEnhancementsV1775)return;
|
||||
window.__cateriumOrderEnhancementsV1775=true;
|
||||
|
||||
const $=(s,r=document)=>r.querySelector(s);
|
||||
const $$=(s,r=document)=>[...r.querySelectorAll(s)];
|
||||
const fmt=v=>typeof money==='function'?money(v):`${Math.round(Number(v||0)).toLocaleString('ru-RU')} ₽`;
|
||||
const safeText=v=>String(v??'').trim();
|
||||
const activeLines=()=>Array.isArray(draft?.lines)?draft.lines.filter(line=>boxes.some(item=>String(item.id)===String(line.id))):[];
|
||||
|
||||
function ensureStyles(){
|
||||
if($('#caterium-order-enhancements-v1775-style'))return;
|
||||
const style=document.createElement('style');
|
||||
style.id='caterium-order-enhancements-v1775-style';
|
||||
style.textContent=`
|
||||
#sunCatalogSearchV1775{width:100%;margin:10px 0 4px;padding:10px 12px;border:1px solid #d6dbe0;border-radius:9px;background:#fff}
|
||||
.sun-line-comment-v1775{grid-column:1/-1;display:grid;grid-template-columns:118px minmax(0,1fr);gap:8px;align-items:center;margin-top:3px}
|
||||
.sun-line-comment-v1775 span{font-size:11px;font-weight:700;color:#7d8790}
|
||||
.sun-line-comment-v1775 input{width:100%;min-width:0;height:31px;padding:5px 8px;font-size:12px;border-radius:7px}
|
||||
.sun-order-reminders-v1775{grid-column:1/-1;margin:2px 0 10px;padding:10px 12px;border:1px solid #e1e5e9;border-radius:10px;background:#fafbfc}
|
||||
.sun-order-reminders-v1775>strong{display:block;margin-bottom:7px;color:#30394f;font-size:12px}
|
||||
.sun-order-reminders-v1775 .opts{display:flex;gap:14px;flex-wrap:wrap}
|
||||
.sun-order-reminders-v1775 label{display:flex!important;flex-direction:row!important;align-items:center!important;gap:7px!important;color:#4f5963!important;font-size:12px!important}
|
||||
.sun-order-reminders-v1775 input{width:16px;height:16px;margin:0}
|
||||
.sun-calendar-reminder-v1775{display:inline-flex;align-items:center;gap:4px;margin-top:5px;padding:3px 6px;border-radius:999px;background:#fff7d7;color:#795c00;font-size:10px;font-weight:800}
|
||||
.sun-toast-v1775{position:fixed;right:18px;bottom:18px;z-index:99999;max-width:min(390px,calc(100vw - 36px));padding:13px 15px;border-radius:12px;background:#15364c;color:#fff;box-shadow:0 10px 30px #0003;font-size:13px;line-height:1.4}
|
||||
.order-summary .sun-summary-grand-v1775{margin-top:7px;padding-top:9px;border-top:2px solid #c99a32;font-size:18px;font-weight:800;color:#15364c}
|
||||
@media(max-width:760px){
|
||||
#sunCatalogSearchV1775{display:block;font-size:16px!important}
|
||||
.sun-line-comment-v1775{grid-template-columns:1fr;gap:4px;margin-top:5px}
|
||||
.sun-line-comment-v1775 input{font-size:16px!important;height:38px}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function ensureCatalogSearch(){
|
||||
const catalog=$('#new .catalog');
|
||||
if(!catalog||$('#sunCatalogSearchV1775'))return;
|
||||
const input=document.createElement('input');
|
||||
input.id='sunCatalogSearchV1775';
|
||||
input.type='search';
|
||||
input.placeholder='Поиск по № бокса или названию';
|
||||
input.autocomplete='off';
|
||||
const head=$('.catalog-head',catalog);
|
||||
head?.insertAdjacentElement('afterend',input);
|
||||
input.addEventListener('input',filterCatalog);
|
||||
}
|
||||
|
||||
function norm(v){return safeText(v).toLowerCase().replace(/ё/g,'е').replace(/\s+/g,' ')}
|
||||
function filterCatalog(){
|
||||
const input=$('#sunCatalogSearchV1775');
|
||||
const query=norm(input?.value);
|
||||
$$('#tiles .tile').forEach(tile=>{
|
||||
if(tile.classList.contains('add')){tile.hidden=Boolean(query);return;}
|
||||
const text=norm(tile.textContent);
|
||||
tile.hidden=Boolean(query&&!text.includes(query));
|
||||
});
|
||||
}
|
||||
|
||||
function decorateLineComments(){
|
||||
const rows=$$('#orderForm .sun-order-line-row');
|
||||
const lines=activeLines();
|
||||
rows.forEach((row,index)=>{
|
||||
const line=lines[index];
|
||||
if(!line)return;
|
||||
let wrap=$('.sun-line-comment-v1775',row);
|
||||
if(!wrap){
|
||||
wrap=document.createElement('label');
|
||||
wrap.className='sun-line-comment-v1775';
|
||||
wrap.innerHTML='<span>Комментарий</span><input type="text" maxlength="300" placeholder="Аллергия, убрать ингредиент, изменение состава…">';
|
||||
row.appendChild(wrap);
|
||||
}
|
||||
const input=$('input',wrap);
|
||||
const wanted=safeText(line.comment);
|
||||
if(document.activeElement!==input&&input.value!==wanted)input.value=wanted;
|
||||
if(!input.dataset.boundV1775){
|
||||
input.dataset.boundV1775='1';
|
||||
input.addEventListener('input',()=>{line.comment=input.value.slice(0,300);});
|
||||
input.addEventListener('change',()=>{line.comment=input.value.trim().slice(0,300);});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function ensureReminderControls(){
|
||||
const schedule=$('#details .sun-order-schedule');
|
||||
if(!schedule||$('#sunOrderRemindersV1775'))return;
|
||||
const box=document.createElement('div');
|
||||
box.id='sunOrderRemindersV1775';box.className='sun-order-reminders-v1775';
|
||||
box.innerHTML='<strong>Напоминание по заказу</strong><div class="opts"><label><input type="checkbox" data-reminder="day"> За 1 день</label><label><input type="checkbox" data-reminder="hour"> За 1 час</label></div>';
|
||||
schedule.insertAdjacentElement('afterend',box);
|
||||
$$('input[data-reminder]',box).forEach(input=>input.addEventListener('change',()=>{
|
||||
draft.reminders={day:Boolean($('[data-reminder="day"]',box)?.checked),hour:Boolean($('[data-reminder="hour"]',box)?.checked)};
|
||||
decorateCalendarReminderBadges();
|
||||
requestNotificationPermission();
|
||||
}));
|
||||
syncReminderControls();
|
||||
}
|
||||
|
||||
function syncReminderControls(){
|
||||
const box=$('#sunOrderRemindersV1775');if(!box)return;
|
||||
const r=draft?.reminders||{};
|
||||
const day=$('[data-reminder="day"]',box),hour=$('[data-reminder="hour"]',box);
|
||||
if(day)day.checked=Boolean(r.day);if(hour)hour.checked=Boolean(r.hour);
|
||||
}
|
||||
|
||||
function deliveryValue(){
|
||||
const input=$('#orderDeliveryCost');
|
||||
if(input)return Math.max(0,Number(input.value||0));
|
||||
return Math.max(0,Number(draft?.costs?.delivery||draft?.deliveryCost||0));
|
||||
}
|
||||
function discountValue(){
|
||||
const candidates=['#orderDiscount','#discount','#orderDiscountAmount'];
|
||||
for(const selector of candidates){const el=$(selector);if(el)return Math.max(0,Number(el.value||0));}
|
||||
return Math.max(0,Number(draft?.discountAmount||draft?.discount||0));
|
||||
}
|
||||
function promoValue(){
|
||||
const candidates=['#orderPromoDiscount','#promoDiscount','#orderPromoAmount'];
|
||||
for(const selector of candidates){const el=$(selector);if(el)return Math.max(0,Number(el.value||0));}
|
||||
return Math.max(0,Number(draft?.promoDiscount||draft?.promoAmount||0));
|
||||
}
|
||||
function financials(){
|
||||
const positions=Math.max(0,Number(typeof calcOrderTotal==='function'?calcOrderTotal():0));
|
||||
const delivery=deliveryValue(),discount=discountValue(),promo=promoValue();
|
||||
const total=Math.max(0,positions-discount-promo+delivery);
|
||||
const paid=Math.max(0,Number($('#prepayment')?.value||draft?.prepayment||0));
|
||||
return {positions,delivery,discount,promo,total,paid,balance:Math.max(0,total-paid)};
|
||||
}
|
||||
|
||||
function updateFinancialSummary(){
|
||||
const host=$('.order-summary');if(!host)return;
|
||||
const f=financials();
|
||||
const position=$('#summaryTotal');if(position)position.textContent=fmt(f.positions);
|
||||
let deliveryRow=$('#sunSummaryDeliveryV1775');
|
||||
if(!deliveryRow){
|
||||
deliveryRow=document.createElement('p');deliveryRow.id='sunSummaryDeliveryV1775';
|
||||
const prepayment=$('#summaryPrepayment')?.closest('p');
|
||||
(prepayment||host.lastElementChild)?.insertAdjacentElement('beforebegin',deliveryRow);
|
||||
}
|
||||
deliveryRow.innerHTML=`<span>Доставка</span><b>${fmt(f.delivery)}</b>`;
|
||||
let totalRow=$('#sunSummaryGrandV1775');
|
||||
if(!totalRow){totalRow=document.createElement('p');totalRow.id='sunSummaryGrandV1775';totalRow.className='sun-summary-grand-v1775';deliveryRow.insertAdjacentElement('afterend',totalRow);}
|
||||
totalRow.innerHTML=`<span>Итого</span><b>${fmt(f.total)}</b>`;
|
||||
const orderTotal=$('#orderTotal');if(orderTotal)orderTotal.value=Math.round(f.total);
|
||||
const balance=$('#balance');if(balance)balance.value=Math.round(f.balance);
|
||||
const summaryPrepayment=$('#summaryPrepayment');if(summaryPrepayment)summaryPrepayment.textContent=fmt(f.paid);
|
||||
const summaryBalance=$('#summaryBalance');if(summaryBalance)summaryBalance.textContent=fmt(f.balance);
|
||||
draft.total=f.total;draft.balance=f.balance;
|
||||
}
|
||||
|
||||
function bindDeliveryTotal(){
|
||||
const input=$('#orderDeliveryCost');if(!input||input.dataset.totalBoundV1775)return;
|
||||
input.dataset.totalBoundV1775='1';
|
||||
['input','change'].forEach(evt=>input.addEventListener(evt,()=>{if(draft.costs)draft.costs.delivery=Math.max(0,Number(input.value||0));updateFinancialSummary();}));
|
||||
}
|
||||
|
||||
function orderDue(order){
|
||||
if(!order?.date||!order?.time)return null;
|
||||
const d=new Date(`${order.date}T${order.time}:00`);return Number.isNaN(d.getTime())?null:d;
|
||||
}
|
||||
function firedKey(orderId,kind,due){return `caterium.reminder.${orderId}.${kind}.${due.toISOString()}`}
|
||||
function showToast(text){
|
||||
$('.sun-toast-v1775')?.remove();
|
||||
const el=document.createElement('div');el.className='sun-toast-v1775';el.textContent=text;document.body.appendChild(el);setTimeout(()=>el.remove(),10000);
|
||||
}
|
||||
async function requestNotificationPermission(){
|
||||
try{if('Notification' in window&&Notification.permission==='default')await Notification.requestPermission();}catch(_){}
|
||||
}
|
||||
function fireReminder(order,kind,due){
|
||||
const label=kind==='day'?'через 1 день':'через 1 час';
|
||||
const title=`Заказ №${order.id||'—'} — ${label}`;
|
||||
const body=[order.event||'Заказ',order.time||'',order.address||''].filter(Boolean).join(' · ');
|
||||
showToast(`${title}: ${body}`);
|
||||
try{if('Notification' in window&&Notification.permission==='granted')new Notification(title,{body,tag:`caterium-order-${order.id}-${kind}`});}catch(_){}
|
||||
}
|
||||
function scanReminders(){
|
||||
if(!Array.isArray(orders))return;
|
||||
const now=Date.now();
|
||||
orders.forEach(order=>{
|
||||
const due=orderDue(order);if(!due||due.getTime()<=now)return;
|
||||
const reminders=order.reminders||{};
|
||||
[['day',86400000],['hour',3600000]].forEach(([kind,lead])=>{
|
||||
if(!reminders[kind])return;
|
||||
const trigger=due.getTime()-lead;
|
||||
if(now<trigger||now>trigger+120000)return;
|
||||
const key=firedKey(order.id,kind,due);if(localStorage.getItem(key))return;
|
||||
localStorage.setItem(key,String(now));fireReminder(order,kind,due);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function decorateCalendarReminderBadges(){
|
||||
$$('.sun-calendar-reminder-v1775').forEach(x=>x.remove());
|
||||
if(!Array.isArray(orders))return;
|
||||
$$('#sunOrderCards [data-card-status]').forEach(status=>{
|
||||
const order=orders.find(o=>String(o.id)===String(status.dataset.cardStatus));if(!order)return;
|
||||
const r=order.reminders||{};const labels=[];if(r.day)labels.push('1 день');if(r.hour)labels.push('1 час');if(!labels.length)return;
|
||||
const badge=document.createElement('span');badge.className='sun-calendar-reminder-v1775';badge.textContent='🔔 '+labels.join(' + ');
|
||||
(status.closest('.sun-order-card')||status.parentElement)?.appendChild(badge);
|
||||
});
|
||||
}
|
||||
|
||||
function postRender(){
|
||||
ensureCatalogSearch();filterCatalog();decorateLineComments();ensureReminderControls();syncReminderControls();bindDeliveryTotal();updateFinancialSummary();decorateCalendarReminderBadges();
|
||||
}
|
||||
|
||||
const wrap=(name,after)=>{
|
||||
const old=window[name];if(typeof old!=='function'||old.__v1775Wrapped)return;
|
||||
const next=function(...args){const result=old.apply(this,args);after?.();setTimeout(postRender,0);return result;};
|
||||
next.__v1775Wrapped=true;window[name]=next;
|
||||
};
|
||||
|
||||
ensureStyles();ensureCatalogSearch();ensureReminderControls();
|
||||
wrap('render');wrap('openOrder',syncReminderControls);wrap('resetDraft',()=>{draft.reminders={day:false,hour:false};syncReminderControls();});wrap('renderOrders',decorateCalendarReminderBadges);
|
||||
const oldUpdate=window.updateOrderSummary;
|
||||
if(typeof oldUpdate==='function'){
|
||||
window.updateOrderSummary=function(...args){const result=oldUpdate.apply(this,args);updateFinancialSummary();return result;};
|
||||
}
|
||||
const oldSave=window.saveOrder;
|
||||
if(typeof oldSave==='function'){
|
||||
window.saveOrder=function(...args){
|
||||
updateFinancialSummary();
|
||||
const result=oldSave.apply(this,args);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
document.addEventListener('input',e=>{if(e.target?.id==='prepayment')updateFinancialSummary();},true);
|
||||
new MutationObserver(()=>{bindDeliveryTotal();decorateCalendarReminderBadges();}).observe(document.body,{childList:true,subtree:true});
|
||||
postRender();scanReminders();setInterval(scanReminders,30000);
|
||||
})();
|
||||
@ -3,16 +3,8 @@
|
||||
const VERSION='17.7.3';
|
||||
const RELEASE='20260909-v17-7-3-clients-server-read';
|
||||
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;
|
||||
if(!img.hasAttribute('loading'))img.loading='lazy';
|
||||
if(!img.hasAttribute('decoding'))img.decoding='async';
|
||||
if(!img.hasAttribute('fetchpriority'))img.setAttribute('fetchpriority','low');
|
||||
};
|
||||
const scan=root=>{
|
||||
if(root instanceof HTMLImageElement)tune(root);
|
||||
root?.querySelectorAll?.('img').forEach(tune);
|
||||
};
|
||||
const tune=img=>{if(!(img instanceof HTMLImageElement)||critical(img))return;if(!img.hasAttribute('loading'))img.loading='lazy';if(!img.hasAttribute('decoding'))img.decoding='async';if(!img.hasAttribute('fetchpriority'))img.setAttribute('fetchpriority','low');};
|
||||
const scan=root=>{if(root instanceof HTMLImageElement)tune(root);root?.querySelectorAll?.('img').forEach(tune);};
|
||||
const pendingImageRoots=new Set();let imageScanTimer=0;
|
||||
function queueImageScan(root){if(!root||root.nodeType!==1)return;for(const existing of [...pendingImageRoots]){if(existing===root||existing.contains?.(root))return;if(root.contains?.(existing))pendingImageRoots.delete(existing)}pendingImageRoots.add(root);if(imageScanTimer)return;imageScanTimer=setTimeout(()=>{imageScanTimer=0;const roots=[...pendingImageRoots];pendingImageRoots.clear();roots.forEach(scan)},80);}
|
||||
|
||||
@ -21,116 +13,32 @@
|
||||
const toast=(text,type='info',ms=4500)=>{try{return window.SunEnterprise?.toast?.(text,type,ms)}catch(_){}};
|
||||
const jpegName=name=>String(name||'photo').replace(/\.[^.]+$/,'')+'.jpg';
|
||||
const canvasBlob=(canvas,type,quality)=>new Promise((resolve,reject)=>canvas.toBlob(b=>b?resolve(b):reject(new Error('Не удалось сжать изображение.')),type,quality));
|
||||
async function decodeImage(file){
|
||||
if('createImageBitmap' in window){const bitmap=await createImageBitmap(file);return {source:bitmap,width:bitmap.width,height:bitmap.height,close:()=>bitmap.close?.()};}
|
||||
const url=URL.createObjectURL(file);try{const img=new Image();await new Promise((resolve,reject)=>{img.onload=resolve;img.onerror=reject;img.src=url});return {source:img,width:img.naturalWidth,height:img.naturalHeight,close:()=>URL.revokeObjectURL(url)};}catch(e){URL.revokeObjectURL(url);throw e;}
|
||||
}
|
||||
async function compressImage(file){
|
||||
if(!(file instanceof File)||!String(file.type||'').startsWith('image/')||/gif|svg/i.test(file.type||''))return file;
|
||||
let decoded;try{decoded=await decodeImage(file);}catch(_){return file;}
|
||||
try{
|
||||
const {width,height,source}=decoded;if(!width||!height)return file;
|
||||
if(file.size<=TARGET&&Math.max(width,height)<=MAX_SIDE)return file;
|
||||
let scale=Math.min(1,MAX_SIDE/Math.max(width,height)),quality=.84,best=null;
|
||||
for(let pass=0;pass<5;pass++){
|
||||
const w=Math.max(1,Math.round(width*scale)),h=Math.max(1,Math.round(height*scale));
|
||||
const canvas=document.createElement('canvas');canvas.width=w;canvas.height=h;
|
||||
const ctx=canvas.getContext('2d',{alpha:false});if(!ctx)return file;ctx.fillStyle='#fff';ctx.fillRect(0,0,w,h);ctx.drawImage(source,0,0,w,h);
|
||||
const blob=await canvasBlob(canvas,'image/jpeg',quality);if(!best||blob.size<best.size)best=blob;if(blob.size<=TARGET)break;
|
||||
quality=Math.max(.60,quality-.08);scale*=.86;
|
||||
}
|
||||
if(!best||best.size>=file.size)return file;
|
||||
return new File([best],jpegName(file.name),{type:'image/jpeg',lastModified:file.lastModified||Date.now()});
|
||||
}finally{decoded.close?.();}
|
||||
}
|
||||
async function decodeImage(file){if('createImageBitmap' in window){const bitmap=await createImageBitmap(file);return {source:bitmap,width:bitmap.width,height:bitmap.height,close:()=>bitmap.close?.()};}const url=URL.createObjectURL(file);try{const img=new Image();await new Promise((resolve,reject)=>{img.onload=resolve;img.onerror=reject;img.src=url});return {source:img,width:img.naturalWidth,height:img.naturalHeight,close:()=>URL.revokeObjectURL(url)};}catch(e){URL.revokeObjectURL(url);throw e;}}
|
||||
async function compressImage(file){if(!(file instanceof File)||!String(file.type||'').startsWith('image/')||/gif|svg/i.test(file.type||''))return file;let decoded;try{decoded=await decodeImage(file);}catch(_){return file;}try{const {width,height,source}=decoded;if(!width||!height)return file;if(file.size<=TARGET&&Math.max(width,height)<=MAX_SIDE)return file;let scale=Math.min(1,MAX_SIDE/Math.max(width,height)),quality=.84,best=null;for(let pass=0;pass<5;pass++){const w=Math.max(1,Math.round(width*scale)),h=Math.max(1,Math.round(height*scale));const canvas=document.createElement('canvas');canvas.width=w;canvas.height=h;const ctx=canvas.getContext('2d',{alpha:false});if(!ctx)return file;ctx.fillStyle='#fff';ctx.fillRect(0,0,w,h);ctx.drawImage(source,0,0,w,h);const blob=await canvasBlob(canvas,'image/jpeg',quality);if(!best||blob.size<best.size)best=blob;if(blob.size<=TARGET)break;quality=Math.max(.60,quality-.08);scale*=.86;}if(!best||best.size>=file.size)return file;return new File([best],jpegName(file.name),{type:'image/jpeg',lastModified:file.lastModified||Date.now()});}finally{decoded.close?.();}}
|
||||
async function prepareAttachment(file){const prepared=await compressImage(file);if(prepared.size>MAX_FILE)throw new Error(`Файл «${file.name}» больше 15 МБ даже после оптимизации.`);return {file:prepared,originalSize:file.size,savedBytes:Math.max(0,file.size-prepared.size)};}
|
||||
async function guardChatFiles(input){
|
||||
const raw=[...(input.files||[])];if(!raw.length)return;
|
||||
const prepared=[];let saved=0;
|
||||
for(const f of raw){try{const p=await prepareAttachment(f);prepared.push(p.file);saved+=p.savedBytes;}catch(e){toast(e.message||String(e),'warn',6000);}}
|
||||
if(!prepared.length){input.value='';return;}
|
||||
const dt=new DataTransfer();prepared.forEach(f=>dt.items.add(f));input.files=dt.files;
|
||||
guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);
|
||||
if(saved>256*1024)toast(`Фото оптимизированы: сэкономлено ${(saved/1024/1024).toFixed(1)} МБ.`,'success',4200);
|
||||
}
|
||||
document.addEventListener('change',e=>{
|
||||
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);
|
||||
async function guardChatFiles(input){const raw=[...(input.files||[])];if(!raw.length)return;const prepared=[];let saved=0;for(const f of raw){try{const p=await prepareAttachment(f);prepared.push(p.file);saved+=p.savedBytes;}catch(e){toast(e.message||String(e),'warn',6000);}}if(!prepared.length){input.value='';return;}const dt=new DataTransfer();prepared.forEach(f=>dt.items.add(f));input.files=dt.files;guardedInputs.add(input);input.dispatchEvent(new Event('change',{bubbles:true}));guardedInputs.delete(input);if(saved>256*1024)toast(`Фото оптимизированы: сэкономлено ${(saved/1024/1024).toFixed(1)} МБ.`,'success',4200);}
|
||||
document.addEventListener('change',e=>{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,MAX_FILE,TARGET,MAX_SIDE,compressImage,prepareAttachment};
|
||||
|
||||
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??'');
|
||||
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<MEMORY_CACHE_MS)return memoryData;
|
||||
const c=window.SunCloudV2?.getClient?.();if(!c)return null;
|
||||
memoryPromise=(async()=>{
|
||||
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({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);}
|
||||
const html=`<div class="sun-dev-kpi"><small>Память сервера</small><b>${esc(d.server_size||d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>База PostgreSQL</small><b>${esc(d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>Файлы Storage</small><b>${esc(d.storage_size||'—')}</b></div><div class="sun-dev-kpi"><small>Объектов Storage</small><b>${Number(d.storage_objects||0)}</b></div>`;
|
||||
if(box.innerHTML!==html)box.innerHTML=html;
|
||||
return d;
|
||||
}
|
||||
async function loadMemory({force=false}={}){const now=Date.now();if(memoryPromise)return memoryPromise;if(!force&&memoryData&&now-memoryAt<MEMORY_CACHE_MS)return memoryData;const c=window.SunCloudV2?.getClient?.();if(!c)return null;memoryPromise=(async()=>{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({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);}const html=`<div class="sun-dev-kpi"><small>Память сервера</small><b>${esc(d.server_size||d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>База PostgreSQL</small><b>${esc(d.database_size||'—')}</b></div><div class="sun-dev-kpi"><small>Файлы Storage</small><b>${esc(d.storage_size||'—')}</b></div><div class="sun-dev-kpi"><small>Объектов Storage</small><b>${Number(d.storage_objects||0)}</b></div>`;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 loadDataLayer(){
|
||||
if(window.CateriumDataV1773||document.getElementById('cateriumDataV1773Script'))return;
|
||||
const script=document.createElement('script');script.id='cateriumDataV1773Script';script.src=`core/data-layer-v1773.js?v=${RELEASE}`;script.async=false;script.onerror=()=>console.error('[Caterium] Не загрузился data-layer-v1773.js');document.head.appendChild(script);
|
||||
}
|
||||
function loadServerAutomation(){
|
||||
if(window.CateriumServerAutomationV1770||document.getElementById('cateriumServerAutomationV1770Script'))return;
|
||||
const script=document.createElement('script');script.id='cateriumServerAutomationV1770Script';script.src=`core/server-automation-v1770.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился server-automation-v1770.js');document.head.appendChild(script);
|
||||
}
|
||||
function loadHotfix(){
|
||||
if(window.SunHotfixV1763||document.getElementById('sunHotfixV1763Script'))return;
|
||||
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=${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);
|
||||
}
|
||||
function loadDeveloperUX(){
|
||||
if(window.SunDeveloperUXV1768||document.getElementById('sunDeveloperUXV1768Script'))return;
|
||||
const script=document.createElement('script');script.id='sunDeveloperUXV1768Script';script.src=`core/developer-console-v1768.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль developer-console-v1768.js');document.head.appendChild(script);
|
||||
}
|
||||
function loadOfferWorkspace(){
|
||||
if(window.SunOfferWorkspaceV1769||document.getElementById('sunOfferWorkspaceV1769Script'))return;
|
||||
const script=document.createElement('script');script.id='sunOfferWorkspaceV1769Script';script.src=`core/offer-workspace-v1769.js?v=${RELEASE}`;script.async=true;script.onerror=()=>console.error('[Caterium] Не загрузился модуль offer-workspace-v1769.js');document.head.appendChild(script);
|
||||
}
|
||||
function loadAuthSecurity(){
|
||||
if(window.CateriumAuthSecurityV1774||document.getElementById('cateriumAuthSecurityV1774Script'))return;
|
||||
const script=document.createElement('script');script.id='cateriumAuthSecurityV1774Script';script.src=`core/auth-security-v1774.js?v=${RELEASE}`;script.async=false;script.onerror=()=>console.error('[Caterium] Не загрузился модуль auth-security-v1774.js');document.head.appendChild(script);
|
||||
}
|
||||
const start=()=>{
|
||||
loadDataLayer();loadServerAutomation();loadHotfix();loadOpsUX();loadUXFix();loadDeveloperUX();loadOfferWorkspace();loadAuthSecurity();scan(document);startMemoryTimer();
|
||||
const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)queueImageScan(n)}));});
|
||||
mo.observe(document.documentElement,{childList:true,subtree:true});
|
||||
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}),loadDataLayer,loadServerAutomation,loadHotfix,loadOpsUX,loadUXFix,loadDeveloperUX,loadOfferWorkspace,loadAuthSecurity,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0}if(imageScanTimer){clearTimeout(imageScanTimer);imageScanTimer=0}pendingImageRoots.clear();}};
|
||||
};
|
||||
const loadScript=(id,src,flag,async=true)=>{if(window[flag]||document.getElementById(id))return;const script=document.createElement('script');script.id=id;script.src=src;script.async=async;script.onerror=()=>console.error(`[Caterium] Не загрузился ${src}`);document.head.appendChild(script);};
|
||||
function loadDataLayer(){loadScript('cateriumDataV1773Script',`core/data-layer-v1773.js?v=${RELEASE}`,'CateriumDataV1773',false)}
|
||||
function loadServerAutomation(){loadScript('cateriumServerAutomationV1770Script',`core/server-automation-v1770.js?v=${RELEASE}`,'CateriumServerAutomationV1770')}
|
||||
function loadHotfix(){loadScript('sunHotfixV1763Script',`core/hotfix-v1763.js?v=${RELEASE}`,'SunHotfixV1763')}
|
||||
function loadOpsUX(){loadScript('sunOpsUXV1762Script',`core/ops-ux-v1762.js?v=${RELEASE}`,'SunOpsUXV1762')}
|
||||
function loadUXFix(){loadScript('sunUXFixV1764Script',`core/ux-fixes-v1764.js?v=${RELEASE}`,'SunUXFixV1764')}
|
||||
function loadDeveloperUX(){loadScript('sunDeveloperUXV1768Script',`core/developer-console-v1768.js?v=${RELEASE}`,'SunDeveloperUXV1768')}
|
||||
function loadOfferWorkspace(){loadScript('sunOfferWorkspaceV1769Script',`core/offer-workspace-v1769.js?v=${RELEASE}`,'SunOfferWorkspaceV1769')}
|
||||
function loadAuthSecurity(){loadScript('cateriumAuthSecurityV1774Script',`core/auth-security-v1774.js?v=${RELEASE}`,'CateriumAuthSecurityV1774',false)}
|
||||
function loadOrderEnhancements(){loadScript('cateriumOrderEnhancementsV1775Script',`core/order-enhancements-v1775.js?v=${RELEASE}`,'__cateriumOrderEnhancementsV1775')}
|
||||
const start=()=>{loadDataLayer();loadServerAutomation();loadHotfix();loadOpsUX();loadUXFix();loadDeveloperUX();loadOfferWorkspace();loadAuthSecurity();loadOrderEnhancements();scan(document);startMemoryTimer();const mo=new MutationObserver(records=>{records.forEach(r=>r.addedNodes.forEach(n=>{if(n.nodeType===1)queueImageScan(n)}));});mo.observe(document.documentElement,{childList:true,subtree:true});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}),loadDataLayer,loadServerAutomation,loadHotfix,loadOpsUX,loadUXFix,loadDeveloperUX,loadOfferWorkspace,loadAuthSecurity,loadOrderEnhancements,disconnect:()=>{mo.disconnect();if(memoryTimer){clearInterval(memoryTimer);memoryTimer=0}if(imageScanTimer){clearTimeout(imageScanTimer);imageScanTimer=0}pendingImageRoots.clear();}};};
|
||||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start();
|
||||
})();
|
||||
17
tests/order-enhancements-v1775.mjs
Normal file
17
tests/order-enhancements-v1775.mjs
Normal file
@ -0,0 +1,17 @@
|
||||
import fs from 'node:fs';
|
||||
const src=fs.readFileSync('public/core/order-enhancements-v1775.js','utf8');
|
||||
const perf=fs.readFileSync('public/core/performance.js','utf8');
|
||||
const checks=[
|
||||
['line comments persisted',/line\.comment/],
|
||||
['allergy/change comment placeholder',/Аллергия, убрать ингредиент/],
|
||||
['day reminder',/data-reminder="day"/],
|
||||
['hour reminder',/data-reminder="hour"/],
|
||||
['catalog search by number/name',/Поиск по № бокса или названию/],
|
||||
['delivery included in total',/positions-discount-promo\+delivery/],
|
||||
['delivery shown separately',/Доставка<\/span>/],
|
||||
['grand total shown',/Итого<\/span>/],
|
||||
['notification support',/new Notification/],
|
||||
['module loaded',/order-enhancements-v1775\.js/],
|
||||
];
|
||||
for(const [name,re] of checks){const target=name==='module loaded'?perf:src;if(!re.test(target))throw new Error(`Missing ${name}`)}
|
||||
console.log('order-enhancements-v1775: ok');
|
||||
Loading…
Reference in New Issue
Block a user