diff --git a/docs/import-cleanup-20260918.md b/docs/import-cleanup-20260918.md new file mode 100644 index 0000000..1b82b58 --- /dev/null +++ b/docs/import-cleanup-20260918.md @@ -0,0 +1,11 @@ +Completed Telegram import cleanup + +The working menu, new-order selection, search, category counts and catalog PDFs exclude items with `hidden: true`. Historical order lookup still resolves these items, preserving original names, quantities, prices, payments and proposal snapshots. Adding a retired item through a stale catalog button is ignored. + +The one-time Telegram archive viewer is removed. Its script remains a small compatibility cleanup for cached markup; it does not expose a stored archive again after synchronization or account changes. + +The separate, owner-authorized production cleanup is limited to the Sun workspace and to catalog entries matching all four markers: category 0, section `Импорт из Telegram`, source `telegram-20260917`, and the `tg-item-` ID prefix. These temporary catalog references are retired rather than physically deleted because existing orders use their IDs. The global archive payload is removed; individual orders and their source details are preserved. + +The operation makes a server backup, uses optimistic revision locking and verifies unchanged orders, client records, unrelated storage and regular catalog entries. Private payload backups and customer information are kept outside this repository. + +Validation: desktop and mobile tests cover retired catalog visibility, search, printed and generated catalogs, historical order totals, paid state, contact details, proposal contents, reload persistence, archive retirement, company branding and banquet behavior (26 tests). Syntax, security, release checks and database smoke checks pass. diff --git a/docs/release-manifest.json b/docs/release-manifest.json index c2413c1..21ce7a0 100644 --- a/docs/release-manifest.json +++ b/docs/release-manifest.json @@ -11,7 +11,7 @@ "serverReady": true, "workspaceAutoDiscovery": true, "invitesTemporarilyDisabled": false, - "pwaCache": "v106-20260918-proposals-six", + "pwaCache": "v107-20260918-import-cleanup", "fullOfferDescriptions": true, "dynamicOfferRows": true, "pdfOfferDescriptionFix": true, @@ -431,5 +431,7 @@ ], "proposalTransparentLogoTrim": true, "proposalLogoAspectRatio": true, - "proposalLogoContrastMasthead": true + "proposalLogoContrastMasthead": true, + "retiredCatalogItemsPreserveOrderHistory": true, + "oneTimeTelegramArchiveUiRemoved": true } diff --git a/public/app-runtime.js b/public/app-runtime.js index e634004..07b286d 100644 --- a/public/app-runtime.js +++ b/public/app-runtime.js @@ -152,7 +152,7 @@ window.SUN_LEGACY_CATALOG_V175=[]; const buildPdf=pages=>window.SunPdfEngine.fromJpegs(pages); async function createCatalogPdfBlob(cat,progress){ - const items=allBoxes().filter(item=>Number(item.category||0)===Number(cat)); + const items=allBoxes().filter(item=>item.hidden!==true&&Number(item.category||0)===Number(cat)); if(!items.length)throw new Error('В этом разделе нет позиций.'); const pages=[],dateText=new Date().toLocaleDateString('ru-RU'),brand=window.CateriumBranding.identity(); for(let i=0;icreateCatalogPdfBlob(currentCategory()); - window.sunCatalogDebugPdfPages=async(cat=currentCategory(),limit=3)=>{const items=allBoxes().filter(item=>Number(item.category||0)===Number(cat)).slice(0,Math.max(1,Number(limit||3)));const dateText=new Date().toLocaleDateString('ru-RU'),pages=[];for(let i=0;i{const items=allBoxes().filter(item=>item.hidden!==true&&Number(item.category||0)===Number(cat)).slice(0,Math.max(1,Number(limit||3)));const dateText=new Date().toLocaleDateString('ru-RU'),pages=[];for(let i=0;i{if(e.target.closest('#new .cats button,#new .sun-catalog-top-toolbar .cats button'))setTimeout(installButton,30)},true); const catalog=document.querySelector('#new .catalog');if(catalog&&'MutationObserver'in window)new MutationObserver(()=>{const b=$('sunLiveCatalogPdfButton');if(!b||b.textContent!=='PDF'||b.onclick!==openCatalogPdfViewer)queueMicrotask(installButton)}).observe(catalog,{childList:true,subtree:true}); @@ -356,7 +356,7 @@ window.SUN_LEGACY_CATALOG_V175=[]; function renderCategoryManager(){ ensureCategoryModal();const root=$('sunCategoryRows');if(!root)return; root.innerHTML=catalogCats.slice().sort((a,b)=>a.order-b.order).map(c=>{ - const count=(typeof boxes!=='undefined'?boxes:[]).filter(b=>Number(b.category||0)===Number(c.id)).length; + const count=(typeof boxes!=='undefined'?boxes:[]).filter(b=>b.hidden!==true&&Number(b.category||0)===Number(c.id)).length; return `
${count} поз.
`; }).join(''); qa('[data-cat-name]',root).forEach(input=>input.onchange=()=>{const c=catById(input.dataset.catName);if(!c)return;c.name=input.value.trim()||c.name;saveCats();renderCategoryTabs();syncEditorCategoryOptions();renderCatalogV5();renderCategoryManager();}); @@ -421,7 +421,7 @@ window.SUN_LEGACY_CATALOG_V175=[]; const fields=ensureBanquetEditor();if(!fields)return;const isBanquet=Number(category)===BANQUET_CATEGORY; fields.section.style.display=isBanquet?'flex':'none';fields.weight.style.display=isBanquet?'flex':'none'; const caption=$('boxPriceCaption');if(caption)caption.textContent=isBanquet?'Цена на 1 гостя, ₽':'Цена, ₽'; - window.CateriumBanquet?.editor(category,item,boxes.filter(i=>Number(i.category)===6)); + window.CateriumBanquet?.editor(category,item,boxes.filter(i=>i.hidden!==true&&Number(i.category)===6)); if(isBanquet){if($('banquetSection'))$('banquetSection').value=item?.catalogSection||'';if($('banquetWeightGrams'))$('banquetWeightGrams').value=parseWeightGrams(item?.weight)||''} } function installIngredientEditorV175(){window.renderIngredients=()=>{if(!edited)return;normalizeBoxItem(edited);const root=$('ingredients');if(!root)return;const head='
Вид / названиеКол-воЕд.Вес 1 шт., г
';root.innerHTML=head+edited.ingredients.map((row,i)=>{const w=ingredientUnitWeight(row);return `
`}).join('')};window.addIngredient=()=>{if(!edited)return;if(!Array.isArray(edited.ingredients))edited.ingredients=[];edited.ingredients.push(['Новая позиция',1,'шт.',0]);window.renderIngredients();};} @@ -437,13 +437,13 @@ window.SUN_LEGACY_CATALOG_V175=[]; function wantedBoxNumber(query){const raw=String(query||'').trim();if(!raw)return null;const match=raw.match(/^(?:бокс\s*)?(?:№\s*)?0*(\d+)\s*$/i);return match?Number(match[1]):null} function itemMatches(item,query){if(!query)return true;if(Number(activeCat)===0){const wanted=wantedBoxNumber(query);return wanted!==null&&boxNumber(item)===wanted}const q=query.trim().toLowerCase();const parts=[item.name,item.catalogSection,item.weight,...(Array.isArray(item.composition)?item.composition:[]),...(Array.isArray(item.ingredients)?item.ingredients.map(x=>x?.[0]):[])];return parts.join(' ').toLowerCase().includes(q)} const BANQUET_CATEGORY=6; - function banquetCatalog(){return boxes.filter(item=>Number(item.category)===BANQUET_CATEGORY)} + function banquetCatalog(){return boxes.filter(item=>item.hidden!==true&&Number(item.category)===BANQUET_CATEGORY)} window.toggleBanquetItem=id=>{window.CateriumBanquet.toggle(draft,banquetCatalog(),String(id));renderCatalogV5();}; function renderBanquetView(items){return window.CateriumBanquet.render({items,catalog:banquetCatalog(),draft})} function renderCatalogV5(){ $('new')?.classList.toggle('sun-banquet-active',Number(activeCat)===BANQUET_CATEGORY); renderCategoryTabs();ensureCatalogTools();const catalogSearch=$('sunCatalogSearch');if(catalogSearch){const boxMode=[0,5].includes(Number(activeCat));catalogSearch.placeholder=boxMode?'Поиск по № бокса':'Поиск по позиции';catalogSearch.inputMode=boxMode?'numeric':'search';}const title=document.querySelector('#new .catalog h1');if(title)title.textContent=catName(activeCat); - const all=boxes.filter(item=>Number(item.category||0)===Number(activeCat)),items=all.filter(item=>itemMatches(item,catalogQuery));const cat=catById(activeCat)||{name:'Каталог',prep:true}; + const all=boxes.filter(item=>item.hidden!==true&&Number(item.category||0)===Number(activeCat)),items=all.filter(item=>itemMatches(item,catalogQuery));const cat=catById(activeCat)||{name:'Каталог',prep:true}; if(Number(activeCat)===BANQUET_CATEGORY){$('tiles').innerHTML=renderBanquetView(items);window.CateriumBanquet.bind($('tiles'),{catalog:banquetCatalog(),draft,rerender:renderCatalogV5});renderOrderLines();updateOrderSummary();return;} const cards=items.map(item=>``).join(''); const addText=Number(activeCat)===0?'Добавить бокс':(Number(activeCat)===5?'Добавить премиум':'Добавить позицию'); @@ -452,7 +452,7 @@ window.SUN_LEGACY_CATALOG_V175=[]; renderOrderLines();updateOrderSummary(); } window.render=renderCatalogV5; - window.add=id=>{const item=boxes.find(x=>String(x.id)===String(id));if(!item)return;let line=(draft.lines||[]).find(x=>String(x.id)===String(id));if(line)line.qty=Math.max(0,Number(line.qty||0))+1;else{if(!Array.isArray(draft.lines))draft.lines=[];draft.lines.push({id:item.id,qty:1,price:Math.max(0,Number(item.price||0))})}renderCatalogV5();}; + window.add=id=>{const item=boxes.find(x=>String(x.id)===String(id));if(!item||item.hidden===true)return;let line=(draft.lines||[]).find(x=>String(x.id)===String(id));if(line)line.qty=Math.max(0,Number(line.qty||0))+1;else{if(!Array.isArray(draft.lines))draft.lines=[];draft.lines.push({id:item.id,qty:1,price:Math.max(0,Number(item.price||0))})}renderCatalogV5();}; window.qty=(id,n)=>{if(!Array.isArray(draft.lines))draft.lines=[];const next=Math.max(0,Number(n||0));if(next<1)draft.lines=draft.lines.filter(x=>String(x.id)!==String(id));else{const line=draft.lines.find(x=>String(x.id)===String(id));if(line)line.qty=next}renderCatalogV5();}; function ensureManagerToolbar(){ @@ -461,7 +461,7 @@ window.SUN_LEGACY_CATALOG_V175=[]; } function renderManagerV5(){ ensureManagerToolbar();const managerSearch=$('sunManagerSearch');if(managerSearch){const boxMode=[0,5].includes(Number(activeCat));managerSearch.placeholder=boxMode?'Поиск по № бокса':'Поиск по позиции';managerSearch.inputMode=boxMode?'numeric':'search';}const title=document.querySelector('#manager .dialog-head h2'),add=document.querySelector('#manager .sun-manager-toolbar .primary')||document.querySelector('#manager .primary');if(title)title.textContent=catName(activeCat);if(add){add.textContent=Number(activeCat)===0?'Добавить бокс':(Number(activeCat)===5?'Добавить премиум':'Добавить позицию');add.onclick=()=>window.editBox(null)} - const items=boxes.filter(i=>Number(i.category||0)===Number(activeCat)&&itemMatches(i,managerQuery)),cat=catById(activeCat)||{prep:true}; + const items=boxes.filter(i=>i.hidden!==true&&Number(i.category||0)===Number(activeCat)&&itemMatches(i,managerQuery)),cat=catById(activeCat)||{prep:true}; $('managerList').innerHTML=items.length?items.map(item=>``).join(''):'

Ничего не найдено.

'; } window.openManager=()=>{managerQuery='';ensureManagerToolbar();if($('sunManagerSearch'))$('sunManagerSearch').value='';renderManagerV5();window.modal?.('manager');}; @@ -471,7 +471,7 @@ window.SUN_LEGACY_CATALOG_V175=[]; const cat=catById(edited.category)||{name:'Каталог',prep:true};$('editTitle').textContent=edited.id?`Изменить: ${cat.name}`:(Number(edited.category)===0?'Новый бокс':(Number(edited.category)===5?'Новая премиум позиция':'Новая позиция'));const zone=document.querySelector('#editor .ingredient-zone');if(zone)zone.style.display=cat.prep?'block':'none';window.renderIngredients?.();window.modal?.('editor'); }; if($('itemCategory'))$('itemCategory').onchange=function(){if(!edited)return;edited.category=Number(this.value);const c=catById(edited.category)||{name:'Каталог',prep:true};const zone=document.querySelector('#editor .ingredient-zone');if(zone)zone.style.display=c.prep?'block':'none';syncBoxWeightEditor(edited.category,edited);syncBanquetEditor(edited.category,edited);$('editTitle').textContent=edited.id?`Изменить: ${c.name}`:(Number(edited.category)===0?'Новый бокс':(Number(edited.category)===5?'Новая премиум позиция':'Новая позиция'));}; - window.saveBox=()=>{if(!edited)return;edited.name=$('boxName').value.trim();edited.price=Math.max(0,Number($('boxPrice').value||0));edited.oldPrice=Math.max(0,Number($('boxOldPrice')?.value||0));if(!(edited.oldPrice>edited.price))delete edited.oldPrice;edited.sale=Boolean(edited.oldPrice&&edited.oldPrice>edited.price);edited.category=Number($('itemCategory').value);if([0,5].includes(edited.category)){const grams=Math.max(0,Math.round(Number($('boxWeightGrams')?.value||0)));edited.weight=grams?`${grams} г`:'';edited.pieces=Math.max(0,Math.round(Number($('boxPiecesCount')?.value||0)));}if(edited.category===BANQUET_CATEGORY){edited.catalogSection=($('banquetSection')?.value||'').trim();const grams=Math.max(0,Math.round(Number($('banquetWeightGrams')?.value||0)));edited.weight=grams?`${grams} г`:'';}window.CateriumBanquet?.saveEditor(edited,boxes.filter(i=>Number(i.category)===6));edited.ingredients=Array.isArray(edited.ingredients)?edited.ingredients:[];if([0,5].includes(edited.category)&&$('boxCompositionText')){const next=$('boxCompositionText').value.split(/\r?\n/).map(x=>x.trim()).filter(Boolean);const before=JSON.stringify(Array.isArray(edited.composition)?edited.composition:[]);edited.composition=next;if(JSON.stringify(next)!==before)edited.compositionSource='manual';}normalizeBoxItem(edited,{cleanup:true});if(!edited.name)return alert('Введите название.');if(!edited.id){edited.id=(typeof sunUUID==='function'?sunUUID():`item-${Date.now()}`);boxes.push(edited)}else{const i=boxes.findIndex(x=>String(x.id)===String(edited.id));if(i>=0)boxes[i]=edited}if(!catById(edited.category)?.hidden)activeCat=edited.category;persist();closeModal('editor');closeModal('manager');renderCatalogV5();renderOrders?.();window.sunClientOfferCatalogChanged?.(edited.id);}; + window.saveBox=()=>{if(!edited)return;edited.name=$('boxName').value.trim();edited.price=Math.max(0,Number($('boxPrice').value||0));edited.oldPrice=Math.max(0,Number($('boxOldPrice')?.value||0));if(!(edited.oldPrice>edited.price))delete edited.oldPrice;edited.sale=Boolean(edited.oldPrice&&edited.oldPrice>edited.price);edited.category=Number($('itemCategory').value);if([0,5].includes(edited.category)){const grams=Math.max(0,Math.round(Number($('boxWeightGrams')?.value||0)));edited.weight=grams?`${grams} г`:'';edited.pieces=Math.max(0,Math.round(Number($('boxPiecesCount')?.value||0)));}if(edited.category===BANQUET_CATEGORY){edited.catalogSection=($('banquetSection')?.value||'').trim();const grams=Math.max(0,Math.round(Number($('banquetWeightGrams')?.value||0)));edited.weight=grams?`${grams} г`:'';}window.CateriumBanquet?.saveEditor(edited,boxes.filter(i=>i.hidden!==true&&Number(i.category)===6));edited.ingredients=Array.isArray(edited.ingredients)?edited.ingredients:[];if([0,5].includes(edited.category)&&$('boxCompositionText')){const next=$('boxCompositionText').value.split(/\r?\n/).map(x=>x.trim()).filter(Boolean);const before=JSON.stringify(Array.isArray(edited.composition)?edited.composition:[]);edited.composition=next;if(JSON.stringify(next)!==before)edited.compositionSource='manual';}normalizeBoxItem(edited,{cleanup:true});if(!edited.name)return alert('Введите название.');if(!edited.id){edited.id=(typeof sunUUID==='function'?sunUUID():`item-${Date.now()}`);boxes.push(edited)}else{const i=boxes.findIndex(x=>String(x.id)===String(edited.id));if(i>=0)boxes[i]=edited}if(!catById(edited.category)?.hidden)activeCat=edited.category;persist();closeModal('editor');closeModal('manager');renderCatalogV5();renderOrders?.();window.sunClientOfferCatalogChanged?.(edited.id);}; window.removeBox=()=>{if(!edited?.id||!confirm(`Удалить «${edited.name}» из каталога?`))return;boxes=boxes.filter(x=>String(x.id)!==String(edited.id));draft.lines=(draft.lines||[]).filter(x=>String(x.id)!==String(edited.id));persist();closeModal('editor');closeModal('manager');renderCatalogV5();renderOrders?.();}; function fileAsDataUrl(file){return new Promise((resolve,reject)=>{const reader=new FileReader();reader.onerror=()=>reject(reader.error||new Error('FileReader error'));reader.onload=()=>resolve(String(reader.result||''));reader.readAsDataURL(file)})} diff --git a/public/core/import-archive.js b/public/core/import-archive.js index 50a4036..7a6e3f2 100644 --- a/public/core/import-archive.js +++ b/public/core/import-archive.js @@ -1,29 +1,12 @@ (()=>{ '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); + // The one-time import is complete. Keep this cleanup entry point for old caches. + // Source details attached to individual orders remain available in order history. + function removeArchiveUI(){ + document.querySelectorAll('#cateriumTelegramArchive,dialog[aria-label="Архив заказов из Telegram"]').forEach(el=>el.remove()); } - 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(); + window.CateriumImportArchive={show:removeArchiveUI,refresh:removeArchiveUI}; + window.addEventListener('sun:cloud-state-applied',removeArchiveUI); + window.addEventListener('sun:cloud-permissions-changed',removeArchiveUI); + if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',removeArchiveUI,{once:true});else removeArchiveUI(); })(); diff --git a/public/core/ops-ux-v1762.js b/public/core/ops-ux-v1762.js index f44afdb..18896e4 100644 --- a/public/core/ops-ux-v1762.js +++ b/public/core/ops-ux-v1762.js @@ -119,7 +119,7 @@ let originalModal=null,originalCloseModal=null,originalSaveBox=null,originalRemoveBox=null; function menuCanEdit(){if(support())return false;const c=cloud();if(!c?.getSession?.()?.user)return true;return can('catalog.edit')} function currentCategory(){return CATEGORIES.find(x=>x.id===menuCategory)||CATEGORIES[0]} - function menuItems(){const q=menuQuery.trim().toLowerCase();return boxes().filter(x=>Number(x.category||0)===menuCategory&&(!q||[x.name,x.weight,x.catalogSection,...(x.composition||[])].join(' ').toLowerCase().includes(q)))} + function menuItems(){const q=menuQuery.trim().toLowerCase();return boxes().filter(x=>x.hidden!==true&&Number(x.category||0)===menuCategory&&(!q||[x.name,x.weight,x.catalogSection,...(x.composition||[])].join(' ').toLowerCase().includes(q)))} function restoreEditorDialog(){if(editorHome&&editorDialog&&editorDialog.parentNode!==editorHome){editorHome.appendChild(editorDialog);editorDialog.classList.remove('sun-menu-editor-docked');}editorHome?.classList.remove('on');} function emptyMenuDetail(text='Выберите позицию слева, чтобы открыть карточку.'){ const pane=$('sunMenuDetailV1762');if(!pane)return;restoreEditorDialog();pane.innerHTML=`
${esc(text)}${!menuCanEdit()?'
только просмотр
':''}
`; diff --git a/public/core/performance.js b/public/core/performance.js index e9a10b0..a7ab9be 100644 --- a/public/core/performance.js +++ b/public/core/performance.js @@ -1,7 +1,7 @@ (()=>{ 'use strict'; const VERSION='17.7.3'; - const RELEASE='20260918-proposals-six'; + const RELEASE='20260918-import-cleanup'; 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(){ diff --git a/public/index.html b/public/index.html index cd74fda..7db2700 100644 --- a/public/index.html +++ b/public/index.html @@ -1,4 +1,4 @@ - - + diff --git a/public/service-worker.js b/public/service-worker.js index d8b0926..ff2decf 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,5 +1,5 @@ -const CACHE='sun-catering-pwa-v106-20260918-proposals-six'; -const VERSION='20260918-proposals-six'; +const CACHE='sun-catering-pwa-v107-20260918-import-cleanup'; +const VERSION='20260918-import-cleanup'; const CORE=[ './vendor/supabase-2.112.4.min.js', `./core/help-center.js?v=${VERSION}`,`./core/help-center.css?v=${VERSION}`,`./help/knowledge-v1.json?v=${VERSION}`, diff --git a/tests/order-import.spec.mjs b/tests/order-import.spec.mjs index 239f1e4..3a5dc23 100644 --- a/tests/order-import.spec.mjs +++ b/tests/order-import.spec.mjs @@ -39,17 +39,56 @@ test('imported history keeps partial payments and delivery is charged once after 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:'

Заказы

'})); +test('completed import archive stays removed even with an old cached payload',async({page})=>{ + await page.route('**/index.html',r=>r.fulfill({contentType:'text/html; charset=utf-8',body:'

Заказы

'})); 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:'Заказ \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('{localStorage.removeItem('sunTelegramImportArchiveV1');window.dispatchEvent(new Event('sun:cloud-state-applied'))}); + await page.evaluate(()=>{ + window.CateriumImportArchive.show();window.CateriumImportArchive.refresh(); + window.dispatchEvent(new Event('sun:cloud-state-applied')); + window.dispatchEvent(new Event('sun:cloud-permissions-changed')); + }); expect(await page.locator('dialog').count()).toBe(0); expect(await page.locator('#cateriumTelegramArchive').count()).toBe(0); }); + +test('retired import positions leave the working catalogs but preserve historical orders and proposals',async({page})=>{ + test.setTimeout(45000); + await page.route('https://**',r=>r.abort()); + await page.goto('/index.html',{waitUntil:'domcontentloaded'}); + await page.waitForFunction(()=>window.SunOpsUXV1762&&window.sunClientOfferDebugCreateSnapshot&&window.__cateriumOrderEnhancementsV1775); + await page.evaluate(()=>{ + boxes=[{id:'5',name:'Фуршетный бокс №5',price:4900,category:0,photo:'',ingredients:[]}, + {id:'tg-item-fixture',name:'Фуршетный набор 8 — Салаты в шотах',price:3900,category:0,hidden:true,catalogSection:'Импорт из Telegram',photo:'',ingredients:[]}]; + orders=[{id:900002,event:'История импорта',date:'2026-09-10',time:'12:00',contact:'Тестовый клиент',phone:'+79990000000',address:'Тестовая улица, 1',note:'Исходные сведения',status:'Новый',lines:[{id:'tg-item-fixture',qty:2,price:3500}],total:7000,prepayment:7000,paymentStatus:'paid',autoCompletionDisabled:true}]; + draft.lines=[];persist();window.render(); + }); + await expect(page.locator('#tiles')).toContainText('Фуршетный бокс №5'); + await expect(page.locator('#tiles')).not.toContainText('Салаты в шотах'); + await page.evaluate(()=>{window.add('tg-item-fixture');window.openManager()}); + expect(await page.evaluate(()=>draft.lines.length)).toBe(0); + await expect(page.locator('#managerList')).not.toContainText('Салаты в шотах'); + await page.evaluate(()=>{window.closeModal('manager');window.SunOpsUXV1762.openMenu()}); + await expect(page.locator('#sunMenuCountV1762')).toHaveText('1 поз.'); + await expect(page.locator('#sunMenuListV1762')).not.toContainText('Импорт из Telegram'); + await page.evaluate(()=>{const input=document.getElementById('sunGlobalSearchInput');input.value='Салаты в шотах';input.dispatchEvent(new Event('input'))}); + expect(await page.locator('#sunGlobalSearchResults [data-action="box"]').count()).toBe(0); + const catalog=await page.evaluate(async()=>{ + window.print=()=>{};await window.sunPrintLiveCatalog(); + const text=document.getElementById('sunLiveCatalogPrintRoot').textContent; + const pages=await window.sunCatalogDebugPdfPages(0,10); + return {text,pages:pages.length}; + }); + expect(catalog.text).toContain('Фуршетный бокс №5');expect(catalog.text).not.toContain('Салаты в шотах');expect(catalog.pages).toBe(1); + await page.evaluate(()=>window.openOrder(900002)); + await expect(page.locator('#lines')).toContainText('Салаты в шотах'); + await expect(page.locator('#orderTotal')).toHaveValue('7000'); + await expect(page.locator('#phone')).toHaveValue('+79990000000'); + const snapshot=await page.evaluate(()=>window.sunClientOfferDebugCreateSnapshot(orders[0])); + expect(snapshot.items).toHaveLength(1);expect(snapshot.items[0]).toMatchObject({name:'Фуршетный набор 8 — Салаты в шотах',qty:2,unitPrice:3500,sum:7000}); + expect(await page.evaluate(()=>window.sunOrderPaymentState(orders[0]))).toBe('paid'); + await page.reload();await page.waitForFunction(()=>window.SunOpsUXV1762); + await expect(page.locator('#tiles')).not.toContainText('Салаты в шотах'); + expect(await page.evaluate(()=>({hidden:boxes.find(b=>b.id==='tg-item-fixture').hidden,lines:orders[0].lines.length,address:orders[0].address}))).toEqual({hidden:true,lines:1,address:'Тестовая улица, 1'}); +}); diff --git a/tests/release-check.mjs b/tests/release-check.mjs index f4bc548..89ddc45 100644 --- a/tests/release-check.mjs +++ b/tests/release-check.mjs @@ -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(read('core/pdf-engine.js').includes('595.28')&&read('core/pdf-engine.js').includes('841.89'),'PDF engine uses A4 MediaBox'); check([...index.matchAll(/@page\{([^}]*)\}/g)].every(m=>/size:A4/i.test(m[1])),'compact @page rules use A4'); -check(sw.includes('v106-20260918-proposals-six')&&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('20260918-proposals-six')&&index.includes('classic-offer-pdf-v1767.js')&&!index.includes('20260907-v17-6-0-stability-security'),'index cache-busting points to v17.7.3'); +check(sw.includes('v107-20260918-import-cleanup')&&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('20260918-import-cleanup')&&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("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'); @@ -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(releaseManifest.version===`v${pkg.version}`,'release manifest version matches package.json'); check(releaseManifest.channel==='production','release manifest channel is production'); -check(String(releaseManifest.pwaCache||'').includes('v106-20260918-proposals-six'),'release manifest points to current PWA cache'); +check(String(releaseManifest.pwaCache||'').includes('v107-20260918-import-cleanup'),'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(runtime.includes('CLOUD_RPC_TIMEOUT_MS=45000')&&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'); @@ -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(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(index.includes('20260918-proposals-six'),'index cache bust is v17.7.3'); -check(sw.includes('v106-20260918-proposals-six')&&sw.includes('data-layer-v1773.js')&&sw.includes('server-automation-v1770.js'),'PWA caches v17.7.3 client foundation modules'); +check(index.includes('20260918-import-cleanup'),'index cache bust is v17.7.3'); +check(sw.includes('v107-20260918-import-cleanup')&&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(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'); diff --git a/tests/static-security.mjs b/tests/static-security.mjs index 384aa43..56d5e27 100644 --- a/tests/static-security.mjs +++ b/tests/static-security.mjs @@ -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'); const gallery=fs.readdirSync(path.join(pub,'offer-gallery')).filter(x=>/\.jpg$/i.test(x)); if(gallery.length!==2)fail(`offer gallery contains ${gallery.length} jpg files, expected 2`);else ok('offer gallery trimmed'); -if(!sw.includes('20260918-proposals-six')||!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('20260918-proposals-six')||!html.includes('classic-offer-pdf-v1767.js'))fail('index still serves stale core asset version');else ok('index cache-busting is current'); +if(!sw.includes('20260918-import-cleanup')||!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('20260918-import-cleanup')||!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("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');