Fix Developer Console memory refresh freeze, auto-complete and fully pay orders one minute after scheduled time, persist offer template per client proposal, and add styled Menu SVG icon. Includes PWA cache update and regression coverage.
225 lines
16 KiB
JavaScript
225 lines
16 KiB
JavaScript
(()=>{
|
|
'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<RELEASE_DAY)return NaN;
|
|
const stamp=new Date(`${date}T${time}:00`).getTime();return Number.isFinite(stamp)?stamp+AUTO_DELAY_MS:NaN;
|
|
}
|
|
function isAutoCompleted(order){return Boolean(order?.sunAutoCompletedAt||order?.sunAutoCompletedV1764)}
|
|
function markOrderCompleted(order,nowMs){
|
|
const total=orderTotal(order),iso=new Date(nowMs).toISOString();
|
|
order.prepayment=total;
|
|
order.balance=0;
|
|
order.status='Отдан заказчику';
|
|
order.paymentStatus='paid';
|
|
order.completedAt=order.completedAt||iso;
|
|
order.paymentCompletedAt=order.paymentCompletedAt||iso;
|
|
order.sunAutoCompletedAt=iso;
|
|
order.sunAutoCompletedV1764=true;
|
|
order.sunAutoCompletedVersion=VERSION;
|
|
return total;
|
|
}
|
|
function autoCompleteOrders(nowMs=Date.now(),{silent=false}={}){
|
|
if(!canAutoWrite())return {changed:0,ids:[]};
|
|
const list=readOrders(),ids=[];
|
|
for(const order of list){
|
|
if(!order||String(order.status||'').trim()==='Отменён'||isAutoCompleted(order))continue;
|
|
const due=dueAt(order);if(!Number.isFinite(due)||nowMs<due)continue;
|
|
markOrderCompleted(order,nowMs);ids.push(String(order.id));
|
|
}
|
|
if(!ids.length)return {changed:0,ids};
|
|
if(!persistOrders(list))return {changed:0,ids:[]};
|
|
try{window.render?.();window.renderOrders?.();window.renderStats?.();window.renderClients?.()}catch(_){}
|
|
try{window.dispatchEvent(new CustomEvent('sun:orders-auto-completed',{detail:{ids,version:VERSION}}))}catch(_){}
|
|
if(!silent){const label=ids.length===1?`Заказ №${ids[0]} автоматически завершён и полностью оплачен.`:`${ids.length} заказа автоматически завершены и полностью оплачены.`;toast(label,'success',5000)}
|
|
return {changed:ids.length,ids};
|
|
}
|
|
|
|
function localToday(){const d=new Date();return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`}
|
|
function classificationDate(order,today=localToday()){
|
|
const date=String(order?.date||'');
|
|
if(isAutoCompleted(order)&&/^\d{4}-\d{2}-\d{2}$/.test(date)&&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 `<div class="sun-v1764-offer-template-head"><div><b>Оформление PDF</b><small>Шаблон закрепляется только за этим предложением.</small></div></div><div class="sun-v1764-offer-template-grid">${templateList().map(t=>`<button type="button" data-v1764-offer-template="${esc(t.id)}" class="${t.id===current?'on':''}"><img src="${esc(t.thumb||'')}" alt=""><span>${esc(t.name||t.id)}</span></button>`).join('')}</div>`;
|
|
}
|
|
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='<svg class="sun-v1764-menu-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 17.5h16"/><path d="M6 16.5a6 6 0 0 1 12 0"/><path d="M12 10V7.5"/><path d="M10.5 7.5h3"/><path d="M3.5 19.5h17"/></svg>';
|
|
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}<span>Меню</span>`;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}<span>Меню</span>`;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)})
|
|
};
|
|
})();
|