Preserve imported order history and saved order pricing
This commit is contained in:
parent
11b2bf84c9
commit
48ffa51748
7
docs/ORDER-HISTORY-IMPORT-20260917.md
Normal file
7
docs/ORDER-HISTORY-IMPORT-20260917.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Preserving imported order history
|
||||||
|
|
||||||
|
Imported orders can carry `autoCompletionDisabled: true`. Both server and local automation leave their status, prepayment and balance unchanged, so old dates cannot manufacture full payment. The import archive is a typed workspace storage value (`sunTelegramImportArchiveV1`); reads require both order and customer viewing permissions. The Orders screen exposes searchable original messages and images using text-only DOM rendering and validated image data URLs. Customer records and source files remain outside this repository.
|
||||||
|
|
||||||
|
Opening an order now hydrates its delivery and discount controls before reading them back. The late order-pane patch retains shared pricing, and order enhancements use its item subtotal plus delivery once. Saved historical prices and promotion discounts survive open/save.
|
||||||
|
|
||||||
|
Validation: deployment checks and database permission tests; 66 browser checks passed, 2 existing skips. A private rehearsal round-tripped the import, confirmed idempotency and payment preservation, and opened/saved every imported order while comparing names, phone numbers, addresses, notes, prices, payment and line counts.
|
||||||
@ -11,7 +11,7 @@
|
|||||||
"serverReady": true,
|
"serverReady": true,
|
||||||
"workspaceAutoDiscovery": true,
|
"workspaceAutoDiscovery": true,
|
||||||
"invitesTemporarilyDisabled": false,
|
"invitesTemporarilyDisabled": false,
|
||||||
"pwaCache": "v84-20260917-company-branding",
|
"pwaCache": "v85-20260917-order-import",
|
||||||
"fullOfferDescriptions": true,
|
"fullOfferDescriptions": true,
|
||||||
"dynamicOfferRows": true,
|
"dynamicOfferRows": true,
|
||||||
"pdfOfferDescriptionFix": true,
|
"pdfOfferDescriptionFix": true,
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"version": "17.7.3",
|
"version": "17.7.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"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/account-center-v1780.js && node --check public/core/performance.js && node --check public/core/auth-security-v1774.js && node --check public/core/trial-promo-developer-v181.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/signature-offer-pdf-v18.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js && node --check public/core/brand-theme.js && node --check public/core/company-branding.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/account-center-v1780.js && node --check public/core/performance.js && node --check public/core/auth-security-v1774.js && node --check public/core/trial-promo-developer-v181.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/signature-offer-pdf-v18.js && node --check public/core/developer-console-v1768.js && node --check public/core/offer-workspace-v1769.js && node --check public/core/brand-theme.js && node --check public/core/company-branding.js && node --check public/core/import-archive.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",
|
"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:release": "node tests/release-check.mjs",
|
||||||
"check:deploy": "npm run check:syntax && npm run test:static && npm run check:release && node tests/backend-cutover.mjs && npm run test:db",
|
"check:deploy": "npm run check:syntax && npm run test:static && npm run check:release && node tests/backend-cutover.mjs && npm run test:db",
|
||||||
|
|||||||
@ -628,7 +628,9 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
updatePromoMeta();
|
updatePromoMeta();
|
||||||
}
|
}
|
||||||
function updatePromoMeta(){const meta=$('promoCodeMeta'),select=$('promoCodeSelect');if(!meta||!select)return;const id=select.value;if(!id){meta.textContent='Выберите активный промокод из раздела «Рассылки».';return}if(id==='__snapshot__'){meta.textContent=`Условия сохранены в заказе: ${draft?.promoCode||''} · ${draft?.promoDiscountType==='amount'?money(draft?.promoDiscountValue||0):`${Number(draft?.promoDiscountValue||0).toLocaleString('ru-RU',{maximumFractionDigits:2})}%`}.`;return}const p=promoById(id);meta.textContent=p?`${p.code} · скидка ${promoDiscountText(p)} · действует ${p.startDate||'без даты'} — ${p.endDate||'без даты'}`:'Промокод недоступен.';}
|
function updatePromoMeta(){const meta=$('promoCodeMeta'),select=$('promoCodeSelect');if(!meta||!select)return;const id=select.value;if(!id){meta.textContent='Выберите активный промокод из раздела «Рассылки».';return}if(id==='__snapshot__'){meta.textContent=`Условия сохранены в заказе: ${draft?.promoCode||''} · ${draft?.promoDiscountType==='amount'?money(draft?.promoDiscountValue||0):`${Number(draft?.promoDiscountValue||0).toLocaleString('ru-RU',{maximumFractionDigits:2})}%`}.`;return}const p=promoById(id);meta.textContent=p?`${p.code} · скидка ${promoDiscountText(p)} · действует ${p.startDate||'без даты'} — ${p.endDate||'без даты'}`:'Промокод недоступен.';}
|
||||||
|
let hydratingOrder=false;
|
||||||
function captureDiscounts(){
|
function captureDiscounts(){
|
||||||
|
if(hydratingOrder)return;
|
||||||
if(!draft)return;draft.manualDiscountValue=Math.max(0,Number($('manualDiscountValue')?.value||0));draft.manualDiscountType=$('manualDiscountType')?.value==='amount'?'amount':'percent';
|
if(!draft)return;draft.manualDiscountValue=Math.max(0,Number($('manualDiscountValue')?.value||0));draft.manualDiscountType=$('manualDiscountType')?.value==='amount'?'amount':'percent';
|
||||||
const select=$('promoCodeSelect');if(!select)return;const id=String(select.value||'');
|
const select=$('promoCodeSelect');if(!select)return;const id=String(select.value||'');
|
||||||
if(!id){draft.promoId='';draft.promoCode='';draft.promoDiscountValue=0;draft.promoDiscountType='percent';draft.promoStartDate='';draft.promoEndDate='';return}
|
if(!id){draft.promoId='';draft.promoCode='';draft.promoDiscountValue=0;draft.promoDiscountType='percent';draft.promoStartDate='';draft.promoEndDate='';return}
|
||||||
@ -636,7 +638,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
const p=promoById(id);if(!p||!promoActive(p)){renderPromoSelect();return}
|
const p=promoById(id);if(!p||!promoActive(p)){renderPromoSelect();return}
|
||||||
draft.promoId=String(p.id||'');draft.promoCode=String(p.code||'').trim();draft.promoDiscountValue=Math.max(0,Number(p.discountValue||0));draft.promoDiscountType=p.discountType==='amount'?'amount':'percent';draft.promoStartDate=String(p.startDate||'');draft.promoEndDate=String(p.endDate||'');updatePromoMeta();
|
draft.promoId=String(p.id||'');draft.promoCode=String(p.code||'').trim();draft.promoDiscountValue=Math.max(0,Number(p.discountValue||0));draft.promoDiscountType=p.discountType==='amount'?'amount':'percent';draft.promoStartDate=String(p.startDate||'');draft.promoEndDate=String(p.endDate||'');updatePromoMeta();
|
||||||
}
|
}
|
||||||
function captureDelivery(){if(!draft)return;draft.deliveryCost=Math.max(0,Number($('deliveryCost')?.value||0));}
|
function captureDelivery(){if(!draft||hydratingOrder)return;draft.deliveryCost=Math.max(0,Number($('deliveryCost')?.value||0));}
|
||||||
function syncDeliveryField(){ensureDiscountUI();if($('deliveryCost'))$('deliveryCost').value=Math.max(0,Number(draft?.deliveryCost||0));}
|
function syncDeliveryField(){ensureDiscountUI();if($('deliveryCost'))$('deliveryCost').value=Math.max(0,Number(draft?.deliveryCost||0));}
|
||||||
function discountAmount(value,type,base){const n=Math.max(0,Number(value||0));return Math.min(base,type==='amount'?n:base*Math.min(100,n)/100)}
|
function discountAmount(value,type,base){const n=Math.max(0,Number(value||0));return Math.min(base,type==='amount'?n:base*Math.min(100,n)/100)}
|
||||||
function pricing(order=draft){const base=baseTotal(order),manual=discountAmount(order?.manualDiscountValue,order?.manualDiscountType,base),afterManual=Math.max(0,base-manual),promo=discountAmount(order?.promoDiscountValue,order?.promoDiscountType,afterManual),itemsTotal=Math.max(0,afterManual-promo),delivery=Math.max(0,Number(order?.deliveryCost||0)),total=itemsTotal+delivery,paid=Math.max(0,Number(order===draft?($('prepayment')?.value||order?.prepayment||0):(order?.prepayment||0))),balance=Math.max(0,total-paid);return {base,manual,promo,itemsTotal,delivery,total,paid,balance,discount:manual+promo}}
|
function pricing(order=draft){const base=baseTotal(order),manual=discountAmount(order?.manualDiscountValue,order?.manualDiscountType,base),afterManual=Math.max(0,base-manual),promo=discountAmount(order?.promoDiscountValue,order?.promoDiscountType,afterManual),itemsTotal=Math.max(0,afterManual-promo),delivery=Math.max(0,Number(order?.deliveryCost||0)),total=itemsTotal+delivery,paid=Math.max(0,Number(order===draft?($('prepayment')?.value||order?.prepayment||0):(order?.prepayment||0))),balance=Math.max(0,total-paid);return {base,manual,promo,itemsTotal,delivery,total,paid,balance,discount:manual+promo}}
|
||||||
@ -685,7 +687,7 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
const previousSave=window.saveOrder;
|
const previousSave=window.saveOrder;
|
||||||
if(typeof previousSave==='function')window.saveOrder=function(...args){captureBuyerFromFields();captureDiscounts();captureDelivery();const result=previousSave.apply(this,args);renderPromoStats();return result};
|
if(typeof previousSave==='function')window.saveOrder=function(...args){captureBuyerFromFields();captureDiscounts();captureDelivery();const result=previousSave.apply(this,args);renderPromoStats();return result};
|
||||||
const previousOpen=window.openOrder;
|
const previousOpen=window.openOrder;
|
||||||
if(typeof previousOpen==='function')window.openOrder=function(...args){const r=previousOpen.apply(this,args);setTimeout(()=>{syncBuyerFields();syncDiscountFields();syncDeliveryField();setOrderPane('order');patchDocButtons();renderCatalogV5();},0);return r};
|
if(typeof previousOpen==='function')window.openOrder=function(...args){hydratingOrder=true;let r;try{r=previousOpen.apply(this,args)}catch(error){hydratingOrder=false;throw error}setTimeout(()=>{try{syncBuyerFields();syncDiscountFields();syncDeliveryField();setOrderPane('order');patchDocButtons();renderCatalogV5()}finally{hydratingOrder=false;updateOrderSummary()}},0);return r};
|
||||||
const previousReset=window.resetDraft;
|
const previousReset=window.resetDraft;
|
||||||
if(typeof previousReset==='function')window.resetDraft=function(...args){const r=previousReset.apply(this,args);draft.buyerRequisites=buyerDefaults();draft.manualDiscountValue=0;draft.manualDiscountType='percent';draft.promoId='';draft.promoCode='';draft.promoDiscountValue=0;draft.promoDiscountType='percent';draft.promoStartDate='';draft.promoEndDate='';draft.deliveryCost=0;delete draft.qrImage;setTimeout(()=>{syncBuyerFields();syncDiscountFields();syncDeliveryField();setOrderPane('order');renderCatalogV5();},0);return r};
|
if(typeof previousReset==='function')window.resetDraft=function(...args){const r=previousReset.apply(this,args);draft.buyerRequisites=buyerDefaults();draft.manualDiscountValue=0;draft.manualDiscountType='percent';draft.promoId='';draft.promoCode='';draft.promoDiscountValue=0;draft.promoDiscountType='percent';draft.promoStartDate='';draft.promoEndDate='';draft.deliveryCost=0;delete draft.qrImage;setTimeout(()=>{syncBuyerFields();syncDiscountFields();syncDeliveryField();setOrderPane('order');renderCatalogV5();},0);return r};
|
||||||
const previousStats=window.renderStats;
|
const previousStats=window.renderStats;
|
||||||
@ -5738,4 +5740,3 @@ window.SUN_LEGACY_CATALOG_V175=[{"id":"1","name":"Фуршетный бокс
|
|||||||
window.SunChatV31=window.SunChatV29;
|
window.SunChatV31=window.SunChatV29;
|
||||||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',boot,{once:true});else boot();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
29
public/core/import-archive.js
Normal file
29
public/core/import-archive.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
(()=>{
|
||||||
|
'use strict';
|
||||||
|
if(window.CateriumImportArchive)return;
|
||||||
|
const KEY='sunTelegramImportArchiveV1';
|
||||||
|
let dialog=null;
|
||||||
|
const read=()=>{try{return JSON.parse(localStorage.getItem(KEY)||'null')}catch(_){return null}};
|
||||||
|
const node=(tag,text)=>{const e=document.createElement(tag);if(text!==undefined)e.textContent=String(text);return e};
|
||||||
|
function close(){dialog?.remove();dialog=null}
|
||||||
|
function show(){
|
||||||
|
const archive=read();if(!archive)return;close();
|
||||||
|
dialog=node('dialog');dialog.setAttribute('aria-label','Архив заказов из Telegram');dialog.style.cssText='width:min(900px,94vw);max-height:88vh;overflow:auto;border:1px solid #ccd2d6;border-radius:16px;padding:24px';
|
||||||
|
const top=node('div');top.style.cssText='display:flex;justify-content:space-between;gap:20px';top.append(node('h2','Исходный архив Telegram'));const exit=node('button','Закрыть');exit.onclick=close;top.append(exit);dialog.append(top);
|
||||||
|
dialog.append(node('p',`${archive.orderCount} заказов · ${archive.clientCount} клиентов. Цены и платежи сохранены по исходным сообщениям. Неуказанная оплата не подтверждает долг. Автоматическое закрытие импортированных заказов отключено.`));
|
||||||
|
const search=node('input');search.type='search';search.placeholder='Номер заказа, имя, телефон или адрес';search.setAttribute('aria-label','Поиск в архиве');search.style.cssText='width:100%;padding:12px;margin:8px 0';dialog.append(search);
|
||||||
|
const list=node('div');dialog.append(list);
|
||||||
|
const render=()=>{list.replaceChildren();const q=search.value.toLocaleLowerCase('ru');for(const message of archive.messages||[]){if(q&&!`${message.id} ${message.text} ${message.transcription||''}`.toLocaleLowerCase('ru').includes(q))continue;const d=node('details');d.style.cssText='padding:10px 0;border-bottom:1px solid #ddd';d.append(node('summary',`${message.sentAt} · ${message.id} · ${(message.text||'Фотография').split('\n')[0]}`));const pre=node('pre',message.text);pre.style.cssText='white-space:pre-wrap;overflow-wrap:anywhere;font:inherit';d.append(pre);if(message.transcription)d.append(node('p',message.transcription));for(const a of archive.attachments||[]){if(a.messageId!==message.id||!/^data:image\/(?:jpeg|png|webp);base64,[A-Za-z0-9+/=]+$/.test(a.data||''))continue;const img=node('img');img.src=a.data;img.alt=a.href;img.loading='lazy';img.style.cssText='display:block;max-width:100%;max-height:80vh;object-fit:contain';d.append(img);}list.append(d);}};
|
||||||
|
search.oninput=render;render();document.body.append(dialog);dialog.showModal();dialog.addEventListener('close',close);
|
||||||
|
}
|
||||||
|
function install(){
|
||||||
|
const archive=read(),ordersView=document.getElementById('orders');let b=document.getElementById('cateriumTelegramArchive');
|
||||||
|
if(!archive){b?.remove();return}if(!ordersView||b)return;
|
||||||
|
b=node('button','Исходный архив Telegram');b.id='cateriumTelegramArchive';b.type='button';b.className='outline';b.onclick=show;
|
||||||
|
const anchor=ordersView.querySelector('h1');if(anchor)anchor.insertAdjacentElement('afterend',b);else ordersView.prepend(b);
|
||||||
|
}
|
||||||
|
window.CateriumImportArchive={show,refresh:install};
|
||||||
|
window.addEventListener('sun:cloud-state-applied',()=>{close();install()});
|
||||||
|
window.addEventListener('sun:cloud-permissions-changed',()=>{close();install()});
|
||||||
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',install,{once:true});else install();
|
||||||
|
})();
|
||||||
@ -9,7 +9,7 @@ function activeLines(){return Array.isArray(draft?.lines)?draft.lines.filter(l=>
|
|||||||
function comments(){let rows=lineRows(),ls=activeLines();rows.forEach((row,i)=>{let l=ls[i];if(!l)return;let w=$('.sun-line-comment-v1775',row);if(!w){w=document.createElement('div');w.className='sun-line-comment-v1775';w.innerHTML='<button type="button" class="sun-line-comment-btn-v1775">💬 Оставить комментарий</button><div class="sun-line-comment-editor-v1775"><input type="text" maxlength="300" placeholder="Аллергия, убрать ингредиент, изменение состава…"><button type="button" class="sun-line-comment-save-v1775">Готово</button></div>';row.appendChild(w)}let btn=$('.sun-line-comment-btn-v1775',w),editor=$('.sun-line-comment-editor-v1775',w),input=$('input',w),save=$('.sun-line-comment-save-v1775',w);let current=safe(l.comment);btn.classList.toggle('has-comment',!!current);btn.textContent=current?'💬 Комментарий добавлен':'💬 Оставить комментарий';if(document.activeElement!==input&&input.value!==current)input.value=current;if(!btn.dataset.bound){btn.dataset.bound='1';btn.addEventListener('click',()=>{editor.classList.toggle('on');if(editor.classList.contains('on')){input.value=safe(l.comment);setTimeout(()=>input.focus(),0)}});input.addEventListener('input',()=>{l.comment=input.value.slice(0,300)});input.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();save.click()}});save.addEventListener('click',()=>{l.comment=input.value.trim().slice(0,300);btn.classList.toggle('has-comment',!!l.comment);btn.textContent=l.comment?'💬 Комментарий добавлен':'💬 Оставить комментарий';editor.classList.remove('on')})}})}
|
function comments(){let rows=lineRows(),ls=activeLines();rows.forEach((row,i)=>{let l=ls[i];if(!l)return;let w=$('.sun-line-comment-v1775',row);if(!w){w=document.createElement('div');w.className='sun-line-comment-v1775';w.innerHTML='<button type="button" class="sun-line-comment-btn-v1775">💬 Оставить комментарий</button><div class="sun-line-comment-editor-v1775"><input type="text" maxlength="300" placeholder="Аллергия, убрать ингредиент, изменение состава…"><button type="button" class="sun-line-comment-save-v1775">Готово</button></div>';row.appendChild(w)}let btn=$('.sun-line-comment-btn-v1775',w),editor=$('.sun-line-comment-editor-v1775',w),input=$('input',w),save=$('.sun-line-comment-save-v1775',w);let current=safe(l.comment);btn.classList.toggle('has-comment',!!current);btn.textContent=current?'💬 Комментарий добавлен':'💬 Оставить комментарий';if(document.activeElement!==input&&input.value!==current)input.value=current;if(!btn.dataset.bound){btn.dataset.bound='1';btn.addEventListener('click',()=>{editor.classList.toggle('on');if(editor.classList.contains('on')){input.value=safe(l.comment);setTimeout(()=>input.focus(),0)}});input.addEventListener('input',()=>{l.comment=input.value.slice(0,300)});input.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();save.click()}});save.addEventListener('click',()=>{l.comment=input.value.trim().slice(0,300);btn.classList.toggle('has-comment',!!l.comment);btn.textContent=l.comment?'💬 Комментарий добавлен':'💬 Оставить комментарий';editor.classList.remove('on')})}})}
|
||||||
function searchBox(){let catalog=$('#new .catalog');if(!catalog||$('#sunCatalogSearchV1775'))return;let i=document.createElement('input');i.id='sunCatalogSearchV1775';i.type='search';i.placeholder='Поиск по № бокса или названию';($('.catalog-head',catalog)||catalog.firstElementChild)?.insertAdjacentElement('afterend',i);i.addEventListener('input',()=>{let q=safe(i.value).toLowerCase().replace(/ё/g,'е');$$('#tiles .tile').forEach(t=>{let text=safe(t.textContent).toLowerCase().replace(/ё/g,'е');t.hidden=t.classList.contains('add')?!!q:!!(q&&!text.includes(q))})})}
|
function searchBox(){let catalog=$('#new .catalog');if(!catalog||$('#sunCatalogSearchV1775'))return;let i=document.createElement('input');i.id='sunCatalogSearchV1775';i.type='search';i.placeholder='Поиск по № бокса или названию';($('.catalog-head',catalog)||catalog.firstElementChild)?.insertAdjacentElement('afterend',i);i.addEventListener('input',()=>{let q=safe(i.value).toLowerCase().replace(/ё/g,'е');$$('#tiles .tile').forEach(t=>{let text=safe(t.textContent).toLowerCase().replace(/ё/g,'е');t.hidden=t.classList.contains('add')?!!q:!!(q&&!text.includes(q))})})}
|
||||||
function delivery(){return Math.max(0,Number($('#orderDeliveryCost')?.value||draft?.costs?.delivery||draft?.deliveryCost||0))}
|
function delivery(){return Math.max(0,Number($('#orderDeliveryCost')?.value||draft?.costs?.delivery||draft?.deliveryCost||0))}
|
||||||
function financial(){let positions=Math.max(0,Number(typeof calcOrderTotal==='function'?calcOrderTotal():0)),d=delivery(),paid=Math.max(0,Number($('#prepayment')?.value||draft?.prepayment||0)),total=positions+d;return{positions,d,total,paid,balance:Math.max(0,total-paid)}}
|
function financial(){const p=window.sunOrderPricing?.(draft);let d=p?Number(p.delivery||0):delivery(),positions=p?Number(p.itemsTotal||0):Math.max(0,Number(typeof calcOrderTotal==='function'?calcOrderTotal():0)),paid=Math.max(0,Number($('#prepayment')?.value||draft?.prepayment||0)),total=positions+d;return{positions,d,total,paid,balance:Math.max(0,total-paid)}}
|
||||||
function summary(){let h=$('.order-summary');if(!h)return;let f=financial();if($('#summaryTotal'))$('#summaryTotal').textContent=fmt(f.positions);let d=$('#sunSummaryDeliveryV1775');if(!d){d=document.createElement('p');d.id='sunSummaryDeliveryV1775';h.appendChild(d)}d.innerHTML=`<span>Доставка</span><b>${fmt(f.d)}</b>`;let t=$('#sunSummaryGrandV1775');if(!t){t=document.createElement('p');t.id='sunSummaryGrandV1775';t.className='sun-summary-grand-v1775';h.appendChild(t)}t.innerHTML=`<span>Итого</span><b>${fmt(f.total)}</b>`;if($('#orderTotal'))$('#orderTotal').value=Math.round(f.total);if($('#balance'))$('#balance').value=Math.round(f.balance);if($('#summaryBalance'))$('#summaryBalance').textContent=fmt(f.balance);draft.total=f.total;draft.balance=f.balance}
|
function summary(){let h=$('.order-summary');if(!h)return;let f=financial();if($('#summaryTotal'))$('#summaryTotal').textContent=fmt(f.positions);let d=$('#sunSummaryDeliveryV1775');if(!d){d=document.createElement('p');d.id='sunSummaryDeliveryV1775';h.appendChild(d)}d.innerHTML=`<span>Доставка</span><b>${fmt(f.d)}</b>`;let t=$('#sunSummaryGrandV1775');if(!t){t=document.createElement('p');t.id='sunSummaryGrandV1775';t.className='sun-summary-grand-v1775';h.appendChild(t)}t.innerHTML=`<span>Итого</span><b>${fmt(f.total)}</b>`;if($('#orderTotal'))$('#orderTotal').value=Math.round(f.total);if($('#balance'))$('#balance').value=Math.round(f.balance);if($('#summaryBalance'))$('#summaryBalance').textContent=fmt(f.balance);draft.total=f.total;draft.balance=f.balance}
|
||||||
function post(){styles();searchBox();comments();summary()}
|
function post(){styles();searchBox();comments();summary()}
|
||||||
function wrap(n){let old=window[n];if(typeof old!=='function'||old.__commentsV1775)return;let fn=function(...a){let r=old.apply(this,a);queueMicrotask(post);setTimeout(post,50);return r};fn.__commentsV1775=true;window[n]=fn}
|
function wrap(n){let old=window[n];if(typeof old!=='function'||old.__commentsV1775)return;let fn=function(...a){let r=old.apply(this,a);queueMicrotask(post);setTimeout(post,50);return r};fn.__commentsV1775=true;window[n]=fn}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
(()=>{
|
(()=>{
|
||||||
'use strict';
|
'use strict';
|
||||||
const VERSION='17.7.3';
|
const VERSION='17.7.3';
|
||||||
const RELEASE='20260916-v18-1-1-template-whitelist-fix';
|
const RELEASE='20260917-order-import';
|
||||||
|
|
||||||
const hasStoredSession=()=>{try{return Object.keys(localStorage).some(k=>/^sb-.*-auth-token$/i.test(k)&&String(localStorage.getItem(k)||'').length>20)}catch(_){return false}};
|
const hasStoredSession=()=>{try{return Object.keys(localStorage).some(k=>/^sb-.*-auth-token$/i.test(k)&&String(localStorage.getItem(k)||'').length>20)}catch(_){return false}};
|
||||||
function installAuthBoot(){
|
function installAuthBoot(){
|
||||||
|
|||||||
@ -76,7 +76,7 @@
|
|||||||
if(!canAutoWrite())return {changed:0,ids:[]};
|
if(!canAutoWrite())return {changed:0,ids:[]};
|
||||||
const list=readOrders(),ids=[];
|
const list=readOrders(),ids=[];
|
||||||
for(const order of list){
|
for(const order of list){
|
||||||
if(!order||String(order.status||'').trim()==='Отменён'||isAutoCompleted(order))continue;
|
if(!order||order.autoCompletionDisabled===true||String(order.status||'').trim()==='Отменён'||isAutoCompleted(order))continue;
|
||||||
const due=dueAt(order);if(!Number.isFinite(due)||nowMs<due)continue;
|
const due=dueAt(order);if(!Number.isFinite(due)||nowMs<due)continue;
|
||||||
markOrderCompleted(order,nowMs);ids.push(String(order.id));
|
markOrderCompleted(order,nowMs);ids.push(String(order.id));
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -1,7 +1,7 @@
|
|||||||
const CACHE='sun-catering-pwa-v84-20260917-company-branding';
|
const CACHE='sun-catering-pwa-v85-20260917-order-import';
|
||||||
const VERSION='20260917-company-branding';
|
const VERSION='20260917-order-import';
|
||||||
const CORE=[
|
const CORE=[
|
||||||
'./','./index.html',`./core/company-branding.js?v=${VERSION}`,`./core/signature-offer-pdf-v18.js?v=${VERSION}`,`./core/brand-theme.js?v=${VERSION}`,
|
'./','./index.html',`./core/import-archive.js?v=${VERSION}`,`./core/company-branding.js?v=${VERSION}`,`./core/signature-offer-pdf-v18.js?v=${VERSION}`,`./core/brand-theme.js?v=${VERSION}`,
|
||||||
`./core/sun-safe.js?v=${VERSION}`,`./core/performance.js?v=${VERSION}`,`./core/account-center-v1780.js?v=${VERSION}`,`./core/login-signature-v1776.js?v=${VERSION}`,`./core/data-layer-v1773.js?v=${VERSION}`,`./core/server-automation-v1770.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}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./core/auth-security-v1774.js?v=${VERSION}`,`./core/order-enhancements-v1775.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/account-center-v1780.js?v=${VERSION}`,`./core/login-signature-v1776.js?v=${VERSION}`,`./core/data-layer-v1773.js?v=${VERSION}`,`./core/server-automation-v1770.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}`,`./core/classic-offer-pdf-v1767.js?v=${VERSION}`,`./core/developer-console-v1768.js?v=${VERSION}`,`./core/offer-workspace-v1769.js?v=${VERSION}`,`./core/auth-security-v1774.js?v=${VERSION}`,`./core/order-enhancements-v1775.js?v=${VERSION}`,`./legacy/bootstrap.js?v=${VERSION}`,`./app-runtime.js?v=${VERSION}`,
|
||||||
'./offer-gallery/001.jpg','./offer-gallery/002.jpg',
|
'./offer-gallery/001.jpg','./offer-gallery/002.jpg',
|
||||||
'./catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg',
|
'./catalog/001.jpg','./catalog/002.jpg','./catalog/003.jpg',
|
||||||
@ -9,7 +9,7 @@ const CORE=[
|
|||||||
'./offer-templates/thumb-light.jpg','./offer-templates/thumb-editorial-grid.jpg','./offer-templates/thumb-midnight-glass.jpg','./offer-templates/thumb-emerald-gold.jpg'
|
'./offer-templates/thumb-light.jpg','./offer-templates/thumb-editorial-grid.jpg','./offer-templates/thumb-midnight-glass.jpg','./offer-templates/thumb-emerald-gold.jpg'
|
||||||
];
|
];
|
||||||
const CRITICAL_FRESH=new Set([
|
const CRITICAL_FRESH=new Set([
|
||||||
'/core/company-branding.js','/core/brand-theme.js','/core/sun-safe.js','/core/performance.js','/core/account-center-v1780.js','/core/login-signature-v1776.js','/core/auth-security-v1774.js','/legacy/bootstrap.js','/app-runtime.js'
|
'/core/import-archive.js','/core/company-branding.js','/core/brand-theme.js','/core/sun-safe.js','/core/performance.js','/core/account-center-v1780.js','/core/login-signature-v1776.js','/core/auth-security-v1774.js','/legacy/bootstrap.js','/app-runtime.js'
|
||||||
]);
|
]);
|
||||||
self.addEventListener('install',event=>{
|
self.addEventListener('install',event=>{
|
||||||
event.waitUntil(caches.open(CACHE).then(cache=>cache.addAll(CORE)).then(()=>self.skipWaiting()));
|
event.waitUntil(caches.open(CACHE).then(cache=>cache.addAll(CORE)).then(()=>self.skipWaiting()));
|
||||||
|
|||||||
46
supabase/migrations/20260917221500_import_order_history.sql
Normal file
46
supabase/migrations/20260917221500_import_order_history.sql
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
-- Imported history must preserve unknown or partial payments.
|
||||||
|
create or replace function public.sun_process_due_orders_internal(p_workspace uuid,p_now timestamptz default now())
|
||||||
|
returns jsonb
|
||||||
|
language plpgsql
|
||||||
|
security definer
|
||||||
|
set search_path='public' as $$
|
||||||
|
declare v_tz text; v_changed integer:=0; v_ids text[]:=array[]::text[]; r record; v_total numeric; v_iso text; v_orders jsonb;
|
||||||
|
begin
|
||||||
|
select timezone into v_tz from public.sun_workspaces where id=p_workspace;
|
||||||
|
if v_tz is null then return jsonb_build_object('changed',0,'ids','[]'::jsonb); end if;
|
||||||
|
v_iso:=to_char(p_now at time zone 'UTC','YYYY-MM-DD"T"HH24:MI:SS.MS"Z"');
|
||||||
|
for r in
|
||||||
|
select order_id,data,version from public.sun_v17_orders
|
||||||
|
where workspace_id=p_workspace
|
||||||
|
and lower(coalesce(data->>'status','')) not in ('отменён','отменен','отдан заказчику','завершён','завершен')
|
||||||
|
and coalesce(data->>'sunAutoCompletedAt','')=''
|
||||||
|
and coalesce(data->>'autoCompletionDisabled','false') <> 'true'
|
||||||
|
and public.sun_order_due_at(data,v_tz) is not null
|
||||||
|
and public.sun_order_due_at(data,v_tz) <= p_now
|
||||||
|
for update
|
||||||
|
loop
|
||||||
|
v_total:=case when coalesce(r.data->>'total','') ~ '^-?\d+(\.\d+)?$' then greatest(0,(r.data->>'total')::numeric) else 0 end;
|
||||||
|
update public.sun_v17_orders
|
||||||
|
set data=r.data||jsonb_build_object('prepayment',v_total,'balance',0,'status','Отдан заказчику','paymentStatus','paid','completedAt',coalesce(nullif(r.data->>'completedAt',''),v_iso),'paymentCompletedAt',coalesce(nullif(r.data->>'paymentCompletedAt',''),v_iso),'sunAutoCompletedAt',v_iso,'sunAutoCompletedV1770',true,'sunAutoCompletedVersion','17.7.0'),version=version+1,updated_at=p_now,updated_by=null
|
||||||
|
where workspace_id=p_workspace and order_id=r.order_id;
|
||||||
|
insert into public.sun_v17_change_events(workspace_id,entity,entity_key,operation,version,client_id,created_by)
|
||||||
|
values(p_workspace,'order',r.order_id,'upsert',r.version+1,'server:auto-complete',null);
|
||||||
|
v_changed:=v_changed+1;v_ids:=array_append(v_ids,r.order_id);
|
||||||
|
end loop;
|
||||||
|
if v_changed>0 then
|
||||||
|
select coalesce(jsonb_agg(o.data order by case when o.order_id ~ '^\d+$' then o.order_id::bigint else null end,o.order_id),'[]'::jsonb) into v_orders from public.sun_v17_orders o where o.workspace_id=p_workspace;
|
||||||
|
update public.sun_app_state s set payload=jsonb_set(coalesce(s.payload,'{}'::jsonb),'{storage,sunOrders}',jsonb_build_object('t','j','v',v_orders),true),revision=s.revision+1,updated_at=p_now,client_id='server:auto-complete' where s.workspace_id=p_workspace;
|
||||||
|
if not found then insert into public.sun_app_state(workspace_id,payload,revision,updated_at,client_id) values(p_workspace,jsonb_build_object('format','sun-cloud-v2','version',2,'storage',jsonb_build_object('sunOrders',jsonb_build_object('t','j','v',v_orders))),1,p_now,'server:auto-complete'); end if;
|
||||||
|
end if;
|
||||||
|
return jsonb_build_object('changed',v_changed,'ids',to_jsonb(v_ids),'workspace',p_workspace,'timezone',v_tz,'processed_at',v_iso);
|
||||||
|
end;$$;
|
||||||
|
|
||||||
|
-- The archive contains both order and customer data.
|
||||||
|
do $migration$
|
||||||
|
declare definition text;
|
||||||
|
begin
|
||||||
|
definition:=pg_get_functiondef('public.sun_can_read_storage_key(uuid,text)'::regprocedure);
|
||||||
|
definition:=replace(definition, 'when p_key=''sunOrders'' then', 'when p_key=''sunTelegramImportArchiveV1'' then public.sun_has_permission(p_workspace,''orders.view'') and public.sun_has_permission(p_workspace,''clients.view'')
|
||||||
|
when p_key=''sunOrders'' then');
|
||||||
|
execute definition;
|
||||||
|
end $migration$;
|
||||||
33
tests/order-import.spec.mjs
Normal file
33
tests/order-import.spec.mjs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import {test,expect} from '@playwright/test';
|
||||||
|
|
||||||
|
test('imported history keeps partial payments and delivery is charged once after save',async({page})=>{
|
||||||
|
page.on('dialog',d=>d.accept());
|
||||||
|
await page.goto('/index.html',{waitUntil:'domcontentloaded'});
|
||||||
|
await page.waitForFunction(()=>window.__cateriumOrderEnhancementsV1775&&window.SunUXFixV1764&&window.sunOrderPricing);
|
||||||
|
const result=await page.evaluate(async()=>{
|
||||||
|
const item={id:'import-fixture',name:'Исходная позиция',price:9900,category:0};boxes.push(item);
|
||||||
|
const order={id:900001,event:'Исторический заказ',date:'2026-09-10',time:'12:00',contact:'Тест',phone:'',address:'',note:'Источник сохранён',status:'Новый',lines:[{id:item.id,qty:2,price:3500}],total:7500,deliveryCost:1500,promoCode:'TEST',promoDiscountValue:1000,promoDiscountType:'amount',prepayment:2000,autoCompletionDisabled:true};
|
||||||
|
orders.push(order);persist();window.openOrder(order.id);await new Promise(r=>setTimeout(r,150));
|
||||||
|
const shown=Number(document.getElementById('orderTotal').value);
|
||||||
|
window.saveOrder();await new Promise(r=>setTimeout(r,150));
|
||||||
|
window.SunUXFixV1764.autoCompleteOrders(Date.parse('2030-01-01T12:00:00Z'),{silent:true});
|
||||||
|
const saved=orders.find(x=>x.id===order.id);
|
||||||
|
return {shown,total:saved.total,prepayment:saved.prepayment,status:saved.status,flag:saved.autoCompletionDisabled,rows:document.querySelectorAll('.sun-order-line-row').length};
|
||||||
|
});
|
||||||
|
expect(result).toEqual({shown:7500,total:7500,prepayment:2000,status:'Новый',flag:true,rows:1});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('private import archive safely shows messages and clears on workspace change',async({page})=>{
|
||||||
|
await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:'<html><body><section id="orders"><h1>Заказы</h1></section></body></html>'}));
|
||||||
|
await page.goto('/index.html');
|
||||||
|
await page.evaluate(()=>localStorage.setItem('sunTelegramImportArchiveV1',JSON.stringify({orderCount:1,clientCount:1,messages:[{id:'message1',sentAt:'01.01.2026',text:'Заказ <img src=x onerror=alert(1)>\nАдрес: тест'}],attachments:[]})));
|
||||||
|
await page.addScriptTag({url:'/core/import-archive.js'});
|
||||||
|
await page.getByRole('button',{name:'Исходный архив Telegram'}).click();
|
||||||
|
await page.getByRole('searchbox').fill('Адрес');
|
||||||
|
await page.locator('summary').click();
|
||||||
|
await expect(page.locator('dialog pre')).toContainText('<img src=x');
|
||||||
|
expect(await page.locator('dialog img').count()).toBe(0);
|
||||||
|
await page.evaluate(()=>{localStorage.removeItem('sunTelegramImportArchiveV1');window.dispatchEvent(new Event('sun:cloud-state-applied'))});
|
||||||
|
expect(await page.locator('dialog').count()).toBe(0);
|
||||||
|
expect(await page.locator('#cateriumTelegramArchive').count()).toBe(0);
|
||||||
|
});
|
||||||
@ -2,7 +2,7 @@ import { defineConfig, devices } from '@playwright/test';
|
|||||||
import {fileURLToPath} from 'node:url';
|
import {fileURLToPath} from 'node:url';
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir:'.',
|
testDir:'.',
|
||||||
testMatch:['app.spec.mjs','theme-startup.spec.mjs','company-branding.spec.mjs'],
|
testMatch:['app.spec.mjs','theme-startup.spec.mjs','company-branding.spec.mjs','order-import.spec.mjs'],
|
||||||
timeout:30000,
|
timeout:30000,
|
||||||
use:{baseURL:'http://127.0.0.1:4173'},
|
use:{baseURL:'http://127.0.0.1:4173'},
|
||||||
webServer:{command:'npx http-server public -p 4173 -c-1',cwd:fileURLToPath(new URL('../',import.meta.url)),port:4173,reuseExistingServer:true},
|
webServer:{command:'npx http-server public -p 4173 -c-1',cwd:fileURLToPath(new URL('../',import.meta.url)),port:4173,reuseExistingServer:true},
|
||||||
|
|||||||
@ -43,7 +43,11 @@ await q('select * from public.sun_v17_clients_snapshot_v1773($1)',[ws]);
|
|||||||
await q("select public.sun_v17_create_backup($1,'daily','test')",[ws]);
|
await q("select public.sun_v17_create_backup($1,'daily','test')",[ws]);
|
||||||
assert.equal((await q('select * from public.sun_v17_list_backups($1)',[ws])).rows.length,1);
|
assert.equal((await q('select * from public.sun_v17_list_backups($1)',[ws])).rows.length,1);
|
||||||
await q('select public.sun_v17_prune_backups($1)',[ws]);
|
await q('select public.sun_v17_prune_backups($1)',[ws]);
|
||||||
|
payload.storage.sunOrders.v.push({id:77,date:'2025-01-01',time:'12:00',status:'Новый',total:7500,prepayment:2000,balance:5500,autoCompletionDisabled:true});
|
||||||
|
row=await one('select * from public.sun_save_app_state_v17($1,$2,$3,$4)',[ws,payload,'import-test',row.revision]);
|
||||||
await q('select public.sun_run_order_automation($1)',[ws]);
|
await q('select public.sun_run_order_automation($1)',[ws]);
|
||||||
|
const historical=(await one('select public.sun_v17_entity_snapshot($1) as s',[ws])).s.orders.find(o=>String(o.id)==='77').data;
|
||||||
|
assert.equal(historical.prepayment,2000);assert.equal(historical.status,'Новый');assert.equal(historical.balance,5500);
|
||||||
await q('select public.caterium_account_snapshot($1)',[ws]);
|
await q('select public.caterium_account_snapshot($1)',[ws]);
|
||||||
await q('select * from public.sun_list_workspace_members($1)',[ws]);
|
await q('select * from public.sun_list_workspace_members($1)',[ws]);
|
||||||
await q("select public.sun_employee_prepare_v28($1,'test3@example.invalid','Viewer','viewer')",[ws]);
|
await q("select public.sun_employee_prepare_v28($1,'test3@example.invalid','Viewer','viewer')",[ws]);
|
||||||
|
|||||||
@ -14,8 +14,8 @@ 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((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(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([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4');
|
||||||
check(sw.includes('v84-20260917-company-branding')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js')&&sw.includes('offer-workspace-v1769.js'),'service worker cache is v17.7.3');
|
check(sw.includes('v85-20260917-order-import')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js')&&sw.includes('offer-workspace-v1769.js'),'service worker cache is v17.7.3');
|
||||||
check(index.includes('20260917-company-branding')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.7.3');
|
check(index.includes('20260917-order-import')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.7.3');
|
||||||
check(performance.includes('SunAttachmentGuard')&&performance.includes('TARGET=2*1024*1024'),'chat photo auto-compression is versioned');
|
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("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('MEMORY_REFRESH_MS=30000')&&performance.includes('MEMORY_TIMEOUT_MS=8000')&&performance.includes('memoryPromise'),'Developer Console memory refresh is bounded');
|
||||||
@ -39,7 +39,7 @@ check(!/sb_secret_[A-Za-z0-9_-]{20,}|service_role\s*[:=]\s*["'][A-Za-z0-9._-]{30
|
|||||||
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
|
check(lock.version===pkg.version&&lock.packages?.['']?.version===pkg.version,'package.json and package-lock.json versions match');
|
||||||
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
|
check(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json');
|
||||||
check(releaseManifest.channel==='production','release manifest channel is production');
|
check(releaseManifest.channel==='production','release manifest channel is production');
|
||||||
check(String(releaseManifest.pwaCache||'').includes('v84-20260917-company-branding'),'release manifest points to current PWA cache');
|
check(String(releaseManifest.pwaCache||'').includes('v85-20260917-order-import'),'release manifest points to current PWA cache');
|
||||||
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0','17.7.1','17.7.2','17.7.3'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.7.3');
|
check(['17.6.2','17.6.3','17.6.4','17.6.5','17.6.6','17.6.7','17.6.8','17.6.9','17.7.0','17.7.1','17.7.2','17.7.3'].every(v=>fs.existsSync(path.join(root,`docs/releases/V${v}-CHANGES.txt`))),'release notes exist through v17.7.3');
|
||||||
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=12000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
|
check(runtime.includes('CLOUD_RPC_TIMEOUT_MS=12000')&&runtime.includes('CLOUD_CONFLICT_MAX_RETRIES=4')&&runtime.includes('retryCount'),'cloud sync has timeout and capped exponential conflict retries');
|
||||||
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes('ERROR_DEDUPE_MS=5*60*1000')&&runtime.includes('mirrorBusy=false')&&runtime.includes('backupBusy=false'),'stability logger uses current version, dedupe and single-flight guards');
|
||||||
@ -65,8 +65,8 @@ check(offerWorkspace.includes('PDF и предпросмотр')&&offerWorkspace
|
|||||||
check(offerWorkspace.includes('SunClassicOfferPDFV1767')&&offerWorkspace.includes('finalGallery=galleryFor'),'custom gallery is injected into PDF renderer');
|
check(offerWorkspace.includes('SunClassicOfferPDFV1767')&&offerWorkspace.includes('finalGallery=galleryFor'),'custom gallery is injected into PDF renderer');
|
||||||
check(releaseManifest.offerWorkspaceTabs===true&&releaseManifest.offerTemplatesSeparateTab===true&&releaseManifest.offerTwoCustomGalleryPhotos===true,'release manifest records offer workspace changes');
|
check(releaseManifest.offerWorkspaceTabs===true&&releaseManifest.offerTemplatesSeparateTab===true&&releaseManifest.offerTwoCustomGalleryPhotos===true,'release manifest records offer workspace changes');
|
||||||
check(pkg.version==='17.7.3','package version is v17.7.3');
|
check(pkg.version==='17.7.3','package version is v17.7.3');
|
||||||
check(index.includes('20260917-company-branding'),'index cache bust is v17.7.3');
|
check(index.includes('20260917-order-import'),'index cache bust is v17.7.3');
|
||||||
check(sw.includes('v84-20260917-company-branding')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js'),'PWA caches v17.7.3 client foundation modules');
|
check(sw.includes('v85-20260917-order-import')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js'),'PWA caches v17.7.3 client foundation modules');
|
||||||
check(fs.existsSync(path.join(root,'public/core/data-layer-v1773.js'))&&fs.existsSync(path.join(root,'public/core/server-automation-v1770.js')),'data layer and server automation modules exist');
|
check(fs.existsSync(path.join(root,'public/core/data-layer-v1773.js'))&&fs.existsSync(path.join(root,'public/core/server-automation-v1770.js')),'data layer and server automation modules exist');
|
||||||
check(ux.includes('CateriumServerAutomationV1770?.enabled'),'cloud browser auto completion is disabled when server automation is active');
|
check(ux.includes('CateriumServerAutomationV1770?.enabled'),'cloud browser auto completion is disabled when server automation is active');
|
||||||
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes("v17.7.3 Clients Server Read"),'stability logger reports v17.7.3');
|
check(runtime.includes("const VERSION = '17.7.3'")&&runtime.includes("v17.7.3 Clients Server Read"),'stability logger reports v17.7.3');
|
||||||
|
|||||||
@ -28,8 +28,8 @@ 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');
|
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));
|
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(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed');
|
||||||
if(!sw.includes('20260917-company-branding')||!sw.includes('login-signature-v1776.js')||!sw.includes('data-layer-v1773.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated for login refresh');
|
if(!sw.includes('20260917-order-import')||!sw.includes('login-signature-v1776.js')||!sw.includes('data-layer-v1773.js')||!sw.includes('server-automation-v1770.js')||!sw.includes('offer-workspace-v1769.js')||sw.includes('offer-gallery-data.js'))fail('service worker cache is stale');else ok('PWA cache updated for login refresh');
|
||||||
if(html.includes('20260907-v17-6-0-stability-security')||html.includes('20260909-v17-7-3-clients-server-read')||!html.includes('20260917-company-branding')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
|
if(html.includes('20260907-v17-6-0-stability-security')||html.includes('20260909-v17-7-3-clients-server-read')||!html.includes('20260917-order-import')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current');
|
||||||
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('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("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('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');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user