[{"src":"mhd242.js?v=1","code":"'use strict';\n// Calendar dates, independent of DST and UTC offsets. Only explicitly configured rules apply.\nfunction shelfDays242(article){\n const days=Number(article?.mhdDays242);\n return article?.mhd&&Number.isInteger(days)&&days>=1&&days<=3650?days:0;\n}\nfunction dateAfterDays242(raw,today=localTodayISO()){\n const days=Number(raw),date=new Date(today+'T00:00:00Z');\n if(!String(raw).trim()||!Number.isInteger(days)||days<1||days>3650)throw Error('Haltbarkeit: 1 bis 3650 ganze Tage eingeben.');\n if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(today)||!Number.isFinite(date.getTime())||date.toISOString().slice(0,10)!==today)throw Error('Einlagerungsdatum prüfen.');\n date.setUTCDate(date.getUTCDate()+days);return date.toISOString().slice(0,10);\n}\nfunction receiptMhd242(article,explicit='',today=localTodayISO()){\n if(explicit)return explicit;\n const days=shelfDays242(article);return days?dateAfterDays242(days,today):'';\n}\n"},{"src":"patch144.js?v=406","code":"'use strict';\nconst ZAPASIO_VERSION144='144';\nfunction localeNumber144(v){let s=String(v??'').trim().replace(/\\s/g,'');if(!s)return 0;if(s.includes(',')&&s.includes('.'))s=s.lastIndexOf(',')>s.lastIndexOf('.')?s.replace(/\\./g,'').replace(',','.'):s.replace(/,/g,'');else s=s.replace(',','.');return Number(s)||0}\nfunction count144(v){const raw=String(v??'').trim().replace(/\\s/g,'');const n=/^\\d{1,3}(\\.\\d{3})+$/.test(raw)?Number(raw.replace(/\\./g,'')):localeNumber144(raw);return Math.max(0,Math.round(n))}\nfunction effectiveMhd144(a){return ['required','optional','none'].includes(a?.mhdPolicy144)?a.mhdPolicy144:(a?.mhd?'required':'none')}\nfor(const a of Object.values(state.products||{})){a.referencePhotos144=Array.isArray(a.referencePhotos144)?a.referencePhotos144:[];a.mhdPolicy144=a.mhdPolicy144||'review'}\n\n/* Prevent the calculation center from leaking into other tabs. */\nconst showTabBefore144=showTab;showTab=function(which,skipPermission=false){document.getElementById('tabCalculations')?.classList.remove('active');return showTabBefore144(which,skipPermission)};\n\n/* Critical path: simultaneous tasks use the longest duration. Only an explicit follow-up adds time. */\nfunction phaseMinutes144(c,phase,draft){const blocks=(state.timeBlocks||[]).filter(b=>b.phase===phase&&workflowBlockEnabled(c,b,draft));return Math.max(0,...blocks.filter(b=>!b.mustFollow144).map(b=>workflowBlockMinutes(c,b,draft)))+blocks.filter(b=>b.mustFollow144).reduce((s,b)=>s+workflowBlockMinutes(c,b,draft),0)}\ntimePlanFor=function(c,draft=false){if(!c||!c.date||!c.readyTime)return null;const setup=phaseMinutes144(c,'setup',draft),onsite=phaseMinutes144(c,'onsite',draft),travel=Math.max(0,Number(c.travelMinutes)||0),travelConfirmed=c.travelConfirmed===true&&travel>0,buffer=Math.max(0,Number(c.bufferMinutes)||0),ready=new Date(`${c.date}T${c.readyTime}:00`);if(Number.isNaN(ready.getTime()))return null;const arrival=new Date(ready.getTime()-(onsite+setup)*60000),departure=travelConfirmed?new Date(arrival.getTime()-(travel+buffer)*60000):null;return{ready,arrival,departure,setup,onsite,travel,travelConfirmed,buffer}};\n\n/* German number entry and whole portions. */\nconst renderFoodBefore144=renderFoodCalc143;renderFoodCalc143=function(){renderFoodBefore144();const q=document.getElementById('foodQty143');if(q){q.type='text';q.inputMode='numeric';q.value='1';q.placeholder='1'}};\nupdateFoodCalc143=function(){const id=document.getElementById('foodRecipe143')?.value,q=count144(document.getElementById('foodQty143')?.value),found=id?findRecipeById(id):null,out=document.getElementById('foodResult143');let cost=0;if(found){const e=recipeEconomicMeta(found.recipe);cost=(Number(e.unitCost)||0)*q;out.innerHTML=e.complete?`<b>${esc(found.recipe.name)}</b><br>${esc(moneyFmt(cost))} für ${q.toLocaleString('de-AT')} ${esc(recipePlanningUnit108(found.recipe))}<br><small>${esc(moneyFmt(Number(e.unitCost)||0))} je Einheit</small>`:`<b>Kosten noch unvollständig</b><br>Artikelzuordnung, Umrechnung oder Einkaufspreise prüfen.`}else out.textContent='Rezept auswählen.';const revenue=localeNumber144(document.getElementById('foodRevenue143')?.value),pct=revenue?cost/revenue*100:0;document.getElementById('foodQuote143').innerHTML=`<div class='metric143 ${pct>35?'warn':''}'><small>Wareneinsatzquote</small><strong>${revenue?pct.toFixed(1).replace('.',',')+' %':'–'}</strong></div><div class='metric143'><small>Wareneinsatz</small><strong>${moneyFmt(cost)}</strong></div><div class='metric143'><small>Netto-Umsatz</small><strong>${moneyFmt(revenue)}</strong></div>`};\n\n/* Detailed hospitality contribution-margin calculation. */\nconst dbVariable144=[['dbPackaging144','Verpackung'],['dbPayment144','Kartenzahlung / Zahlungsgebühren'],['dbPlatform144','Lieferplattform / Provision'],['dbEnergyVar144','Variable Energie'],['dbTransportVar144','Variable Fahrtkosten']],dbFixed144=[['dbRent144','Miete / Standplatz'],['dbElectric144','Strom, Gas & Heizung'],['dbWater144','Wasser, Abfall & Entsorgung'],['dbInsurance144','Versicherungen'],['dbVehicle144','Fahrzeug, Leasing & Steuer'],['dbMaintenance144','Wartung & Reparaturen'],['dbFees144','Genehmigungen & Gebühren'],['dbAdmin144','Buchhaltung, Software & Telefon'],['dbDepreciation144','Abschreibung & Finanzierung'],['dbMarketing144','Marketing'],['dbCleaning144','Reinigung & Lager'],['dbOtherFixed144','Sonstige Fixkosten']];\nconst dbField144=([id,label])=>calcMoneyField143(id,label);const sumDb144=rows=>rows.reduce((s,[id])=>s+localeNumber144(document.getElementById(id)?.value),0);\nrenderContribution143=function(){document.getElementById('calc143_contribution').innerHTML=`<article class='calcCard143'><h2>Deckungsbeitrag & kostendeckender Preis</h2><p>Direkte Kosten und Fixkosten werden getrennt ausgewiesen.</p><h3>Umsatz und direkte Kosten</h3><div class='calcForm143'>${calcMoneyField143('dbRevenue143','Netto-Umsatz')}${calcMoneyField143('dbGoods143','Wareneinsatz')}${calcMoneyField143('dbPersonnel143','Direktes Personal')}${calcMoneyField143('dbExternal143','Fremdleistungen')}${dbVariable144.map(dbField144).join('')}<div class='field'><label>Verkaufte Portionen</label><input id='dbGuests143' inputmode='numeric' placeholder='0' oninput='updateContribution143()'></div></div><details class='settingsFold' open><summary><span>Fixkosten aufteilen</span><span class='foldPlus'>+</span></summary><div class='dbCosts144'>${dbFixed144.map(dbField144).join('')}</div></details><div id='dbResult143' class='metricGrid143'></div><div class='calcHint143'>DB I = Umsatz − Wareneinsatz − übrige variable Kosten. DB II zieht direktes Personal und Fremdleistungen ab.</div><p class='calcSource144'>Die alte Faktor-Methode „Wareneinsatz × 3“ ist nur eine grobe Orientierung und kein Zielpreis.</p></article>`;updateContribution143()};\nupdateContribution143=function(){const revenue=localeNumber144(document.getElementById('dbRevenue143')?.value),goods=localeNumber144(document.getElementById('dbGoods143')?.value),personnel=localeNumber144(document.getElementById('dbPersonnel143')?.value),external=localeNumber144(document.getElementById('dbExternal143')?.value),variable=sumDb144(dbVariable144),fixed=sumDb144(dbFixed144),portions=count144(document.getElementById('dbGuests143')?.value),db1=revenue-goods-variable,db2=db1-personnel-external,result=db2-fixed,breakEven=goods+variable+personnel+external+fixed,out=document.getElementById('dbResult143');if(out)out.innerHTML=`<div class='metric143 ${db1<0?'bad':''}'><small>DB I</small><strong>${moneyFmt(db1)}</strong></div><div class='metric143 ${db2<0?'bad':''}'><small>DB II</small><strong>${moneyFmt(db2)}</strong></div><div class='metric143 ${result<0?'bad':''}'><small>Betriebsergebnis</small><strong>${moneyFmt(result)}</strong></div><div class='metric143'><small>Kostendeckend netto / Portion</small><strong>${portions?moneyFmt(breakEven/portions):'–'}</strong></div><div class='metric143'><small>DB-II-Quote</small><strong>${revenue?(db2/revenue*100).toFixed(1).replace('.',',')+' %':'–'}</strong></div><div class='metric143'><small>Break-even-Umsatz</small><strong>${moneyFmt(breakEven)}</strong></div>`};\n\n/* Daily closing: net price, immediate refresh and comparisons based only on saved data. */\nconst loadDailyBefore144=loadDailyCloseForDate;loadDailyCloseForDate=function(){loadDailyBefore144();dailyCloseDraft.forEach(x=>{if(x.sellPriceNet===undefined)x.sellPriceNet=Number(findRecipeById(x.recipeId)?.recipe?.sellPriceNet)||0});renderDailyCloseRows()};\nrenderDailyCloseRows=function(){const box=document.getElementById('dailyCloseRows');if(!box)return;box.innerHTML=dailyCloseDraft.map((x,i)=>`<section class='dailyCloseRow'><div class='field dailyCloseRecipe'><label>Rezept</label><select onchange=\"setDailyCloseField(${i},'recipeId',this.value);dailyCloseDraft[${i}].sellPriceNet=Number(findRecipeById(this.value)?.recipe?.sellPriceNet)||0;renderDailyCloseRows()\">${recipeChoices104(x.recipeId)}</select></div><div class='field'><label>Vorbereitet (optional)</label><input type='number' min='0' step='1' placeholder='Nicht erfasst' value='${x.prepared??''}' onchange=\"setDailyCloseField(${i},'prepared',this.value)\"></div><div class='field'><label>Verkauft</label><input type='number' min='0' step='1' value='${Number(x.sold)||0}' onchange=\"setDailyCloseField(${i},'sold',this.value)\"></div><div class='field'><label>Übrig (optional)</label><input type='number' min='0' step='1' placeholder='Nicht erfasst' value='${x.leftover??''}' onchange=\"setDailyCloseField(${i},'leftover',this.value)\"></div><div class='field moneyInput143'><label>VK netto / Portion</label><input inputmode='decimal' placeholder='0,00' value='${x.sellPriceNet?String(x.sellPriceNet).replace('.',','):''}' onchange=\"dailyCloseDraft[${i}].sellPriceNet=localeNumber144(this.value);renderDailyCloseSummary()\"></div><label class='dailyCloseSoldOut'><input type='checkbox' ${x.soldOut?'checked':''} onchange=\"setDailyCloseField(${i},'soldOut',this.checked)\"><span>Ausverkauft</span></label><button class='danger' onclick='removeDailyCloseRow(${i})'>×</button></section>`).join('');renderDailyCloseSummary()};\nconst saveDailyBefore144=saveDailyClose;saveDailyClose=function(){saveDailyBefore144();setTimeout(()=>calcSection143==='daily'&&renderDaily143(),0)};\nrenderDaily143=function(){const el=document.getElementById('calc143_daily'),rows=(state.dailyClosings||[]).slice().sort((a,b)=>String(b.date).localeCompare(String(a.date))),latest=rows[0],prev=latest?rows.find(x=>(new Date(latest.date+'T12:00:00')-new Date(x.date+'T12:00:00'))/86400000===7):null,items=latest?.items||[],max=Math.max(1,...items.map(i=>Number(i.sold)||0),...((prev?.items)||[]).map(i=>Number(i.sold)||0)),sum=k=>items.reduce((s,i)=>s+(Number(i[k])||0),0),rev=items.reduce((s,i)=>s+(Number(i.sold)||0)*(Number(i.sellPriceNet)||0),0);el.innerHTML=`<article class='calcCard143'><div style='display:flex;justify-content:space-between;gap:12px;align-items:center'><div><h2>Tagesabschlüsse</h2><p>Produkte, Portionen und Vergleich zum gleichen Wochentag der Vorwoche.</p></div><button class='primary' onclick='openDailyClose()'>+ Tagesabschluss</button></div>${latest?`<div class='dailyStats144'><div><small>Vorbereitet</small><b>${dailyOptionalTotal255(items,'prepared')}</b></div><div><small>Verkauft</small><b>${sum('sold')}</b></div><div><small>Übrig</small><b>${dailyOptionalTotal255(items,'leftover')}</b></div><div><small>Netto-Umsatz</small><b>${moneyFmt(rev)}</b></div></div>${dailyEconomicsHtml255(items)}<h3>${esc(formatDate(latest.date))}${prev?' · Vorwoche im Vergleich':' · noch kein Vorwochenvergleich'}</h3><div class='chart144'>${items.map(i=>{const v=Number(i.sold)||0,p=Number(prev?.items?.find(x=>x.recipeId===i.recipeId)?.sold)||0;return`<div class='bar144'><span>${esc(findRecipeById(i.recipeId)?.recipe?.name||'Unbekannt')}</span><span class='barTrack144'><i style='width:${v/max*100}%'></i></span><b>${v}${prev?' / '+p:''}</b></div>`}).join('')}</div>`:''}<div class='closeList143'>${rows.length?rows.map(x=>{const sold=(x.items||[]).reduce((s,i)=>s+(Number(i.sold)||0),0),r=(x.items||[]).reduce((s,i)=>s+(Number(i.sold)||0)*(Number(i.sellPriceNet)||0),0);return`<button class='closeRow143' onclick=\"openDailyClose('${escJs(x.date)}')\"><b>${esc(formatDate(x.date))}</b><span><b>${esc(x.location||'Ohne Standort')}</b><small>${(x.items||[]).length} Produkte</small></span><span><small>Verkauft</small><b>${sold}</b></span><span><small>Netto</small><b>${moneyFmt(r)}</b></span></button>`}).join(''):'<p class=\"muted\">Noch keine Tagesabschlüsse gespeichert.</p>'}</div></article>`};\n\n/* Explicit MHD policy and admin-controlled reference photos. */\nfunction installArticle144(){const row=document.getElementById('aeMhd')?.closest('.checkRow');if(row&&!document.getElementById('aeMhdPolicy144'))row.outerHTML=`<div class='field full'><label>MHD-Einstellung</label><select id='aeMhdPolicy144' onchange='document.getElementById(\"aeMhd\").checked=this.value===\"required\";renderExistingMhdEditor();updateMhdFields406(true)'><option value='review'>Noch zu prüfen</option><option value='required'>MHD erforderlich</option><option value='optional'>MHD optional</option><option value='none'>Kein MHD</option></select><input id='aeMhd' type='checkbox' hidden><div class='tiny'>Bestehende Artikel werden nicht automatisch umgestellt.</div></div>`;const policy=document.getElementById('aeMhdPolicy144');if(policy&&!document.getElementById('aeMhdDays242'))policy.insertAdjacentHTML('afterend',`<label for=\"aeMhdDays242\">Haltbarkeit in Tagen oder MHD dieser Ware</label><input id=\"aeMhdDays242\" type=\"text\" inputmode=\"decimal\" oninput=\"this.value=formatMhdDigits262(this.value)\" placeholder=\"z. B. 10 oder 10.10.2026\"><small>Tage gelten für künftige Eingänge. Ein Datum gilt nur für diese Ware.</small>`);const p=document.getElementById('aePhotoPreview')?.closest('.field');if(p&&!document.getElementById('refs144'))p.insertAdjacentHTML('afterend',`<div class='full'><h3>Referenzfotos zur Produkterkennung</h3><div id='refs144' class='refGrid144'></div><input id='refInput144' type='file' accept='image/*' capture='environment' hidden onchange='addEditorRef144(this.files[0])'><button type='button' class='soft' onclick='document.getElementById(\"refInput144\").click()'>+ Referenzfoto</button></div>`)}\nfunction updateMhdFields406(clear=false){const policy=document.getElementById('aeMhdPolicy144'),input=document.getElementById('aeMhdDays242');if(!input)return;const hide=policy?.value==='none';if(hide&&clear)input.value='';input.hidden=hide;input.disabled=hide;const label=document.querySelector('label[for=\"aeMhdDays242\"]');if(label)label.hidden=hide;const hint=input.nextElementSibling;if(hint?.tagName==='SMALL')hint.hidden=hide;}\nlet editorRefs144=[];function renderRefs144(){const e=document.getElementById('refs144');if(e)e.innerHTML=editorRefs144.length?editorRefs144.map((r,i)=>`<div class='ref144'><img src='${r.data}' alt='Referenzfoto'><button type='button' class='danger' onclick='editorRefs144.splice(${i},1);renderRefs144()'>Entfernen</button></div>`).join(''):'<p class=\"muted\">Noch keine Referenzfotos.</p>'}\nasync function compress144(file){return new Promise((ok,no)=>{const im=new Image(),u=URL.createObjectURL(file);im.onload=()=>{const s=Math.min(1,900/Math.max(im.width,im.height)),c=document.createElement('canvas');c.width=Math.round(im.width*s);c.height=Math.round(im.height*s);c.getContext('2d').drawImage(im,0,0,c.width,c.height);URL.revokeObjectURL(u);ok(c.toDataURL('image/jpeg',.7))};im.onerror=no;im.src=u})}\nasync function hash144(data){return new Promise(ok=>{const im=new Image();im.onload=()=>{const c=document.createElement('canvas');c.width=9;c.height=8;const x=c.getContext('2d');x.drawImage(im,0,0,9,8);const d=x.getImageData(0,0,9,8).data,g=[];for(let i=0;i<72;i++)g.push((d[i*4]+d[i*4+1]+d[i*4+2])/3);let h='';for(let y=0;y<8;y++)for(let q=0;q<8;q++)h+=g[y*9+q]>g[y*9+q+1]?'1':'0';ok(h)};im.src=data})}\nasync function addEditorRef144(file){if(!file||!hasPermission('articles'))return;if(editorRefs144.length>=6){showFeedback('Maximal 6 Referenzfotos','warn');return}const data=await compress144(file);editorRefs144.push({id:'ref-'+Date.now(),data,hash:await hash144(data),createdAt:new Date().toISOString(),confirmed:true,packIndex:0});renderRefs144()}\nconst openArticleBefore144=openArticleEditor;openArticleEditor=function(code=null){installArticle144();openArticleBefore144(code);const a=code?state.products[code]:null;document.getElementById('aeMhdDays242').value=a?.mhdDays242||'';document.getElementById('aeMhdPolicy144').value=a?.mhdPolicy144||'review';document.getElementById('aeMhd').checked=effectiveMhd144(a)==='required';editorRefs144=clone(a?.referencePhotos144||[]);renderRefs144();updateMhdFields406()};\nconst saveArticleBefore144=saveArticleEditor;saveArticleEditor=function(...args){const noMhd=document.getElementById('aeMhdPolicy144')?.value==='none';if(noMhd)document.getElementById('aeMhdDays242').value='';const daysRaw=noMhd?'':document.getElementById('aeMhdDays242')?.value.trim()||'';if(daysRaw){try{dateAfterDays242(daysRaw)}catch(error){showFeedback('Haltbarkeit prüfen','warn',4000,error.message);return}document.getElementById('aeMhdPolicy144').value='required'}document.getElementById('aeMhd').checked=document.getElementById('aeMhdPolicy144')?.value==='required';return saveArticleBefore144(...args)};\n\n/* Mobile photo flow. No image can book stock without explicit confirmation. */\nlet photoDraft144=null;\nfunction installPhoto144(){if(!document.getElementById('photoModal144'))document.body.insertAdjacentHTML('beforeend',`<div class='modal' id='photoModal144'><div class='card'><div class='modalHeader'><div><h2>Produkt fotografieren</h2><p class='muted'>Erkennen, bestätigen und erst danach buchen.</p></div><button class='close' onclick='closePhoto144()'>× Schließen</button></div><div id='photoBody144' class='photo144'></div></div></div><input id='photoInput144' type='file' accept='image/*' capture='environment' hidden onchange='const f=this.files[0];this.value=\"\";analysePhoto144(f)'>`);const m=document.querySelector('.mobileSearchBox');if(m&&!document.getElementById('photoBtn144'))m.insertAdjacentHTML('afterend',`<button id='photoBtn144' class='primary' style='width:100%;margin-top:10px' onclick='startPhoto144()'>📷 Produkt fotografieren</button>`);const l=document.querySelector('#tabLager .scan');if(l&&!document.getElementById('photoDesk144'))l.insertAdjacentHTML('afterend',`<button id='photoDesk144' class='soft' style='margin-top:10px' onclick='startPhoto144()'>📷 Produkt fotografieren</button>`)}\nfunction closePhoto144(){document.getElementById('photoModal144').style.display='none';photoDraft144=null}function startPhoto144(){if(!guardPermission('scan','Produkterkennung'))return;installPhoto144();photoDraft144={step:'capture',candidates:[],count:1,lines:[],mode:'same'};document.getElementById('photoModal144').style.display='flex';renderPhoto144()}\nfunction renderPhoto144(){const b=document.getElementById('photoBody144'),d=photoDraft144;if(!b||!d)return;if(d.step==='capture'){b.innerHTML=`<p>Fotografiere vorzugsweise die Vorderseite der Verpackung.</p><button class='primary' onclick='document.getElementById(\"photoInput144\").click()'>Kamera öffnen</button><button class='soft' onclick='closePhoto144()'>Abbrechen</button>`;return}if(d.step==='analyse'){b.innerHTML='<p><b>Foto wird lokal geprüft …</b></p>';return}if(d.step==='choose'){b.innerHTML=`<h3>${d.candidates.length?'Passenden Artikel bestätigen':'Kein Artikel erkannt'}</h3><div class='photoCandidates144'>${d.candidates.map(c=>{const a=state.products[c.code],p=getPacks(a,c.code)[c.packIndex]||getPacks(a,c.code)[0];return`<button class='photoCandidate144' onclick=\"selectPhoto144('${escJs(c.code)}',${c.packIndex})\"><img src='${c.preview||a.photo||''}'><span><b>${esc(a.name)}</b><small>${esc(p[0])} · ${formatPackMeasure(packUsableQty(p),a.unit)}</small></span><span>${Math.round(c.score*100)} %</span></button>`}).join('')}</div><div class='photoActions144'><button class='soft' onclick='pickExisting144()'>Vorhandenen Artikel auswählen</button>${canCreateArticles()?`<button class='soft' onclick='beginProductFromPhoto216()'>Neues Produkt anlegen</button>`:''}<button class='soft' onclick='document.getElementById(\"photoInput144\").click()'>Erneut fotografieren</button><button class='soft' onclick='closePhoto144()'>Abbrechen</button></div>`;return}const a=state.products[d.code],p=getPacks(a,d.code)[d.packIndex]||getPacks(a,d.code)[0],policy=effectiveMhd144(a);if(d.step==='confirm'){b.innerHTML=`<h3>${esc(a.name)} erkannt</h3><p>${esc(p[0])} · ${formatPackMeasure(packUsableQty(p),a.unit)}</p><div class='photoActions144'><button class='primary' onclick='photoDraft144.step=\"quantity\";renderPhoto144()'>Richtig</button><button class='soft' onclick='pickExisting144()'>Anderes Produkt auswählen</button></div>`;return}if(d.step==='quantity'){b.innerHTML=`<h3>Menge eingeben</h3><div class='field'><label>Anzahl ${esc(p[0])}</label><input id='photoCount144' inputmode='numeric' value='${d.count}'></div><div class='photoActions144'><button class='primary' onclick='quantityNext144()'>Weiter</button><button class='soft' onclick='photoDraft144.step=\"confirm\";renderPhoto144()'>Zurück</button><button class='soft' onclick='closePhoto144()'>Abbrechen</button></div>`;return}if(d.step==='mhd'){const used=d.lines.reduce((s,x)=>s+x.count,0),left=d.count-used;b.innerHTML=`<h3>${policy==='optional'?'MHD optional':'MHD erforderlich'}</h3>${d.count>1?`<div class='field'><label>Haben alle ${d.count} Packungen dasselbe MHD?</label><select onchange='photoDraft144.mode=this.value;photoDraft144.lines=[];renderPhoto144()'><option value='same' ${d.mode==='same'?'selected':''}>Ja, gleiches MHD</option><option value='split' ${d.mode==='split'?'selected':''}>Nein, unterschiedliche MHD</option></select></div>`:''}<div class='mhdLines144'>${d.lines.map((x,i)=>`<div class='mhdLine144'><b>${x.count} Packung(en)</b><b>${esc(formatDate(x.date))}</b><button class='danger' onclick='photoDraft144.lines.splice(${i},1);renderPhoto144()'>×</button></div>`).join('')}</div>${d.mode==='same'&&!d.lines.length?`<div class='field'><label>MHD</label><input id='photoDate144' type='date' value='${esc(d.suggestedMhd256||\"\")}'></div>`:d.mode==='split'&&left?`<p><b>${used} von ${d.count} Packungen erfasst</b></p><div class='field'><label>Teilmenge (${left} offen)</label><input id='photoPart144' inputmode='numeric' value='1'></div><div class='field'><label>MHD</label><input id='photoDate144' type='date' value='${esc(d.suggestedMhd256||\"\")}'></div><button class='soft' onclick='addMhd144()'>MHD-Zeile hinzufügen</button>`:''}<div class='photoActions144'>${policy==='optional'?`<button class='soft' onclick='photoDraft144.lines=[];photoDraft144.step=\"summary\";renderPhoto144()'>Ohne MHD weiter</button>`:''}<button class='primary' onclick='finishMhd144()'>Weiter</button><button class='soft' onclick='photoDraft144.step=\"quantity\";renderPhoto144()'>Zurück</button></div>`;return}if(d.step==='summary')b.innerHTML=`<h3>Zusammenfassung</h3><p><b>${esc(a.name)}</b><br>${d.count} × ${esc(p[0])}<br>${formatPackMeasure(d.count*packUsableQty(p),a.unit)}</p>${d.lines.length?`<ul>${d.lines.map(x=>`<li>${x.count} Packung(en): ${esc(formatDate(x.date))}</li>`).join('')}</ul>`:'<p>Kein MHD erfasst.</p>'}<div class='photoActions144'><button class='primary' onclick='savePhoto144()'>Wareneingang speichern</button><button class='soft' onclick='photoDraft144.step=\"${policy==='none'?'quantity':'mhd'}\";renderPhoto144()'>Zurück</button><button class='soft' onclick='closePhoto144()'>Abbrechen</button></div>`}\nfunction ham144(a,b){if(!a||!b||a.length!==b.length)return 64;let n=0;for(let i=0;i<a.length;i++)n+=a[i]!==b[i];return n}\nasync function analysePhoto144(file){if(!file)return;photoDraft144.step='analyse';renderPhoto144();try{const data=await compress144(file),hash=await hash144(data),hits=[];if('BarcodeDetector'in window)try{const bm=await createImageBitmap(file),codes=await new BarcodeDetector().detect(bm),h=findScanCode(codes[0]?.rawValue||'');if(h)hits.push({code:h.productCode,packIndex:Math.max(0,getPacks(state.products[h.productCode],h.productCode).indexOf(h.pack)),score:1,preview:data})}catch{}for(const [code,a] of Object.entries(state.products)){const refs=a.referencePhotos144||[];for(const r of refs){const referenceHash=r.hash||await hash144(r.data);const score=1-ham144(hash,referenceHash)/64;if(score>.58)hits.push({code,packIndex:Number(r.packIndex)||0,score,preview:r.data})}/* Existing article photos are valid references too. Read only: no migration or product save. */if(a.photo?.startsWith('data:image/')&&!refs.some(r=>r.data===a.photo)){const score=1-ham144(hash,await hash144(a.photo))/64;if(score>.58)hits.push({code,packIndex:0,score,preview:a.photo})}}const unique=[];for(const h of hits.sort((a,b)=>b.score-a.score))if(!unique.some(x=>x.code===h.code&&x.packIndex===h.packIndex))unique.push(h);photoDraft144.candidates=unique.slice(0,3);photoDraft144.step='choose';renderPhoto144()}catch(e){showFeedback('Foto konnte nicht verarbeitet werden','warn');photoDraft144.step='capture';renderPhoto144()}}\nfunction selectPhoto144(code,packIndex=0){Object.assign(photoDraft144,{code,packIndex,step:'confirm'});renderPhoto144()}function pickExisting144(){const q=prompt('Artikelname oder Barcode');if(q===null)return;const h=findScanCode(q),r=mobileProductSearch(q);if(h)selectPhoto144(h.productCode,Math.max(0,getPacks(state.products[h.productCode],h.productCode).indexOf(h.pack)));else if(r.length)selectPhoto144(r[0][0]);else showFeedback('Kein Artikel gefunden','warn')}\nfunction quantityNext144(){const n=count144(document.getElementById('photoCount144')?.value);if(n<1){showFeedback('Mindestens 1 Packung eingeben','warn');return}photoDraft144.count=n;photoDraft144.lines=[];const article=state.products[photoDraft144.code];if(article.mhdPolicy144==='review'){showFeedback('Bitte zuerst die MHD-Regel im Artikel festlegen.','warn');return}const days=shelfDays242(article);photoDraft144.autoDays242=days;photoDraft144.receiptDay242=localTodayISO();photoDraft144.suggestedMhd256=receiptMhd242(article,photoDraft144.receiptMhd258||'');photoDraft144.step=effectiveMhd144(state.products[photoDraft144.code])==='none'?'summary':'mhd';renderPhoto144()}\nfunction validDate144(v){if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(v))return false;const [y,m,d]=v.split('-').map(Number),x=new Date(Date.UTC(y,m-1,d));return x.getUTCFullYear()===y&&x.getUTCMonth()===m-1&&x.getUTCDate()===d}\nfunction confirmDate144(date,done){if(!validDate144(date)){showFeedback('Gültiges Kalenderdatum eingeben','warn');return}const now=new Date(localDateKey()+'T00:00:00'),dt=new Date(date+'T00:00:00');if(dt<now)return askConfirm('Dieses MHD ist abgelaufen. Bewusst als Zugang speichern?',done,'Bewusst bestätigen','Abgelaufenes MHD');if((dt-now)/31557600000>5)return askConfirm('Dieses MHD liegt ungewöhnlich weit in der Zukunft.',done,'Datum übernehmen','MHD prüfen');done()}\nfunction addMhd144(){const d=photoDraft144,c=count144(document.getElementById('photoPart144')?.value),left=d.count-d.lines.reduce((s,x)=>s+x.count,0),date=document.getElementById('photoDate144')?.value;if(c<1||c>left)return showFeedback('Teilmenge prüfen','warn');confirmDate144(date,()=>{d.lines.push({count:c,date});renderPhoto144()})}\nfunction finishMhd144(){const d=photoDraft144,policy=effectiveMhd144(state.products[d.code]);if(d.mode==='same'&&!d.lines.length){const date=document.getElementById('photoDate144')?.value;if(!date){if(policy==='optional'){d.step='summary';renderPhoto144();return}return showFeedback('MHD ist erforderlich','warn')}return confirmDate144(date,()=>{d.lines=[{count:d.count,date}];d.step='summary';renderPhoto144()})}const sum=d.lines.reduce((s,x)=>s+x.count,0);if(sum!==d.count)return showFeedback(`${sum} von ${d.count} Packungen erfasst`,'warn');d.step='summary';renderPhoto144()}\nfunction savePhoto144(){if(typeof savePhotoDurable275==='function')return savePhotoDurable275();if(photoDraft144?.autoDays242&&(shelfDays242(state.products[photoDraft144.code])!==photoDraft144.autoDays242||localTodayISO()!==photoDraft144.receiptDay242)){showFeedback('MHD erneut prüfen','warn',4000,'Haltbarkeit oder Einlagerungstag hat sich geändert. Bitte die Menge erneut bestätigen.');return}const d=photoDraft144,a=state.products[d.code],p=getPacks(a,d.code)[d.packIndex]||getPacks(a,d.code)[0],total=d.count*packUsableQty(p),before=clone(state);a.stock=(Number(a.stock)||0)+total;if(document.getElementById('photoCommission168')?.checked)a.consignmentQty168=(Number(a.consignmentQty168)||0)+total;d.lines.forEach(x=>addBatch(d.code,x.count*packUsableQty(p),x.date,{packName:p[0],packSize:packUsableQty(p),packCount:x.count,packCode:p[2]||'',consignment168:!!document.getElementById('photoCommission168')?.checked}));state.log.push(`${stamp()} | ${a.name} | +${total} | Wareneingang per Produktfoto`);addMovement('in',d.code,total,{unitCost:Number(a.price)||0,supplier:a.supplier||'',source:'photo-confirmed',reason:document.getElementById('photoCommission168')?.checked?'Kommission':'Eigener Bestand'});if(!persistState('Wareneingang gespeichert',false)){state=before;return}closePhoto144();render();showFeedback('Wareneingang gespeichert','success',2500,`${d.count} × ${p[0]} · ${a.name}`)}\n\nconst renderBefore144=render;render=function(){const r=renderBefore144();installPhoto144();installArticle144();document.querySelectorAll('#tabSettings h1 small').forEach(x=>x.textContent='Testversion 144');return r};installArticle144();installPhoto144();/* Initial full refresh is performed after all patches load. */\n"},{"src":"patch145.js?v=247","code":"'use strict';\nconst ZAPASIO_VERSION145='145';\nconst TAX145={AT:{currency:'EUR',rates:[[10,'Speisen / Gastronomie'],[20,'Getränke / Regelsatz'],[4.9,'Ausgewählte Lebensmittel-Lieferung']]},DE:{currency:'EUR',rates:[[7,'Speisen / Gastronomie'],[19,'Getränke / Regelsatz']]},PL:{currency:'PLN',rates:[[8,'Gastronomie / Speisen'],[23,'Getränke / Regelsatz']]}};\nfunction taxCountry145(){return TAX145[business130().country]||TAX145.AT}\nfunction fmt145(v){return moneyFmt(v)}\nfunction selectedRecipe145(){const id=document.getElementById('foodRecipe145')?.value;return id?findRecipeById(id):null}\nfunction foodModel145(){const f=selectedRecipe145(),portions=Math.max(1,count144(document.getElementById('foodPortions145')?.value||1)),meta=f?recipeEconomicMeta(f.recipe):null,unitCost=Number(meta?.unitCost)||0,goods=unitCost*portions,netPer=Math.max(0,localeNumber144(document.getElementById('foodNetPer145')?.value)),other=Math.max(0,localeNumber144(document.getElementById('foodOther145')?.value)),vat=Math.max(0,localeNumber144(document.getElementById('foodVat145')?.value)),netTotal=netPer*portions,result=netTotal-goods-other,quote=netTotal?goods/netTotal*100:0,grossPer=netPer*(1+vat/100),target=Math.max(0,localeNumber144(document.getElementById('foodTarget145')?.value)),targetNet=goods+other+target;return{f,portions,meta,unitCost,goods,netPer,other,vat,netTotal,result,quote,grossPer,grossTotal:netTotal*(1+vat/100),targetNet,targetPer:targetNet/portions,targetGross:targetNet*(1+vat/100),targetGrossPer:targetNet/portions*(1+vat/100)}}\nfunction taxOptions145(selected){return taxCountry145().rates.map(([r,l])=>`<option value='${r}' ${Number(selected)===r?'selected':''}>${String(r).replace('.',',')} % · ${esc(l)}</option>`).join('')+`<option value='custom'>Anderen Satz eingeben</option>`}\nfunction renderFoodCalc143(){const el=document.getElementById('calc143_food'),b=business130(),defaultVat=Number(b.vat)||taxCountry145().rates[0][0];el.innerHTML=`<div class='foodCalc145'><article class='calcCard143'><h2>Wareneinsatz aus Rezept</h2><p>Portionskosten aus den tatsächlich hinterlegten Zutaten und Einkaufspreisen.</p><div class='field'><label>Rezept</label><select id='foodRecipe145' onchange='foodRecipeChanged145()'><option value=''>Bitte auswählen</option>${recipeOptions143()}</select></div><div class='field'><label>Portionen</label><input id='foodPortions145' inputmode='numeric' value='1' oninput='updateFoodCalc143()'></div><div id='foodRecipeResult145' class='calcHint143'>Rezept auswählen.</div></article><article class='calcCard143'><h2>Preis, Quote und Ergebnis</h2><p>Der Verkaufspreis wird pro Portion eingegeben. Der Gesamtumsatz wird automatisch berechnet.</p><div class='calcForm143'><div class='field moneyInput143'><label>Netto-VK pro Portion</label><input id='foodNetPer145' inputmode='decimal' placeholder='0,00' oninput='updateFoodCalc143()'></div><div class='field moneyInput143'><label>Weitere Kosten gesamt netto</label><input id='foodOther145' inputmode='decimal' placeholder='0,00' oninput='updateFoodCalc143()'></div><div class='field'><label>Umsatzsteuer / Leistungsart</label><select id='foodVatSelect145' onchange='setFoodVat145(this.value)'>${taxOptions145(defaultVat)}</select></div><div class='field'><label>Verwendeter Steuersatz (%)</label><input id='foodVat145' inputmode='decimal' value='${String(defaultVat).replace('.',',')}' oninput='updateFoodCalc143()'></div></div><div class='taxHint145'>Vorschlag für ${esc(VAT130[b.country]?.label||'Österreich')}. Bitte die konkrete Leistung prüfen, besonders bei Getränken, gemischten Pauschalen und reinen Lieferungen.</div><div id='foodMetrics145' class='foodMetrics145'></div><div class='target145'><h3>Zielwertrechner</h3><div class='field moneyInput143'><label>Gewünschter Netto-Gewinn gesamt</label><input id='foodTarget145' inputmode='decimal' placeholder='0,00' oninput='updateFoodCalc143()'></div><div id='foodTargetResult145' class='foodMetrics145'></div></div><p class='calcFormula145'>Wareneinsatzquote = Wareneinsatz ÷ Netto-VK gesamt × 100. Das geschätzte Netto-Ergebnis zieht Wareneinsatz und zusätzlich eingetragene Kosten ab.</p></article></div>`;updateFoodCalc143()}\nfunction foodRecipeChanged145(){const f=selectedRecipe145();if(f&&Number(f.recipe.sellPriceNet)>0)document.getElementById('foodNetPer145').value=String(Number(f.recipe.sellPriceNet)).replace('.',',');updateFoodCalc143()}\nfunction setFoodVat145(v){if(v!=='custom')document.getElementById('foodVat145').value=String(v).replace('.',',');updateFoodCalc143()}\nfunction updateFoodCalc143(){const m=foodModel145(),out=document.getElementById('foodRecipeResult145'),metrics=document.getElementById('foodMetrics145'),target=document.getElementById('foodTargetResult145');if(!out||!metrics||!target)return;if(!m.f)out.textContent='Rezept auswählen.';else if(!m.meta?.complete)out.innerHTML='<b>Kosten noch unvollständig</b><br>Fehlende Artikelzuordnung, Umrechnung oder Einkaufspreise im Rezept prüfen.';else out.innerHTML=`<b>${esc(m.f.recipe.name)}</b><br>${fmt145(m.goods)} für ${m.portions.toLocaleString('de-AT')} Portionen<br><small>${fmt145(m.unitCost)} je Portion</small>`;const cls=m.result>0?'good':m.result<0?'bad':'warn',resultLabel=m.result>=0?'Geschätzter Netto-Gewinn':'Geschätzter Netto-Verlust';metrics.innerHTML=`<div class='foodMetric145 ${m.quote>35?'warn':'good'}'><small>Wareneinsatzquote</small><strong>${m.netTotal?m.quote.toFixed(1).replace('.',',')+' %':'–'}</strong></div><div class='foodMetric145'><small>Netto-VK gesamt</small><strong>${fmt145(m.netTotal)}</strong></div><div class='foodMetric145'><small>Brutto-VK pro Portion</small><strong>${fmt145(m.grossPer)}</strong></div><div class='foodMetric145'><small>Brutto-VK gesamt</small><strong>${fmt145(m.grossTotal)}</strong></div><div class='foodMetric145'><small>Wareneinsatz gesamt</small><strong>${fmt145(m.goods)}</strong></div><div class='foodMetric145 ${cls}'><small>${resultLabel}</small><strong class='profit145'>${fmt145(m.result)}</strong></div>`;target.innerHTML=`<div class='foodMetric145'><small>Vorgeschlagener Netto-VK gesamt</small><strong>${fmt145(m.targetNet)}</strong></div><div class='foodMetric145'><small>Netto pro Portion</small><strong>${fmt145(m.targetPer)}</strong></div><div class='foodMetric145'><small>Brutto-VK gesamt</small><strong>${fmt145(m.targetGross)}</strong></div><div class='foodMetric145'><small>Brutto pro Portion</small><strong>${fmt145(m.targetGrossPer)}</strong></div>`}\n\nconst renderContributionBefore145=renderContribution143;renderContribution143=function(){renderContributionBefore145();const form=document.querySelector('#calc143_contribution .calcForm143');if(form&&!document.getElementById('dbRecipe145'))form.insertAdjacentHTML('beforebegin',`<div class='recipeTake145'><div class='field'><label>Wareneinsatz aus Rezept übernehmen</label><select id='dbRecipe145'><option value=''>Bitte auswählen</option>${recipeOptions143()}</select></div><button class='soft' onclick='takeDbRecipe145()'>Übernehmen</button></div>`);updateContribution143()};\nfunction takeDbRecipe145(){const id=document.getElementById('dbRecipe145')?.value,f=id?findRecipeById(id):null,portions=Math.max(1,count144(document.getElementById('dbGuests143')?.value||1));if(!f)return showFeedback('Bitte ein Rezept auswählen','warn');const e=recipeEconomicMeta(f.recipe);if(!e.complete)return showFeedback('Rezeptkosten unvollständig','warn');document.getElementById('dbGoods143').value=String((Number(e.unitCost)||0)*portions).replace('.',',');updateContribution143();showFeedback('Wareneinsatz übernommen','success',1800,`${f.recipe.name} · ${portions} Portionen`)}\n\nquickCalcNumber130=function(id){return Math.max(0,localeNumber144(document.getElementById(id)?.value))};\nconst calculateQuickBefore145=calculateQuick130;calculateQuick130=function(){calculateQuickBefore145();const out=document.getElementById('quickCalcResult130');if(!out)return;const persons=Math.max(1,count144(document.getElementById('qcPersons130')?.value)),hours=quickCalcNumber130('qcHours130'),staff=Math.max(1,count144(document.getElementById('qcStaff130')?.value)),food=persons*(quickCalcNumber130('qcStarter130')+quickCalcNumber130('qcMain130')+quickCalcNumber130('qcDessert130')),cost=food+hours*staff*quickCalcNumber130('qcHourly130')+quickCalcNumber130('qcKm130')*quickCalcNumber130('qcKmRate130')+quickCalcNumber130('qcOther130'),net=cost*(1+quickCalcNumber130('qcMarkup130')/100),profit=net-cost;out.insertAdjacentHTML('beforeend',`<div class='quickCalcMetric130'><small>Geschätzter Netto-Gewinn</small><strong style='color:${profit>=0?'#075d34':'#a1261e'}'>${fmt145(profit)}</strong></div>`)};\n\nconst renderBefore145=render;render=function(){const r=renderBefore145();document.querySelectorAll('#tabSettings h1 small').forEach(x=>x.textContent='Testversion 145');return r};/* Initial full refresh is performed after all patches load. */\n"},{"src":"patch146.js?v=247","code":"'use strict';\n// Compare overlapping product regions; leave stored photos and stock untouched.\nconst featureCache146 = new WeakMap();\nconst regions146 = [0, .05, .1].flatMap(x => [.2, .25, .3, .35].map(y => ({x, y})));\nfunction region146(image, x, y, width, height) {\n  const canvas = document.createElement('canvas');\n  canvas.width = 9; canvas.height = 8;\n  const ctx = canvas.getContext('2d', {willReadFrequently: true});\n  ctx.drawImage(image, Math.round(x * image.width), Math.round(y * image.height),\n    Math.max(1, Math.round(width * image.width)), Math.max(1, Math.round(height * image.height)), 0, 0, 9, 8);\n  const pixels = ctx.getImageData(0, 0, 9, 8).data, gray = [], color = [0, 0, 0];\n  for (let i = 0; i < 72; i++) {\n    const j = 4 * i;\n    gray.push((pixels[j] + pixels[j + 1] + pixels[j + 2]) / 3);\n    for (let c = 0; c < 3; c++) color[c] += pixels[j + c] / (72 * 255);\n  }\n  let hash = '';\n  for (let row = 0; row < 8; row++) for (let col = 0; col < 8; col++)\n    hash += gray[row * 9 + col] > gray[row * 9 + col + 1] ? '1' : '0';\n  return {hash, color};\n}\nfunction describe146(data) {\n  return new Promise((resolve, reject) => {\n    const image = new Image();\n    image.onload = () => {\n      try { resolve({whole: region146(image, 0, 0, 1, 1),\n        regions: regions146.map(({x, y}) => ({x, y, ...region146(image, x, y, .9, .65)}))}); }\n      catch (error) { reject(error); }\n    };\n    image.onerror = reject; image.src = data;\n  });\n}\nfunction cached146(owner, data) {\n  const cached = featureCache146.get(owner);\n  if (cached?.data === data) return cached.features;\n  const features = describe146(data);\n  featureCache146.set(owner, {data, features});\n  return features;\n}\nfunction similarity146(query, reference) {\n  const whole = 1 - ham144(query.whole.hash, reference.whole.hash) / 64;\n  let cropped = 0;\n  for (const a of query.regions) for (const b of reference.regions) {\n    if (Math.abs(a.x - b.x) > .051 || Math.abs(a.y - b.y) > .051) continue;\n    const shape = 1 - ham144(a.hash, b.hash) / 64;\n    if (shape <= cropped - .14) continue;\n    const distance = Math.hypot(...a.color.map((value, i) => value - b.color[i])) / Math.sqrt(3);\n    cropped = Math.max(cropped, shape * .86 + (1 - distance) * .14);\n  }\n  return Math.max(whole, cropped);\n}\nasync function analysePhoto144(file) {\n  if (!file || !photoDraft144) return;\n  const draft = photoDraft144;\n  draft.step = 'analyse'; renderPhoto144();\n  try {\n    const data = await compress144(file), query = await describe146(data), hits = [];\n    const recognition = {references:0, readableReferences:0, barcode:false, text:false, photo:false, ocrChars:0, ocrAvailable:typeof window.ZapasioOcr?.recognizeImageText==='function'||typeof window.ZapasioAndroid?.recognizeImageText==='function', barcodeAvailable:typeof window.ZapasioBarcode?.decodeBarcode==='function'||'BarcodeDetector' in window};\n    draft.captured179=data;\n    const ocrImage=typeof ocrData179==='function'?await ocrData179(file).catch(()=>data):data;\n    const nativeBarcode=typeof window.ZapasioBarcode?.decodeBarcode==='function'&&typeof zNativeBarcode166==='function'\n      ?zNativeBarcode166(ocrImage).catch(()=>''):Promise.resolve('');\n    const ocr = typeof requestOcr147 === 'function' ? await requestOcr147(ocrImage) : '';\n    recognition.ocrChars=ocr.trim().length;\n    draft.ocrText179=ocr;\n    draft.mhdSuggestion179=typeof mhdSuggestion179==='function'?mhdSuggestion179(ocr):'';\n    if ('BarcodeDetector' in window) try {\n      const bitmap = await createImageBitmap(file);\n      try {\n        const codes = await new BarcodeDetector().detect(bitmap);\n        for(const code of codes){const match=findScanCode(code.rawValue||'');\n          if (match) hits.push({code: match.productCode,\n            packIndex: Math.max(0, getPacks(state.products[match.productCode], match.productCode).indexOf(match.pack)),\n            score: 1, preview: data, source: 'barcode'});}\n      } finally { bitmap.close?.(); }\n    } catch (_) { /* BarcodeDetector is not available on every Android WebView. */ }\n    if (!hits.some(hit=>hit.source==='barcode')) {\n      const barcode=await nativeBarcode;\n      const match=barcode&&findScanCode(barcode);\n      if (match) hits.push({code:match.productCode,\n        packIndex:Math.max(0,getPacks(state.products[match.productCode],match.productCode).indexOf(match.pack)),\n        score:1,preview:data,source:'barcode'});\n    }\n    if (!hits.some(hit=>hit.source==='barcode') && typeof ocrBarcodeCandidates179==='function') {\n      for (const printed of ocrBarcodeCandidates179(ocr)) {\n        const match=findScanCode(printed);\n        if (match) hits.push({code:match.productCode,\n          packIndex:Math.max(0,getPacks(state.products[match.productCode],match.productCode).indexOf(match.pack)),\n          score:.94,preview:data,source:'ocr-code'});\n      }\n    }\n    for (const [code, article] of Object.entries(state.products)) {\n      const ocrScore = typeof ocrScore147 === 'function' ? ocrScore147(ocr, article.name) : 0;\n      if (ocrScore) hits.push({code, packIndex:0, score:ocrScore,\n        preview:article.photo || data, source:'text'});\n      const refs = article.referencePhotos144 || [];\n      for (const ref of refs) {\n        if (!ref.data?.startsWith('data:image/')) continue;\n        recognition.references++;\n        const features = await cached146(ref, ref.data).catch(() => null);\n        if (!features) continue;\n        recognition.readableReferences++;\n        const score = similarity146(query, features);\n        if (score >= .5) hits.push({code, packIndex: Number(ref.packIndex) || 0,\n          score, preview: ref.data, source: 'photo'});\n      }\n      if (article.photo?.startsWith('data:image/') && !refs.some(ref => ref.data === article.photo)) {\n        const features = await cached146(article, article.photo).catch(() => null);\n        if (!features) continue;\n        const score = similarity146(query, features);\n        if (score >= .5) hits.push({code, packIndex: 0,\n          score, preview: article.photo, source: 'photo'});\n      }\n    }\n    if (photoDraft144 !== draft) return;\n    const unique = [];\n    for (const hit of hits.sort((a,b)=>({barcode:3,'ocr-code':2,text:1,photo:0}[b.source]-{barcode:3,'ocr-code':2,text:1,photo:0}[a.source])||b.score-a.score))\n      if (!unique.some(x => x.code === hit.code && x.packIndex === hit.packIndex)) unique.push(hit);\n    draft.candidates = typeof photoCandidates179==='function'?photoCandidates179(unique):unique.slice(0,3);\n    for(const hit of draft.candidates) recognition[hit.source==='photo'?'photo':hit.source==='text'?'text':'barcode']=true;\n    draft.recognition=recognition;\n    draft.step = 'choose'; renderPhoto144();\n  } catch (error) {\n    if (photoDraft144 !== draft) return;\n    showFeedback('Foto konnte nicht verarbeitet werden', 'warn', 6000, error?.message||'Bildformat oder Speicher prüfen.');\n    draft.step = 'capture'; renderPhoto144();\n  }\n}\nconst renderPhotoBefore146 = renderPhoto144;\nrenderPhoto144 = function() {\n  renderPhotoBefore146();\n  if (photoDraft144?.step === 'capture') {\n    const body = document.getElementById('photoBody144');\n    if (!body) return;\n    body.innerHTML = `<p>Fotografiere das ganze Produkt möglichst scharf. Name und Sorte sollen lesbar sein.</p>\n      <div class='cameraActions146'>\n        <button class='primary' onclick='document.getElementById(\"photoInput144\").click()'>Foto aufnehmen</button>\n        <button class='soft' onclick='closePhoto144()'>Abbrechen</button>\n      </div>`;\n    return;\n  }\n  if (photoDraft144?.step === 'confirm') {\n    const draft = photoDraft144, article = state.products[draft.code];\n    if (!article) return;\n    const pack = getPacks(article, draft.code)[draft.packIndex] || getPacks(article, draft.code)[0];\n    const body = document.getElementById('photoBody144');\n    body.innerHTML = `<div class='scanArticle146'>\n      <img src='${article.photo || draft.candidates.find(item => item.code === draft.code)?.preview || ''}' alt=''>\n      <div><h3>${esc(article.name)}</h3><p>${esc(pack[0])} · ${formatPackMeasure(packUsableQty(pack), article.unit)}</p>\n      <p><b>Im Lager:</b> ${esc(formatQtySmart(Number(article.stock) || 0, article.unit))}</p></div></div>\n      <div class='scanActions146'>\n        <button class='primary' onclick='photoDraft144.step=\"quantity\";renderPhoto144()'>+ Wareneingang</button>\n        <button class='soft' onclick='photoStockAction146(\"out\")'>− Entnehmen</button>\n        <button class='soft' onclick='photoStockAction146(\"waste\")'>Ausschuss</button>\n        <button class='soft' onclick='pickExisting144()'>Anderen Artikel wählen</button>\n        ${hasPermission('articles') ? `<button class='soft' onclick='photoEditArticle146()'>Artikel bearbeiten</button>\n          <button class='soft danger' onclick='photoDeleteArticle146()'>Artikel löschen</button>` : ''}\n        <button class='soft' onclick='closePhoto144()'>Schließen</button>\n      </div>`;\n    return;\n  }\n  if (photoDraft144?.step !== 'choose') return;\n  const body = document.getElementById('photoBody144'), heading = body?.querySelector('h3');\n  if (heading && photoDraft144.candidates.length) heading.textContent = 'Mögliche Artikel – bitte prüfen';\n  body?.querySelectorAll('.photoCandidate144').forEach((button, i) => {\n    if (button.lastElementChild) button.lastElementChild.textContent =\n      photoDraft144.candidates[i]?.source === 'barcode' ? 'Barcode' :\n      photoDraft144.candidates[i]?.source === 'ocr-code' ? 'Nummer auf Etikett' :\n      photoDraft144.candidates[i]?.source === 'text' ? 'Etiketttext' : 'Foto ähnlich';\n  });\n};\nlet cameraStream146 = null;\nfunction stopCamera146() {\n  if (cameraStream146) cameraStream146.getTracks().forEach(track => track.stop());\n  cameraStream146 = null;\n  const video = document.getElementById('cameraVideo146');\n  if (video) video.srcObject = null;\n}\nconst closePhotoBefore146 = closePhoto144;\nclosePhoto144 = function() { stopCamera146(); closePhotoBefore146(); };\nfunction photoStockAction146(mode) {\n  const draft = photoDraft144;\n  if (!draft?.code || !state.products[draft.code]) return;\n  current = draft.code;\n  const pack = getPacks(state.products[current], current)[draft.packIndex];\n  lastScanPackCode = pack?.[2] || '';\n  closePhoto144();\n  openModal(mode);\n}\nfunction photoEditArticle146() {\n  const code = photoDraft144?.code;\n  if (!code || !hasPermission('articles')) return;\n  closePhoto144(); openArticleEditor(code);\n}\nfunction photoDeleteArticle146() {\n  const code = photoDraft144?.code;\n  if (!code || !hasPermission('articles')) return;\n  closePhoto144(); removeArticle(code);\n}\nconst startPhotoBefore146 = startPhoto144;\nstartPhoto144 = function(file) {\n  const oldDraft = photoDraft144;\n  startPhotoBefore146();\n  if (photoDraft144 && photoDraft144 !== oldDraft && photoDraft144.step === 'capture') {\n    if(file)void analysePhoto144(file);\n    else document.getElementById('photoInput144')?.click();\n  }\n};\nfunction styleCameraButtons146() {\n  for (const id of ['photoBtn144', 'photoDesk144']) {\n    const button = document.getElementById(id);\n    if (!button) continue;\n    button.classList.add('cameraFab146');\n    button.setAttribute('aria-label', 'Produkt fotografieren');\n    button.title = 'Produkt fotografieren';\n    button.innerHTML = `<svg viewBox='0 0 24 24' aria-hidden='true' focusable='false'><path d='M4 7h4l2-2h4l2 2h4v12H4z'/><circle cx='12' cy='13' r='3.5'/></svg>`;\n  }\n}\nconst renderMobileSelectedBefore146 = renderMobileSelectedProduct;\nrenderMobileSelectedProduct = function() {\n  renderMobileSelectedBefore146();\n  if (!hasPermission('articles') || !state.products[current]) return;\n  const actions = document.querySelector('#mobileSelectedProduct .mobileProductActions');\n  if (!actions) return;\n  const code = current;\n  actions.insertAdjacentHTML('beforeend', `<button class='soft' onclick=\"openArticleEditor('${escJs(code)}')\">Artikel bearbeiten</button>\n    <button class='soft danger' onclick=\"removeArticle('${escJs(code)}')\">Artikel löschen</button>`);\n};\nconst renderBefore146 = render;\nrender = function() {\n  const result = renderBefore146();\n  document.querySelectorAll('#tabSettings h1 small').forEach(label => label.textContent = 'Testversion 146');\n  styleCameraButtons146();\n  return result;\n};\n/* Initial full refresh is performed after all patches load. */\n"},{"src":"patch147.js?v=333","code":"'use strict';\n// Native Android OCR is local to the device. Browsers without the bridge keep\n// the visual and barcode checks; no image is uploaded for text recognition.\nlet ocrNext147 = 0;\nconst ocrPending147 = new Map();\nfunction requestOcr147(data) {\n  const bridge=typeof window.ZapasioOcr?.recognizeImageText==='function'?window.ZapasioOcr:window.ZapasioAndroid;\n  if (typeof bridge?.recognizeImageText !== 'function') return Promise.resolve('');\n  return new Promise(resolve => {\n    const id = ++ocrNext147;\n    const timer = setTimeout(() => { ocrPending147.delete(id); resolve(''); }, 12000);\n    ocrPending147.set(id, text => { clearTimeout(timer); resolve(text || ''); });\n    try { bridge.recognizeImageText(data, id); }\n    catch (_) { ocrPending147.get(id)?.(''); ocrPending147.delete(id); }\n  });\n}\nwindow.zapasioOcrResult147 = function(id, value) {\n  const finish = ocrPending147.get(Number(id));\n  ocrPending147.delete(Number(id));\n  finish?.(String(value || ''));\n};\nfunction tokens147(value) {\n  return String(value || '').toLocaleLowerCase('de').normalize('NFKD')\n    .replace(/[\\u0300-\\u036f]/g,'').replace(/ß/g,'ss').split(/[^a-z0-9]+/)\n    .filter(token => token.length >= 4 && !['bio','produkt','flasche','stuck','getrank','liter','gramm','naturlich','frisch'].includes(token))\n    .map(token => ['tomato','tomatoes','tomate','tomaten','pomidor','pomidory'].includes(token) ? 'tomaten' : token);\n}\nfunction ocrScore147(text, name) {\n  const found = tokens147(text), expected = [...new Set(tokens147(name))];\n  if (!found.length || !expected.length) return 0;\n  const matched = expected.filter(word => found.some(candidate =>\n    candidate === word || (word.length >= 6 && candidate.length >= 6 &&\n      (candidate.includes(word) || word.includes(candidate)))));\n  // Some real article names consist of one distinctive word (for example\n  // \"Bowlbecher\"). A complete long token is useful as a review hint.\n  // One distinctive word on a label is a useful hint, even when the stored\n  // name has a second word such as \"Becher\" or \"TK\". It remains a suggestion.\n  if (matched.length < 2 && !(matched.length === 1 && expected.length <= 2 &&\n    matched[0].length >= 6 && !['becher','deckel','verpackung','portion'].includes(matched[0]) &&\n    (expected.length > 1 || found.includes(expected[0])))) return 0;\n  const ratio = matched.length / expected.length;\n  return ratio >= .5 ? Math.min(.92, .77 + ratio * .15) : 0;\n}\n// Keep the current view while auto-saving settings. The explicit save control\n// confirms the result without moving the user to another section.\npersistAdmin = function() {\n  const y = window.scrollY;\n  if (!persistState('Einstellung gespeichert', false)) return false;\n  render();\n  window.scrollTo({top: y, behavior: 'instant'});\n  return true;\n};\nconst renderCategoryBefore147 = renderCategoryAdmin;\nrenderCategoryAdmin = function() {\n  renderCategoryBefore147();\n  const area = document.getElementById('categoryAdmin');\n  if (area && !area.querySelector('.categorySave147'))\n    area.insertAdjacentHTML('afterbegin', `<div class='categorySave147'><button class='primary' type='button' onclick='saveCategories147()' aria-label='Kategorien speichern'>\n      <svg viewBox='0 0 24 24' aria-hidden='true'><path d='M4 3h13l4 4v14H3V3zM7 3v6h9V3M7 21v-8h10v8'/></svg>Speichern</button></div>`);\n};\nfunction saveCategories147() {\n  if (document.activeElement?.closest('#categoryAdmin')) document.activeElement.blur();\n  if (persistState('Kategorien gespeichert', false)) showFeedback('Kategorien gespeichert', 'success');\n}\n\nlet lastCapturedPhoto147 = '';\nlet creatingFromPhoto147 = false;\nconst analysePhotoBefore147 = analysePhoto144;\nanalysePhoto144 = async function(file) {\n  if (!file) return;\n  // Reuse the camera image on creation; do not overwrite an existing article.\n  try { lastCapturedPhoto147 = await compress144(file); }\n  catch (_) { lastCapturedPhoto147 = ''; }\n  return analysePhotoBefore147(file);\n};\nasync function createFromPhoto147() {\n  const photo = lastCapturedPhoto147;\n  closePhoto144();\n  openArticleEditor();\n  if (!photo || document.getElementById('articleModal')?.style.display !== 'flex') return;\n  creatingFromPhoto147 = true;\n  articlePhotoData = photo;\n  document.getElementById('aePhotoPreview').innerHTML = `<img src='${photo}' alt='Übernommenes Produktfoto'>`;\n  articlePhotoPending = true;\n  try {\n    editorRefs144.push({id:'ref-'+Date.now(),data:photo,hash:await hash144(photo),\n      createdAt:new Date().toISOString(),confirmed:true,packIndex:0});\n    renderRefs144();\n  } finally { articlePhotoPending = false; }\n}\nconst renderPhotoBefore147 = renderPhoto144;\nrenderPhoto144 = function() {\n  renderPhotoBefore147();\n  if (photoDraft144?.step === 'choose') {\n    const button = [...document.querySelectorAll('#photoBody144 button')]\n      .find(el => el.textContent.trim() === 'Neuen Artikel anlegen');\n    if (button) button.onclick = createFromPhoto147;\n  }\n  if (photoDraft144?.step === 'mhd') {\n    const field = document.getElementById('photoDate144');\n    if (field) prepareMhdField147(field);\n  }\n};\nconst saveArticleBefore147 = saveArticleEditor;\nsaveArticleEditor = function() {\n  const fromPhoto = creatingFromPhoto147;\n  const wasCreating = !editingArticleCode;\n  const result = saveArticleBefore147();\n  // A failed validation keeps the form, the picture and the current position.\n  if (document.getElementById('articleModal')?.style.display !== 'none') return result;\n  creatingFromPhoto147 = false;\n  // The current receipt flow resumes after durable device storage. Opening the\n  // legacy selection here would reopen the modal before that completion gate.\n  if (typeof queueArticleContinuation420!=='function' && (typeof productCreationIntent331==='undefined'||productCreationIntent331!=='catalog') && (fromPhoto || wasCreating) && state.products[current] && window.matchMedia?.('(max-width: 820px)').matches) {\n    mobileShow('search');\n    mobileSelectProduct(current);\n  }\n  return result;\n};\nconst closeArticleBefore147 = closeArticleEditor;\ncloseArticleEditor = function(clearPending=true) {\n  closeArticleBefore147(clearPending);\n  if (clearPending) creatingFromPhoto147 = false;\n};\n\n// Only autogenerated defaults are replaced at first edit; saved values stay.\ndocument.addEventListener('focusin', event => {\n  const el = event.target;\n  if (!el?.matches?.('input:not([type=hidden])')) return;\n  if (el.matches('.aePackName, #categoryAdmin input[data-enter-commit]') &&\n      /(?:Neue Packungsgröße|Neue Kategorie|Neue Untergruppe)/i.test(el.value)) el.select();\n  if (el.closest('#articleModal, #categoryAdmin, #modal')) el.setAttribute('enterkeyhint','next');\n});\ndocument.addEventListener('keydown', event => {\n  const el = event.target;\n  if (event.key !== 'Enter' || event.isComposing || !el?.matches?.('input') ||\n      ['file','checkbox','radio','hidden','submit'].includes(el.type)) return;\n  const scope = el.closest('#articleModal, #categoryAdmin, #modal');\n  if (!scope) return;\n  event.preventDefault();\n  const fields = [...scope.querySelectorAll('input:not([type=hidden]):not([type=file]):not([type=checkbox]),select,textarea')]\n    .filter(x => !x.disabled && x.getClientRects().length);\n  const next = fields[fields.indexOf(el)+1];\n  el.blur();\n  if (next) setTimeout(() => {\n    const fresh = [...scope.querySelectorAll('input:not([type=hidden]):not([type=file]):not([type=checkbox]),select,textarea')]\n      .filter(x => !x.disabled && x.getClientRects().length);\n    (next.isConnected ? next : fresh[fields.indexOf(el)+1])?.focus();\n  }, 0);\n});\n\n// Month-only dates keep their precision as YYYY-MM. Calculations use the last\n// day of that month; labels display MM.JJ without inventing a day for the user.\nfunction parseMhd147(raw) {\n  const value = String(raw ?? '').trim();\n  let year, month, day, monthly = false, match;\n  if ((match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value))) {\n    [, year, month, day] = match.map(String);\n  } else if ((match = /^(\\d{4})-(\\d{2})$/.exec(value))) {\n    year = match[1]; month = match[2]; monthly = true;\n  } else if ((match = /^(\\d{1,2})\\.(\\d{1,2})\\.(\\d{2}|\\d{4})\\.?$/.exec(value))) {\n    day = match[1]; month = match[2]; year = match[3];\n  } else if ((match = /^(\\d{1,2})\\.(\\d{2}|\\d{4})\\.?$/.exec(value))) {\n    month = match[1]; year = match[2]; monthly = true;\n  } else return null;\n  year = Number(year.length === 2 ? '20' + year : year);\n  month = Number(month); day = Number(day);\n  if (year < 2000 || year > 2099 || month < 1 || month > 12) return null;\n  if (monthly) return {iso:`${year}-${String(month).padStart(2,'0')}`,\n    label:`${String(month).padStart(2,'0')}.${String(year).slice(-2)}`,monthOnly:true};\n  const date = new Date(Date.UTC(year, month-1, day));\n  if (!(day > 0) || date.getUTCFullYear() !== year || date.getUTCMonth() !== month-1 || date.getUTCDate() !== day) return null;\n  return {iso:`${year}-${String(month).padStart(2,'0')}-${String(day).padStart(2,'0')}`,\n    label:`${String(day).padStart(2,'0')}.${String(month).padStart(2,'0')}.${String(year).slice(-2)}`,monthOnly:false};\n}\nconst parseDateBefore147 = parseDateISO;\nparseDateISO = function(value) {\n  const m = /^(\\d{4})-(\\d{2})$/.exec(String(value||''));\n  if (!m) return parseDateBefore147(value);\n  const parsed = parseMhd147(value);\n  if (!parsed) return null;\n  return new Date(Number(m[1]), Number(m[2]), 0, 12);\n};\nconst formatDateBefore147 = formatDate;\nformatDate = function(value) {\n  return /^\\d{4}-\\d{2}$/.test(String(value||'')) ? (parseMhd147(value)?.label || '–') : formatDateBefore147(value);\n};\nfunction prepareMhdField147(input, value='') {\n  if (!input || input.dataset.adapted231==='expiry') return;\n  input.type = 'text';\n  input.inputMode = 'numeric';\n  input.placeholder = 'TT.MM.JJ oder MM.JJ';\n  input.autocomplete = 'off';\n  input.classList.add('mhdText147');\n  if (value) input.value = parseMhd147(value)?.iso || value;\n}\nconst renderMhdBefore147 = renderExistingMhdEditor;\nrenderExistingMhdEditor = function() {\n  renderMhdBefore147();\n  document.querySelectorAll('#aeBatchRows .aeBatchDate').forEach((input, i) =>\n    prepareMhdField147(input, articleBatchDraft[i]?.mhd));\n};\nconst collectMhdBefore147 = collectExistingMhdEditor;\ncollectExistingMhdEditor = function() {\n  collectMhdBefore147();\n  articleBatchDraft.forEach(batch => { batch.mhd = parseMhd147(batch.mhd)?.iso || batch.mhd; });\n};\nconst validateMhdBefore147 = validateExistingMhdEditor;\nvalidateExistingMhdEditor = function() {\n  if (document.getElementById('aeMhd')?.checked &&\n      articleBatchDraft.some(batch => !parseMhd147(batch.mhd))) {\n    showFeedback('MHD prüfen', 'warn', 4000, 'TT.MM.JJ oder MM.JJ eingeben. Monat und Jahr sind erforderlich.');\n    return false;\n  }\n  return validateMhdBefore147();\n};\nvalidDate144 = value => !!parseMhd147(value);\nconfirmDate144 = function(value, done) {\n  const parsed = parseMhd147(value);\n  if (!parsed) return showFeedback('MHD prüfen', 'warn', 4000, 'TT.MM.JJ oder MM.JJ eingeben.');\n  const today = new Date(localDateKey()+'T12:00:00'), expiry = parseDateISO(parsed.iso);\n  if (expiry < today) return askConfirm('Dieses MHD ist abgelaufen. Bewusst als Zugang speichern?', done, 'Bewusst bestätigen', 'Abgelaufenes MHD');\n  if ((expiry-today)/31557600000 > 5) return askConfirm('Dieses MHD liegt ungewöhnlich weit in der Zukunft.', done, 'Datum übernehmen', 'MHD prüfen');\n  done();\n};\naddMhd144 = function() {\n  const draft = photoDraft144, count = count144(document.getElementById('photoPart144')?.value),\n    left = draft.count-draft.lines.reduce((sum,line) => sum+line.count,0),\n    parsed = parseMhd147(document.getElementById('photoDate144')?.value);\n  if (count < 1 || count > left) return showFeedback('Teilmenge prüfen','warn');\n  if (!parsed) return showFeedback('MHD prüfen','warn',4000,'TT.MM.JJ oder MM.JJ eingeben.');\n  confirmDate144(parsed.iso, () => { draft.lines.push({count,date:parsed.iso}); renderPhoto144(); });\n};\nfinishMhd144 = function() {\n  const draft = photoDraft144, policy = effectiveMhd144(state.products[draft.code]);\n  if (draft.mode === 'same' && !draft.lines.length) {\n    const raw = document.getElementById('photoDate144')?.value;\n    if (!raw && policy === 'optional') { draft.step='summary'; renderPhoto144(); return; }\n    const parsed = parseMhd147(raw);\n    if (!parsed) return showFeedback('MHD prüfen','warn',4000,'TT.MM.JJ oder MM.JJ eingeben.');\n    return confirmDate144(parsed.iso, () => { draft.lines=[{count:draft.count,date:parsed.iso}];draft.step='summary';renderPhoto144(); });\n  }\n  const sum = draft.lines.reduce((n,line) => n+line.count,0);\n  if (sum !== draft.count) return showFeedback(`${sum} von ${draft.count} Packungen erfasst`, 'warn');\n  draft.step='summary'; renderPhoto144();\n};\nconst openModalBefore147 = openModal;\nopenModal = function(mode) {\n  openModalBefore147(mode);\n  if (mode === 'in') prepareMhdField147(document.getElementById('mhd'));\n};\nconst bookBefore147 = book;\nbook = function() {\n  if (mode === 'in' && recognizedMhdPolicy256(p())!=='none' && (recognizedMhdPolicy256(p())==='required'||document.getElementById('mhd')?.value)) {\n    const field = document.getElementById('mhd');\n    const parsed = parseMhd147(field?.value);\n    if (!parsed) return showFeedback('MHD prüfen','warn',4000,'TT.MM.JJ oder MM.JJ eingeben.');\n    field.value = parsed.iso;\n  }\n  return bookBefore147();\n};\n\nconst renderBefore147 = render;\nrender = function() {\n  const result = renderBefore147();\n  const save = document.querySelector('#articleModal button[onclick=\"saveArticleEditor()\"]');\n  save?.parentElement?.classList.add('articleSave147');\n  document.querySelectorAll('#tabSettings h1 small').forEach(label => label.textContent = 'Testversion 147');\n  return result;\n};\n/* Initial full refresh is performed after all patches load. */\n\n\n// Display matching expiry lots together; storage, ownership and lot IDs stay intact.\nconst articleInfoBeforeMhdFix147 = articleInfoData127;\narticleInfoData127 = function(code) {\n  const info = articleInfoBeforeMhdFix147(code);\n  if (!info?.next) return info;\n  const date = parseMhd147(info.next.mhd)?.iso || info.next.mhd;\n  return {...info, next: {...info.next, qty: info.batches\n    .filter(batch => (parseMhd147(batch.mhd)?.iso || batch.mhd) === date)\n    .reduce((total, batch) => total + Number(batch.qty), 0)}};\n};\n"},{"src":"patch148.js","code":"'use strict';\n// Route requests use the authenticated server function.\nlet routeResult148 = null;\nfunction routeKey148() { return ''; }\nfunction setRouteKey148() { showFeedback('TomTom wird zentral eingerichtet','warn'); }\nfunction renderRouteControls148() {\n  const travel = document.getElementById('planTravelMinutes');\n  if (travel && !document.getElementById('routeTools148')) {\n    const grid = travel.closest('.cateringFormGrid');\n    grid?.insertAdjacentHTML('beforeend', `<div class='routeTools148' id='routeTools148'>\n      <button type='button' class='soft' onclick='calculateRoute148()'>↗ Abfahrt mit TomTom berechnen</button>\n      <small id='routeStatus148'>Start, Ziel und Einsatzdatum eingeben. Verkehrsdaten werden erst per Knopfdruck abgefragt.</small></div>`);\n  }\n}\nfunction routeStatus148(message) {\n  const el = document.getElementById('routeStatus148');\n  if (el) el.textContent = message;\n}\nfunction routeLocalIso148(date) {\n  return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}T${String(date.getHours()).padStart(2,'0')}:${String(date.getMinutes()).padStart(2,'0')}:00`;\n}\nasync function calculateRoute148() {\n  showFeedback('Für die Routenplanung bitte anmelden','warn');\n}\nconst travelMinutesChangedBefore148 = travelMinutesChanged;\ntravelMinutesChanged = function() { routeResult148 = null; travelMinutesChangedBefore148(); };\nconst cateringDraftBefore148 = cateringDraftObject;\ncateringDraftObject = function() {\n  const draft = cateringDraftBefore148();\n  if (routeResult148 && draft.address === routeResult148.to &&\n      draft.date === routeResult148.date && draft.readyTime === routeResult148.readyTime &&\n      draft.travelMinutes === routeResult148.travelMinutes &&\n      (typeof routeStart172==='function'?routeStart172():state.businessSettings?.startLocation) === routeResult148.from)\n    draft.routeSource148 = {...routeResult148};\n  else draft.routeSource148 = null;\n  return draft;\n};\nconst clearCateringBefore148 = clearCateringDraft;\nclearCateringDraft = function(...args) { routeResult148 = null; return clearCateringBefore148(...args); };\nconst editCateringBefore148 = editCateringPlan;\neditCateringPlan = function(...args) {\n  const result = editCateringBefore148(...args);\n  const id = args[0];\n  routeResult148 = state.planning.caterings.find(c=>c.id===id)?.routeSource148 || null;\n  routeStatus148(routeResult148 ? `Letzte Route: ${routeResult148.travelMinutes} Min., berechnet ${new Date(routeResult148.checkedAt).toLocaleString('de-AT')}. Vor Abfahrt neu berechnen.` : 'Route berechnen oder Fahrzeit manuell eingeben.');\n  return result;\n};\nconst timePlanHtmlBefore148 = timePlanHtml;\ntimePlanHtml = function(c,draft=false) {\n  const html = timePlanHtmlBefore148(c,draft);\n  const source = draft ? routeResult148 : c?.routeSource148;\n  return source && c?.travelMinutes === source.travelMinutes && c?.address === source.to &&\n      c?.date === source.date && c?.readyTime === source.readyTime &&\n      (draft?(typeof routeStart172==='function'?routeStart172():state.businessSettings?.startLocation):\n        (c?.startOverride172||state.businessSettings?.startLocation)) === source.from ?\n    html + `<div class='planMuted'>TomTom: geschätzte ${esc(source.travelMinutes)} Min. · berechnet ${esc(new Date(source.checkedAt).toLocaleString('de-AT'))}. Verkehr vor Fahrtbeginn erneut prüfen.</div>` : html;\n};\nconst renderBefore148 = render;\nrender = function() { const result = renderBefore148(); renderRouteControls148(); return result; };\nrenderRouteControls148();\n"},{"src":"patch149.js?v=189","code":"'use strict';\nfunction syncProductFocus149() {\n  const search = document.getElementById('mobileScreenSearch');\n  if (!search) return;\n  const searching = !!document.getElementById('mobileArticleSearch')?.value.trim();\n  search.classList.toggle('productFocus149', searching || !!mobileProductChosen);\n}\nconst searchChangedBefore149 = mobileSearchChanged;\nmobileSearchChanged = function() { const value = searchChangedBefore149(); syncProductFocus149(); return value; };\nconst searchResultsBefore149 = renderMobileSearchResults;\nrenderMobileSearchResults = function(...args) { const value = searchResultsBefore149(...args); syncProductFocus149(); return value; };\nconst selectProductBefore149 = mobileSelectProduct;\nmobileSelectProduct = function(...args) { const value = selectProductBefore149(...args); syncProductFocus149(); return value; };\nconst backListBefore149 = mobileBackList103;\nmobileBackList103 = function() { const value = backListBefore149(); syncProductFocus149(); return value; };\nconst renderSearchBefore149 = renderMobileSearch;\nrenderMobileSearch = function() { const value = renderSearchBefore149(); syncProductFocus149(); return value; };\n\nconst renderPhotoBefore149 = renderPhoto144;\nrenderPhoto144 = function() {\n  renderPhotoBefore149();\n  const draft = photoDraft144, body = document.getElementById('photoBody144');\n  if (!draft || !body) return;\n  const title = document.querySelector('#photoModal144 .modalHeader h2');\n  const subtitle = document.querySelector('#photoModal144 .modalHeader p');\n  if (title) title.textContent = draft.step === 'confirm' ? 'Artikel gefunden' : draft.step === 'choose' ? 'Artikel auswählen' : 'Artikel erkennen';\n  if (subtitle) subtitle.textContent = draft.step === 'choose' ? 'Prüfe, welches Foto zu deinem Artikel passt.' :\n    draft.step === 'confirm' ? 'Wähle, was du mit diesem Artikel tun möchtest.' : 'Foto aufnehmen und Artikel zuordnen.';\n  if (draft.step === 'choose') {\n    const hasMatches = draft.candidates.length > 0;\n    const diagnosis=draft.recognition;\n    body.innerHTML = `<div class='photoLead149'><h3>${hasMatches ? 'Ist dein Artikel dabei?' : 'Kein sicherer Treffer'}</h3>\n      <p>${hasMatches ? 'Tippe auf den passenden Artikel. Noch wird kein Bestand geändert.' : 'Du kannst einen vorhandenen Artikel suchen oder den fotografierten Artikel anlegen.'}</p></div>\n      ${hasMatches ? `<div class='photoCandidates144'>${draft.candidates.map(candidate => {\n        const article = state.products[candidate.code];\n        if (!article) return '';\n        const pack = getPacks(article,candidate.code)[candidate.packIndex] || getPacks(article,candidate.code)[0];\n        const source = candidate.source === 'barcode' ? 'Barcode erkannt' : candidate.source === 'ocr-code' ? 'Nummer auf Etikett' : candidate.source === 'text' ? 'Text auf Etikett' : 'Bildvorschlag – unsicher';\n        return `<button type='button' class='photoCandidate144' onclick=\"selectPhoto144('${escJs(candidate.code)}',${candidate.packIndex})\">\n          <img src='${esc(article.photo || candidate.preview || '')}' alt=''><span><b>${esc(article.name)}</b>\n          <small>${esc(pack[0])} · ${esc(formatPackMeasure(packUsableQty(pack),article.unit))}</small><span class='photoSource149'>${source}</span></span>\n          <span class='chooseArrow149' aria-hidden='true'>›</span></button>`;\n      }).join('')}</div>` : ''}\n      ${diagnosis ? `<details class='photoDiagnosis149'><summary>Erkennungsprüfung anzeigen</summary><p>${diagnosis.references} gespeicherte Referenzfotos geprüft, ${diagnosis.readableReferences} lesbar. OCR-Schnittstelle: ${diagnosis.ocrAvailable?'vorhanden':'fehlt'}, gelesene Zeichen: ${diagnosis.ocrChars}. Barcode-Schnittstelle: ${diagnosis.barcodeAvailable?'vorhanden':'fehlt'}. Vorhandene Schnittstellen garantieren noch keinen Treffer. ${hasMatches?'Vorschläge bitte selbst prüfen.':'Kein passender Barcode, Etiketttext oder Bildvorschlag gefunden.'}</p><p>Gelesener Etiketttext: ${esc((draft.ocrText179||'').trim().slice(0,180)||'kein Text erkannt')}</p></details>`:''}\n      <div class='photoOther149'><p>${hasMatches ? 'Nicht der richtige Artikel?' : 'Wie möchtest du weitermachen?'}</p>\n        <div class='photoOtherActions149'>\n          <button type='button' class='soft' onclick='pickExisting144()'>Artikel manuell suchen</button>\n          ${canCreateArticles() ? `<button type='button' class='soft' onclick='createFromPhoto147()'>Neuen Artikel anlegen</button>` : ''}\n          <button type='button' class='soft' onclick='document.getElementById(\"photoInput144\").click()'>Erneut fotografieren</button>\n        </div></div>`;\n  }\n  if (draft.step === 'confirm') {\n    const article = state.products[draft.code];\n    if (!article) return;\n    const pack = getPacks(article,draft.code)[draft.packIndex] || getPacks(article,draft.code)[0];\n    body.innerHTML = `<div class='photoSummary149'><img src='${esc(article.photo || draft.candidates.find(x => x.code === draft.code)?.preview || '')}' alt=''>\n      <div><h3>${esc(article.name)}</h3><p>${esc(pack[0])} · ${esc(formatPackMeasure(packUsableQty(pack),article.unit))}</p>\n      <p>Im Lager: <strong>${esc(formatQtySmart(Number(article.stock) || 0,article.unit))}</strong></p></div></div>\n      <div class='photoActionSection149'><h4>Bestand ändern</h4>\n        <button type='button' class='primary' onclick='photoDraft144.step=\"quantity\";renderPhoto144()'>+ Wareneingang buchen</button>\n        <div class='photoActionPair149'><button type='button' class='soft' onclick='photoStockAction146(\"out\")'>− Entnehmen</button>\n          <button type='button' class='soft' onclick='photoStockAction146(\"waste\")'>Ausschuss</button></div></div>\n      ${hasPermission('articles') ? `<details class='photoManage149'><summary>Artikel verwalten</summary>\n        <div class='photoActionPair149'><button type='button' class='soft' onclick='photoEditArticle146()'>Bearbeiten</button>\n          <button type='button' class='soft danger' onclick='photoDeleteArticle146()'>Löschen</button></div></details>` : ''}\n      <button type='button' class='soft' onclick='pickExisting144()'>Anderen Artikel wählen</button>`;\n  }\n};\nsyncProductFocus149();\n"},{"src":"patch150.js","code":"'use strict';\n// Spoken stock questions are read-only. Never turn a full sentence into a new\n// article name or infer an inventory change from a voice transcription.\nfunction voiceIsStockQuestion150(utterance) {\n  return /\\b(wie\\s*viel(?:e|en)?|wieviel(?:e|en)?|bestand|noch\\s+(?:da|vorhanden|auf\\s+lager)|how\\s+(?:many|much)|ile\\b)\\b/i.test(String(utterance || ''));\n}\nfunction voiceNotice150(key, query = '') {\n  const labels = {\n    de: {missing:['Artikelname fehlt','Bitte nenne den Artikel, zum Beispiel „Wie viele Suppenbecher habe ich noch?“'],notFound:['Artikel nicht gefunden',`Für „${query}“ wurde kein Bestand gefunden.`]},\n    pl: {missing:['Brakuje nazwy artykułu','Podaj nazwę artykułu, na przykład „Ile mamy pojemników na zupę?”'],notFound:['Nie znaleziono artykułu',`Nie znaleziono stanu magazynowego dla „${query}”.`]},\n    en: {missing:['Item name missing','Name the item, for example “How many soup containers do we have left?”'],notFound:['Item not found',`No stock was found for “${query}”.`]}\n  };\n  return (labels[business130().language] || labels.de)[key];\n}\nfunction voiceQuery150(utterance) {\n  let text = String(utterance || '').trim().replace(/[?!.,]+$/g,'').trim();\n  const stock = voiceIsStockQuestion150(text);\n  if (stock) {\n    text = text.replace(/^(?:wie\\s*viel(?:e|en)?|wieviel(?:e|en)?|was\\s+ist\\s+der\\s+bestand\\s+(?:von|für)?|wie\\s+hoch\\s+ist\\s+der\\s+bestand\\s+(?:von|für)?|how\\s+(?:many|much)|ile(?:\\s+(?:mamy|zostalo|zostało))?)\\s*/i,'');\n    text = text.replace(/^(?:von\\s+)?(?:den|dem|die|der|das|an)\\s+/i,'');\n    text = text.replace(/\\s+(?:haben|hat|habe|gibt\\s+es)\\s+(?:wir\\s+)?(?:davon\\s+)?(?:noch\\s+)?(?:auf\\s+lager|vorrätig|vorhanden|da)?$/i,'');\n    text = text.replace(/\\s+(?:sind\\s+)?(?:noch\\s+)?(?:auf\\s+lager|vorrätig|vorhanden|da)$/i,'');\n    text = text.replace(/\\s+(?:haben\\s+wir\\s+noch|ist\\s+noch\\s+da|noch\\s+haben\\s+wir)$/i,'');\n    text = text.replace(/\\s+(?:do\\s+we\\s+have|are\\s+left|mamy|zostalo)$/i,'');\n  } else {\n    text = text.replace(/^(?:suche|such|finde|zeig(?:e)?\\s+(?:mir\\s+)?|öffne|scan(?:ne)?|search(?:\\s+for)?|find)\\s+(?:den|die|das|nach|artikel\\s+)?/i,'');\n  }\n  return {query:text.trim().replace(/[?!.,]+$/g,'').trim(),stock};\n}\nfunction voiceMatches150(query) {\n  const exact = findScanCode(query);\n  if (exact && state.products[exact.productCode]) return [[exact.productCode,state.products[exact.productCode]]];\n  const normalized = mobileNormalize(query);\n  if (!normalized) return [];\n  const names = Object.entries(state.products).filter(([,product]) => mobileNormalize(product.name) === normalized);\n  if (names.length) return names;\n  const nameParts = Object.entries(state.products).filter(([,product]) =>\n    mobileNormalize(product.name).includes(normalized));\n  return nameParts.length ? nameParts : mobileProductSearch(query);\n}\nfunction voiceAnswer150(message) {\n  let box = document.getElementById('voiceAnswer150');\n  if (!box) {\n    const search = document.getElementById('mobileSearchResults');\n    if (!search) return;\n    search.insertAdjacentHTML('beforebegin', \"<div id='voiceAnswer150' class='voiceAnswer150' role='status' aria-live='polite'></div>\");\n    box = document.getElementById('voiceAnswer150');\n  }\n  box.textContent = message;\n  box.classList.toggle('visible',!!message);\n}\nconst voiceBefore150 = handleVoiceResult130;\nhandleVoiceResult130 = function(text) {\n  if (voiceMode130 !== 'search') return voiceBefore150(text);\n  const input = document.getElementById(voiceTarget130);\n  if (!input) return;\n  const parsed = voiceQuery150(text);\n  if (!parsed.query) {\n    showFeedback(...[voiceNotice150('missing')[0], 'warn', 3500, voiceNotice150('missing')[1]]);\n    return;\n  }\n  if (!parsed.stock && parsed.query === String(text||'').trim()) {\n    voiceAnswer150('');\n    return voiceBefore150(text);\n  }\n  input.value = parsed.query;\n  input.dispatchEvent(new Event('input',{bubbles:true}));\n  if (!parsed.stock) {\n    if (input.id === 'mobileArticleSearch') mobileSearchSubmit();\n    else if (input.id === 'dashBarcode') dashboardLookup();\n    else if (input.id === 'barcode') lookup();\n    return;\n  }\n  const matches = voiceMatches150(parsed.query);\n  if (!matches.length) {\n    voiceAnswer150('');\n    const [title, detail] = voiceNotice150('notFound', parsed.query);\n    showFeedback(title,'warn',4000,detail);\n    return;\n  }\n  if (matches.length === 1) {\n    const [code,product] = matches[0], amount = formatQtySmart(Number(product.stock)||0,product.unit);\n    if (input.id === 'mobileArticleSearch') {\n      mobileSelectProduct(code);\n      voiceAnswer150(`${product.name}: ${amount} im Lager.`);\n    } else {\n      showFeedback(`${product.name}: ${amount} im Lager`,'success',5500);\n      if (input.id === 'dashBarcode') dashboardLookup();\n      else if (input.id === 'barcode') lookup();\n    }\n    return;\n  }\n  if (input.id === 'mobileArticleSearch') {\n    mobileCategory103 = '';\n    renderMobileSearchResults(parsed.query);\n    voiceAnswer150(`${matches.length} passende Artikel gefunden. Wähle die richtige Größe; der Bestand steht bei jedem Artikel.`);\n  } else showFeedback(`${matches.length} passende Artikel gefunden`,'warn',5000,'Bitte den gewünschten Artikel auswählen.');\n};\nconst changedBefore150 = mobileSearchChanged;\nmobileSearchChanged = function() { voiceAnswer150(''); return changedBefore150(); };\nconst backBefore150 = mobileBackList103;\nmobileBackList103 = function() { voiceAnswer150(''); return backBefore150(); };\n"},{"src":"patch151.js","code":"'use strict';\n// Identify a product mention inside a sentence before using the spoken text as\n// a search query. Match complete words so short article names cannot trigger\n// on unrelated parts of a sentence.\nfunction voiceMention151(utterance) {\n  const spoken = mobileNormalize(utterance);\n  if (!spoken) return '';\n  const padded = ` ${spoken} `;\n  const ignored = new Set(['artikel','produkt','produkte','bestand','lager','noch','habe','haben','hast','wieviele','wieviel','viele','viel','wie','ich','wir','uns','mir','die','das','der','den','dem','ein','eine','einen','meine','unser','unsere','stück','stuck','packung','flasche','kann','finde','suche','zeige','offne','bitte','gross','klein','auf','von','fur','für','und','ist','sind','was']);\n  let best = '';\n  for (const product of Object.values(state.products || {})) {\n    const labels = [product.name, ...(Array.isArray(product.synonyms) ? product.synonyms : [])];\n    for (const label of labels) {\n      const words = mobileNormalize(label).split(' ').filter(Boolean);\n      for (let count = words.length; count >= 1; count--) {\n        for (let start = 0; start + count <= words.length; start++) {\n          const parts = words.slice(start, start + count);\n          // Short real product names (e.g. Brot or Reis) must also match.\n          if (!parts.some(word => word.length >= 3 && !ignored.has(word))) continue;\n          const phrase = parts.join(' ');\n          if (padded.includes(` ${phrase} `) && phrase.length > best.length) best = phrase;\n        }\n      }\n    }\n  }\n  return best;\n}\nconst voiceBefore151 = handleVoiceResult130;\nhandleVoiceResult130 = function(transcript) {\n  if (voiceMode130 !== 'search') return voiceBefore151(transcript);\n  const input = document.getElementById(voiceTarget130);\n  if (!input) return;\n  const utterance = String(transcript || '').trim();\n  const parsed = voiceQuery150(utterance);\n  const mention = voiceMention151(utterance);\n  const question = voiceIsStockQuestion150(utterance) || /\\b(habe\\s+ich\\s+noch|haben\\s+wir\\s+noch)\\b/i.test(utterance);\n  // Keep the normal search for a short, direct article name or barcode.\n  if (!mention && !question) return voiceBefore151(transcript);\n  const query = mention || parsed.query;\n  if (!query) {\n    const [title, detail] = voiceNotice150('missing');\n    showFeedback(title,'warn',3500,detail);\n    return;\n  }\n  // Questions must never offer to create an article from the entire sentence.\n  const matches = voiceMatches150(query);\n  if (!matches.length) {\n    voiceAnswer150('');\n    const [title, detail] = voiceNotice150('notFound', query);\n    showFeedback(title,'warn',4000,detail);\n    return;\n  }\n  input.value = query;\n  input.dispatchEvent(new Event('input',{bubbles:true}));\n  if (matches.length === 1) {\n    const [code,product] = matches[0];\n    if (input.id === 'mobileArticleSearch') {\n      mobileSelectProduct(code);\n      if (question) voiceAnswer150(inventorySentence152(product));\n    } else {\n      if (question) showFeedback(inventorySentence152(product),'success',5500);\n      if (input.id === 'dashBarcode') dashboardLookup();\n      else if (input.id === 'barcode') lookup();\n    }\n    return;\n  }\n  if (input.id === 'mobileArticleSearch') {\n    mobileCategory103 = '';\n    mobileProductChosen = false;\n    renderMobileSearchResults(query);\n    voiceAnswer150(inventoryChoice152(matches.length));\n  } else {\n    if (input.id === 'dashBarcode') dashboardLookup();\n    else if (input.id === 'barcode') lookup();\n  }\n};\n"},{"src":"patch152.js","code":"'use strict';\n\n// Only speak a result triggered by a voice query. Written searches stay quiet.\nfunction inventorySpeech152(message) {\n  if (!message || voiceMode130 !== 'search') return;\n  // The Android WebView may expose speechSynthesis without producing sound.\n  // Prefer the native Android text-to-speech engine when it is available.\n  if (globalThis.ZapasioAndroid?.speakText) {\n    try {\n      // A native engine still loading returns false; let the browser speak.\n      if (globalThis.ZapasioAndroid.speakText(message, voiceLocale130()) !== false) return;\n    } catch (error) {\n      console.warn('Native speech output unavailable', error);\n    }\n  }\n  const synth = globalThis.speechSynthesis;\n  if (!synth || !globalThis.SpeechSynthesisUtterance) return;\n  try {\n    synth.cancel();\n    const utterance = new SpeechSynthesisUtterance(message);\n    utterance.lang = voiceLocale130();\n    utterance.rate = 0.95;\n    synth.speak(utterance);\n  } catch (error) {\n    console.warn('Speech output unavailable', error);\n  }\n}\n\nconst previousVoiceAnswer152 = voiceAnswer150;\nvoiceAnswer150 = function(message) {\n  previousVoiceAnswer152(message);\n  if (message) inventorySpeech152(message);\n};\n\nfunction inventoryStockWord152() {\n  return ({de:'im Lager',en:'in stock',pl:'w magazynie'})[business130().language] || 'im Lager';\n}\nfunction inventorySentence152(product) {\n  return `${product.name}: ${formatQtySmart(Number(product.stock) || 0, product.unit)} ${inventoryStockWord152()}.`;\n}\nfunction inventoryChoice152(number) {\n  const lang = business130().language;\n  if (lang === 'pl') return `Znaleziono ${number} pasujących artykułów. Wybierz właściwy artykuł.`;\n  if (lang === 'en') return `${number} matching items found. Please select the correct item.`;\n  return `${number} passende Artikel gefunden. Bitte wähle den gewünschten Artikel.`;\n}\n\n// The desktop answer uses a toast instead of the mobile answer panel.\nconst previousVoiceResult152 = handleVoiceResult130;\nhandleVoiceResult130 = function(transcript) {\n  if (voiceMode130 !== 'search') return previousVoiceResult152(transcript);\n  const input = document.getElementById(voiceTarget130);\n  if (!input || input.id === 'mobileArticleSearch') return previousVoiceResult152(transcript);\n  const utterance = String(transcript || '');\n  const query = voiceMention151(utterance) || voiceQuery150(utterance).query;\n  const matches = voiceMatches150(query);\n  const isQuestion = voiceIsStockQuestion150(utterance) || /\\b(habe\\s+ich\\s+noch|haben\\s+wir\\s+noch)\\b/i.test(utterance);\n  const result = previousVoiceResult152(transcript);\n  if (isQuestion && matches.length === 1) {\n    const product = matches[0][1];\n    inventorySpeech152(inventorySentence152(product));\n  } else if (isQuestion && matches.length > 1) {\n    inventorySpeech152(inventoryChoice152(matches.length));\n  }\n  return result;\n};\n"},{"src":"patch154.js","code":"'use strict';\n\n// Display money in minor units, while keeping the full calculated recipe cost.\nconst formatMoneyInput154 = value => Number(value).toLocaleString('de-AT', {\n  useGrouping: false, minimumFractionDigits: 2, maximumFractionDigits: 2\n});\n\nfunction isMoneyInput154(input) {\n  if (!(input instanceof HTMLInputElement) || input.type === 'number' || input.inputMode !== 'decimal') return false;\n  const field = input.closest('.field, .moneyInput143, .priceQuoteInputs82');\n  const label = field?.querySelector('label')?.textContent || '';\n  return input.closest('.moneyInput143') !== null || /(?:€|EUR|PLN|\\b(?:Preis|EK|Umsatz|Wareneinsatz|Kosten|Gebühr|Miete|Provision|Erlös)\\b)/i.test(label);\n}\n\ndocument.addEventListener('input', event => {\n  if (event.target?.id === 'dbGoods143') delete event.target.dataset.preciseMoney154;\n}, true);\n\ndocument.addEventListener('focusout', event => {\n  const input = event.target;\n  if (!isMoneyInput154(input) || !input.value.trim()) return;\n  const raw = input.value.trim();\n  // Grouping is deliberately omitted in editable fields: older forms parse a\n  // comma as decimal separator but do not all parse thousands separators.\n  const cleaned = raw.replace(/\\s/g, '');\n  if (!/^-?(?:\\d+(?:[.,]\\d+)?|\\d{1,3}(?:\\.\\d{3})+(?:,\\d+)?)$/.test(cleaned)) return;\n  const value = localeNumber144(cleaned);\n  if (!Number.isFinite(value)) return;\n  input.value = formatMoneyInput154(value);\n  if (input.id === 'dbGoods143' && input.dataset.preciseMoney154 !== undefined) updateContribution143();\n  else if (input.value !== raw) input.dispatchEvent(new Event('input', { bubbles: true }));\n});\n\n// The recipe may calculate fractions of a cent. Keep its original value for\n// downstream totals, and show the input as a conventional two-decimal amount.\nconst takeDbRecipeBefore154 = takeDbRecipe145;\ntakeDbRecipe145 = function () {\n  const previous = document.getElementById('dbGoods143');\n  if (previous) delete previous.dataset.preciseMoney154;\n  takeDbRecipeBefore154();\n  const input = document.getElementById('dbGoods143');\n  if (!input || !input.value.trim()) return;\n  const precise = localeNumber144(input.value);\n  input.dataset.preciseMoney154 = String(precise);\n  input.value = formatMoneyInput154(precise);\n  updateContribution143();\n};\n\nconst updateContributionBefore154 = updateContribution143;\nupdateContribution143 = function () {\n  const input = document.getElementById('dbGoods143');\n  const precise = input?.dataset.preciseMoney154;\n  if (precise === undefined) return updateContributionBefore154();\n  const shown = input.value;\n  try {\n    input.value = precise;\n    return updateContributionBefore154();\n  } finally {\n    input.value = shown;\n  }\n};\n"},{"src":"patch155.js","code":"'use strict';\n\n// One navigation hierarchy for the workshop workflow. Keep the existing\n// action buttons and their permission checks; only change where they live.\nconst NAV_GROUPS155 = [\n  {id:'stock',title:'Lager',items:['tabLagerBtn','tabStockBtn','tabRankingBtn','tabAdminBtn']},\n  {id:'plan',title:'Planen',items:['tabRecipesBtn','tabPlanningBtn','navEntry155','navPlanCalc155']},\n  {id:'buy',title:'Einkauf',items:['navShoppingListBtn','navOrder155','navNeed155','navPlanShopping155','navSuppliers155']},\n  {id:'prepare',title:'Vorbereitung',items:['navPreparationBtn','navLoadBtn','navQuickLoadBtn','navSetupBtn','navOnsiteBtn']},\n  {id:'calc',title:'Kalkulation',items:['navCalcBtn','navCalcFood155','navCalcDb155','navCalcQuick155','navCalcDaily155']},\n  {id:'system',title:'System',items:['tabSettingsBtn','tabHelpBtn']}\n];\nconst EXTRA_NAV155 = {\n  navEntry155:['Einsatz bearbeiten',\"openPlanningFromNav('entry')\"],\n  navPlanCalc155:['Einsatzkalkulation',\"openPlanningFromNav('calc')\"],\n  navOrder155:['Bestellungen',\"openPurchasingOverview('orders')\"],\n  navNeed155:['Bedarf prüfen',\"openPurchasingOverview('needs')\"],\n  navPlanShopping155:['Bedarf für Einsatz',\"openPlanningFromNav('shopping')\"],\n  navSuppliers155:['Lieferanten',\"openPlanningFromNav('suppliers')\"],\n  navCalcFood155:['Wareneinsatz',\"openCalculations143('food')\"],\n  navCalcDb155:['Deckungsbeitrag',\"openCalculations143('contribution')\"],\n  navCalcQuick155:['Schnellkalkulation',\"openCalculations143('quick')\"],\n  navCalcDaily155:['Tagesabschlüsse',\"openCalculations143('daily')\"]\n};\nlet openNavGroup155 = 'stock';\nfunction extraNavButton155(id) {\n  const button=document.createElement('button');\n  button.type='button';button.id=id;button.className='sideNavBtn zSubNav155';\n  button.textContent=EXTRA_NAV155[id][0];button.setAttribute('onclick',EXTRA_NAV155[id][1]);\n  return button;\n}\nfunction renameNav155(button,label) {\n  const text=[...button.childNodes].find(node=>node.nodeType===Node.TEXT_NODE&&node.textContent.trim());\n  if(text)text.textContent=label;\n}\nfunction showNavGroup155(id,open) {\n  const nav=document.getElementById('customSideNav');\n  if(!nav)return;\n  for(const group of nav.querySelectorAll('.zNavGroup155')) {\n    const expanded=!!open&&group.dataset.group===id;\n    group.querySelector('.zNavToggle155').setAttribute('aria-expanded',String(expanded));\n    group.querySelector('.zNavChildren155').hidden=!expanded;\n  }\n  openNavGroup155=open?id:'';\n}\nfunction activeNavGroup155() {\n  const active=document.querySelector('#customSideNav .zNavChildren155 .sideNavBtn.active');\n  const group=active?.closest('.zNavGroup155');\n  if(group)showNavGroup155(group.dataset.group,true);\n}\nfunction buildWorkshopNav155() {\n  const nav=document.getElementById('customSideNav');if(!nav)return;\n  const original=new Map([...nav.querySelectorAll('.sideNavBtn')].filter(x=>!EXTRA_NAV155[x.id]).map(x=>[x.id,x]));\n  const selected=nav.querySelector('.sideNavBtn.active')?.id;\n  const today=original.get('tabDashboardBtn');\n  nav.replaceChildren();\n  if(today){today.classList.add('zNavToday155');nav.appendChild(today)}\n  for(const spec of NAV_GROUPS155){\n    const group=document.createElement('section');group.className='zNavGroup155';group.dataset.group=spec.id;\n    const toggle=document.createElement('button');toggle.type='button';toggle.className='zNavToggle155';\n    toggle.setAttribute('aria-controls','zNavChildren155_'+spec.id);\n    toggle.innerHTML=`<span>${spec.title}</span><svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"m7 10 5 5 5-5\"/></svg>`;\n    const children=document.createElement('div');children.className='zNavChildren155';children.id='zNavChildren155_'+spec.id;\n    toggle.onclick=()=>{\n      showNavGroup155(spec.id,toggle.getAttribute('aria-expanded')!=='true');\n      if(spec.id==='buy')openPurchasingOverview('list');\n    };\n    for(const id of spec.items){const child=original.get(id)||extraNavButton155(id);if(id==='navCalcBtn'){child.onclick=()=>openCalculations143('event');renameNav155(child,'Einsatzkalkulationen')}if(id==='tabLagerBtn')renameNav155(child,'Artikel suchen');if(id==='navPreparationBtn')renameNav155(child,'Aufgaben vorbereiten');child.classList.toggle('zSubNav155',id!=='tabLagerBtn'&&id!=='tabRecipesBtn'&&id!=='navShoppingListBtn'&&id!=='navPreparationBtn'&&id!=='navCalcBtn'&&id!=='tabSettingsBtn');children.appendChild(child)}\n    group.append(toggle,children);nav.appendChild(group);\n  }\n  if(selected)document.getElementById(selected)?.classList.add('active');\n  const active=document.querySelector('#customSideNav .zNavChildren155 .sideNavBtn.active');\n  showNavGroup155(active?.closest('.zNavGroup155')?.dataset.group||openNavGroup155||'stock',true);\n  document.body.classList.add('zWorkshop155');\n}\nconst applyMenuOrderBefore155=applyMenuOrder;\napplyMenuOrder=function(){applyMenuOrderBefore155();buildWorkshopNav155()};\n\nconst setActiveSideNavBefore155=setActiveSideNav;\nsetActiveSideNav=function(which,section='') {\n  setActiveSideNavBefore155(which,section);\n  const id=which==='ranking'?'tabRankingBtn':which==='purchasing'?({list:'navShoppingListBtn',orders:'navOrder155',needs:'navNeed155'}[purchasingSection]||'navShoppingListBtn'):\n    which==='planning'?({entry:'navEntry155',shopping:'navPlanShopping155',suppliers:'navSuppliers155',calc:'navPlanCalc155'}[section]||''):\n    which==='calculations'?({event:'navCalcBtn',food:'navCalcFood155',contribution:'navCalcDb155',quick:'navCalcQuick155',daily:'navCalcDaily155'}[calcSection143]||'navCalcBtn'):'';\n  if(id){document.querySelectorAll('#customSideNav .sideNavBtn.active').forEach(b=>b.classList.remove('active'));document.getElementById(id)?.classList.add('active')}\n  activeNavGroup155();\n};\nconst setCalcSectionBefore155=setCalcSection143;\nsetCalcSection143=function(section){setCalcSectionBefore155(section);setActiveSideNav('calculations')};\nconst setPurchasingBefore155=setPurchasingSection;\nsetPurchasingSection=function(section){setPurchasingBefore155(section);setActiveSideNav('purchasing')};\n\n// Currency belongs to the business, independently of interface language.\nfunction installCurrencySetting155(){\n  const card=document.getElementById('businessCountry130');\n  if(!card||document.getElementById('businessCurrency155'))return;\n  const language=[...card.querySelectorAll('.field')].find(x=>x.textContent.includes('Sprache der App'));\n  const field=document.createElement('div');field.className='field';\n  field.innerHTML='<label for=\"businessCurrency155\">Währung der Beträge</label><select id=\"businessCurrency155\" onchange=\"setBusiness130(\\'currency\\',this.value)\"><option value=\"EUR\">Euro (€)</option><option value=\"PLN\">Złoty (zł)</option></select><small>Ein Wechsel rechnet bestehende Beträge nicht um.</small>';\n  language?.after(field);\n  field.querySelector('select').value=business130().currency==='PLN'?'PLN':'EUR';\n}\nconst originalCurrencyText155=new WeakMap();\nfunction updateCurrencyLabels155(){\n  const currency=business130().currency==='PLN'?'PLN':'EUR';\n  document.body.dataset.currency155=currency;\n  for(const label of document.querySelectorAll('.contentWrap label,.modal label')){\n    const walker=document.createTreeWalker(label,NodeFilter.SHOW_TEXT);\n    for(let node=walker.nextNode();node;node=walker.nextNode()){\n      if(!originalCurrencyText155.has(node)&&node.textContent.includes('€'))originalCurrencyText155.set(node,node.textContent);\n      const original=originalCurrencyText155.get(node);\n      if(original)node.textContent=currency==='PLN'?original.replaceAll('€','zł'):original;\n    }\n  }\n  for(const input of document.querySelectorAll('input[aria-label]')){\n    if(!input.dataset.euroAria155&&input.getAttribute('aria-label').includes('in Euro'))input.dataset.euroAria155=input.getAttribute('aria-label');\n    if(input.dataset.euroAria155)input.setAttribute('aria-label',currency==='PLN'?input.dataset.euroAria155.replace('in Euro','in Złoty'):input.dataset.euroAria155);\n  }\n}\nconst renderBefore155=render;\nrender=function(){const result=renderBefore155();if(!document.querySelector('#customSideNav .zNavGroup155'))buildWorkshopNav155();installCurrencySetting155();updateCurrencyLabels155();return result};\nbuildWorkshopNav155();installCurrencySetting155();updateCurrencyLabels155();\n"},{"src":"patch156.js","code":"\n'use strict';\n// Workflows and current pilot limitations; complements the numbered help steps.\nconst ZAPASIO_FAQ156 = [\n  {\n    \"title\": \"Einstieg und Bedienung\",\n    \"items\": [\n      {\n        \"question\": \"Wo beginne ich, wenn ich Zapasio zum ersten Mal nutze?\",\n        \"answer\": \"Lege zuerst einen Artikel mit Einheit, Lieferant, Einkaufspreis und kaufbarer Bestellpackung an. Buche den vorhandenen Bestand. Verknüpfe danach Artikel mit Rezepten und lege einen Einsatz mit Personen und Speisenmengen an. Erst dann kann Zapasio den Einkaufsbedarf sinnvoll berechnen.\"\n      },\n      {\n        \"question\": \"Was ist ein „Einsatz“?\",\n        \"answer\": \"Ein Einsatz ist etwa ein Catering, eine Veranstaltung oder das Tagesgeschäft. Du planst dafür Datum, Personen, Speisen und Mengen. Daraus entstehen der Zutatenbedarf sowie Aufgaben für Einkauf und Vorbereitung.\"\n      },\n      {\n        \"question\": \"Muss ich alle Bereiche der App bedienen?\",\n        \"answer\": \"Nein. Für die tägliche Arbeit am Handy sind vor allem Suchen oder Scannen, Bestand, Einkauf, Vorbereitung und Beladen wichtig. Artikelpflege, Rezepte und Kalkulation lassen sich am größeren Bildschirm leichter bearbeiten.\"\n      },\n      {\n        \"question\": \"Wo finde ich Hilfe, wenn ich in einem Schritt nicht weiterkomme?\",\n        \"answer\": \"Unter „Hilfe“ steht der Arbeitsablauf vom Artikel bis zur Einkaufsliste. Prüfe zuerst, ob beim Artikel Einheit, Preis und Bestellpackung, beim Rezept die Zutaten und beim Einsatz die Mengen hinterlegt sind.\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Artikel, Bestand und MHD\",\n    \"items\": [\n      {\n        \"question\": \"Was ist der Unterschied zwischen Artikel, Packung und Bestand?\",\n        \"answer\": \"Der Artikel beschreibt das Produkt, zum Beispiel Hafermilch. Die Packung beschreibt, wie es gekauft wird, zum Beispiel ein 1-l-Karton. Der Bestand ist die tatsächlich eingelagerte Menge in der festgelegten Lagereinheit.\"\n      },\n      {\n        \"question\": \"Warum meldet Zapasio „Bestellpackung fehlt“?\",\n        \"answer\": \"Zapasio kennt zwar den Bedarf, aber noch keine kaufbare Packung. Öffne den Artikel, ergänze etwa „500-g-Packung“ oder „1-l-Flasche“ und kennzeichne sie als Bestellpackung. Prüfe dabei die Umrechnung zur Lagereinheit.\"\n      },\n      {\n        \"question\": \"Kann ein Artikel mehrere Packungsgrößen haben?\",\n        \"answer\": \"Ja. Hinterlege die tatsächlich kaufbaren Gebinde beim Artikel und kontrolliere die Mengen je Gebinde. Für die Einkaufsliste wird auf ganze kaufbare Packungen gerechnet.\"\n      },\n      {\n        \"question\": \"Wie buche ich neu eingekaufte Ware ins Lager?\",\n        \"answer\": \"Erfasse einen Wareneingang mit Artikel, gekaufter Packung, Anzahl und tatsächlichem Preis. Wenn der Artikel ein MHD benötigt, trage es dabei ein. Beim Einkauf über die Liste gibt es einen eigenen Schritt „Einkauf ins Lager übernehmen“; das bloße Abhaken reicht nicht.\"\n      },\n      {\n        \"question\": \"Muss jeder Artikel ein Mindesthaltbarkeitsdatum haben?\",\n        \"answer\": \"Nein. Aktiviere MHD beim betreffenden Artikel nur, wenn es für die Ware benötigt wird. Bei Produkten wie Verpackungsmaterial ist in der Regel kein MHD erforderlich.\"\n      },\n      {\n        \"question\": \"Was mache ich bei gleichem Artikel mit verschiedenen MHDs?\",\n        \"answer\": \"Erfasse die Ware als getrennte Bestandschargen mit dem jeweiligen Datum. Prüfe beim Buchen die Menge pro MHD, damit die Anzeige und die Warnungen stimmen.\"\n      },\n      {\n        \"question\": \"Warum stimmt der angezeigte Bestand nicht mit dem Regal überein?\",\n        \"answer\": \"Prüfe die Lagereinheit, Gebindeumrechnung und letzten Zu- oder Abgänge. Zähle die vorhandene Ware nach und korrigiere den Bestand über eine Buchung. Kontrolliere bei MHD-Artikeln auch die einzelnen Chargen.\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Suchen, Foto, Barcode und Sprache\",\n    \"items\": [\n      {\n        \"question\": \"Wie finde ich einen Artikel schnell?\",\n        \"answer\": \"Suche nach dem Namen oder nutze den Barcode, wenn er beim Artikel hinterlegt ist. Am Handy kannst du außerdem die Kamera verwenden. Prüfe den vorgeschlagenen Treffer, bevor du eine Bestandsänderung bestätigst.\"\n      },\n      {\n        \"question\": \"Erkennt Zapasio jedes Produkt auf einem Foto?\",\n        \"answer\": \"Nein. Die Fotoerkennung ist eine Pilotfunktion. Sie kann einen vorhandenen Artikel vorschlagen; ähnliche Verpackungen, schlechte Beleuchtung oder fehlende Referenzbilder können zu einem falschen oder keinem Treffer führen. Vergleiche Name und Packungsgröße vor dem Buchen.\"\n      },\n      {\n        \"question\": \"Was mache ich, wenn ein Foto oder Barcode keinen Artikel findet?\",\n        \"answer\": \"Suche zunächst nach dem Namen. Falls der Artikel wirklich fehlt, lege ihn mit Einheit und Packungsgröße an und hinterlege, soweit angeboten, Barcode oder Produktfoto für spätere Suchen.\"\n      },\n      {\n        \"question\": \"Kann ich Zapasio fragen: „Wie viele Suppenbecher habe ich noch?“\",\n        \"answer\": \"Die Sprachabfrage kann nach einem genannten Artikel suchen und den ermittelten Bestand als Antwort anzeigen. Prüfe bei ähnlichen Artikeln, ob der richtige Treffer gewählt wurde. Die Antwort zum Vorlesen ist vorbereitet; die Wiedergabe auf dem konkreten Android-Gerät muss noch praktisch geprüft werden.\"\n      },\n      {\n        \"question\": \"Warum höre ich am Handy keine Antwort?\",\n        \"answer\": \"Prüfe Medienlautstärke, Stummmodus und die auf dem Gerät verfügbare Sprachausgabe. Die native Android-Vorlesefunktion liegt als Pilot vor, ist auf dem betroffenen Handy aber noch nicht abschließend getestet. Bis dahin bleibt die Antwort auf dem Bildschirm maßgeblich.\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Rezepte, Einsätze und Einkauf\",\n    \"items\": [\n      {\n        \"question\": \"Warum taucht eine Zutat nicht im Einkaufsbedarf auf?\",\n        \"answer\": \"Prüfe, ob das Rezept mit einem Lagerartikel verknüpft ist, die Ausbeute und Portionsgröße stimmen und das Rezept im Einsatz mit einer Menge ausgewählt wurde. Prüfe anschließend den vorhandenen Bestand.\"\n      },\n      {\n        \"question\": \"Wie öffne ich den Einkauf?\",\n        \"answer\": \"Am PC links „Einkauf“ öffnen und dann Einkaufsliste, Bestellungen oder Bedarf prüfen wählen. Am Handy führt „Einkauf“ zur Einkaufsliste. Wenn dir eine Berechtigung fehlt, zeigt die App einen Hinweis.\"\n      },\n      {\n        \"question\": \"Wie entsteht die Einkaufsliste?\",\n        \"answer\": \"Zapasio vergleicht den Bedarf aus den geplanten Einsätzen mit dem erfassten Vorrat. Es berücksichtigt die zugeordneten Lieferanten und kaufbaren Gebinde und rundet benötigte Bestellungen auf ganze Packungen auf. Prüfe die Vorschläge vor dem Einkauf.\"\n      },\n      {\n        \"question\": \"Warum steht ein Artikel nicht auf der Liste zum Mitnehmen?\",\n        \"answer\": \"Die Liste zum Mitnehmen enthält Artikel, deren Bezugsweg auf „Selbst vor Ort einkaufen“ steht. Artikel für Vorbestellung oder Onlineshop werden separat angezeigt. Prüfe beim Artikel beziehungsweise Lieferanten die Zuordnung und Bestellart.\"\n      },\n      {\n        \"question\": \"Kann ich die Einkaufsliste drucken oder per E-Mail mitnehmen?\",\n        \"answer\": \"Ja, die App bietet Drucken beziehungsweise PDF und bereitet eine E-Mail im Mailprogramm vor. Die Nachricht wird erst versendet, wenn du sie dort selbst abschickst.\"\n      },\n      {\n        \"question\": \"Bestellt Zapasio automatisch beim Lieferanten?\",\n        \"answer\": \"Nein. Je nach hinterlegtem Bezugsweg zeigt Zapasio eine Einkaufsliste oder einen Bestellentwurf für E-Mail, Telefon oder andere Kanäle. Eine Bestellung wird von dir geprüft und ausgelöst.\"\n      },\n      {\n        \"question\": \"Ist ein abgehakter Einkauf schon Lagerbestand?\",\n        \"answer\": \"Nein. Ein Häkchen merkt den Einkauf auf der Liste vor. Erfasse danach, was tatsächlich gekauft wurde, mit Packung, Anzahl, Preis und gegebenenfalls MHD und bestätige „Einkauf ins Lager übernehmen“.\"\n      },\n      {\n        \"question\": \"Was passiert, wenn ich weniger oder eine andere Packung kaufe?\",\n        \"answer\": \"Passe beim Übernehmen des Einkaufs die wirklich gekaufte Packung und Anzahl an. Nicht erhaltene Positionen bleiben offen. Trage den tatsächlichen Nettopreis ein, damit Bestand und Preisdaten nicht mit dem ursprünglichen Vorschlag verwechselt werden.\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Vorbereitung, Beladen und Auswertung\",\n    \"items\": [\n      {\n        \"question\": \"Woher kommen die Aufgaben in „Vorbereitung“?\",\n        \"answer\": \"Die App erstellt Aufgaben aus den zugeordneten Rezepten und Mengen des gewählten Einsatzes. Du kannst eigene Aufgaben ergänzen und die erledigten Schritte abhaken. Fehlen Rezepte oder Mengen, fehlen auch die daraus abgeleiteten Aufgaben.\"\n      },\n      {\n        \"question\": \"Wofür ist die Ladeliste gedacht?\",\n        \"answer\": \"Mit der Ladeliste kontrollierst du vor der Abfahrt Speisen, Material und Ausstattung für den Einsatz. Gleiche die Anzeige immer mit dem tatsächlich verladenen Inhalt ab.\"\n      },\n      {\n        \"question\": \"Warum fehlt in der Kalkulation ein Preis oder wirkt ein Betrag falsch?\",\n        \"answer\": \"Prüfe beim betroffenen Artikel Einkaufspreis, Einheit und Gebindeumrechnung sowie im Rezept Zutatenmenge und Ausbeute. Die Kalkulation arbeitet mit den hinterlegten Daten und Annahmen; fehlende oder veraltete Preise verzerren das Ergebnis.\"\n      },\n      {\n        \"question\": \"Ersetzt die Auswertung meine Buchhaltung?\",\n        \"answer\": \"Nein. Sie unterstützt die Planung und den Vergleich von Soll und Ist. Für verbindliche Buchhaltung und steuerliche Zahlen müssen die zugrunde liegenden Belege und Regeln gesondert geprüft werden.\"\n      }\n    ]\n  },\n  {\n    \"title\": \"Web, Android und Spracheinstellungen\",\n    \"items\": [\n      {\n        \"question\": \"Braucht die Android-App dauerhaft ein USB-Kabel zum PC?\",\n        \"answer\": \"Für die normale Nutzung nach Installation ist kein USB-Kabel vorgesehen. Der derzeitige Android-Pilot ist als Projektpaket vorhanden; eine hier fertig gebaute und auf deinem Handy geprüfte APK liegt noch nicht vor. Für die Installation und den Praxistest ist daher noch ein eigener Schritt nötig.\"\n      },\n      {\n        \"question\": \"Funktioniert Zapasio ohne Internet?\",\n        \"answer\": \"Der derzeitige Android-Pilot lädt die Web-App und benötigt dafür eine Internetverbindung. Verlasse dich beim Einsatz ohne Netz noch nicht auf eine vollständige Offline-Nutzung.\"\n      },\n      {\n        \"question\": \"Ändert die Spracheinstellung schon die gesamte App?\",\n        \"answer\": \"Noch nicht zuverlässig. Deutsch, Englisch und Polnisch sind als erste Sprachen für die vollständige Oberfläche vorgesehen; derzeit können weiterhin deutschsprachige Texte außerhalb der Menüleiste erscheinen. Die vollständige Übersetzung von Web und Android ist noch offen.\"\n      },\n      {\n        \"question\": \"Sind Web und Android auf demselben Funktionsstand?\",\n        \"answer\": \"Die Android-Pilot-App zeigt die Web-Oberfläche und ergänzt gerätespezifische Funktionen. Die Darstellung, Kameranutzung und Sprachausgabe müssen auf dem konkreten Handy geprüft werden; aus einer neuen Webversion folgt nicht automatisch eine fertig installierte Android-Version.\"\n      },\n      {\n        \"question\": \"Werden meine Daten auf allen Geräten automatisch synchronisiert?\",\n        \"answer\": \"Eine verlässliche geräteübergreifende Synchronisierung ist für den Pilotstand noch nicht nachgewiesen. Prüfe vor dem Wechsel zwischen Geräten, welche Daten tatsächlich im jeweiligen Konto und auf dem jeweiligen Gerät verfügbar sind. Eine verbindliche Zusage erfordert einen gemeinsamen Datenspeicher und einen erfolgreichen Mehrgeräte-Test.\"\n      },\n      {\n        \"question\": \"Kann ich mich auf diese Fassung schon im ganzen Betrieb verlassen?\",\n        \"answer\": \"Zapasio befindet sich im Pilotstadium. Kritische Abläufe wie Bestandsbuchung, Einkauf, Android-Sprachausgabe und geräteübergreifende Datenhaltung sollten mit echten Betriebsfällen geprüft werden, bevor die App als alleinige Grundlage für Lager und Einkauf dient.\"\n      }\n    ]\n  }\n];\nfunction renderFaq156(){\n  const host=document.querySelector('#tabHelp .helpPanel');if(!host||document.getElementById('faq156'))return;\n  const root=document.createElement('section');root.id='faq156';root.className='dashboardCard faq156';\n  const h=document.createElement('h2');h.textContent='Häufige Fragen';root.appendChild(h);\n  const intro=document.createElement('p');intro.textContent='Suche nach einem Thema oder öffne eine Frage. Die Antworten beschreiben den aktuellen Pilotstand.';root.appendChild(intro);\n  const search=document.createElement('input');search.type='search';search.id='faqSearch156';search.placeholder='Frage oder Stichwort suchen';search.setAttribute('aria-label','FAQ durchsuchen');root.appendChild(search);\n  const status=document.createElement('p');status.className='faqStatus156';status.setAttribute('role','status');root.appendChild(status);\n  for(const group of ZAPASIO_FAQ156){\n    const section=document.createElement('section');section.className='faqGroup156';\n    const title=document.createElement('h3');title.textContent=group.title;section.appendChild(title);\n    for(const entry of group.items){\n      const details=document.createElement('details');details.className='faqItem156';\n      const summary=document.createElement('summary');summary.textContent=entry.question;\n      const answer=document.createElement('p');answer.textContent=entry.answer;\n      details.append(summary,answer);section.appendChild(details);\n    }\n    root.appendChild(section);\n  }\n  search.addEventListener('input',()=>{\n    const query=search.value.trim().toLocaleLowerCase('de');let visible=0;\n    for(const group of root.querySelectorAll('.faqGroup156')){\n      let inGroup=0;for(const item of group.querySelectorAll('.faqItem156')){\n        item.hidden=!!query&&!item.textContent.toLocaleLowerCase('de').includes(query);\n        if(!item.hidden){visible++;inGroup++}\n      }group.hidden=!inGroup;\n    }\n    status.textContent=query?(visible?visible+' passende Fragen':'Keine passende Frage gefunden. Versuche einen anderen Begriff.'):'';\n  });\n  host.appendChild(root);\n}\nrenderFaq156();\n"},{"src":"patch157.js?v=342","code":"'use strict';\n// Keep remote updates quiet while a user is working. A focused but unchanged\n// search field is not an unsaved form and must not halt synchronization.\ncloudUiBusy119 = function(){\n  if(document.querySelector('dialog[open]'))return true;\n  if([...document.querySelectorAll('.modal')].some(x=>!['selfShoppingModal','articleInfoModal127'].includes(x.id)&&getComputedStyle(x).display!=='none'))return true;\n  if([...document.querySelectorAll('.shoppingManualAdd174[open] form')].some(form=>\n    [...form.elements].some(input=>input.value!==input.defaultValue||'checked'in input&&input.checked!==input.defaultChecked)))return true;\n  const active=document.activeElement;\n  if(!active)return false;\n  if(active.isContentEditable)return true;\n  if(!['INPUT','TEXTAREA','SELECT'].includes(active.tagName))return false;\n  if(['search','checkbox','radio','button','submit'].includes(String(active.type||'').toLowerCase())||['barcode','dashBarcode'].includes(active.id))return false;\n  return active.value!==active.defaultValue;\n};\n\ncloudPullSilent119 = async function(){\n  const c=cloudRuntime(),before=c.revision;\n  const result=await cloudRequest('/rest/v1/rpc/gs_read',{p_workspace:cloudConfig().workspace});\n  if(!result.data)throw Error('Gemeinsamer Datenstand fehlt.');\n  // A local save, a newer refresh or an open form may have appeared during fetch.\n  if(c.busy||c.pending||c.blocked||c.revision!==before||cloudUiBusy119())return false;\n  const active=document.activeElement;\n  const search=active&&(active.type==='search'||['barcode','dashBarcode'].includes(active.id))&&active.id?{\n    id:active.id,value:active.value,start:active.selectionStart,end:active.selectionEnd\n  }:null;\n  const scrollX=window.scrollX,scrollY=window.scrollY;\n  saveSafetySnapshot(clone(state),'Automatischer Live-Abgleich');\n  state=migrate(validBackupState(result.data));\n  globalThis.gsBootBlocked=false;\n  localStorage.setItem('gs_livetest_frankfurt_v1',JSON.stringify(state));\n  c.revision=result.revision;c.base=result.data;c.blocked=false;\n  c.status='Live aktualisiert · '+new Date().toLocaleTimeString('de-AT');\n  render();\n  if(document.getElementById('tabPurchasing')?.classList.contains('active'))renderPurchasingOverview();\n  else if(document.getElementById('selfShoppingModal')?.style.display==='flex')renderSelfShopping();\n  updateCloudStatus();\n  if(search){\n    const field=document.getElementById(search.id);\n    if(field){field.value=search.value;field.focus({preventScroll:true});\n      if(search.start!==null&&search.end!==null)field.setSelectionRange(search.start,search.end);\n    }\n  }\n  window.scrollTo(scrollX,scrollY);\n  return true;\n};\n\nlet syncCheckRunning157=false;\ncheckCloudUpdates = async function(){\n  const c=cloudRuntime();\n  if(syncCheckRunning157||!c.enabled||!c.user||c.busy||c.pending||c.blocked)return;\n  syncCheckRunning157=true;\n  try{\n    const result=await cloudRequest('/rest/v1/rpc/gs_revision',{p_workspace:cloudConfig().workspace});\n    if(result.revision!==c.revision&&!cloudUiBusy119())await cloudPullSilent119();\n    // An open form is checked again on the next poll. No conflict is raised\n    // unless a local write actually collides with the server revision.\n  }catch(error){\n    c.status='Verbindung unterbrochen · nächster Versuch folgt';updateCloudStatus();\n  }finally{syncCheckRunning157=false}\n};\ndocument.addEventListener('visibilitychange',()=>{if(!document.hidden)void checkCloudUpdates()});\nwindow.addEventListener('online',()=>{void checkCloudUpdates()});\n"},{"src":"patch158.js?v=411&release=411","code":"'use strict';\n// Visual-only workshop layout. The existing actions, permissions and data\n// calculations remain the source of truth for every link and status.\nconst zIcons158={\n  stock:'<path d=\"M4 7.5 12 3l8 4.5v9L12 21l-8-4.5zM12 12 4 7.5m8 4.5 8-4.5M12 12v9\"/>',\n  cart:'<path d=\"M3 4h2l2 11h12l2-8H6M9 20h.01M18 20h.01\"/>',\n  prep:'<rect x=\"5\" y=\"5\" width=\"14\" height=\"16\" rx=\"2\"/><path d=\"M9 5V3h6v2m-6 8 2 2 4-4\"/>',\n  calendar:'<rect x=\"4\" y=\"5\" width=\"16\" height=\"16\" rx=\"2\"/><path d=\"M8 3v4M16 3v4M4 10h16M8 15h3\"/>',\n  scan:'<path d=\"M4 8V4h4M16 4h4v4M20 16v4h-4M8 20H4v-4M8 9v6M11 8v8M14 9v6M17 8v8\"/>',\n  photo:'<path d=\"M4 7h4l2-2h4l2 2h4v12H4z\"/><circle cx=\"12\" cy=\"13\" r=\"3.5\"/>',\n  in:'<path d=\"M4 12v8h16v-8M4 20h16M12 3v12m-4-4 4 4 4-4\"/>',\n  out:'<path d=\"M4 7h11M11 3l4 4-4 4M4 15h16v6H4z\"/>',\n  check:'<path d=\"m4 12 5 5L20 6\"/>',\n  search:'<circle cx=\"10\" cy=\"10\" r=\"7\"/><path d=\"m15 15 6 6\"/>',\n  mic:'<rect x=\"9\" y=\"3\" width=\"6\" height=\"12\" rx=\"3\"/><path d=\"M5 11a7 7 0 0 0 14 0M12 18v3M8 21h8\"/>',\n  warning:'<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"M12 7v6M12 17h.01\"/>'\n};\nfunction zIcon158(name){return `<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\">${zIcons158[name]||zIcons158.stock}</svg>`}\n\nNAV_GROUPS155.splice(0,NAV_GROUPS155.length,\n  {id:'stock',title:'Lager',items:['tabLagerBtn','tabStockBtn','tabAdminBtn']},\n  {id:'recipes',title:'Rezepte',items:['tabRecipesBtn']},\n  {id:'plan',title:'Einsätze',items:['tabPlanningBtn','navEntry155','navPlanShopping155','navPlanCalc155']},\n  {id:'buy',title:'Einkauf',items:['navShoppingListBtn','navOrder155','navNeed155','navSuppliers155']},\n  {id:'prepare',title:'Vorbereitung',items:['navPreparationBtn','navSetupBtn','navOnsiteBtn']},\n  {id:'load',title:'Ladeliste',items:['navLoadBtn','navQuickLoadBtn']},\n  {id:'analytics',title:'Auswertungen',items:['tabRankingBtn','navCalcBtn','navCalcFood155','navCalcDb155','navCalcQuick155','navCalcDaily155']},\n  {id:'system',title:'System',items:['tabSettingsBtn','tabHelpBtn']}\n);\nconst zOriginalBuildNav158=buildWorkshopNav155;\nbuildWorkshopNav155=function(){\n  zOriginalBuildNav158();\n  const today=document.getElementById('tabDashboardBtn');\n  if(today){today.lastChild.textContent='Start';today.classList.add('zMainLink158')}\n  for(const group of document.querySelectorAll('#customSideNav .zNavGroup155')){\n    const id=group.dataset.group,toggle=group.querySelector('.zNavToggle155'),children=group.querySelector('.zNavChildren155'),primary=children?.querySelector('.sideNavBtn');\n    if(!toggle||!primary)continue;\n    toggle.classList.add('zMainLink158');primary.classList.add('zPrimaryHidden158');\n    if(id==='system'){\n      toggle.hidden=true;primary.classList.remove('zPrimaryHidden158');\n      for(const item of children.querySelectorAll('.sideNavBtn'))item.classList.remove('zPrimaryHidden158');\n      children.hidden=false;\n      continue;\n    }\n    toggle.onclick=()=>{\n      showNavGroup155(id,true);\n      primary.click();\n    };\n    if(children.querySelectorAll('.sideNavBtn').length===1){children.hidden=true;toggle.setAttribute('aria-expanded','false')}\n  }\n};\nbuildWorkshopNav155();\n\nfunction zHeader158(){\n  const bar=document.querySelector('.appMain>.topbar');if(!bar)return;\n  const brand=bar.querySelector('.topbarBrand img');if(brand)brand.src='assets/zapasio-logo-ci-lossless424.webp';\n  let detail=bar.querySelector('.zHeaderInfo158');\n  if(!detail){detail=document.createElement('div');detail.className='zHeaderInfo158';bar.insertBefore(detail,bar.querySelector('.mobileMenuBtn'))}\n  const c=cloudRuntime(),name=currentAccessUser()?.name||'Benutzer',initial=name.trim().slice(0,1).toLocaleUpperCase()||'Z';\n  const connected=!!(c.enabled&&c.user&&c.base!==null&&!c.blocked);\n  const connectionLabel=!c.enabled?'Nur auf diesem Gerät':!c.user?'Anmeldung erforderlich':c.blocked?'Abgleich prüfen':c.base===null?'Datenübernahme offen':'Live verbunden';\n  detail.innerHTML=`<span class=\"zBusiness158\">Grünstoff OG</span><span class=\"zLive158 ${connected?'online':''}\" role=\"status\"><i></i>${connectionLabel}</span><span class=\"zAvatar158\" aria-hidden=\"true\">${esc(initial)}</span><span class=\"zUserName158\">${esc(name)}</span>`;\n  const footer=document.querySelector('.sidebarFooter .sidebarStatus');\n  if(footer){footer.replaceChildren();const dot=document.createElement('i');footer.append(dot,document.createTextNode(' '+connectionLabel));footer.classList.toggle('zConnected158',connected)}\n  const side=document.querySelector('.sidebarBrand');if(side&&!side.classList.contains('zBrand158')){\n    side.classList.add('zBrand158');\n    const image=side.querySelector('img');if(image)image.src='assets/zapasio-pot-ci-lossless424.webp';\n    side.querySelector('strong')?.replaceChildren(document.createTextNode('Zapasio'));\n    side.querySelector('small')?.remove();\n  }\n}\nconst zOldCloudStatus158=updateCloudStatus;\nupdateCloudStatus=function(){zOldCloudStatus158();zHeader158()};\n\nfunction zNext158(){const today=localTodayISO();return(state.planning?.caterings||[]).filter(c=>c.date&&c.date>=today&&!c.workflow?.closed&&c.day390?.status!=='closed'&&!['cancelled','archived'].includes(c.status390)).slice().sort((a,b)=>String(a.date).localeCompare(String(b.date)))[0]||null}\nfunction zData158(){\n  const warnings=groupedWarnings(),next=zNext158(),active=activePlanningEntry(),shopping=active?procurementFor(active):[],prep=active?preparationProgress(active):{total:0,done:0,pct:0};\n  return{warnings,next,active,shopping,prep,products:Object.keys(state.products||{}).length,attention:warnings.length+shopping.length+Math.max(0,prep.total-prep.done)};\n}\nfunction zOpenNext158(id){return openSelectedPlanning411(id,'overview')}\nfunction zMobileNext158(id){return openSelectedPlanning411(id,'overview',true)}\nfunction zBook158(kind){\n  if(matchMedia('(max-width:820px)').matches)mobileShow('search');else showTab('lager');\n  if(state.products?.[current])openModal(kind);\n  else showFeedback('Zuerst einen Artikel auswählen','info',3500,'Danach kannst du die Buchung öffnen.');\n}\nfunction zMobileSearch158(){const val=document.getElementById('zQuickSearch158')?.value||'';mobileShow('search');const field=document.getElementById('mobileArticleSearch');if(field){field.value=val;field.dispatchEvent(new Event('input',{bubbles:true}));if(val)mobileSearchSubmit();else field.focus()}}\nconst zOldVoiceResult158=handleVoiceResult130;\nhandleVoiceResult130=function(text){zOldVoiceResult158(text);if(voiceTarget130==='zQuickSearch158'&&voiceMode130==='search')zMobileSearch158()};\n\nfunction zStatus158(d){\n  if(!d.attention)return `<section class=\"zStatusCard158\" aria-label=\"Betriebsstatus\"><span class=\"zStatusIcon158\">${zIcon158('check')}</span><span><strong>Keine aktuellen Hinweise</strong><small>Aktuell keine Warnungen oder offenen Aufgaben erkannt.</small></span></section>`;\n  const mhd=d.warnings.filter(w=>w.mhd?.length).length,stock=d.warnings.filter(w=>w.stock).length,prep=Math.max(0,d.prep.total-d.prep.done);\n  const parts=[mhd?'MHD prüfen: '+mhd:'',stock?'Mindestbestand prüfen: '+stock:'',d.shopping.length?'Einkaufspositionen: '+d.shopping.length:'',prep?'Vorbereitungsaufgaben: '+prep:''].filter(Boolean);\n  return `<details class=\"zStatusCard158 attentionStatus437 hasAttention ${d.warnings.some(w=>w.level==='danger')?'hasCritical':''}\" aria-label=\"Betriebsstatus\"><summary><span class=\"zStatusIcon158\">${zIcon158('warning')}</span><span><strong>${d.attention} ${d.attention===1?'Eintrag':'Einträge'} prüfen</strong><small>${esc(parts.join(' · '))}</small><span class=\"attentionOpen437\">Betroffene Artikel und Aufgaben anzeigen</span></span><span class=\"attentionChevron437\" aria-hidden=\"true\">⌄</span></summary><div class=\"attentionDetails437\">${zAttention158(d,false,true)}</div></details>`;\n}\nfunction showAllAttention437(){const scope=document.getElementById(innerWidth>820?'desktopHome123':'mobileTodayContent'),box=scope?.querySelector('.attentionStatus437');if(!box)return;box.open=true;box.querySelector('summary')?.focus();box.scrollIntoView({block:'start',behavior:'instant'})}\nfunction zEvent158(c,mobile=false){\n  if(!c)return `<section class=\"zNextCard158\"><span class=\"zNextIcon158\">${zIcon158('calendar')}</span><div><small>Als Nächstes</small><strong>Kein kommender Einsatz</strong><p>Plane einen Einsatz, damit Einkauf und Vorbereitung folgen können.</p></div><button class=\"primary\" onclick=\"newPlanningEntry()\">Einsatz planen</button></section>`;\n  const details=[Number(c.guests)>0?Number(c.guests)+' Personen':'',c.readyTime?'Ausgabe '+esc(c.readyTime):''].filter(Boolean).join(' · ');\n  return `<section class=\"zNextCard158\"><span class=\"zNextIcon158\">${zIcon158('calendar')}</span><div><small>Als Nächstes</small><strong>${esc(c.event||'Einsatz')} · ${esc(formatDate(c.date))}</strong><p>${details||esc(c.type||'Einsatz')}</p></div><button class=\"primary\" onclick=\"${mobile?`zMobileNext158('${escJs(c.id)}')`:`zOpenNext158('${escJs(c.id)}')`}\">Einsatz öffnen</button></section>`;\n}\nfunction zAttention158(d,mobile=false,all=false){\n  const limit=mobile?2:3,list=(all?d.warnings:d.warnings.slice(0,limit)).map(w=>`<button type=\"button\" class=\"zAttentionRow158 attentionRow437 ${w.level==='danger'?'critical':'soon'}\" onclick=\"openWarningGroup243('${escJs(w.key)}')\"><span>${zIcon158('warning')}</span><span class=\"attentionText437\"><b>${esc(w.name)}</b><small>${esc([w.stock?.text,...(w.mhd||[]).map(m=>m.text)].filter(Boolean).join(' · '))}</small></span><span class=\"attentionAction437\">Artikel prüfen</span><span class=\"zChevron158\">›</span></button>`);\n  if(!all&&d.warnings.length>limit)list.push(`<button type=\"button\" class=\"soft attentionAll437\" onclick=\"showAllAttention437()\">Alle ${d.warnings.length} Lagerhinweise anzeigen</button>`);\n  if(d.shopping.length)list.push(`<button class=\"zAttentionRow158 soon\" onclick=\"openPurchasingOverview('list')\"><span>${zIcon158('cart')}</span><b>${d.shopping.length} Einkaufsposition${d.shopping.length===1?'':'en'} offen</b><small>Einkauf prüfen</small><span class=\"zChevron158\">›</span></button>`);\n  const prepOpen=Math.max(0,d.prep.total-d.prep.done);\n  if(prepOpen)list.push(`<button class=\"zAttentionRow158 soon\" onclick=\"${mobile?\"mobileOpenPlanning('preparation')\":\"openPlanningFromNav('preparation')\"}\"><span>${zIcon158('prep')}</span><b>${prepOpen} Vorbereitungsaufgabe${prepOpen===1?'':'n'} offen</b><small>Aufgaben öffnen</small><span class=\"zChevron158\">›</span></button>`);\n  if(!list.length)list.push('<p class=\"zEmpty158\">Keine dringenden Hinweise.</p>');\n  return list.join('');\n}\nfunction zDesktop158(){\n  const host=document.getElementById('desktopHome123');if(!host)return;\n  const d=zData158(),name=currentAccessUser()?.name||'',hour=new Date().getHours(),hello=hour<11?'Guten Morgen':hour<18?'Guten Tag':'Guten Abend',prepOpen=Math.max(0,d.prep.total-d.prep.done);\n  host.innerHTML=`<div class=\"zDesktop158\"><div class=\"zGreeting158\"><h1>${hello}${name?', '+esc(name):''}</h1></div>${zStatus158(d)}${zEvent158(d.next)}\n    <section class=\"zModules158\" aria-label=\"Arbeitsbereiche\">\n      <button class=\"zModule158\" onclick=\"showTab('stock')\">${zIcon158('stock')}<span>Lagerbestand</span><strong>${d.products}</strong><small>Artikel ansehen</small></button>\n      <button class=\"zModule158 ${d.shopping.length?'notice':''}\" onclick=\"openPurchasingOverview('list')\">${zIcon158('cart')}<span>Bestellvorschläge</span><strong>${d.shopping.length}</strong><small>${d.shopping.length?'Positionen prüfen':'Nichts offen'}</small></button>\n      <button class=\"zModule158 ${prepOpen?'notice':''}\" onclick=\"openPlanningFromNav('preparation')\">${zIcon158('prep')}<span>Vorbereitung</span><strong>${prepOpen}</strong><small>${prepOpen?'Aufgaben offen':'Keine Aufgaben offen'}</small></button>\n      <button class=\"zModule158\" onclick=\"openPlanningFromNav('overview')\">${zIcon158('calendar')}<span>Nächster Einsatz</span><strong class=\"zDate158\">${d.next?esc(formatDate(d.next.date)):'–'}</strong><small>${d.next?esc(d.next.event||'Einsatz'):'Noch keiner geplant'}</small></button>\n    </section><section class=\"zPanel158\"><h2>Direkt öffnen</h2><div class=\"zQuickActions158\">\n      <button onclick=\"homeOpenScan123()\">${zIcon158('search')}<span>Suche</span></button>\n      <button onclick=\"homeCaptureReceipt183()\">${zIcon158('photo')}<span>Bon oder Rechnung fotografieren</span></button>\n      <button onclick=\"openPurchasingOverview('list')\">${zIcon158('cart')}<span>Einkaufsliste</span></button>\n      <button onclick=\"showTab('recipes')\">${zIcon158('prep')}<span>Rezepte</span></button>\n      <button onclick=\"openGoodsCalculator294()\">${zIcon158('prep')}<span>Warenrechner</span></button><button onclick=\"openYield266()\">${zIcon158('prep')}<span>Verschnitt & Ausbeute</span></button><button onclick=\"openNotes123()\">${zIcon158('check')}<span>Notizen</span></button>\n      <button onclick=\"openProductCreate331()\">${zIcon158('stock')}<span>Neues Produkt</span></button>\n    </div></section><section class=\"zPanel158 zAttention158\"><h2>Deine Aufmerksamkeit</h2>${zAttention158(d)}</section></div>`;\n}\nfunction zMobile158(){\n  const el=document.getElementById('mobileTodayContent');if(!el)return;\n  const d=zData158(),c=mobileWorkingPlan()||d.active,prep=c?ensurePreparation(c):null,auto=c?preparationAutoTasks(c):[],manual=prep?.manual||[];\n  if(c!==d.active){d.active=c;d.shopping=c?procurementFor(c):[];d.prep=c?preparationProgress(c):{total:0,done:0,pct:0};d.attention=d.warnings.length+d.shopping.length+Math.max(0,d.prep.total-d.prep.done)}\n  const tasks=[...auto.filter(t=>!prep.done[t.id]).map(t=>({id:t.id,title:t.title||t.label||t.name||'Vorbereitung',manual:false})),...manual.filter(t=>!t.done).map(t=>({id:t.id,title:t.title||t.label||t.name||'Vorbereitung',manual:true}))].slice(0,3);\n  const checks=tasks.length?tasks.map(t=>`<label class=\"zTaskRow158\"><input type=\"checkbox\" aria-label=\"${esc(t.title)} erledigt\" onchange=\"${t.manual?'togglePrepManual':'setPrepDone'}('${escJs(c.id)}','${escJs(t.id)}',this.checked)\"><span>${esc(t.title)}</span></label>`).join(''):'<p class=\"zEmpty158\">Für den gewählten Einsatz sind keine offenen Vorbereitungsaufgaben vorhanden.</p>';\n  const prefs=mobileHomePrefs122();\n  el.innerHTML=`<div class=\"zMobile158\"><h1 class=\"zStartTitle215\">Was möchtest du tun?</h1><button type=\"button\" class=\"zStartSearch215\" onclick=\"startUniversalCapture223()\">${zIcon158('photo')}<span><strong>Fotografieren oder scannen</strong><small>Produkt, Barcode, Bon oder Lieferschein</small></span><span aria-hidden=\"true\">›</span></button>\n    <section class=\"zMobileActions158 zStartMenu215\" aria-label=\"Hauptmenü\">\n      <button onclick=\"mobileShow('search')\">${zIcon158('search')}<strong>Nach Name suchen</strong></button>\n      <button onclick=\"openPurchasingOverview('list')\">${zIcon158('cart')}<strong>Einkaufsliste</strong></button>\n      <button onclick=\"mobileOpenFullTab('recipes')\">${zIcon158('prep')}<strong>Rezepte</strong></button>\n      <button onclick=\"openGoodsCalculator294()\">${zIcon158('prep')}<strong>Warenrechner</strong></button><button onclick=\"openYield266()\">${zIcon158('prep')}<strong>Verschnitt & Ausbeute</strong></button><button onclick=\"openNotes123()\">${zIcon158('check')}<strong>Notizen</strong></button>\n      <button onclick=\"openProductCreate331()\">${zIcon158('stock')}<strong>Neues Produkt</strong></button>\n    </section>${zStatus158(d)}${zEvent158(d.next,true)}<section class=\"zPanel158 zMobileTasks158\"><div class=\"zPanelHead158\"><h2>Heute zu tun</h2><button class=\"soft\" onclick=\"mobileShow('tasks')\">Alle Aufgaben</button></div>${checks}</section>\n    ${d.warnings.length?`<section class=\"zPanel158 zAttention158\"><h2>Deine Aufmerksamkeit</h2>${zAttention158(d,true)}</section>`:''}\n    ${prefs.variant==='c'?`<div class=\"zPersonalImage158\">${prefs.image?`<img src=\"${prefs.image}\" alt=\"Persönliches Startbild\">`:''}<button class=\"soft\" onclick=\"document.getElementById('homeImageInput122').click()\">${prefs.image?'Startbild ändern':'Startbild hinzufügen'}</button>${prefs.image?'<button class=\"soft\" onclick=\"removeMobileHomeImage122()\">Startbild entfernen</button>':''}<input class=\"homeImageInput122\" id=\"homeImageInput122\" type=\"file\" accept=\"image/*\" onchange=\"uploadMobileHomeImage122(this.files[0])\"></div>`:''}</div>`;\n}\nrenderMobileToday=function(){zMobile158()};\nrenderDesktopHome123=function(){\n  const root=document.getElementById('tabDashboard');if(!root)return;\n  root.classList.add('desktopHomeMode123');\n  let host=document.getElementById('desktopHome123');\n  if(!host){host=document.createElement('section');host.id='desktopHome123';host.className='desktopHome123';root.prepend(host)}\n  zDesktop158();\n};\nconst zOldRender158=render;\nrender=function(){const value=zOldRender158();zHeader158();document.querySelectorAll('#tabSettings h1 small').forEach(label=>label.textContent='Version 46');return value};\n\nfunction zHelp158(){\n  const help=document.querySelector('#tabHelp .helpPanel');if(!help||help.dataset.design158)return;\n  help.dataset.design158='true';\n  const faq=help.querySelector('#faq156');if(!faq)return;\n  for(const group of [...faq.querySelectorAll('.faqGroup156')]){\n    const fold=document.createElement('details');fold.className=group.className;\n    const summary=document.createElement('summary');summary.textContent=group.querySelector('h3')?.textContent||'Fragen';fold.appendChild(summary);\n    for(const child of [...group.querySelectorAll('.faqItem156')])fold.appendChild(child);\n    group.replaceWith(fold);\n  }\n  faq.querySelector('#faqSearch156')?.addEventListener('input',()=>{\n    const active=faq.querySelector('#faqSearch156').value.trim();\n    faq.querySelectorAll('.faqGroup156').forEach(fold=>{if(active&&!fold.hidden)fold.open=true});\n  });\n  const route=document.createElement('nav');route.className='zHelpTopics158';route.setAttribute('aria-label','Hilfethemen');\n  route.innerHTML=[...faq.querySelectorAll('.faqGroup156')].map((group,i)=>`<button type=\"button\" class=\"soft\" onclick=\"document.querySelectorAll('#faq156 .faqGroup156')[${i}].open=true;document.querySelectorAll('#faq156 .faqGroup156')[${i}].scrollIntoView({behavior:'smooth',block:'start'})\">${esc(group.querySelector('summary').textContent)}</button>`).join('');\n  faq.insertBefore(route,faq.querySelector('#faqSearch156'));\n}\nzHelp158();zHeader158();zDesktop158();document.querySelectorAll('#tabSettings h1 small').forEach(label=>label.textContent='Version 46');if(matchMedia('(max-width:820px)').matches)renderMobileToday();\n"},{"src":"patch159.js","code":"/* Cloud-Mitglieder werden unabhängig vom gemeinsamen Betriebsdokument verwaltet. */\nlet cloudMembers159=null,cloudMembersBusy159=false;\nconst accessRenderBefore159=renderAccessUserAdmin;\nrenderAccessUserAdmin=function(){\n const cloud=cloudRuntime();\n if(!cloud.enabled)return accessRenderBefore159();\n const host=document.getElementById('accessUserAdmin');if(!host)return;\n const hint=document.querySelector('#accessSettingsCard .accessHint');\n if(hint)hint.innerHTML='<b>Gemeinsame Zugänge:</b> Hier verwaltest du echte Mitarbeiterkonten. Änderungen gelten nach dem nächsten Abgleich auch in der Android-App.';\n const add=document.querySelector('#accessSettingsCard button[onclick=\"addAccessUser()\"] span');\n if(add)add.textContent='Mitarbeiter anlegen';\n if(cloud.user?.role!=='admin'){host.innerHTML='<p class=\"muted\">Nur Administratoren können Mitarbeiter verwalten.</p>';return}\n if(!cloudMembers159){host.innerHTML='<p class=\"muted\">Mitarbeiter werden geladen …</p>';if(!cloudMembersBusy159)loadCloudMembers159();return}\n const count=cloudMembers159.length;\n host.innerHTML=`<div class=\"memberPricing159\"><b>${count} aktive Zugänge</b><span>2 Zugänge enthalten · ab dem 3. Zugang 10,00 € netto je zusätzlichem Zugang und Monat in der offiziellen App.</span><small>Pilotversion: Hier wird keine Zahlung ausgelöst. Eine Abrechnung muss vor dem offiziellen Start gesondert eingerichtet werden.</small></div><div id=\"newCloudMember159\" hidden class=\"accessUserCard\"><h3>Neuen Mitarbeiter einladen</h3><div class=\"accessRole\"><label class=\"field\">Name<input id=\"newMemberName159\" autocomplete=\"name\" placeholder=\"Vor- und Nachname\"></label><label class=\"field\">E-Mail<input id=\"newMemberEmail159\" type=\"email\" autocomplete=\"email\" placeholder=\"name@firma.at\"></label><label class=\"field\">Rolle<select id=\"newMemberRole159\" onchange=\"renderNewMemberPermissions159()\"><option value=\"operator\">Mitarbeiter</option><option value=\"admin\">Administrator</option></select></label></div><div id=\"newMemberPermissions159\" class=\"permissionGrid\"></div><button class=\"primary\" onclick=\"inviteCloudMember159()\">Einladung senden und Zugang anlegen</button><span id=\"inviteStatus159\" role=\"status\"></span></div>${cloudMembers159.map((m,i)=>`<section class=\"accessUserCard\"><div class=\"accessUserHead\"><div><h3>${esc(m.member_name)}</h3><small>${esc(m.email)} · ${m.role==='admin'?'Administrator · alle Rechte':'Mitarbeiter · individuelle Rechte'}</small></div>${m.user_id===cloud.user.id?'<span class=\"badge\">Mein Zugang</span>':`<button class=\"danger\" onclick=\"removeCloudMember159(${i})\">Zugang entziehen</button>`}</div><div class=\"accessRole\"><label class=\"field\">Name<input value=\"${esc(m.member_name)}\" onchange=\"updateCloudMember159(${i},'name',this.value)\"></label><label class=\"field\">E-Mail<input type=\"email\" value=\"${esc(m.email)}\" disabled title=\"Für eine neue Adresse einen neuen Zugang einladen\"></label><label class=\"field\">Rolle<select ${m.user_id===cloud.user.id?'disabled':''} onchange=\"updateCloudMember159(${i},'role',this.value)\"><option value=\"operator\" ${m.role!=='admin'?'selected':''}>Mitarbeiter</option><option value=\"admin\" ${m.role==='admin'?'selected':''}>Administrator</option></select></label></div><div class=\"permissionGrid\">${permissionDefs.map(([key,label])=>`<label class=\"permissionCheck\"><input type=\"checkbox\" ${m.role==='admin'||m.permissions?.[key]?'checked':''} ${m.role==='admin'||key==='users'?'disabled':''} onchange=\"updateCloudMember159(${i},'${key}',this.checked)\">${esc(label)}</label>`).join('')}</div></section>`).join('')}</div>`;\n renderNewMemberPermissions159();\n};\nfunction cloudDefaultPermissions159(){return{today:true,scan:true,stock:true,shopping:true,preparation:true,load:true,setup:true,onsite:true}}\nasync function loadCloudMembers159(){\n if(cloudMembersBusy159||cloudRuntime().user?.role!=='admin')return;\n cloudMembersBusy159=true;\n try{cloudMembers159=await cloudRequest('/rest/v1/rpc/gs_members_access',{p_workspace:cloudConfig().workspace});renderAccessUserAdmin()}\n catch(e){const host=document.getElementById('accessUserAdmin');if(host)host.innerHTML='<p class=\"planWarn\">Mitarbeiter konnten nicht geladen werden: '+esc(e.message)+'</p><button class=\"soft\" onclick=\"loadCloudMembers159()\">Erneut versuchen</button>'}\n finally{cloudMembersBusy159=false}\n}\nfunction renderNewMemberPermissions159(){\n const grid=document.getElementById('newMemberPermissions159');if(!grid)return;\n const admin=document.getElementById('newMemberRole159')?.value==='admin';\n grid.innerHTML=permissionDefs.map(([key,label])=>`<label class=\"permissionCheck\"><input type=\"checkbox\" data-permission159=\"${key}\" ${admin||cloudDefaultPermissions159()[key]?'checked':''} ${admin||key==='users'?'disabled':''}>${esc(label)}</label>`).join('');\n}\nconst addAccessUserBefore159=addAccessUser;\naddAccessUser=function(){\n if(!cloudRuntime().enabled)return addAccessUserBefore159();\n if(cloudRuntime().user?.role!=='admin')return;\n const form=document.getElementById('newCloudMember159');if(!form)return;\n form.hidden=false;form.scrollIntoView({behavior:'smooth',block:'nearest'});form.querySelector('input')?.focus()\n};\nasync function inviteCloudMember159(){\n const form=document.getElementById('newCloudMember159'),status=document.getElementById('inviteStatus159');\n const name=form.querySelector('#newMemberName159').value.trim(),email=form.querySelector('#newMemberEmail159').value.trim(),role=form.querySelector('#newMemberRole159').value;\n const permissions=Object.fromEntries([...form.querySelectorAll('[data-permission159]')].filter(x=>x.checked&&!x.disabled).map(x=>[x.dataset.permission159,true]));\n if(name.length<2||!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)){status.textContent='Bitte Name und E-Mail prüfen.';return}\n const button=form.querySelector('button.primary');button.disabled=true;status.textContent='Zugang wird angelegt …';\n try{\n  const result=await cloudRequest('/functions/v1/gs-invite-member',{workspace:cloudConfig().workspace,name,email,role,permissions});\n  cloudMembers159=null;await loadCloudMembers159();\n  showFeedback(result.invited?'Einladung verschickt':'Zugang freigegeben','success',4500,result.invited?'Die Person legt über den E-Mail-Link ihr Passwort fest.':'Ein bestehendes Konto kann sich mit seinem Passwort anmelden.');\n }catch(e){status.textContent=e.message;showFeedback('Zugang nicht angelegt','error',6000,e.message)}\n finally{button.disabled=false}\n}\nasync function saveMemberChanges159(m,update){\n try{await cloudRequest('/rest/v1/rpc/gs_set_member_access',{p_workspace:cloudConfig().workspace,p_email:m.email,p_name:update.name||m.member_name,p_role:update.role||m.role,p_permissions:update.permissions||m.permissions||{}});cloudMembers159=null;await loadCloudMembers159();showFeedback('Rechte gespeichert','success')}\n catch(e){cloudMembers159=null;await loadCloudMembers159();showFeedback('Rechte nicht gespeichert','error',6000,e.message)}\n}\nfunction updateCloudMember159(index,key,value){\n const member=cloudMembers159?.[index];if(!member)return;\n const update=key==='name'?{name:String(value).trim()}:key==='role'?{role:value}:{permissions:{...member.permissions,[key]:!!value}};\n if(key==='role'&&value==='admin'&&member.role!=='admin'){askConfirm('Administratoren haben Zugriff auf alle Betriebsdaten und können Mitarbeiterrechte ändern. Diesen Zugang wirklich zum Administrator machen?',()=>saveMemberChanges159(member,update),'Adminrechte vergeben','Zugriffsrechte');return}\n saveMemberChanges159(member,update)\n}\nfunction removeCloudMember159(index){\n const m=cloudMembers159?.[index];if(!m||m.user_id===cloudRuntime().user?.id)return;\n askDeleteConfirm('Zugang von „'+m.member_name+'“ entziehen?',async()=>{try{await cloudRequest('/rest/v1/rpc/gs_remove_member',{p_workspace:cloudConfig().workspace,p_user:m.user_id});cloudMembers159=null;await loadCloudMembers159();showFeedback('Zugang entzogen','success')}catch(e){showFeedback('Zugang nicht entzogen','error',6000,e.message)}},'Zugang entziehen')\n}\nconst checkCloudUpdatesBefore159=checkCloudUpdates;\nconst applyPermissionsUIBefore159=applyPermissionsUI;\napplyPermissionsUI=function(){applyPermissionsUIBefore159();const today=document.getElementById('tabDashboardBtn');if(today)today.style.display=hasPermission('today')?'':'none'};\nconst showTabBefore159=showTab;\nshowTab=function(which,skipPermission=false){if(which==='dashboard'&&!skipPermission&&!guardPermission('today','Heute'))return;return showTabBefore159(which,skipPermission)};\ncheckCloudUpdates=async function(){\n const c=cloudRuntime();if(!c.enabled||!c.user||c.busy||c.pending||c.blocked)return;\n try{const info=await cloudRequest('/rest/v1/rpc/gs_revision',{p_workspace:cloudConfig().workspace});\n if(info.role!==c.user.role||JSON.stringify(info.permissions)!==JSON.stringify(c.user.permissions)){c.user.role=info.role;c.user.permissions=info.permissions||{};applyPermissionsUI();if(!cloudUiBusy119())await cloudPullSilent119();else {c.status='Zugriffsrechte geändert · Eingabe abschließen';updateCloudStatus()}return}\n if(info.revision!==c.revision){if(!cloudUiBusy119()){await cloudPullSilent119();return}c.status='Neue Änderung wartet · Eingabe abschließen';updateCloudStatus()}\n }catch(e){if(/Kein Zugriff/.test(e.message)){c.user=null;c.token='';c.refresh='';clearCloudSession119();document.body.classList.add('cloudLocked92');openCloudSettings();return}c.status='Verbindung unterbrochen · nächster Versuch folgt';updateCloudStatus()}\n};\n\nconst openCloudAdminBefore159=openCloudAdmin;\nopenCloudAdmin=async function(){\n await openCloudAdminBefore159();\n const form=document.getElementById('memberEmail92')?.closest('.opsGrid92');\n if(form){const heading=form.previousElementSibling?.previousElementSibling;const note=form.previousElementSibling;const button=form.nextElementSibling;heading?.remove();note?.remove();button?.remove();form.remove();const body=document.querySelector('#opsDialog92 .opsBody92');body?.insertAdjacentHTML('afterbegin','<p>Mitarbeiter und ihre Rechte verwaltest du in <b>Einstellungen → Benutzer & Zugriffsrechte</b>.</p>')}\n};\n"},{"src":"patch160.js","code":"/* Echte Berechtigungsfehler von Versionskonflikten unterscheiden. */\nflushCloudSave=async function(){\n const c=cloudRuntime();if(c.busy||!c.pending||c.blocked)return;\n c.busy=true;let task=null;\n try{\n  while(c.pending&&!c.blocked){\n   task=c.pending;const firstUpload=c.base===null;c.pending=null;\n   const result=await cloudRequest('/rest/v1/rpc/gs_write',{p_workspace:cloudConfig().workspace,p_revision:c.revision,p_data:task.data,p_action:task.message});\n   c.revision=result.revision;c.base=task.data;c.permissionDenied160='';c.failedTask160=null;\n   if(firstUpload)finishCloudLogin();\n   c.status='Gemeinsam gespeichert · '+new Date().toLocaleTimeString('de-AT');updateCloudStatus();\n  }\n  showFeedback('Gemeinsam gespeichert','success');\n }catch(error){\n  const message=String(error.message||'Unbekannter Fehler');\n  if(/Rechte fehlen für:|Diese Änderung ist für deinen Zugang nicht freigegeben|Diese Änderung benötigt einen Administrator|Keine Schreibberechtigung/.test(message)){\n   c.failedTask160=c.pending||task;c.pending=null;c.permissionDenied160=message;c.blocked=true;\n   c.status='Nicht gespeichert · '+message;\n   showFeedback('Zugriff prüfen','error',9000,message+' Die Eingabe bleibt auf diesem Gerät gesichert.');\n  }else{\n   c.blocked=true;c.status='Nicht abgeglichen: '+message;\n   showFeedback('Abgleich angehalten','error',7000,'Änderung ist nur lokal. Gemeinsamen Stand prüfen. '+message);\n  }\n  updateCloudStatus();\n }finally{c.busy=false;if(typeof moveSensitiveToSession139==='function')moveSensitiveToSession139()}\n};\nasync function retryCloudWrite160(){\n const c=cloudRuntime();if(!c.user||!c.failedTask160||c.busy)return;\n try{\n  const info=await cloudRequest('/rest/v1/rpc/gs_revision',{p_workspace:cloudConfig().workspace});\n  if(info.revision!==c.revision){c.permissionDenied160='';c.status='Neuer gemeinsamer Datenstand vorhanden';updateCloudStatus();showFeedback('Änderungen prüfen','warn',5000,'Während der Eingabe wurde auf einem anderen Gerät gespeichert.');return}\n  c.user.permissions=info.permissions||{};c.user.role=info.role||c.user.role;\n  c.pending=c.failedTask160;c.failedTask160=null;c.permissionDenied160='';c.blocked=false;\n  applyPermissionsUI();await flushCloudSave();\n }catch(e){showFeedback('Erneuter Versuch fehlgeschlagen','error',6000,e.message)}\n}\nconst updateCloudStatusBefore160=updateCloudStatus;\nupdateCloudStatus=function(){\n updateCloudStatusBefore160();\n const c=cloudRuntime(),bar=document.getElementById('mobileSync102');\n if(!bar||!c.permissionDenied160)return;\n const label=bar.querySelector('span');if(label)label.textContent='Auf diesem Gerät gespeichert · Berechtigung zum Senden fehlt';\n const button=bar.querySelector('button');if(button){button.textContent='Erneut versuchen';button.onclick=retryCloudWrite160}\n};\nconst persistStateBefore160=persistState;\npersistState=function(...args){\n const c=cloudRuntime();\n if(c.enabled&&c.blocked&&c.permissionDenied160){showFeedback('Änderung noch nicht gespeichert','error',6500,c.permissionDenied160+' Bitte zuerst „Erneut versuchen“ wählen.');return false}\n return persistStateBefore160(...args)\n};\nlet permissionCheckBusy160=false;\nsetInterval(async()=>{\n const c=cloudRuntime();\n if(permissionCheckBusy160||!c.permissionDenied160||!c.failedTask160||!c.user||c.busy)return;\n permissionCheckBusy160=true;\n try{\n  const info=await cloudRequest('/rest/v1/rpc/gs_revision',{p_workspace:cloudConfig().workspace});\n  if(info.revision===c.revision&&JSON.stringify(info.permissions)!==JSON.stringify(c.user.permissions))await retryCloudWrite160();\n }catch{}\n finally{permissionCheckBusy160=false}\n},6000);\n"},{"src":"patch161.js","code":"/* Eigene Berechtigung für Wareneingänge; 'scan' bleibt für andere Lagerbuchungen. */\npermissionDefs.splice(3,0,['stockAdd','Wareneingang / Bestand erhöhen']);\nconst defaultPermissionsBefore161=cloudDefaultPermissions159;\ncloudDefaultPermissions159=function(){return {...defaultPermissionsBefore161(),stockAdd:true}};\nconst openModalBefore161=openModal;\nopenModal=function(kind){if(kind==='in'&&!guardPermission('stockAdd','Wareneingang'))return;return openModalBefore161(kind)};\nconst zBookBefore161=zBook158;\nzBook158=function(kind){if(kind==='in'&&!guardPermission('stockAdd','Wareneingang'))return;return zBookBefore161(kind)};\nconst savePhotoBefore161=savePhoto144;\nsavePhoto144=function(...args){if(!guardPermission('stockAdd','Wareneingang per Foto'))return;return savePhotoBefore161(...args)};\nconst receivePurchaseBefore161=receivePurchaseOrder;\nreceivePurchaseOrder=function(...args){if(!guardPermission('stockAdd','Lieferung einbuchen'))return;return receivePurchaseBefore161(...args)};\nconst receiptBefore161=confirmShoppingReceipt;\nconfirmShoppingReceipt=function(...args){if(!guardPermission('stockAdd','Einkauf ins Lager übernehmen'))return;return receiptBefore161(...args)};\nconst applyPermissionsBefore161=applyPermissionsUI;\napplyPermissionsUI=function(){\n applyPermissionsBefore161();\n for(const button of document.querySelectorAll('button[onclick]')){\n  const action=button.getAttribute('onclick')||'';\n  if(action.includes(\"zBook158('in')\")||action.includes(\"openModal('in')\"))button.style.display=hasPermission('stockAdd')?'':'none';\n }\n};\nrenderAccessUserAdmin();\n"},{"src":"patch162.js?v=240","code":"/* Gemeinsame Startansicht, eindeutige Buchung und persönliches Konto. */\nlet zBookingChoice162='';\nconst zOriginalQuickBook162=zBook158;\nzBook158=function(kind){\n  if(kind==='in'&&!guardPermission('stockAdd','Wareneingang'))return;\n  if(kind!=='in'&&!guardPermission('scan','Entnahme'))return;\n  zBookingChoice162=kind;\n  if(matchMedia('(max-width:820px)').matches){\n    mobileCategory103='';mobileProductChosen=false;mobileShow('search');\n    const input=document.getElementById('mobileArticleSearch');\n    if(input){input.value='';renderMobileSearchResults('');input.focus()}\n  }else{\n    showTab('lager',true);\n    const input=document.getElementById('barcode');\n    if(input){input.value='';input.focus()}\n  }\n  showFeedback(kind==='in'?'Artikel für Wareneingang auswählen':'Artikel für Entnahme auswählen','info',4500,'Artikel suchen oder scannen und anschließend die Buchung starten.');\n};\nconst zOriginalSelect162=mobileSelectProduct;\nmobileSelectProduct=function(code,pack){\n  const result=zOriginalSelect162(code,pack);\n  if(zBookingChoice162&&state.products?.[code]){\n    const kind=zBookingChoice162;zBookingChoice162='';\n    openModal(kind);\n  }\n  return result;\n};\nconst zOriginalModal162=openModal;\nopenModal=function(kind){\n  if(kind==='in'&&!guardPermission('stockAdd','Wareneingang'))return;\n  zBookingChoice162='';\n  const result=zOriginalModal162(kind);\n  const modal=document.getElementById('modal'),title=document.getElementById('mtitle'),item=state.products?.[current];\n  if(modal?.style.display==='flex'&&item&&title){\n    title.textContent=kind==='in'?'Wareneingang':title.textContent;\n    let label=document.getElementById('zBookingArticle162');\n    if(!label){label=document.createElement('div');label.id='zBookingArticle162';label.className='zBookingArticle162';title.closest('.modalHeader').after(label)}\n    label.innerHTML='<strong>'+esc(item.name)+'</strong><small>'+esc(current)+' · Aktueller Bestand: '+esc(formatQtySmart(Number(item.stock)||0,unitText(item)))+'</small>';\n  }\n  return result;\n};\nconst zOriginalHeader162=zHeader158;\nzHeader158=function(){\n  zOriginalHeader162();\n  const brand=document.querySelector('.appMain>.topbar .topbarBrand');\n  if(brand){\n    brand.setAttribute('role','button');brand.setAttribute('tabindex','0');\n    brand.setAttribute('aria-label','Zur Startseite');\n    brand.onclick=zGoHome162;\n    brand.onkeydown=e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();zGoHome162()}};\n  }\n  const side=document.querySelector('.sidebarBrand');\n  if(side){\n    side.setAttribute('role','button');side.setAttribute('tabindex','0');\n    side.setAttribute('aria-label','Zur Startseite');side.onclick=zGoHome162;\n    side.onkeydown=e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();zGoHome162()}};\n  }\n  const avatar=document.querySelector('.zHeaderInfo158 .zAvatar158');\n  if(!avatar)return;\n  let button=document.getElementById('zAccountButton162');\n  if(!button){\n    button=document.createElement('button');button.type='button';button.id='zAccountButton162';button.className='zAccountButton162';\n    button.setAttribute('aria-label','Benutzerkonto öffnen');button.setAttribute('aria-haspopup','menu');\n    button.setAttribute('aria-expanded','false');button.onclick=zToggleAccount162;\n  }\n  avatar.replaceWith(button);\n  button.textContent=avatar.textContent;\n  let menu=document.getElementById('zAccountMenu162');\n  if(!menu){\n    menu=document.createElement('div');menu.id='zAccountMenu162';menu.className='zAccountMenu162';\n    menu.setAttribute('role','menu');menu.hidden=true;\n    document.querySelector('.appMain>.topbar').append(menu);\n  }\n  menu.innerHTML='<strong>'+esc(currentAccessUser()?.name||'Benutzer')+'</strong><small>'+esc(cloudRuntime().user?.email||'')+'</small><button type=\"button\" role=\"menuitem\" onclick=\"zOpenSettings240()\">Einstellungen</button><button type=\"button\" role=\"menuitem\" onclick=\"zLogout162()\">Ausloggen</button>';\n};\nfunction zToggleAccount162(){\n  const menu=document.getElementById('zAccountMenu162'),button=document.getElementById('zAccountButton162');\n  if(!menu)return;menu.hidden=!menu.hidden;button?.setAttribute('aria-expanded',String(!menu.hidden));\n}\nasync function zLogout162(){\n  document.getElementById('zAccountMenu162').hidden=true;\n  document.getElementById('zAccountButton162')?.setAttribute('aria-expanded','false');\n  if(cloudRuntime().user)await logoutCloud();\n  else showFeedback('Kein Konto angemeldet','info',3000);\n}\nfunction zGoHome162(){\n  zBookingChoice162='';\n  document.getElementById('zAccountMenu162')?.setAttribute('hidden','');\n  for(const modal of document.querySelectorAll('.modal'))modal.style.display='none';\n  if(matchMedia('(max-width:820px)').matches)mobileShow('today');\n  else showTab('dashboard',true);\n  closeSidebar();window.scrollTo({top:0,behavior:'smooth'});\n}\ndocument.addEventListener('click',e=>{\n  const menu=document.getElementById('zAccountMenu162'),button=document.getElementById('zAccountButton162');\n  if(menu&&!menu.hidden&&!menu.contains(e.target)&&!button?.contains(e.target)){\n    menu.hidden=true;button?.setAttribute('aria-expanded','false');\n  }\n});\ndocument.addEventListener('keydown',e=>{if(e.key==='Escape'){const menu=document.getElementById('zAccountMenu162');if(menu&&!menu.hidden){menu.hidden=true;document.getElementById('zAccountButton162')?.focus()}}});\n\nconst zApplyPermissions162=applyPermissionsUI;\napplyPermissionsUI=function(){\n  zApplyPermissions162();\n  const create=document.querySelector('.mobileAdd118');\n  if(create)create.hidden=!canCreateArticles();\n  const access=document.getElementById('accessSettingsCard');\n  if(access&&cloudRuntime().enabled)access.style.display=cloudRuntime().user?.role==='admin'?'':'none';\n  for(const button of document.querySelectorAll('.mobileProductActions button')){\n    const action=button.getAttribute('onclick')||'';\n    if(action.includes(\"openModal('in')\"))button.hidden=!hasPermission('stockAdd');\n    else if(/openModal\\(|openTrude\\(/.test(action))button.hidden=!hasPermission('scan');\n  }\n};\nconst zRenderMobileSelected162=renderMobileSelectedProduct;\nrenderMobileSelectedProduct=function(){zRenderMobileSelected162();applyPermissionsUI()};\nconst zOriginalRender162=render;\nrender=function(){\n  const result=zOriginalRender162();applyPermissionsUI();\n  document.querySelectorAll('#tabSettings h1 small').forEach(label=>label.textContent='Version 55');\n  return result\n};\nconst zOriginalResume162=resumeCloudSession119;\nresumeCloudSession119=async function(){\n  try{return await zOriginalResume162()}\n  finally{requestAnimationFrame(()=>document.body.classList.add('zBootReady162'))}\n};\nzHeader158();\ndocument.querySelectorAll('#tabSettings h1 small').forEach(label=>label.textContent='Version 55');\nif(!cloudRuntime().enabled||cloudRuntime().user)requestAnimationFrame(()=>document.body.classList.add('zBootReady162'));\nsetTimeout(()=>document.body.classList.add('zBootReady162'),7000);\n"},{"src":"patch163.js","code":"/* Administratoren öffnen auf dem Handy die vollständige, responsive Webansicht. */\nconst zHeaderBefore163=zHeader158;\nzHeader158=function(){\n  zHeaderBefore163();\n  const menu=document.getElementById('zAccountMenu162');\n  const admin=cloudRuntime().user?.role==='admin'||(!cloudRuntime().enabled&&currentAccessUser().role==='admin');\n  if(!menu)return;\n  const mobile=matchMedia('(max-width:820px)').matches;\n  if(!admin){\n    document.body.classList.remove('zWebView163');\n    sessionStorage.removeItem('zapasio_admin_webview163');\n    return;\n  }\n  if(!mobile)return;\n  const active=document.body.classList.contains('zWebView163');\n  const action=document.createElement('button');\n  action.type='button';action.setAttribute('role','menuitem');\n  action.textContent=active?'Zur Handyansicht':'Vollständige Webansicht';\n  action.onclick=()=>zSwitchWebView163(!active);\n  menu.insertBefore(action,menu.querySelector('button[onclick=\"zLogout162()\"]'));\n};\nfunction zSwitchWebView163(active){\n  if(!guardPermission('users','die vollständige Webansicht'))return;\n  sessionStorage.setItem('zapasio_admin_webview163',active?'1':'0');\n  document.body.classList.toggle('zWebView163',active);\n  document.getElementById('zAccountMenu162')?.setAttribute('hidden','');\n  if(active){\n    document.body.classList.add('mobile-detail-mode');\n    showTab('dashboard',true);\n  }else{\n    mobileShow('today');\n  }\n  closeSidebar();\n  zHeader158();\n  window.scrollTo({top:0,behavior:'smooth'});\n}\nconst zGoHomeBefore163=zGoHome162;\nzGoHome162=function(){\n  if(document.body.classList.contains('zWebView163')){\n    for(const modal of document.querySelectorAll('.modal'))modal.style.display='none';\n    document.getElementById('zAccountMenu162')?.setAttribute('hidden','');\n    document.body.classList.add('mobile-detail-mode');\n    showTab('dashboard',true);\n    closeSidebar();window.scrollTo({top:0,behavior:'smooth'});\n    return;\n  }\n  return zGoHomeBefore163();\n};\nconst zResumeBefore163=resumeCloudSession119;\nresumeCloudSession119=async function(){\n  const result=await zResumeBefore163();\n  if(result&&cloudRuntime().user?.role==='admin'&&sessionStorage.getItem('zapasio_admin_webview163')==='1'&&matchMedia('(max-width:820px)').matches){\n    document.body.classList.add('zWebView163','mobile-detail-mode');\n    showTab('dashboard',true);zHeader158();\n  }\n  return result;\n};\nconst zRenderBefore163=render;\nrender=function(){\n  const result=zRenderBefore163();\n  document.querySelectorAll('#tabSettings h1 small').forEach(x=>x.textContent='Version 56');\n  return result;\n};\nzHeader158();\ndocument.querySelectorAll('#tabSettings h1 small').forEach(x=>x.textContent='Version 56');\n"},{"src":"patch164.js","code":"/* Kochbuch getrennt ansehen und bearbeiten. Die Datenbank prüft Schreibrechte separat. */\nconst recipePermissionIndex164=permissionDefs.findIndex(([key])=>key==='recipes');\npermissionDefs.splice(recipePermissionIndex164,1,\n  ['recipeView','Rezepte ansehen'],['recipes','Rezepte bearbeiten']);\nfunction canViewRecipes164(){return hasPermission('recipeView')||hasPermission('recipes')}\nfunction canEditRecipes164(){return hasPermission('recipes')}\nfunction recipeEditGuard164(){return guardPermission('recipes','Rezepte bearbeiten')}\n\nconst recipeEditorBefore164=recipeEditorHtml;\nrecipeEditorHtml=function(group,recipe,gi,ri){\n  if(canEditRecipes164())return recipeEditorBefore164(group,recipe,gi,ri);\n  const rows=(recipe.ingredients||[]).map(ingredient=>{\n    const product=state.products?.[ingredient.productCode];\n    const prepared=ingredient.recipeId?findRecipeById(ingredient.recipeId)?.recipe:null;\n    return {name:ingredient.name||product?.name||prepared?.name||'Zutat',\n      quantity:formatQtySmart(Number(ingredient.qty)||0,ingredient.unit||''),\n      packaging:ingredient.kind==='packaging'};\n  });\n  const list=(items)=>items.length?\n    '<ul class=\"recipeReadList164\">'+items.map(item=>\n      '<li><span>'+esc(item.name)+'</span><strong>'+esc(item.quantity)+'</strong></li>').join('')+'</ul>':\n    '<p class=\"muted\">Keine Einträge vorhanden.</p>';\n  const portion=recipePortioning108(recipe)?'<p>'+recipePortionText108(recipe)+'</p>':'';\n  return '<div class=\"recipeReadonly164\" aria-label=\"Rezept nur ansehen\">'+\n    '<p class=\"recipeReadNotice164\">Nur ansehen · Bearbeitung ist für diesen Zugang nicht freigegeben.</p>'+\n    '<section><h4>Fertige Menge</h4><p>'+esc(formatQtySmart(Number(recipe.yieldQty)||1,recipe.yieldUnit||'Portionen'))+'</p>'+portion+'</section>'+\n    '<section><h4>Zutaten</h4>'+list(rows.filter(x=>!x.packaging))+'</section>'+\n    (recipe.toGoEnabled?'<section><h4>Verpackung</h4>'+list(rows.filter(x=>x.packaging))+'</section>':'')+\n    '</div>';\n};\nconst renderRecipesBefore164=renderRecipes;\nrenderRecipes=function(){\n  renderRecipesBefore164();\n  const root=document.getElementById('tabRecipes');\n  if(!root)return;\n  const edit=canEditRecipes164();\n  root.querySelectorAll('.recipeAddRow, #recipeAdmin .recipeBody>button, .cookbookTools button[onclick=\"addRecipeGroup()\"]')\n    .forEach(button=>button.hidden=!edit);\n  const intro=root.querySelector('.pageHeader p');\n  if(intro)intro.textContent=edit?'Rezepturen, Unterrezepte und Zutaten einfach pflegen.':'Rezepturen und Zutaten ansehen.';\n};\nconst showTabBefore164=showTab;\nshowTab=function(which,skipPermission=false){\n  if(which==='recipes'&&!skipPermission){\n    if(!canViewRecipes164())return guardPermission('recipeView','Rezepte ansehen');\n    return showTabBefore164(which,true);\n  }\n  return showTabBefore164(which,skipPermission);\n};\nconst permissionsBefore164=applyPermissionsUI;\napplyPermissionsUI=function(){\n  permissionsBefore164();\n  const visible=canViewRecipes164();\n  const nav=document.getElementById('tabRecipesBtn');\n  if(nav)nav.style.display=visible?'':'none';\n  const group=document.querySelector('#customSideNav .zNavGroup155[data-group=\"recipes\"]>.zNavToggle155');\n  if(group)group.style.display=visible?'':'none';\n  const mobile=document.querySelector('.mobileMoreGrid button[onclick=\"mobileOpenFullTab(\\'recipes\\')\"]');\n  if(mobile)mobile.style.display=visible?'':'none';\n};\nconst userFoldsOpen164=new Set();\nconst renderAccessBefore164=renderAccessUserAdmin;\nrenderAccessUserAdmin=function(){\n  renderAccessBefore164();\n  const host=document.getElementById('accessUserAdmin');\n  if(!host)return;\n  [...host.querySelectorAll(':scope > section.accessUserCard')].forEach((card,index)=>{\n    const head=card.querySelector('.accessUserHead');\n    const name=head?.querySelector('h3')?.textContent||'Mitarbeiter';\n    const description=head?.querySelector('small')?.textContent||'Zugriff verwalten';\n    const id=cloudRuntime().enabled?\n      (cloudMembers159?.[index]?.user_id||'cloud-'+index):\n      (state.accessUsers?.[index]?.id||'local-'+index);\n    const fold=document.createElement('details');\n    fold.className='accessUserCard userFold164';fold.dataset.user=id;\n    fold.open=userFoldsOpen164.has(id);\n    const summary=document.createElement('summary');\n    summary.innerHTML='<span><b>'+esc(name)+'</b><small>'+esc(description)+'</small></span><span class=\"foldMark164\" aria-hidden=\"true\"></span>';\n    const body=document.createElement('div');body.className='userFoldBody164';\n    card.classList.remove('accessUserCard');\n    body.append(card);\n    fold.append(summary,body);\n    fold.addEventListener('toggle',()=>fold.open?userFoldsOpen164.add(id):userFoldsOpen164.delete(id));\n    host.appendChild(fold);\n  });\n};\nfor(const functionName of [\n  'addRecipeFromInput','addRecipe','renameRecipe','removeRecipe',\n  'addRecipeGroup','renameRecipeGroup','addIngredientRow','removeIngredientRow',\n  'scheduleRecipeAutosave','saveRecipeDetails','collectRecipeEditorAt',\n  'changeRecipeProduct','changeRecipeVariant','toggleRecipeToGo','addPackagingRow'\n]){\n  const original=globalThis[functionName];\n  if(typeof original!=='function')continue;\n  globalThis[functionName]=function(...args){\n    if(!recipeEditGuard164())return;\n    return original(...args);\n  };\n}\nconst renderBefore164=render;\nrender=function(){\n  const result=renderBefore164();\n  document.querySelectorAll('#tabSettings h1 small').forEach(label=>label.textContent='Version 57');\n  return result;\n};\ndocument.querySelectorAll('#tabSettings h1 small').forEach(label=>label.textContent='Version 57');\napplyPermissionsUI();\nrenderAccessUserAdmin();\n"},{"src":"patch165.js","code":"/* TomTom über authentifizierte Serverfunktion; Adressen in der Einsatzplanung vorschlagen. */\nasync function tomtomRequest165(action,details={}){\n  if(!cloudRuntime().enabled||!cloudRuntime().user)throw Error('Für Adressvorschläge bitte zuerst anmelden.');\n  return cloudRequest('/functions/v1/gs-tomtom',{\n    workspace:cloudConfig().workspace,action,...details\n  });\n}\nconst addressDebounce165=new WeakMap();\nconst addressSequence165=new WeakMap();\nconst addressCache165=new Map();\nfunction closeAddressOptions165(input){\n  const list=document.getElementById(input.getAttribute('aria-controls')||'');\n  if(list)list.hidden=true;\n  input.setAttribute('aria-expanded','false');\n}\nfunction addressChoices165(input,rows,message=''){\n  const list=document.getElementById(input.getAttribute('aria-controls')||'');\n  if(!list)return;\n  list.replaceChildren();\n  if(message){\n    const note=document.createElement('div');note.className='addressInfo165';note.textContent=message;list.append(note);\n  }else if(!rows.length){\n    const note=document.createElement('div');note.className='addressInfo165';note.textContent='Keine passende Adresse gefunden. Du kannst die Adresse selbst eingeben.';list.append(note);\n  }else{\n    for(const item of rows){\n      const option=document.createElement('button');option.type='button';\n      option.setAttribute('role','option');option.className='addressOption165';\n      option.textContent=item.label;\n      option.addEventListener('mousedown',event=>event.preventDefault());\n      option.addEventListener('click',()=>{\n        input.value=item.label;\n        closeAddressOptions165(input);\n        if(input.id==='businessStartLocation')input.dispatchEvent(new Event('change',{bubbles:true}));\n        else{routeResult148=null;updateCateringPreview()}\n        input.focus();\n      });\n      list.append(option);\n    }\n  }\n  list.hidden=false;input.setAttribute('aria-expanded','true');\n}\nasync function searchAddress165(input,query,sequence){\n  try{\n    const cached=addressCache165.get(query.toLocaleLowerCase('de'));\n    const result=cached&&Date.now()-cached.time<120000?cached.result:\n      await tomtomRequest165('search',{query});\n    if(!cached||Date.now()-cached.time>=120000)\n      addressCache165.set(query.toLocaleLowerCase('de'),{time:Date.now(),result});\n    if(addressSequence165.get(input)!==sequence||input.value.trim()!==query)return;\n    addressChoices165(input,result.results||[]);\n  }catch(error){\n    if(addressSequence165.get(input)!==sequence||input.value.trim()!==query)return;\n    addressChoices165(input,[],error.message||'Adresssuche vorübergehend nicht erreichbar.');\n  }\n}\nfunction installAddressSuggestions165(input){\n  if(!input||input.dataset.tomtomBound165)return;\n  input.dataset.tomtomBound165='true';\n  input.setAttribute('autocomplete','off');\n  input.setAttribute('role','combobox');\n  input.setAttribute('aria-autocomplete','list');\n  input.setAttribute('aria-expanded','false');\n  const list=document.createElement('div');list.id='addressList165_'+input.id;\n  list.className='addressList165';list.setAttribute('role','listbox');list.hidden=true;\n  input.setAttribute('aria-controls',list.id);\n  input.parentElement.appendChild(list);\n  input.placeholder=input.id==='planAddress'?'PLZ, Ort oder Straße eingeben':'PLZ, Ort oder Startadresse eingeben';\n  input.addEventListener('input',()=>{\n    if(input.id==='planAddress')routeResult148=null;\n    const query=input.value.trim(),sequence=(addressSequence165.get(input)||0)+1;\n    addressSequence165.set(input,sequence);\n    clearTimeout(addressDebounce165.get(input));\n    if(query.length<2||/^\\d+$/.test(query)&&query.length<3){\n      closeAddressOptions165(input);return;\n    }\n    addressDebounce165.set(input,setTimeout(()=>searchAddress165(input,query,sequence),400));\n  });\n  input.addEventListener('keydown',event=>{\n    if(event.key==='Escape'){closeAddressOptions165(input);return}\n    const options=[...list.querySelectorAll('button.addressOption165')];\n    if(list.hidden||!options.length)return;\n    if(event.key==='ArrowDown'||event.key==='ArrowUp'){\n      event.preventDefault();(event.key==='ArrowDown'?options[0]:options.at(-1)).focus();\n    }\n  });\n  list.addEventListener('keydown',event=>{\n    const buttons=[...list.querySelectorAll('button.addressOption165')];\n    const index=buttons.indexOf(document.activeElement);\n    if(event.key==='Escape'){closeAddressOptions165(input);input.focus()}\n    else if(event.key==='ArrowDown'||event.key==='ArrowUp'){\n      event.preventDefault();(buttons[(index+(event.key==='ArrowDown'?1:buttons.length-1))%buttons.length]||input).focus();\n    }\n  });\n  input.addEventListener('blur',()=>setTimeout(()=>{\n    if(!list.contains(document.activeElement))closeAddressOptions165(input);\n  },160));\n}\nconst routeControlsBefore165=renderRouteControls148;\nrenderRouteControls148=function(){\n  routeControlsBefore165();\n  installAddressSuggestions165(document.getElementById('planAddress'));\n  installAddressSuggestions165(document.getElementById('businessStartLocation'));\n  const status=document.getElementById('routeStatus148');\n  if(status&&!status.dataset.copy165){\n    status.dataset.copy165='true';\n    status.textContent='Start- und Zieladresse auswählen, Datum und Ankunftszeit eingeben. Die Fahrzeit wird erst nach Tippen auf „Berechnen“ abgefragt.';\n  }\n  const label=document.querySelector('#routeKey148 label');\n  if(label)label.textContent='TomTom API-Schlüssel (optional, nur diese Sitzung)';\n  const hint=document.getElementById('routeKeyStatus148');\n  if(hint&&!cloudRuntime().user)\n    hint.textContent=routeKey148()?'Schlüssel für diese Sitzung bereit.':'Nach der Anmeldung wird der zentrale TomTom-Zugang geprüft.';\n  if(hint&&cloudRuntime().user&&hint.dataset.central165!==String(cloudRuntime().user.id)){\n    hint.dataset.central165=String(cloudRuntime().user.id);\n    hint.textContent=routeKey148()?'Schlüssel für diese Sitzung bereit.':'Zentraler TomTom-Zugang wird geprüft …';\n    tomtomRequest165('status').then(response=>{\n      if(!hint.isConnected)return;\n      hint.textContent=response.configured?'Zentraler TomTom-Zugang aktiv. Kein Schlüssel auf diesem Gerät nötig.':\n        routeKey148()?'Schlüssel für diese Sitzung bereit.':'Noch kein zentraler Zugang: Hier einen TomTom API-Schlüssel für diese Sitzung eingeben.';\n    }).catch(()=>{if(hint.isConnected)hint.textContent=routeKey148()?\n      'Schlüssel für diese Sitzung bereit.':'Zentraler TomTom-Zugang noch nicht eingerichtet.'});\n  }\n};\nconst routeKeyBefore165=setRouteKey148;\nsetRouteKey148=function(input){routeKeyBefore165(input);const hint=document.getElementById('routeKeyStatus148');if(hint)hint.textContent=routeKey148()?'Schlüssel für diese Sitzung bereit.':'Sitzungsschlüssel entfernt.'};\nconst calculateRouteBefore165=calculateRoute148;\ncalculateRoute148=async function(){\n  if(!cloudRuntime().enabled||!cloudRuntime().user)return calculateRouteBefore165();\n  const from=String(typeof routeStart172==='function'?routeStart172():state.businessSettings?.startLocation||'').trim();\n  const to=String(document.getElementById('planAddress')?.value||'').trim();\n  if(!/\\d/.test(from)||from.length<9||!/\\d/.test(to)||to.length<9)\n    return showFeedback('Adressen ergänzen','warn',5500,'Für die Fahrzeit Start und Ziel als vollständige Adresse mit Straße, Hausnummer und Ort auswählen.');\n  const draft=cateringDraftObject(),timing=timePlanFor(draft,true);\n  if(!timing)return showFeedback('Zeitangaben fehlen','warn',4500,'Einsatzdatum und „Ausgabe bereit ab“ eingeben.');\n  const target=new Date(timing.arrival.getTime()-timing.buffer*60000);\n  const button=document.querySelector('#routeTools148 button');\n  if(button)button.disabled=true;\n  const previous=routeResult148;\n  routeStatus148('TomTom prüft Adressen und Verkehrslage …');\n  try{\n    const result=await tomtomRequest165('route',{from,to,arriveAt:routeLocalIso148(target)});\n    const summary=result.summary,minutes=Math.ceil(Number(summary?.travelTimeInSeconds)/60);\n    if(!Number.isFinite(minutes)||minutes<=0||!summary?.departureTime)throw Error('Route konnte nicht berechnet werden.');\n    const input=document.getElementById('planTravelMinutes');\n    if(!input||input.value!==String(draft.travelMinutes||'')||\n      document.getElementById('planAddress')?.value.trim()!==to||\n      document.getElementById('planDate')?.value!==draft.date||\n      document.getElementById('planReadyTime')?.value!==draft.readyTime||\n      (typeof routeStart172==='function'?routeStart172():state.businessSettings?.startLocation)!==from)\n      throw Error('Einsatz während der Berechnung geändert. Bitte erneut berechnen.');\n    input.value=minutes;document.getElementById('planTravelConfirmed').checked=true;\n    routeResult148={from,to,date:draft.date,readyTime:draft.readyTime,\n      travelMinutes:minutes,checkedAt:new Date().toISOString(),source:'TomTom',\n      origin:result.origin,destination:result.destination,departureTime:summary.departureTime,\n      basis:result.basis,estimate:true};\n    updateCateringPreview();\n    const departure=new Date(new Date(summary.departureTime).getTime()-timing.buffer*60000);\n    routeStatus148('Geschätzte Fahrzeit '+minutes+' Min. ('+\n      (result.basis==='historical'?'typischer Verkehr':'aktuelle und typische Verkehrslage')+') · '+\n      result.origin+' → '+result.destination+'. Empfohlene Abfahrt mit '+timing.buffer+\n      ' Min. Puffer: '+clockFmt(departure)+'. Einsatz speichern.');\n    if(previous?.departureTime&&Math.abs(new Date(previous.departureTime)-new Date(summary.departureTime))>=10*60000)\n      showFeedback('Abfahrtsempfehlung geändert','warn',7000,\n        'Die neue Schätzung weicht um mindestens 10 Minuten ab. Bitte die Einsatzplanung prüfen.');\n  }catch(error){\n    routeResult148=null;\n    routeStatus148(error.message||'Route nicht verfügbar. Fahrzeit kann manuell eingegeben werden.');\n    showFeedback('Route nicht berechnet','warn',5000,error.message);\n  }finally{if(button)button.disabled=false}\n};\nconst renderBefore165=render;\nrender=function(){const value=renderBefore165();document.querySelectorAll('#tabSettings h1 small').forEach(x=>x.textContent='Version 58');return value};\nrenderRouteControls148();\ndocument.querySelectorAll('#tabSettings h1 small').forEach(x=>x.textContent='Version 58');\n"},{"src":"patch166.js","code":"'use strict';\n/* Barcode photo for article creation: local scan, explicit review, no article save. */\nlet zBarcodeId166 = 0;\nconst zBarcodePending166 = new Map();\nwindow.zapasioBarcodeResult166 = (id, value) => {\n  const finish = zBarcodePending166.get(Number(id));\n  zBarcodePending166.delete(Number(id));\n  finish?.(String(value || ''));\n};\nfunction zNativeBarcode166(data) {\n  if (typeof window.ZapasioBarcode?.decodeBarcode !== 'function') return Promise.resolve('');\n  return new Promise(resolve => {\n    const id = ++zBarcodeId166;\n    const timeout = setTimeout(() => {\n      zBarcodePending166.delete(id);\n      resolve('');\n    }, 9000);\n    zBarcodePending166.set(id, value => {\n      clearTimeout(timeout);\n      resolve(value);\n    });\n    try { window.ZapasioBarcode.decodeBarcode(data, id); }\n    catch (_) { clearTimeout(timeout); zBarcodePending166.delete(id); resolve(''); }\n  });\n}\nasync function zReadBarcode166(file) {\n  if (typeof window.ZapasioBarcode?.decodeBarcode === 'function') {\n    const bitmap = await createImageBitmap(file);\n    const scale = Math.min(1, 1800 / Math.max(bitmap.width, bitmap.height));\n    const canvas = document.createElement('canvas');\n    canvas.width = Math.round(bitmap.width * scale);\n    canvas.height = Math.round(bitmap.height * scale);\n    canvas.getContext('2d').drawImage(bitmap, 0, 0, canvas.width, canvas.height);\n    bitmap.close?.();\n    const image = canvas.toDataURL('image/jpeg', .88);\n    const native = await zNativeBarcode166(image);\n    if (native && (validGtin179(normalizeBarcode306(native)) || resolveBarcode306(native))) return normalizeBarcode306(native);\n  }\n  if (typeof BarcodeDetector !== 'undefined') {\n    const bitmap = await createImageBitmap(file);\n    try {\n      const detector = typeof scanDetector430==='function'?await scanDetector430():new BarcodeDetector();\n      if(!detector)return '';\n      const found = await detector.detect(bitmap);\n      const codes=[...new Set(found.map(hit=>normalizeBarcode306(hit.rawValue)).filter(code=>validGtin179(code)||resolveBarcode306(code)))];\n      return codes.length===1?codes[0]:'';\n    } finally { bitmap.close?.(); }\n  }\n  return '';\n}\nfunction zInstallBarcode166() {\n  const code = document.getElementById('aeCode');\n  if (!code || document.getElementById('zBarcodePhoto166')) return;\n  const button = document.createElement('button');\n  button.type = 'button';\n  button.className = 'soft zBarcodePhoto166';\n  button.id = 'zBarcodePhoto166';\n  button.textContent = 'Barcode fotografieren';\n  button.onclick = () => document.getElementById('zBarcodeInput166')?.click();\n  const input = document.createElement('input');\n  input.type = 'file'; input.accept = 'image/*'; input.setAttribute('capture','environment');\n  input.id = 'zBarcodeInput166'; input.hidden = true;\n  input.onchange = async () => {\n    const file = input.files?.[0];\n    input.value = '';\n    if (!file) return;\n    const codeField = document.getElementById('aeCode');\n    if (!codeField || codeField.readOnly) return;\n    button.disabled = true; button.textContent = 'Barcode wird gelesen …';\n    try {\n      const value = (await zReadBarcode166(file)).trim().toUpperCase();\n      if (!value) {\n        showFeedback('Barcode nicht lesbar', 'warn', 4500, 'Bitte erneut fotografieren oder die Nummer eingeben.');\n        return;\n      }\n      if (document.getElementById('articleModal')?.style.display !== 'flex') return;\n      codeField.value = value;\n      codeField.dispatchEvent(new Event('input', {bubbles: true}));\n      codeField.focus();\n      if (checkMainBarcodeLive()) showFeedback('Barcode übernommen', 'success', 3200, value + ' – bitte vor dem Speichern prüfen.');\n      else showFeedback('Barcode bereits vergeben', 'warn', 4500, 'Bitte den angezeigten Artikel prüfen.');\n    } catch (_) {\n      showFeedback('Barcode nicht lesbar', 'warn', 4500, 'Bitte erneut fotografieren oder die Nummer eingeben.');\n    } finally {\n      button.disabled = false; button.textContent = 'Barcode fotografieren';\n    }\n  };\n  code.closest('.field')?.append(button, input);\n}\nconst zOpenArticleBefore166 = openArticleEditor;\nopenArticleEditor = function(code = null) {\n  const result = zOpenArticleBefore166(code);\n  zInstallBarcode166();\n  const photo = document.getElementById('zBarcodePhoto166');\n  if (photo) photo.hidden = !!code;\n  return result;\n};\n\n/* MHD text fields already share parseMhd147 and validity checks. */\nfunction zMhdFormat166(raw) {\n  const digits = String(raw).replace(/\\D/g, '').slice(0, 8);\n  if (digits.length <= 2) return digits;\n  if (digits.length <= 4) return digits.slice(0, 2) + '.' + digits.slice(2);\n  return digits.slice(0, 2) + '.' + digits.slice(2, 4) + '.' + digits.slice(4);\n}\ndocument.addEventListener('input', event => {\n  const field = event.target;\n  if (!field?.classList?.contains('mhdText147') || field.type !== 'text' || event.isComposing) return;\n  const raw = field.value;\n  if (/^\\d{4}-\\d{2}(-\\d{2})?$/.test(raw)) return;\n  const before = raw.slice(0, field.selectionStart ?? raw.length).replace(/\\D/g, '').length;\n  const formatted = zMhdFormat166(raw);\n  if (formatted === raw) return;\n  field.value = formatted;\n  let pos = 0, seen = 0;\n  while (pos < formatted.length && seen < before) if (/\\d/.test(formatted[pos++])) seen++;\n  field.setSelectionRange(pos, pos);\n}, true);\ndocument.addEventListener('blur', event => {\n  const field = event.target;\n  if (!field?.classList?.contains('mhdText147') || !field.value) return;\n  const valid = parseMhd147(field.value);\n  if (valid) field.value = valid.label;\n}, true);\n\n/* The full web view needs the responsive dashboard and the workshop sidebar. */\nfunction zWebMenu166() {\n  const active = document.body.classList.contains('zWebView163');\n  if (!active) return;\n  document.body.classList.add('mobile-detail-mode');\n}\nconst zSwitchWebBefore166 = zSwitchWebView163;\nzSwitchWebView163 = function(active) {\n  const result = zSwitchWebBefore166(active);\n  zWebMenu166();\n  return result;\n};\nzWebMenu166();\n\n/* Give every home task a verb, its available amount and the source event. */\nconst zMobileBefore166 = renderMobileToday;\nrenderMobileToday = function() {\n  zMobileBefore166();\n  const panel = document.querySelector('.zMobileTasks158');\n  if (!panel) return;\n  const c = mobileWorkingPlan() || zData158().active;\n  if (!c) return;\n  const prep = ensurePreparation(c);\n  const auto = preparationAutoTasks(c).filter(task => !prep.done[task.id]);\n  const manual = (prep.manual || []).filter(task => !task.done);\n  const shopping = procurementFor(c);\n  const tasks = [...auto.map(t => ({...t, kind: 'prep'})), ...manual.map(t => ({...t, kind: 'manual'}))].slice(0, 3);\n  const title = panel.querySelector('h2')?.outerHTML || '<h2>Heute zu tun</h2>';\n  const rows = tasks.map(t => {\n    const ingredient = t.section === 'Zutaten bereitstellen';\n    const verb = ingredient ? ' bereitstellen' : '';\n    const heading = (t.label || 'Aufgabe') + verb;\n    const detail = [t.meta, c.event || 'Einsatz'].filter(Boolean).join(' · ');\n    if (t.kind === 'manual') {\n      if (/\\b(vorbereiten|herstellen|bereitstellen|einräumen|beladen|einladen|prüfen|reinigen)\\b/i.test(t.label || ''))\n        return `<label class=\"zTaskRow158 zTaskDetail166\"><input type=\"checkbox\" aria-label=\"${esc(t.label)} erledigt\" onchange=\"togglePrepManual('${escJs(c.id)}','${escJs(t.id)}',this.checked)\"><span><strong>${esc(t.label)}</strong><small>Manuell eingetragen · ${esc(c.event || 'Einsatz')}</small></span></label>`;\n      return `<div class=\"zTaskDetail166\"><div><strong>Aufgabe klären: ${esc(t.label || 'Aufgabe')}</strong><small>Manuell eingetragen · ${esc(c.event || 'Einsatz')}</small></div><button type=\"button\" class=\"soft\" onclick=\"mobileOpenPlanning('preparation')\">Öffnen</button></div>`;\n    }\n    return `<label class=\"zTaskRow158 zTaskDetail166\"><input type=\"checkbox\" aria-label=\"${esc(heading)} erledigt\" onchange=\"setPrepDone('${escJs(c.id)}','${escJs(t.id)}',this.checked)\"><span><strong>${esc(heading)}</strong><small>${esc(detail)}</small></span></label>`;\n  }).join('');\n  const purchase = shopping.slice(0, 2).map(item =>\n    `<button type=\"button\" class=\"zTaskDetail166 zTaskLink166\" onclick=\"openPurchasingOverview('list')\"><span><strong>Einkaufen: ${esc(item.name)}</strong><small>${esc(formatQtySmart(item.qty, item.unit))} fehlen · ${esc(c.event || 'Einsatz')}</small></span><span aria-hidden=\"true\">›</span></button>`\n  ).join('') + (shopping.length > 2\n    ? `<button type=\"button\" class=\"zTaskDetail166 zTaskLink166\" onclick=\"openPurchasingOverview('list')\"><span><strong>${shopping.length - 2} weitere Einkaufspositionen</strong><small>Bestellvorschläge öffnen</small></span><span aria-hidden=\"true\">›</span></button>`\n    : '');\n  panel.innerHTML = `<div class=\"zPanelHead158\">${title}<button class=\"soft\" onclick=\"mobileShow('tasks')\">Alle Aufgaben</button></div>${rows || '<p class=\"zEmpty158\">Keine offenen Vorbereitungsaufgaben.</p>'}${purchase}`;\n};\nif (matchMedia('(max-width:820px)').matches) renderMobileToday();\nconst zRenderBefore166 = render;\nrender = function() {\n  const result = zRenderBefore166();\n  document.querySelectorAll('#tabSettings h1 small').forEach(label => label.textContent = 'Version 59');\n  return result;\n};\ndocument.querySelectorAll('#tabSettings h1 small').forEach(label => label.textContent = 'Version 59');\n"},{"src":"patch167.js?v=396","code":"'use strict';\nglobalThis.ZAPASIO_WEB_VERSION = document.querySelector('meta[name=\"zapasio-version\"]')?.content || String(document.title||'').match(/Teststand\\s+(\\d+)/)?.[1] || '396';\n/* Show partial speech quietly; only a final result may search or answer. */\nfunction voiceUi167(key) {\n  const language = business130().language;\n  const labels = {\n    de: {heard:'Erkannt: ', start:'Spracheingabe starten', listening:'Ich höre zu …', failed:'Sprache nicht erkannt', retry:'Bitte nochmals versuchen.', unavailable:'Mikrofon nicht verfügbar'},\n    pl: {heard:'Rozpoznano: ', start:'Rozpocznij wprowadzanie głosowe', listening:'Słucham …', failed:'Nie rozpoznano mowy', retry:'Spróbuj ponownie.', unavailable:'Mikrofon niedostępny'},\n    en: {heard:'Recognized: ', start:'Start voice input', listening:'Listening …', failed:'Speech not recognized', retry:'Please try again.', unavailable:'Microphone unavailable'}\n  };\n  return (labels[language] || labels.de)[key];\n}\nwindow.zapasioVoicePartial167 = text => {\n  if (!voiceButton130) return;\n  voiceButton130.title = String(text || '').slice(0, 100);\n  voiceButton130.setAttribute('aria-label', voiceUi167('heard') + String(text || '').slice(0, 100));\n};\nconst zStartVoiceBefore167 = startVoice130;\nlet zActiveRecognition167 = null;\nstartVoice130 = function(targetId, mode = 'dictation', button = null) {\n  globalThis.speechSynthesis?.cancel();\n  zActiveRecognition167?.abort();\n  zActiveRecognition167 = null;\n  if (globalThis.ZapasioAndroid?.startSpeech) {\n    const result = zStartVoiceBefore167(targetId, mode, button);\n    if (button) button.title = voiceUi167('listening');\n    return result;\n  }\n  const SR = globalThis.SpeechRecognition || globalThis.webkitSpeechRecognition;\n  if (!SR) {\n    button?.classList.remove('listening');\n    showFeedback(voiceUi167('unavailable'), 'warn', 4000);\n    return;\n  }\n  voiceTarget130 = targetId;\n  voiceMode130 = mode;\n  voiceButton130 = button;\n  button?.classList.add('listening');\n  if (button) button.title = voiceUi167('listening');\n  const recognition = new SR();\n  zActiveRecognition167 = recognition;\n  recognition.lang = voiceLocale130();\n  recognition.continuous = false;\n  recognition.interimResults = true;\n  recognition.maxAlternatives = 1;\n  let completed = false;\n  recognition.onresult = event => {\n    for (let i = event.resultIndex; i < event.results.length; i++) {\n      const phrase = event.results[i][0]?.transcript || '';\n      if (event.results[i].isFinal && !completed) {\n        completed = true;\n        handleVoiceResult130(phrase);\n      } else if (!completed) window.zapasioVoicePartial167(phrase);\n    }\n  };\n  recognition.onerror = event => {\n    if (recognition !== zActiveRecognition167) return;\n    if (event.error !== 'aborted') showFeedback(voiceUi167('failed'), 'warn', 3000, voiceUi167('retry'));\n    button?.classList.remove('listening');\n  };\n  recognition.onend = () => {\n    if (recognition !== zActiveRecognition167) return;\n    button?.classList.remove('listening');\n    if (button) { button.title = voiceUi167('start'); button.setAttribute('aria-label', voiceUi167('start')); }\n    zActiveRecognition167 = null;\n  };\n  try { recognition.start(); }\n  catch (_) {\n    button?.classList.remove('listening');\n    showFeedback(voiceUi167('unavailable'), 'warn');\n  }\n};\nconst zVoiceResultBefore167 = globalThis.ZapasioVoiceResult;\nglobalThis.ZapasioVoiceResult = function(text, error = '') {\n  voiceButton130?.classList.remove('listening');\n  if (voiceButton130) {\n    voiceButton130.title = voiceUi167('start');\n    voiceButton130.setAttribute('aria-label', voiceUi167('start'));\n  }\n  if (error) {\n    showFeedback(voiceUi167('failed'), 'warn', 3500, voiceUi167('retry'));\n    return;\n  }\n  return zVoiceResultBefore167(text, error);\n};\ninventorySpeech152 = function(message) {\n  if (!message || voiceMode130 !== 'search') return;\n  const synth = globalThis.speechSynthesis;\n  synth?.cancel();\n  if (globalThis.ZapasioAndroid?.speakText) {\n    try {\n      if (globalThis.ZapasioAndroid.speakText(message, voiceLocale130()) !== false) return;\n    } catch (_) { /* Continue with the browser voice. */ }\n  }\n  if (!synth || !globalThis.SpeechSynthesisUtterance) return;\n  const utterance = new SpeechSynthesisUtterance(message);\n  utterance.lang = voiceLocale130();\n  utterance.rate = 1.06;\n  utterance.pitch = 1;\n  const voices = synth.getVoices();\n  const language = utterance.lang.toLowerCase();\n  utterance.voice = voices.find(voice => voice.lang.toLowerCase() === language)\n    || voices.find(voice => voice.lang.toLowerCase().startsWith(language.slice(0, 2))) || null;\n  synth.speak(utterance);\n};\nconst zVoiceHandleBefore167 = handleVoiceResult130;\nfunction zVoiceNavigation167(text) {\n  const spoken = String(text || '').trim().toLocaleLowerCase();\n  const match = /^(?:öffne|zeige(?: mir)?|open|show|otwórz|pokaż)\\s+(?:(?:den|die|das|the)\\s+)?(lager|bestand|stock|inventory|magazyn|einkauf|einkaufsliste|shopping|zakupy|aufgaben|tasks|zadania|vorbereitung|preparation|przygotowanie)[.!?]?$/iu.exec(spoken);\n  if (!match) return false;\n  const target = match[1];\n  if (/^(lager|bestand|stock|inventory|magazyn)$/.test(target)) {\n    if (matchMedia('(max-width:820px)').matches) mobileShow('search');\n    else showTab('stock');\n  } else if (/^(einkauf|einkaufsliste|shopping|zakupy)$/.test(target)) {\n    openPurchasingOverview('list');\n  } else if (/^(aufgaben|tasks|zadania)$/.test(target)) {\n    if (matchMedia('(max-width:820px)').matches) mobileShow('tasks');\n    else openPlanningFromNav('preparation');\n  } else {\n    if (matchMedia('(max-width:820px)').matches) mobileOpenPlanning('preparation');\n    else openPlanningFromNav('preparation');\n  }\n  const language = business130().language;\n  inventorySpeech152(language === 'pl' ? 'Otwieram.' : language === 'en' ? 'Opening.' : 'Geöffnet.');\n  return true;\n}\nhandleVoiceResult130 = function(text) {\n  if (voiceMode130 === 'search' && zVoiceNavigation167(text)) return;\n  if (voiceMode130 === 'search' && voiceTarget130 === 'zQuickSearch158') {\n    mobileShow('search');\n    voiceTarget130 = 'mobileArticleSearch';\n    return zOldVoiceResult158(text);\n  }\n  return zVoiceHandleBefore167(text);\n};\nconst zRenderBefore167 = render;\nrender = function() {\n  const result = zRenderBefore167();\n  document.querySelectorAll('#tabSettings h1 small').forEach(label => label.textContent = 'Webstand ' + globalThis.ZAPASIO_WEB_VERSION);\n  return result;\n};\ndocument.querySelectorAll('#tabSettings h1 small').forEach(label => label.textContent = 'Webstand ' + globalThis.ZAPASIO_WEB_VERSION);\n"},{"src":"patch178.js","code":"'use strict';\n/*\n * Sprachsuche: kurze Erkennungsfehler auch innerhalb natürlicher Sätze\n * auflösen. Die Zuordnung bleibt bewusst konservativ und verändert keinen\n * Bestand; eine Buchung erfordert weiterhin die vorhandene Bestätigung.\n */\nconst voiceMentionBefore178 = voiceMention151;\n\nconst VOICE_FILLER_178 = new Set([\n  // Deutsch\n  'wie','viel','viele','wieviel','wieviele','habe','haben','hast','hat','ich',\n  'wir','ihr','noch','da','vorhanden','vorratig','vorrätig','bestand','lager',\n  'artikel','produkt','produkte','bitte','zeige','zeig','finde','suche','mir',\n  'uns','mein','meine','unser','unsere','der','die','das','den','dem','ein',\n  'eine','einen','von','fur','für','auf','ist','sind','gibt','es',\n  // Englisch\n  'how','many','much','do','does','we','i','have','has','left','stock','in',\n  'the','please','show','find','item','items',\n  // Polnisch\n  'ile','mamy','mam','masz','jest','zostalo','zostało','jeszcze','na',\n  'stanie','magazynie','pokaz','pokaż','znajdz','znajdź','prosze','proszę'\n]);\n\nfunction voiceMeaningfulWords178(value) {\n  return mobileNormalize(value).split(' ').filter(word =>\n    word.length >= 3 && !VOICE_FILLER_178.has(word)\n  );\n}\n\nfunction voiceAdjacentSwap178(left, right) {\n  if (left.length !== right.length) return false;\n  const different = [];\n  for (let index = 0; index < left.length; index++)\n    if (left[index] !== right[index]) different.push(index);\n  return different.length === 2 && different[1] === different[0] + 1 &&\n    left[different[0]] === right[different[1]] &&\n    left[different[1]] === right[different[0]];\n}\n\nfunction voiceMention178(utterance) {\n  const direct = voiceMentionBefore178(utterance);\n  if (direct) return direct;\n\n  const spokenWords = voiceMeaningfulWords178(utterance);\n  if (!spokenWords.length) return '';\n  const spokenForms = new Set(spokenWords);\n  for (let index = 0; index + 1 < spokenWords.length; index++)\n    spokenForms.add(spokenWords[index] + spokenWords[index + 1]);\n\n  let best = null;\n  let ambiguous = false;\n  for (const product of Object.values(state.products || {})) {\n    const labels = [product.name, ...(Array.isArray(product.synonyms) ? product.synonyms : [])];\n    for (const label of labels) {\n      for (const canonical of voiceMeaningfulWords178(label)) {\n        for (const spoken of spokenForms) {\n          const exact = spoken === canonical;\n          const close = !exact && Math.min(spoken.length, canonical.length) >= 4 &&\n            Math.abs(spoken.length - canonical.length) <= 1 &&\n            (mobileLev(spoken, canonical) <= 1 || voiceAdjacentSwap178(spoken, canonical));\n          if (!exact && !close) continue;\n          const score = (exact ? 100 : 60) + canonical.length;\n          if (!best || score > best.score) {\n            best = {term: canonical, score};\n            ambiguous = false;\n          } else if (score === best.score && canonical !== best.term) {\n            ambiguous = true;\n          }\n        }\n      }\n    }\n  }\n  return ambiguous ? '' : best?.term || '';\n}\n\nvoiceMention151 = voiceMention178;\n"},{"src":"patch168.js","code":"'use strict';\nfunction zCommissionQty168(article) {\n  return Math.min(Math.max(0, Number(article?.stock) || 0), Math.max(0, Number(article?.consignmentQty168) || 0));\n}\nfunction zCommissionSummary168(article) {\n  const commission = zCommissionQty168(article);\n  return 'Eigener Bestand: ' + formatQtySmart(Math.max(0, Number(article.stock) - commission), article.unit)\n    + ' · Kommission: ' + formatQtySmart(commission, article.unit);\n}\nfunction zCommissionControl168() {\n  let choice = document.getElementById('zCommissionChoice168');\n  if (!choice) {\n    choice = document.createElement('label');\n    choice.id = 'zCommissionChoice168';\n    choice.className = 'zCommissionChoice168';\n    choice.innerHTML = '<input id=\"zCommission168\" type=\"checkbox\"><span><b>Kommissionsware</b><small>Diese Menge getrennt vom eigenen Bestand führen.</small></span>';\n    document.getElementById('qty')?.closest('.field')?.before(choice);\n  }\n  const article = state.products?.[current];\n  choice.hidden = !article || !['in','out','waste','trude'].includes(mode);\n  const checked = document.getElementById('zCommission168');\n  if (checked) checked.checked = false;\n  choice.querySelector('b').textContent = mode === 'in' ? 'Kommission' : 'Aus Kommission entnehmen';\n  choice.querySelector('small').textContent = mode === 'in'\n    ? 'Häkchen setzen, wenn die gelieferte Menge auf Kommission kommt.'\n    : 'Häkchen setzen, wenn diese Menge vom Kommissionsbestand abgeht.';\n  let info = document.getElementById('zCommissionInfo168');\n  if (!info) {\n    info = document.createElement('p');\n    info.id = 'zCommissionInfo168'; info.className = 'zCommissionInfo168';\n    choice.after(info);\n  }\n  info.hidden = choice.hidden;\n  if (article) info.textContent = zCommissionSummary168(article);\n}\nconst zOpenModalBefore168 = openModal;\nopenModal = function(kind) {\n  const result = zOpenModalBefore168(kind);\n  if (document.getElementById('modal')?.style.display === 'flex') zCommissionControl168();\n  return result;\n};\nconst zBookBefore168 = book;\nbook = function() {\n  const article = state.products?.[current];\n  if (!article) return zBookBefore168();\n  const qty = Number(document.getElementById('qty')?.value);\n  const multiplier = Number(document.getElementById('pack')?.value) || 1;\n  const total = qty * multiplier;\n  const chosen = !!document.getElementById('zCommission168')?.checked;\n  if (mode !== 'in' && mode !== 'return' && Number.isFinite(total) && total > 0) {\n    const available = chosen ? zCommissionQty168(article)\n      : Math.max(0, Number(article.stock) - zCommissionQty168(article));\n    if (total > available + 1e-8) {\n      showFeedback(chosen ? 'Kommissionsbestand reicht nicht' : 'Eigener Bestand reicht nicht',\n        'warn', 4500, 'Verfügbar: ' + formatQtySmart(available, article.unit)\n        + '. Die Bestände werden getrennt geführt.');\n      return;\n    }\n  }\n  return zBookBefore168();\n};\n/* Stock movements outside the quick booking use owned stock unless explicitly selected. */\ntakeStockLots = function(code, qty, commission = false) {\n  const article = state.products[code];\n  const available = commission ? zCommissionQty168(article)\n    : Math.max(0, Number(article?.stock) - zCommissionQty168(article));\n  if (!article || !Number.isFinite(qty) || qty <= 0 || qty > available + 1e-8)\n    throw Error((article?.name || code) + ': ' + (commission ? 'Kommissionsbestand' : 'eigener Bestand') + ' reicht nicht.');\n  const lots = [], batches = (state.batches?.[code] || []).slice()\n    .sort((a,b) => (a.mhd || '9999').localeCompare(b.mhd || '9999'));\n  let left = qty;\n  for (const batch of batches) {\n    if (!!batch.consignment168 !== !!commission) continue;\n    const taken = Math.min(left, Number(batch.qty) || 0);\n    if (taken > 0) { lots.push({...clone(batch), qty: taken, consignment168: !!commission}); batch.qty -= taken; left -= taken; }\n    if (left <= 1e-8) break;\n  }\n  if (left > 1e-8) {\n    if (article.mhd) throw Error(article.name + ': passende MHD-Chargen reichen nicht.');\n    lots.push({id:'untracked-'+Date.now(), qty:left, mhd:'', consignment168:!!commission});\n  }\n  state.batches ??= {};\n  state.batches[code] = batches.filter(batch => Number(batch.qty) > 1e-8);\n  article.stock = Math.max(0, Number(article.stock) - qty);\n  if (commission) article.consignmentQty168 = Math.max(0, zCommissionQty168({...article, stock:Number(article.stock)+qty}) - qty);\n  return lots;\n};\nconsumeBatches = function(code, qty, commission = false) {\n  if (!state.batches?.[code] || qty <= 0) return;\n  let left = qty;\n  const batches = state.batches[code].sort((a,b) => (a.mhd || '9999').localeCompare(b.mhd || '9999'));\n  for (const batch of batches) {\n    if (!!batch.consignment168 !== !!commission || left <= 1e-8) continue;\n    const taken = Math.min(left, Number(batch.qty) || 0);\n    batch.qty -= taken;\n    if (Number(batch.packSize) > 0) batch.packCount = Math.max(0, Math.ceil((batch.qty - 1e-9) / Number(batch.packSize)));\n    left -= taken;\n  }\n  if (left > 1e-8) throw Error('Passende MHD-Chargen für diese Bestandsart reichen nicht.');\n  state.batches[code] = batches.filter(batch => Number(batch.qty) > 1e-8);\n};\nconst zReturnStockBefore168 = returnStockLots;\nreturnStockLots = function(code, lots) {\n  zReturnStockBefore168(code, lots);\n  const commission = lots.filter(lot => lot.consignment168).reduce((sum, lot) => sum + Number(lot.qty || 0), 0);\n  if (commission) state.products[code].consignmentQty168 =\n    (Number(state.products[code].consignmentQty168) || 0) + commission;\n};\nconst zOpenArticleBefore168 = openArticleEditor;\nopenArticleEditor = function(code = null) {\n  const result = zOpenArticleBefore168(code);\n  const stock = document.getElementById('aeStock');\n  if (!stock || document.getElementById('aeConsignmentChoice168')) return result;\n  const label = document.createElement('label');\n  label.id = 'aeConsignmentChoice168'; label.className = 'zCommissionChoice168';\n  label.innerHTML = '<input id=\"aeConsignment168\" type=\"checkbox\"><span><b>Startbestand auf Kommission</b><small>Nur beim Anlegen eines neuen Artikels. Spätere Lieferungen über Wareneingang buchen.</small></span>';\n  stock.closest('.field')?.before(label);\n  return result;\n};\nconst zArticleBefore168 = openArticleEditor;\nopenArticleEditor = function(code = null) {\n  const result = zArticleBefore168(code);\n  const label = document.getElementById('aeConsignmentChoice168');\n  if (label) label.hidden = !!code;\n  const check = document.getElementById('aeConsignment168');\n  if (check) check.checked = false;\n  return result;\n};\nconst zSaveArticleBefore168 = saveArticleEditor;\nsaveArticleEditor = function() {\n  if (editingArticleCode) {\n    const article = state.products[editingArticleCode];\n    const next = Number(document.getElementById('aeStock')?.value);\n    if (Number.isFinite(next) && next + 1e-8 < zCommissionQty168(article)) {\n      showFeedback('Kommissionsbestand prüfen','warn',4500,\n        'Der Gesamtbestand darf nicht kleiner als der Kommissionsbestand sein.');\n      return;\n    }\n  }\n  return zSaveArticleBefore168();\n};\nconst zRenderPhotoBefore168 = renderPhoto144;\nrenderPhoto144 = function() {\n  zRenderPhotoBefore168();\n  if (photoDraft144?.step !== 'summary') return;\n  const body = document.getElementById('photoBody144');\n  if (body && !body.querySelector('#photoCommission168')) {\n    body.querySelector('.photoActions144')?.insertAdjacentHTML('beforebegin',\n      '<label class=\"zCommissionChoice168\"><input type=\"checkbox\" id=\"photoCommission168\"><span><b>Kommission</b><small>Diese Lieferung als Kommissionsware einbuchen.</small></span></label>');\n  }\n};\nconst zLoadTasksBefore168 = loadListAutoTasks;\nloadListAutoTasks = function(c) {\n  const rows = zLoadTasksBefore168(c);\n  for (const row of rows) if (row.id.startsWith('drink:')) {\n    const i = Number(row.id.split(':')[1]);\n    const article = state.products?.[c.drinks?.[i]?.productCode];\n    if (article) row.meta += ' · ' + zCommissionSummary168(article);\n  }\n  return rows;\n};\nconst zArticleInfoBefore168 = openArticleInfo127;\nopenArticleInfo127 = function(code) {\n  const result = zArticleInfoBefore168(code);\n  const article = state.products?.[code];\n  const modal = document.getElementById('articleInfoModal127');\n  if (article && modal) {\n    let info = modal.querySelector('#zArticleInfoCommission168');\n    if (!info) {\n      info = document.createElement('p');\n      info.id = 'zArticleInfoCommission168';\n      info.className = 'zCommissionInfo168';\n      modal.querySelector('.articleInfoSection')?.prepend(info);\n    }\n    info.textContent = zCommissionSummary168(article);\n  }\n  return result;\n};\n"},{"src":"patch169.js?v=247","code":"'use strict';\n// Premium access is decided by the tenant RPC. Stock barcode scanning and\n// manually saving a product photo do not call this endpoint.\nconst analysePhotoBefore169=analysePhoto144;\nanalysePhoto144=async function(file){\n  if(!file)return;\n  const runtime=cloudRuntime(),workspace=cloudConfig()?.workspace;\n  if(!runtime.user||!workspace){\n    showFeedback('Fotoerkennung benötigt einen angemeldeten Betrieb','warn');return;\n  }\n  try{\n    await cloudRequest('/rest/v1/rpc/gs_charge_usage',{\n      p_workspace:workspace,p_feature:'photo',p_request:crypto.randomUUID()});\n    await analysePhotoBefore169(file);\n  }catch(error){\n    showFeedback('Fotoerkennung nicht verfügbar','warn',6000,error.message);\n    if(photoDraft144){photoDraft144.step='capture';renderPhoto144()}\n  }\n};\n\n// The provider key is an Edge Function secret; never accept or send a browser\n// session key, including values left from an earlier app version.\nsessionStorage.removeItem('zapasio_tomtom_key');\nrouteKey148=function(){return ''};\nsetRouteKey148=function(){showFeedback('TomTom wird zentral eingerichtet','warn')};\nconst renderRouteBefore169=renderRouteControls148;\nrenderRouteControls148=function(){\n  renderRouteBefore169();\n  document.getElementById('routeKey148')?.remove();\n};\nconst calculateRouteBefore169=calculateRoute148;\ncalculateRoute148=async function(){\n  if(!cloudRuntime().user){\n    showFeedback('Für die Routenplanung anmelden','warn');return;\n  }\n  return calculateRouteBefore169();\n};\n\nasync function openCommercial169(){\n  if(!cloudRuntime().user)return openCloudSettings();\n  try{\n    const workspace=cloudConfig().workspace;\n    const s=await cloudRequest('/rest/v1/rpc/gs_commercial_status',{p_workspace:workspace});\n    const title={legacy:'Bestehender Zugang',pending:'Paket noch nicht aktiviert',\n      demo:'Siebentägige Demo',active:'Paket aktiv',past_due:'Zahlung prüfen',\n      cancelled:'Gekündigt'}[s.status]||'Zugang';\n    const date=s.status==='demo'?s.demoEndsAt:s.paidUntil;\n    const remaining=date?`<p>Gültig bis ${esc(new Date(date).toLocaleString('de-AT'))}</p>`:'';\n    const usage=Object.entries(s.usage||{}).map(([k,v])=>`<li>${esc(k)}: ${Number(v)||0} Abfragen</li>`).join('');\n    let catalog=null;\n    if(cloudRuntime().user.role==='admin'){\n      try{catalog=(await cloudRequest('/functions/v1/gs-billing',{\n        action:'catalog',workspace})).catalog}catch{}\n    }\n    const quote=(key)=>catalog?.[key]?\n      new Intl.NumberFormat('de-AT',{style:'currency',currency:'EUR'}).format(catalog[key].cents/100)+\n        (catalog[key].taxBehavior==='exclusive'?' netto':'')+' / Monat':'Preis noch nicht eingerichtet';\n    const booking=catalog&&['pending','demo'].includes(s.status)&&cloudRuntime().user.role==='admin'?\n      `<details class='settingsFold'><summary>Paket auswählen</summary><div class='opsGrid92'>\n      <label>Grundpaket<select id='billingPlan169'><option value='basic'>Basis · ${esc(quote('basic'))}</option>\n      ${catalog.pro?`<option value='pro'>Erweitert · ${esc(quote('pro'))}</option>`:''}</select></label>\n      <label>Nutzerplätze (zwei enthalten; weiterer Platz ${esc(quote('seat'))})\n        <input type='number' id='billingSeats169' min='2' max='1000' value='${Math.max(2,Number(s.seats)||2)}'></label>\n      <label>Fotoerkennung<select id='billingPhoto169'><option value=''>Ohne</option>\n      ${catalog.photo_small?`<option value='small'>250 Erkennungen · ${esc(quote('photo_small'))}</option>`:''}\n      ${catalog.photo_large?`<option value='large'>1.000 Erkennungen · ${esc(quote('photo_large'))}</option>`:''}</select></label>\n      <label><input type='checkbox' id='billingRoute169' ${catalog.route?'':'disabled'}> Routenplanung · ${esc(quote('route'))}</label>\n      <label><input type='checkbox' id='billingOffer169' ${catalog.offer?'':'disabled'}> Angebotsimport · ${esc(quote('offer'))}</label>\n      </div><button class='primary' onclick='checkout169()'>Bei Stripe prüfen und bezahlen</button>\n      <p>Freischaltung erst nach bestätigter Zahlung. Die Summe und Steuer werden vor Abschluss bei Stripe angezeigt.</p></details>`:'';\n    openOpsPanel('Paket & Zusatzmodule',`<h3>${esc(title)}</h3>${remaining}\n      <p>Nutzerplätze: ${Number(s.seats)||2}. Zwei Plätze sind im Grundpaket vorgesehen.</p>\n      ${usage?`<ul>${usage}</ul>`:''}\n      ${s.status==='pending'&&cloudRuntime().user.role==='admin'?`<button class='primary' onclick='startDemo169()'>Demo für 7 Tage starten</button>`:''}\n      ${booking}\n      ${s.status==='active'||s.status==='past_due'||s.status==='cancelled'?`<button class='soft' onclick='billingPortal169()'>Paket ändern, Zahlung verwalten oder kündigen</button>`:''}\n      ${!catalog&&s.status!=='legacy'?`<p class='planWarn'>Die Bezahlung ist noch nicht eingerichtet. Bis zur bestätigten Zahlung werden keine bezahlten Module freigeschaltet.</p>`:''}`);\n  }catch(error){showFeedback('Paketstatus nicht erreichbar','error',6000,error.message)}\n}\nasync function checkout169(){\n  const modules={};\n  const photo=document.getElementById('billingPhoto169')?.value;\n  if(photo)modules.photo=photo;\n  if(document.getElementById('billingRoute169')?.checked)modules.route=true;\n  if(document.getElementById('billingOffer169')?.checked)modules.offer=true;\n  try{\n    const result=await cloudRequest('/functions/v1/gs-billing',{\n      workspace:cloudConfig().workspace,plan:document.getElementById('billingPlan169')?.value,\n      seats:Number(document.getElementById('billingSeats169')?.value),modules});\n    if(!result.url?.startsWith('https://checkout.stripe.com/'))throw Error('Ungültiges Zahlungsziel');\n    location.assign(result.url);\n  }catch(error){showFeedback('Bezahlung nicht gestartet','error',6500,error.message)}\n}\nasync function billingPortal169(){\n  try{\n    const result=await cloudRequest('/functions/v1/gs-billing',{\n      action:'portal',workspace:cloudConfig().workspace});\n    if(!result.url?.startsWith('https://billing.stripe.com/'))throw Error('Ungültiges Zahlungsziel');\n    location.assign(result.url);\n  }catch(error){showFeedback('Zahlungsverwaltung nicht erreichbar','error',6000,error.message)}\n}\nasync function startDemo169(){\n  try{\n    await cloudRequest('/rest/v1/rpc/gs_start_demo',{p_workspace:cloudConfig().workspace});\n    showFeedback('Demo gestartet','success');openCommercial169();\n  }catch(error){showFeedback('Demo nicht gestartet','error',6000,error.message)}\n}\nconst renderBefore169=render;\nrender=function(){\n  const value=renderBefore169();renderRouteControls148();\n  const settings=document.getElementById('tabSettings');\n  if(settings&&!document.getElementById('commercialButton169')){\n    const button=document.createElement('button');button.id='commercialButton169';\n    button.className='soft';button.type='button';button.textContent='Paket & Zusatzmodule';\n    button.addEventListener('click',openCommercial169);\n    settings.querySelector('h1')?.after(button);\n  }\n  return value;\n};\n/* Initial full refresh is performed after all patches load. */\n"},{"src":"assets254.js?v=1","code":"\n'use strict';\nconst assetLoads254=new Map();\nfunction loadAsset254(name){\n const assets={pdf:['PDFLib','assets/pdf-lib.min.js'],zip:['JSZip','assets/jszip.min.js'],ocr:['Tesseract','assets/ocr/tesseract.min.js']};\n const spec=assets[name];if(!spec)return Promise.reject(Error('Komponente nicht verfügbar'));\n if(globalThis[spec[0]])return Promise.resolve(globalThis[spec[0]]);\n if(assetLoads254.has(name))return assetLoads254.get(name);\n const task=new Promise((resolve,reject)=>{const script=document.createElement('script');let finished=false;const finish=(error)=>{if(finished)return;finished=true;clearTimeout(timer);script.onload=script.onerror=null;if(error){script.remove();reject(error)}else resolve(globalThis[spec[0]])};const timer=setTimeout(()=>finish(Error('Laden dauert zu lange. Erneut versuchen.')),20000);script.src=spec[1];script.onload=()=>finish(globalThis[spec[0]]?null:Error('Komponente konnte nicht geladen werden'));script.onerror=()=>finish(Error('Verbindung prüfen und erneut versuchen'));document.head.appendChild(script)}).catch(error=>{assetLoads254.delete(name);throw error});\n assetLoads254.set(name,task);return task;\n}\n"},{"src":"patch170.js?v=369","code":"'use strict';\nlet offerReview170=null,offerEngine170=null;\nconst offerEuro170=x=>new Intl.NumberFormat('de-AT',{style:'currency',currency:'EUR'}).format(x);\nfunction offerRecipes170(){return (state.recipeGroups||[]).flatMap(g=>(g.recipes||[]).map(r=>({id:r.id,name:r.name})));}\nfunction offerDate170(value){\n  const p=String(value||'').match(/^(\\d{1,2})[.\\/-](\\d{1,2})[.\\/-](\\d{4})$/);\n  return p?`${p[3]}-${p[2].padStart(2,'0')}-${p[1].padStart(2,'0')}`:'';\n}\nfunction offerRecipeOptions170(id=''){\n  return `<option value=''>Bitte Rezept zuordnen</option>`+offerRecipes170().map(r=>\n    `<option value='${esc(r.id)}' ${r.id===id?'selected':''}>${esc(r.name)}</option>`).join('');\n}\nfunction offerSplitLine170(){return `<div class='offerReviewFields170 offerSplitLine170'>\n  <label>Rezept<select data-recipe>${offerRecipeOptions170()}</select></label>\n  <label>Portionen<input type='number' min='0' step='1' data-quantity placeholder='0'></label></div>`;}\nfunction addOfferSplit170(button){\n button.insertAdjacentHTML('beforebegin',offerSplitLine170());\n}\nfunction offerSourceLabel170(source){\n const rows=offerReview170?.doc?.sourceRows||[];\n const page=rows.find(row=>row.text===source)?.page||rows.find(row=>source?.includes(row.text)&&row.text.length>12)?.page;\n return `<small class='offerOrigin170'>${page?'Seite '+page+' · ':''}Original: ${esc(source)}</small>`;\n}\nfunction offerReviewStatus170(doc,analysis,price){\n const has=kind=>analysis.fields.some(field=>field.kind===kind&&field.value);\n const checks=[['Kunde',has('client')],['Datum',has('eventDate')],['Personen',has('people')],['Netto',!!price]];\n const missing=checks.filter(([,found])=>!found).map(([label])=>label);\n return `<div class='offerStatus170' role='status'><b>${missing.length?missing.length+' Kernangaben offen':'Kernangaben erkannt, bitte prüfen'}</b><span>${checks.map(([label,found])=>`<span class='${found?'offerKnown170':'offerOpen170'}'>${esc(label)}: ${found?'erkannt':'offen'}</span>`).join('')}</span><small>${analysis.items.length} Positionen vorgeschlagen · ${analysis.uncertainties.length} Hinweise. Erkannt heißt nicht bestätigt.</small></div>`;\n}\nfunction offerRows170(){\n  const review=offerReview170;\n  return review.analysis.items.map((item,index)=>{\n    if(item.type==='alternative')return `<section class='offerReviewRow170' data-kind='alternative' data-index='${index}'>\n      ${offerSourceLabel170(item.source)}<b>Auswahl und tatsächliche Mengenaufteilung</b>\n      <label>Portionen insgesamt<input type='number' min='1' step='1' data-alt-total value='${item.totalQuantity??''}' placeholder='Bitte Gesamtmenge bestätigen'></label>\n      ${item.choices.map((choice,i)=>`<div class='offerChoice170'><label>${esc(choice.name)}\n        <input type='number' min='0' step='1' data-choice='${i}' value='0'></label></div>`).join('')}\n      <label><input type='checkbox' data-confirm-alternative> Mengenaufteilung geprüft</label></section>`;\n    if(item.type==='food'&&item.bundleTotal)return `<section class='offerReviewRow170' data-kind='food' data-index='${index}'>\n      ${offerSourceLabel170(item.source)}<b>${esc(item.name||'Paket')} · ${item.bundleTotal} Positionen: tatsächliche Speisen aufteilen</b>\n      <p>Jede tatsächlich bestellte Variante braucht ein Rezept und eine Teilmenge. Alle Teilmengen müssen zusammen ${item.bundleTotal} ergeben.</p>\n      ${offerSplitLine170()}<button type='button' class='soft' onclick='addOfferSplit170(this)'>+ weitere Speise</button></section>`;\n    if(item.type==='food')return `<section class='offerReviewRow170' data-kind='food' data-index='${index}'>\n      ${offerSourceLabel170(item.source)}${item.optional?'<label><input type=\"checkbox\" data-optional-food> Optionale Speise tatsächlich übernehmen</label>':''}\n      <small>${item.recipeId?'Rezeptzuordnung und Portionsmenge prüfen.':'Rezept noch nicht zugeordnet. Varianten bitte nach tatsächlicher Menge aufteilen; eine Sammelposition ist keine bestätigte Rezeptmenge.'}</small>\n      <div class='offerReviewFields170'>\n      <label>Rezept<select data-recipe>${offerRecipeOptions170(item.recipeId)}</select></label>\n      <label>Portionen<input type='number' min='0' step='1' data-quantity value='${item.quantity??''}'></label>\n      </div></section>`;\n    if(item.type==='drink')return `<section class='offerReviewRow170' data-kind='drink' data-index='${index}'>\n      ${offerSourceLabel170(item.source)}<label><input type='checkbox' data-include> Getränk übernehmen</label>\n      <div class='offerReviewFields170'><label>Getränkeartikel<select data-product>\n       <option value=''>Artikel manuell prüfen</option>${Object.entries(state.products||{}).map(([code,a])=>\n        `<option value='${esc(code)}'>${esc(a.name)}</option>`).join('')}</select></label>\n       <label>Anzahl<input type='number' min='1' step='1' data-drink-qty value='${item.quantity??''}' placeholder='Bitte bestätigen'></label></div></section>`;\n    return `<section class='offerReviewRow170' data-kind='${esc(item.type)}' data-index='${index}'>\n      ${offerSourceLabel170(item.source)}<label><input type='checkbox' data-include>\n      ${esc({material:'Material',drink:'Getränk',service:'Leistung',discount:'Rabatt'}[item.type]||item.type)} übernehmen</label></section>`;\n  }).join('');\n}\nfunction offerPriceLine170(entry){\n const source=String(entry.source||'').trim();\n const money='(\\\\d{1,3}(?:[. ]\\\\d{3})*(?:,\\\\d{1,2})?|\\\\d+(?:[.,]\\\\d{1,2})?)\\\\s*(?:EUR|€)';\n const tax=source.match(new RegExp(`^(\\\\d+(?:[.,]\\\\d+)?)\\\\s*%\\\\s*(?:USt|MwSt)[.]?\\\\s+${money}\\\\s+${money}\\\\s+${money}$`,'i'));\n const position=source.match(new RegExp(`^(.+?)\\\\s+(\\\\d{1,4})\\\\s+${money}\\\\s+${money}$`,'i'));\n const cell=(label,value)=>`<div class='offerPriceCell170'><span>${label}</span><strong>${esc(value)}</strong></div>`;\n if(tax)return `<div class='offerPriceLine170'><b>Steuer · ${esc(tax[1])} % USt</b><div class='offerPriceValues170'>${cell('Netto',tax[2]+' €')}${cell('USt',tax[3]+' €')}${cell('Brutto',tax[4]+' €')}</div></div>`;\n if(position)return `<div class='offerPriceLine170'><b>${esc(position[1])}</b><div class='offerPriceValues170'>${cell('Menge',position[2])}${cell('Einzelpreis',position[3]+' €')}${cell('Gesamt',position[4]+' €')}</div></div>`;\n return `<div class='offerPriceLine170'><span class='offerPriceRaw170'>${esc(source)}</span></div>`;\n}\nasync function openOfferImport170(){\n if(!guardPermission('planning','Angebotsimport'))return;\n if(!cloudRuntime().user){showFeedback('Bitte zuerst anmelden','warn');return}\n openOpsPanel('Angebot prüfen',`<p>PDF, Word (.docx) oder Excel (.xlsx) auswählen. Die Originaldatei wird nicht verändert.\n   Es wird nichts bestellt oder aus dem Lager gebucht.</p>\n   ${!globalThis.ZapasioAndroid&&!/ZapasioAndroid\\//.test(navigator.userAgent)?\"<div id='offerDrop170' class='offerDrop170' role='button' tabindex='0' aria-label='Angebotsdatei hier ablegen oder auswählen'>Datei hierher ziehen oder klicken, um sie auszuwählen<small>PDF, Word oder Excel</small></div>\":''}\n   <label>Angebotsdatei<input id='offerFile170' type='file' accept='.pdf,.docx,.xlsx,application/pdf' onchange='analyseOfferFile170(this.files[0])'></label>\n   <div id='offerReview170' role='status'></div>`);\n const zone=document.getElementById('offerDrop170'),input=document.getElementById('offerFile170');\n if(zone&&input){\n  zone.addEventListener('click',()=>input.click());\n  zone.addEventListener('keydown',event=>{if(event.key==='Enter'||event.key===' '){event.preventDefault();input.click()}});\n  zone.addEventListener('dragenter',event=>{event.preventDefault();zone.classList.add('dragging')});\n  zone.addEventListener('dragover',event=>{event.preventDefault();event.dataTransfer.dropEffect='copy';zone.classList.add('dragging')});\n  zone.addEventListener('dragleave',event=>{if(!zone.contains(event.relatedTarget))zone.classList.remove('dragging')});\n  zone.addEventListener('drop',event=>{\n   event.preventDefault();zone.classList.remove('dragging');\n   const files=[...event.dataTransfer.files];\n   if(files.length!==1){document.getElementById('offerReview170').textContent='Bitte genau eine Datei ablegen.';return}\n   analyseOfferFile170(files[0]);\n  });\n }\n}\nasync function analyseOfferFile170(file){\n const target=document.getElementById('offerReview170');if(!file||!target)return;\n target.textContent='Datei wird gelesen …';\n try{\n  const [{readOfferFile170},{parseOffer170,calculateOffer170,reviewOfferItems170,preferredOfferPrice170}]=await Promise.all([\n    import('./document170.mjs?v=97'),import('./offer170.mjs?v=369')]);\n  offerEngine170={parseOffer170,calculateOffer170,reviewOfferItems170,preferredOfferPrice170};\n  const doc=await readOfferFile170(file);\n  if(doc.needsOcr)throw Error('Mindestens eine PDF-Seite benötigt OCR. Auf Android erneut versuchen oder ein durchsuchbares PDF verwenden.');\n  if(!doc.text.trim())throw Error('Kein lesbarer Angebotstext gefunden');\n  const analysis=parseOffer170(doc.text,offerRecipes170());\n  if(analysis.documentType==='supplier_price_list')throw Error('Das ist eine Lieferantenpreisliste und kein Angebot für einen Einsatz. Es wurde nichts übernommen.');\n  if(analysis.documentType==='non_catering_document')throw Error(analysis.reason+'. Die Datei wurde nicht als Catering Einsatz übernommen; es fällt keine Analysegebühr an.');\n  const workspace=cloudConfig().workspace;\n  const existing=await cloudRequest('/rest/v1/rpc/gs_offer_existing',{p_workspace:workspace,p_hash:doc.hash});\n  if(existing){target.textContent='Dieses Angebot wurde bereits als Einsatz übernommen. Einsatzkennung: '+existing;return}\n  const request=crypto.randomUUID();\n  await cloudRequest('/rest/v1/rpc/gs_charge_usage',{p_workspace:workspace,p_feature:'offer',p_request:request});\n  offerReview170={doc,analysis,request};\n  showOfferReview170();\n }catch(error){target.textContent='Analyse nicht möglich: '+error.message}\n}\nfunction showOfferReview170(){\n const {doc,analysis}=offerReview170,target=document.getElementById('offerReview170');\n const field=kind=>analysis.fields.find(x=>x.kind===kind)?.value;\n const prices=analysis.fields.filter(x=>x.kind.endsWith('_price'));\n const preferredPrice=offerEngine170.preferredOfferPrice170(analysis.fields);\n const price=preferredPrice?.value||null;\n const persons=field('people')||'';\n const gross=price?.tax!=='net';\n const origin=kind=>analysis.fields.find(x=>x.kind===kind)?.source||'';\n const dateSource=origin('eventDate');\n const offerIssue=doc.text.match(/Erstellungsdatum:\\s*(\\d{1,2}[.]\\d{1,2}[.]\\d{4})/i)?.[1]||'';\n const suspiciousDate=offerIssue&&offerDate170(field('eventDate'))&&offerDate170(field('eventDate'))<offerDate170(offerIssue);\n const optionalPositions=analysis.items.filter(x=>x.optional);\n const mathWarnings=analysis.uncertainties.filter(x=>x.reason.startsWith('Positionssumme'));\n target.innerHTML=`<div class='offerReview170'><h3>Prüfansicht · ${esc(doc.name)}</h3>\n   ${offerReviewStatus170(doc,analysis,preferredPrice)}\n   <p class='planWarn'>Vorschläge bitte anhand der Originalstellen prüfen. Unklare Angaben ändern oder ausdrücklich bestätigen.</p>\n   ${!field('people')?'<p class=\"planWarn\">Personenzahl offen. Die angegebenen Portionen und Getränke wurden nicht als Gästezahl übernommen.</p>':''}\n   ${optionalPositions.length?'<p class=\"planWarn\">Optionale Preispositionen im Angebot: Vor der Übernahme prüfen, ob sie im Nettobetrag enthalten sind und ob sie bestellt wurden.</p>':''}\n   ${mathWarnings.length?`<p class='planWarn'>${mathWarnings.length} Position${mathWarnings.length===1?'':'en'}: Menge × Einzelpreis ergibt nicht die erkannte Summe. Betrag und Position im Original prüfen.</p><label><input type='checkbox' id='offerMathConfirmed170'> Diese Abweichung im Original geprüft; Nettobetrag gegebenenfalls berichtigt</label>`:''}\n   ${analysis.uncertainties.length?`<details><summary>${analysis.uncertainties.length} Erkennungshinweise ansehen</summary><ul>${analysis.uncertainties.slice(0,15).map(x=>`<li>${esc(x.reason)}: ${esc(x.source)}</li>`).join('')}</ul></details>`:''}\n   <details><summary>Originaltext einsehen</summary><pre class='offerSource170'>${esc(doc.text)}</pre></details>\n   <div class='offerReviewFields170'>\n   <label>Kunde / Unternehmen<input id='offerClient170' value='${esc(field('client')||'')}' required><small>Original: ${esc(origin('client')||'nicht eindeutig gefunden')}</small></label>\n   <label>Einsatzname<input id='offerEvent170' value='${esc(field('client')?'Catering · '+field('client'):'')}' placeholder='z. B. Firmenfeier oder Catering' required><small>Vorschlag bitte passend zum tatsächlichen Anlass ändern.</small></label>\n   <label>Einsatzdatum<input id='offerDate170' type='date' value='${esc(offerDate170(field('eventDate')))}' required><small>Original: ${esc(dateSource||'nicht eindeutig gefunden')}</small></label>\n   <label>Einsatzort<input id='offerAddress170' value='${esc(field('eventAddress')||'')}'><small>Original: ${esc(origin('eventAddress')||'nicht eindeutig gefunden')}</small></label>\n   <label>Personen<input id='offerPeople170' type='number' min='1' step='1' value='${esc(persons)}' required><small>Original: ${esc(origin('people')||'nicht angegeben; bitte beim Kunden prüfen')}</small></label>\n   <label>Preisart<select id='offerPriceType170'>\n      <option value='total' ${price?.unit!=='per_person'?'selected':''}>Gesamtpreis</option>\n      <option value='per_person' ${price?.unit==='per_person'?'selected':''}>Preis pro Person</option>\n    </select></label>\n   <label>Betrag netto (€)<input id='offerPrice170' inputmode='decimal' value='${price?.tax==='net'?esc(new Intl.NumberFormat('de-DE',{minimumFractionDigits:2,maximumFractionDigits:2}).format(price.amount)):''}'\n     placeholder='Bei Brutto oder unklar: Netto nach Prüfung eingeben'></label></div>\n   <details class='offerMore170'><summary>Weitere Kundendaten und mehrtägiger Einsatz</summary><div class='offerReviewFields170'>\n   <label>Ansprechperson<input id='offerContact170' value='${esc(field('contact')||'')}'><small>Original: ${esc(origin('contact')||'nicht eindeutig gefunden')}</small></label>\n   <label>Kundennummer<input id='offerCustomerNo170' value='${esc(field('customerNumber')||'')}'><small>Original: ${esc(origin('customerNumber')||'nicht gefunden')}</small></label>\n   <label>Kundenadresse<input id='offerCustomerAddress170' value='${esc(field('customerAddress')||'')}'><small>Getrennt vom Einsatzort.</small></label>\n   <label>Enddatum, falls mehrtägig<input id='offerEndDate170' type='date' value='${esc(offerDate170(field('eventEndDate')))}'><small>Original: ${esc(origin('eventEndDate')||'kein zweiter Tag erkannt')}</small></label>\n   </div></details>\n   ${suspiciousDate?'<p class=\"planWarn\">Achtung: Das erkannte Einsatzdatum liegt vor dem Erstellungsdatum des Angebots. Bitte prüfen.</p>':''}\n   <section class='offerPriceSelected170' aria-label='Quelle des Nettobetrags'><h4>Quelle des Nettobetrags</h4>${preferredPrice?offerPriceLine170(preferredPrice):'<p>Nicht eindeutig erkannt. Bitte den Nettobetrag im Original prüfen und oben eintragen.</p>'}</section>\n   <details class='offerPrices170'><summary>Weitere Preisangaben im Original</summary><div class='offerPriceList170'>${prices.filter(x=>x!==preferredPrice).length?prices.filter(x=>x!==preferredPrice).map(offerPriceLine170).join(''):'Keine weiteren Preisangaben erkannt.'}</div></details>\n   <label><input type='checkbox' id='offerPriceConfirmed170'> Nettoverkaufspreis und Preisart geprüft${gross?' (Steuerangabe war unklar oder brutto)':''}</label>\n   <h3>Speisen, Material und Leistungen</h3>${offerRows170()}\n   <h3>Kosten vor der Übernahme</h3><label>Einsatzart<select id='offerKind170'>\n      <option value='catering'>Catering</option><option value='stand'>Stand / Foodtruck</option></select></label>\n   <div id='offerCosts170' class='offerReviewFields170'></div>\n   <div class='offerReviewFields170'><label>Zielgewinn (€)<input id='offerTarget170' inputmode='decimal' value='0'></label>\n   <label>Zielgewinnquote (%)<input id='offerMargin170' inputmode='decimal' placeholder='optional'></label></div>\n   <div id='offerCalc170' aria-live='polite'></div>\n   <label><input type='checkbox' id='offerIncomplete170'> Fehlende Rezeptzuordnungen und Kosten bewusst als offen übernehmen; es erfolgt keine Lagerbuchung und der Gewinn bleibt vorläufig</label>\n   <button class='primary' id='offerConfirm170' onclick='confirmOffer170()'>Geprüften Einsatz übernehmen</button>\n   <p>Erst nach diesem Klick entsteht ein Einsatz. Das Originalangebot bleibt unverändert.</p></div>`;\n target.addEventListener('input',refreshOffer170);\n target.addEventListener('change',refreshOffer170);\n renderOfferCosts170();refreshOffer170();\n}\nconst offerCostLabels170={personnel:'Personal',travel:'Fahrt',preparation:'Vorbereitung',cleanup:'Nachbereitung',\n packaging:'Zusätzliche Verpackung',reusable:'Mehrweg',standFee:'Standgebühr',rent:'Miete',agency:'Vermittlungsgebühr',other:'Sonstige Kosten'};\nfunction renderOfferCosts170(){\n const box=document.getElementById('offerCosts170'),kind=document.getElementById('offerKind170')?.value;\n if(!box)return;\n const keys=kind==='stand'?['personnel','travel','preparation','cleanup','packaging','reusable','standFee','other']:\n   ['personnel','travel','preparation','cleanup','packaging','reusable','agency','other'];\n box.innerHTML=keys.map(key=>`<label>${offerCostLabels170[key]} (€)\n   <input inputmode='decimal' data-cost='${key}' placeholder='Fehlt / noch geschätzt'></label>`).join('');\n}\nfunction offerNumber170(value){\n let s=String(value||'').trim().replace(/\\s/g,'');if(!s)return NaN;\n if(s.includes(','))s=s.replace(/\\./g,'').replace(',','.');\n else if(/^\\d{1,3}(?:\\.\\d{3})+$/.test(s))s=s.replace(/\\./g,'');\n return Number(s);\n}\nfunction offerSelection170(){\n const corrections={};\n for(const row of document.querySelectorAll('#offerReview170 .offerReviewRow170')){\n  const item=offerReview170.analysis.items[Number(row.dataset.index)];\n  if(item.type==='food'){\n   corrections[row.dataset.index]=item.bundleTotal?{splits:[...row.querySelectorAll('.offerSplitLine170')].map(line=>({\n      recipeId:line.querySelector('[data-recipe]').value,quantity:Number(line.querySelector('[data-quantity]').value)}))}:{recipeId:row.querySelector('[data-recipe]').value,\n     quantity:Number(row.querySelector('[data-quantity]').value),\n     included:!item.optional||!!row.querySelector('[data-optional-food]')?.checked};\n  }else if(item.type==='alternative'){\n   corrections[row.dataset.index]={quantities:[...row.querySelectorAll('[data-choice]')].map(x=>Number(x.value)),\n     totalQuantity:Number(row.querySelector('[data-alt-total]')?.value),\n     confirmed:row.querySelector('[data-confirm-alternative]').checked};\n  }\n }\n return offerEngine170.reviewOfferItems170(offerReview170.analysis.items,corrections);\n}\nfunction offerExtras170(){\n const drinks=[],material=[],services=[],missing=[];\n for(const row of document.querySelectorAll('#offerReview170 .offerReviewRow170')){\n  const item=offerReview170.analysis.items[Number(row.dataset.index)];\n  if(!row.querySelector('[data-include]')?.checked)continue;\n  if(item.type==='drink'){\n   const productCode=row.querySelector('[data-product]').value;\n   const qty=Number(row.querySelector('[data-drink-qty]').value);\n   if(!productCode||!Number.isInteger(qty)||qty<=0)missing.push('Getränkeartikel und Anzahl: '+item.source);\n   else drinks.push({name:state.products[productCode].name,productCode,qty,\n      unit:'Flasche',price:Number(state.products[productCode].price)||0,\n      supplier:state.products[productCode].supplier||''});\n  }else if(item.type==='material')material.push(item.source);\n  else services.push({type:item.type,source:item.source});\n }\n return {drinks,material,services,missing};\n}\nfunction refreshOffer170(event){\n if(!offerReview170)return;\n if(event?.target?.id==='offerKind170')renderOfferCosts170();\n const persons=Number(document.getElementById('offerPeople170')?.value),amount=offerNumber170(document.getElementById('offerPrice170')?.value);\n const priceNet=document.getElementById('offerPriceType170')?.value==='per_person'?amount*persons:amount;\n const {lines,missing:foodMissing}=offerSelection170(),extra=offerExtras170();\n const missing=[...foodMissing,...extra.missing];\n let goods=0,complete=!!lines.length;\n for(const line of lines){const recipe=findRecipeById(line.recipeId)?.recipe,e=recipe&&recipeEconomicMeta(recipe);\n  if(!e?.complete)complete=false;else goods+=e.unitCost*line.qty;}\n for(const drink of extra.drinks){\n  if(!(drink.price>0))complete=false;else goods+=drink.price*drink.qty;\n }\n const costs={},explicit=[];\n for(const el of document.querySelectorAll('#offerCosts170 [data-cost]')){\n   explicit.push(el.dataset.cost);costs[el.dataset.cost]=offerNumber170(el.value);\n }\n const targetProfit=offerNumber170(document.getElementById('offerTarget170')?.value)||0;\n const marginValue=offerNumber170(document.getElementById('offerMargin170')?.value);\n const calc=offerEngine170.calculateOffer170({priceNet,people:persons,recipeCost:complete?goods:NaN,\n  recipeComplete:complete,costs,explicitCostKeys:explicit,targetProfit,\n  targetMargin:Number.isFinite(marginValue)?marginValue/100:null});\n const format=x=>x===null?'offen':offerEuro170(x);\n document.getElementById('offerCalc170').innerHTML=`<div class='offerMetrics170'>\n  <div>Gesamtkosten <b>${format(calc.total)}</b></div><div>Gewinn / Verlust <b>${format(calc.profit)}</b></div>\n  <div>Gewinnquote <b>${calc.margin===null?'offen':(calc.margin*100).toFixed(1)+' %'}</b></div></div>\n  <details class='offerCalcMore170'><summary>Weitere Kennzahlen ansehen</summary><div class='offerMetrics170'>\n  <div>Wareneinsatzquote <b>${calc.goodsRatio===null?'offen':(calc.goodsRatio*100).toFixed(1)+' %'}</b></div>\n  <div>Preis / Person <b>${format(calc.perPerson?.price??null)}</b></div>\n  <div>Kosten / Person <b>${format(calc.perPerson?.cost??null)}</b></div>\n  <div>Gewinn / Person <b>${format(calc.perPerson?.profit??null)}</b></div>\n  <div>Nötig für Zielbetrag <b>${format(calc.requiredForAmount)}</b></div>\n  <div>Nötig / Person <b>${format(calc.requiredPerPerson)}</b></div>\n  <div>Nötig für Zielquote <b>${format(calc.requiredForMargin)}</b></div></div></details>\n  ${calc.warning==='loss'?'<p class=\"planWarn\">Voraussichtlicher Verlust.</p>':\n    calc.warning==='below_target'?'<p class=\"planWarn\">Zielgewinn wird nicht erreicht.</p>':''}\n  ${[...new Set([...calc.missing,...missing])].length?`<p class='planWarn'>Noch offen oder geschätzt: ${esc([...new Set([...calc.missing.map(x=>offerCostLabels170[x]||x),...missing])].join(' · '))}. Gewinn nur vorläufig.</p>`:''}`;\n return {persons,priceNet,lines,missing,calc,extra};\n}\nasync function confirmOffer170(){\n if(!offerReview170||!cloudRuntime().user)return;\n const review=offerReview170,{persons,priceNet,lines,missing,calc,extra}=refreshOffer170();\n const acceptOpen=!!document.getElementById('offerIncomplete170')?.checked;\n const customer=document.getElementById('offerClient170')?.value.trim();\n const event=document.getElementById('offerEvent170')?.value.trim();\n const date=document.getElementById('offerDate170')?.value;\n if(!customer||!event||!date||!Number.isInteger(persons)||persons<1||!Number.isFinite(priceNet)||priceNet<0||\n    !document.getElementById('offerPriceConfirmed170')?.checked||\n    (review.analysis.uncertainties.some(x=>x.reason.startsWith('Positionssumme'))&&!document.getElementById('offerMathConfirmed170')?.checked)||\n    missing.some(x=>x.startsWith('Paketmenge ')||x.startsWith('Rezept und Teilmenge: '))||\n    ((missing.length||!lines.length||!calc.reliable)&&!acceptOpen)){\n  showFeedback('Angebot noch nicht vollständig geprüft','warn',7000,\n    'Kunde, Einsatzname, Datum, Nettopreis, Rezepte, Mengen und Kosten prüfen. Fehlende Kosten ausdrücklich schätzen.');return;\n }\n const runtime=cloudRuntime();if(runtime.busy||runtime.pending||runtime.blocked){showFeedback('Zuerst Datenabgleich abschließen','warn');return}\n const previous=clone(state),id='offer-'+crypto.randomUUID();\n const readCost=key=>offerNumber170(document.querySelector(`[data-cost=\"${key}\"]`)?.value)||0;\n const groupedOther=['preparation','cleanup','packaging','reusable','standFee','agency','other']\n   .reduce((sum,key)=>sum+readCost(key),0);\n const c={id,type:'Catering',event,date,customer170:customer,\n   customerContact170:document.getElementById('offerContact170')?.value.trim()||'',\n   customerNumber170:document.getElementById('offerCustomerNo170')?.value.trim()||'',\n   customerAddress170:document.getElementById('offerCustomerAddress170')?.value.trim()||'',\n   offerEndDate170:document.getElementById('offerEndDate170')?.value||'',\n   address:document.getElementById('offerAddress170')?.value.trim()||'',\n   readyTime:'12:00',guests:persons,revenue:priceNet,lines,offerFileName:review.doc.name,\n   offerHash170:review.doc.hash,offerReview170:{source:review.analysis.sourceLines,\n     confirmedPositions:extra.services,\n     unresolvedPositions:missing.slice(),\n     costBreakdown:Object.fromEntries(Object.keys(offerCostLabels170).map(key=>[key,readCost(key)])),\n     missingCosts:calc.missing,reliable:calc.reliable,checkedAt:new Date().toISOString()},\n   status:'Planung',actual:{},preparation:{done:{},manual:[]},\n   loadList:{done:{},manual:extra.material.map(label=>({id:'offer-'+crypto.randomUUID(),label,done:false}))},\n   drinks:extra.drinks,drinksEnabled:!!extra.drinks.length,travelMinutes:0,\n   bufferMinutes:Number(state.businessSettings?.defaultBufferMinutes)||15,\n   travelConfirmed:false,resourceCosts:emptyResourceCosts('Catering'),\n   costs:{extraGoods:0,personnel:readCost('personnel'),travel:readCost('travel'),\n     rental:readCost('rent'),other:groupedOther},\n   costSnapshotHistory:[]};\n c.costSnapshot=buildCateringCostSnapshot(c);\n state.planning.caterings.push(c);initWorkflowSuggestions(c);\n try{\n  const answer=await cloudRequest('/rest/v1/rpc/gs_confirm_offer_import',{\n    p_workspace:cloudConfig().workspace,p_revision:runtime.revision,p_data:state,\n    p_hash:review.doc.hash,p_request:review.request,p_catering_id:id,p_file_name:review.doc.name});\n  runtime.revision=answer.revision;runtime.base=clone(state);\n  localStorage.setItem('gs_livetest_frankfurt_v1',JSON.stringify(state));\n  offerReview170=null;document.getElementById('opsDialog92')?.close();\n  activeCateringId=id;render();showFeedback('Geprüfter Einsatz übernommen','success');\n }catch(error){state=previous;showFeedback('Übernahme fehlgeschlagen','error',7000,error.message)}\n}\nfunction addOfferButton170(){\n const actions=document.querySelector('#tabPlanning .planningHeroActions');\n if(!actions||document.getElementById('offerImportButton170'))return;\n const button=document.createElement('button');button.id='offerImportButton170';button.type='button';\n button.className='soft';button.textContent='Angebot prüfen';button.addEventListener('click',openOfferImport170);\n actions.append(button);\n}\nconst renderBefore170=render;\nrender=function(){const result=renderBefore170();addOfferButton170();return result};\nconst calcHtmlBefore170=calcHtml;\ncalcHtml=function(c){\n const html=calcHtmlBefore170(c);\n const missing=c?.offerReview170?.missingCosts||[];\n return missing.length?html+`<p class='planWarn'>Angebotskalkulation vorläufig: ${esc(missing.join(' · '))}.\n   Fehlende Kosten wurden bei der Übernahme bewusst als offen markiert.</p>`:html;\n};\naddOfferButton170();\n"},{"src":"patch181.js?v=275","code":"'use strict';\n// Web-only inventory migration. The original file is parsed locally and never uploaded.\nlet inventoryDraft181=null,inventoryEngine181=null;\nfunction webInventory181(){return !globalThis.ZapasioAndroid&&!/ZapasioAndroid\\//.test(navigator.userAgent)}\nfunction renderInventorySettings181(){\n const root=document.getElementById('settingsPanel130_stock');\n if(!root)return;\n let card=document.getElementById('inventoryImportCard181');\n if(!webInventory181()){card?.remove();return}\n if(card)return;\n card=document.createElement('details');card.id='inventoryImportCard181';card.className='card settingsFold';\n card.innerHTML=`<summary><span>Bestehendes Inventar übernehmen</span><span class='foldPlus'>+</span></summary>\n  <div class='inventoryImportIntro181'><p>Artikel und Anfangsbestände aus Excel oder CSV übernehmen. Jede Zeile vor dem Speichern prüfen.</p>\n  <a class='soft inventoryDownload181' href='assets/Zapasio-Inventarvorlage.xlsx' download='Zapasio-Inventarvorlage.xlsx'>Excel Vorlage herunterladen</a>\n  <button type='button' class='primary' onclick='openInventoryImport181()'>Excel oder CSV prüfen</button></div>`;\n root.appendChild(card);\n}\nconst renderBefore181=render;\nrender=function(){const result=renderBefore181();renderInventorySettings181();return result};\nrenderInventorySettings181();\nasync function openInventoryImport181(){\n if(!webInventory181())return;\n if(currentAccessUser().role!=='admin'){showFeedback('Kein Zugriff','warn',3600,'Den Datenumzug kann nur die Betriebsverwaltung durchführen.');return}\n if(cloudRuntime().enabled&&(!cloudRuntime().user||cloudRuntime().base===null||cloudRuntime().blocked)){\n  showFeedback('Datenstand zuerst verbinden','warn');return}\n inventoryDraft181=null;\n openOpsPanel('Inventar aus Excel übernehmen',`<p>Die Datei wird auf diesem Gerät gelesen. Artikel und Anfangsbestände ändern sich erst nach deiner Bestätigung.</p>\n  <a href='assets/Zapasio-Inventarvorlage.xlsx' download='Zapasio-Inventarvorlage.xlsx'>Excel Vorlage herunterladen</a>\n  <div class='inventoryDrop181' id='inventoryDrop181' role='button' tabindex='0' aria-label='Excel oder CSV hier ablegen oder auswählen'>Excel oder CSV hierher ziehen oder klicken<small>Eine Datei bis 5 MB</small></div>\n  <label>Oder Datei auswählen<input id='inventoryFile181' type='file' accept='.xlsx,.csv,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'></label>\n  <div id='inventoryReview181' role='status'></div>`);\n const zone=document.getElementById('inventoryDrop181'),input=document.getElementById('inventoryFile181');\n input.addEventListener('change',()=>readInventoryUpload181(input.files[0]));\n zone.addEventListener('click',()=>input.click());\n zone.addEventListener('keydown',event=>{if(['Enter',' '].includes(event.key)){event.preventDefault();input.click()}});\n zone.addEventListener('dragenter',event=>{event.preventDefault();zone.classList.add('dragging')});\n zone.addEventListener('dragover',event=>{event.preventDefault();event.dataTransfer.dropEffect='copy';zone.classList.add('dragging')});\n zone.addEventListener('dragleave',event=>{if(!zone.contains(event.relatedTarget))zone.classList.remove('dragging')});\n zone.addEventListener('drop',event=>{event.preventDefault();zone.classList.remove('dragging');const files=[...event.dataTransfer.files];\n  if(files.length!==1){document.getElementById('inventoryReview181').textContent='Bitte genau eine Datei ablegen.';return}readInventoryUpload181(files[0]);});\n}\nasync function readInventoryUpload181(file){\n const target=document.getElementById('inventoryReview181');if(!file||!target)return;\n target.textContent='Datei wird gelesen …';inventoryDraft181=null;\n try{\n  inventoryEngine181=await import('./inventory-import181.mjs?v=1');\n  if(/\\.xlsx$/i.test(file.name)&&typeof loadAsset254==='function')await loadAsset254('zip');\n  const sheets=await inventoryEngine181.readInventoryFile181(file,globalThis.JSZip);\n  const hash=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',await file.arrayBuffer()))).map(x=>x.toString(16).padStart(2,'0')).join('');\n  if((state.inventoryImports181||[]).some(x=>x.hash===hash))throw Error('Diese Datei wurde bereits übernommen. Für Korrekturen bitte den bestehenden Bestand prüfen.');\n  const selected=Math.max(0,sheets.findIndex(x=>x.name==='Import'&&x.rows.length>1));\n  inventoryDraft181={fileName:file.name,hash,sheets,selected,header:0,map:{},review:[]};\n  selectInventorySheet181(selected);\n }catch(error){target.textContent='Datei kann nicht geprüft werden: '+error.message}\n}\nfunction selectInventorySheet181(index){\n const draft=inventoryDraft181;if(!draft)return;draft.selected=Number(index);\n const rows=draft.sheets[draft.selected]?.rows||[];\n draft.header=inventoryEngine181.suggestHeader181(rows);\n draft.map=inventoryEngine181.suggestMapping181(rows[draft.header]?.cells||[]);\n renderInventoryMapping181();\n}\nfunction selectInventoryHeader181(index){\n const draft=inventoryDraft181;if(!draft)return;draft.header=Number(index);\n draft.map=inventoryEngine181.suggestMapping181(draft.sheets[draft.selected].rows[draft.header]?.cells||[]);\n renderInventoryMapping181();\n}\nfunction changeInventoryMapping181(id,index){if(!inventoryDraft181)return;inventoryDraft181.map[id]=Number(index);renderInventoryMapping181()}\nfunction renderInventoryMapping181(){\n const draft=inventoryDraft181,target=document.getElementById('inventoryReview181');if(!draft||!target)return;\n const sheet=draft.sheets[draft.selected],rows=sheet.rows,header=rows[draft.header];\n if(!header){target.textContent='Auf diesem Blatt stehen keine Daten. Bitte ein anderes Blatt auswählen oder die Vorlage ausfüllen.';return}\n const columns=header.cells.map((x,i)=>({text:String(x||`Spalte ${i+1}`).slice(0,65),index:i}));\n target.innerHTML=`<div class='inventoryReview181'><h3>Spalten zuordnen · ${esc(draft.fileName)}</h3>\n <div class='inventorySelects181'><label>Tabellenblatt<select onchange='selectInventorySheet181(this.value)'>${draft.sheets.map((x,i)=>`<option value='${i}' ${draft.selected===i?'selected':''}>${esc(x.name)}</option>`).join('')}</select></label>\n <label>Überschriften in Zeile<select onchange='selectInventoryHeader181(this.value)'>${rows.slice(0,25).map((x,i)=>`<option value='${i}' ${draft.header===i?'selected':''}>${x.number}: ${esc(x.cells.filter(Boolean).slice(0,2).join(' · ').slice(0,45))}</option>`).join('')}</select></label></div>\n <div class='inventoryMappings181'>${inventoryEngine181.inventoryFields181.map(([id,label,required])=>`<label>${esc(label)}${required?' *':''}<select onchange=\"changeInventoryMapping181('${id}',this.value)\"><option value='-1'>Nicht vorhanden</option>${columns.map(c=>`<option value='${c.index}' ${draft.map[id]===c.index?'selected':''}>${esc(c.text)}</option>`).join('')}</select></label>`).join('')}</div>\n <p class='muted'>* Pflichtspalten. Anfangsbestand ist in Lagereinheit angegeben. Die Datei wird nicht direkt ins Lager übernommen.</p><div id='inventoryRows181'></div></div>`;\n renderInventoryRows181();\n}\nfunction renderInventoryRows181(){\n const draft=inventoryDraft181,target=document.getElementById('inventoryRows181');if(!draft||!target)return;\n const required=['name','stock','unit'];if(required.some(k=>draft.map[k]<0)){\n  target.textContent='Bitte Artikelname, Anfangsbestand und Lagereinheit einer Spalte zuordnen.';return}\n const entries=Object.entries(state.products||{}),existing=entries.flatMap(([code,a])=>['n:'+a.name,'b:'+code,...(a.packs||[]).map(p=>'b:'+p[2])]);\n draft.review=inventoryEngine181.reviewInventoryRows181(draft.sheets[draft.selected].rows,draft.header,draft.map,existing);\n if(!draft.review.length){target.textContent='Unter den Überschriften stehen keine Artikel. Bitte ein anderes Blatt auswählen oder Daten eintragen.';return}\n const valid=draft.review.filter(x=>!x.errors.length),invalid=draft.review.length-valid.length;\n target.innerHTML=`<p class='inventoryCounts181'>${draft.review.length} Zeilen · ${valid.length} übernehmbar · ${invalid} zu prüfen</p>\n <div class='inventoryTable181'><table><thead><tr><th>Zeile</th><th>Artikel</th><th>Bestand</th><th>Gebinde</th><th>EK netto</th><th>Prüfung</th></tr></thead><tbody>\n ${draft.review.map(r=>`<tr class='${r.errors.length?'inventoryError181':''}'><td>${r.row}</td><td>${esc(r.name||'–')}</td>\n <td>${Number.isFinite(r.stock)?esc(r.stock):'–'} ${esc(r.unit||'')}</td><td>${esc(r.packName||'–')}${r.packName&&Number.isFinite(r.packQty)?' · '+esc(r.packQty)+' '+esc(r.unit):''}</td>\n <td>${r.price===null?'–':Number.isFinite(r.price)?esc(r.price)+' €':'–'}</td><td>${r.errors.length?esc(r.errors.join('; ')):'Bereit'}</td></tr>`).join('')}\n </tbody></table></div><p class='muted'>Fehlerhafte oder doppelte Zeilen bitte in Excel korrigieren und die Datei erneut auswählen. Es wird nur eine fehlerfreie Datei übernommen.</p>\n <label class='inventoryConfirm181'><input type='checkbox' id='inventoryConfirm181' ${valid.length===draft.review.length?'':'disabled'}> Ich habe Artikel, Gebinde und Anfangsbestände geprüft.</label>\n <button type='button' class='primary' ${valid.length===draft.review.length?'':'disabled'} onclick='confirmInventoryImport181()'>${draft.review.length} Artikel und Anfangsbestände übernehmen</button>`;\n}\nfunction confirmInventoryImport181(){\n const draft=inventoryDraft181;if(!draft||!webInventory181()||currentAccessUser().role!=='admin')return;\n if(!document.getElementById('inventoryConfirm181')?.checked){showFeedback('Bitte zuerst die Daten prüfen','warn');return}\n const fresh=inventoryEngine181.reviewInventoryRows181(draft.sheets[draft.selected].rows,draft.header,draft.map,\n  Object.entries(state.products||{}).flatMap(([code,a])=>['n:'+a.name,'b:'+code,...(a.packs||[]).map(p=>'b:'+p[2])]));\n if(!fresh.length||fresh.some(x=>x.errors.length)){showFeedback('Import erneut prüfen','warn',4500,'Ein Artikel ist inzwischen vorhanden oder ein Wert ist ungültig.');renderInventoryRows181();return}\n if((state.inventoryImports181||[]).some(x=>x.hash===draft.hash)){showFeedback('Datei bereits übernommen','warn');return}\n const done=operationTransaction('Inventar Anfangsbestand übernommen','articles',()=>{\n  if(!hasPermission('stockAdd')||currentAccessUser().role!=='admin')throw Error('Berechtigung für Anfangsbestände fehlt');\n  state.inventoryImports181??=[];\n  if(state.inventoryImports181.some(x=>x.hash===draft.hash))throw Error('Diese Datei wurde bereits übernommen');\n  for(const row of fresh){\n   const code=row.barcode?row.barcode.toUpperCase():nextInternalBarcode();\n   if(state.products[code]||barcodeConflict(code,'')||row.packBarcode&&barcodeConflict(row.packBarcode,''))throw Error(row.name+': Barcode schon vergeben');\n   let category=row.category||'Sonstiges';\n   const known=state.categories.find(x=>x.name.toLowerCase()===category.toLowerCase());\n   if(known)category=known.name;\n   else state.categories.push({id:'cat-import-'+crypto.randomUUID(),name:category,subgroups:['Allgemein']});\n   const packCode=row.packBarcode?row.packBarcode.toUpperCase():nextInternalBarcode();\n   const packs=[[row.unit,1,code,false,1,1,row.unit,1,row.unit]];\n   if(row.packName||row.packBarcode||row.packQty!==1)packs.push([row.packName||'Gebinde',row.packQty,packCode,true,row.packQty,row.packQty,row.unit,row.packQty,row.unit]);\n   const unitPrice=row.price===null?0:row.price/row.packQty;\n   const article={name:row.name,cat:category,overviewGroup:category,storage:row.storage||'Allgemein',\n    stock:row.stock,min:row.min??0,price:unitPrice,priceQuoteQty:1,priceQuoteUnit:row.unit,\n    supplier:row.supplier||'',unit:row.unit,mhd:!!row.mhd,packs,photo:'',\n    internalId:'prod-'+crypto.randomUUID(),supplierArticles:[],supplierArticleNumber181:row.supplierCode,\n    priceHistory:[],mainArticleId:'',mainFactor:1,prepLossPct:0,drainWeightEnabled:false,stockContent:null};\n   state.products[code]=article;\n   state.batches[code]=[];\n   if(row.mhd)addBatch(code,row.stock,row.mhd,{packName:row.packName,packSize:row.packQty,packCode});\n   if(row.stock>0)addMovement('initial',code,row.stock,{unitCost:unitPrice,supplier:row.supplier,reason:'Inventarimport Anfangsbestand'});\n   state.log.push(`${stamp()} | ${row.name} | Anfangsbestand aus Excel | ${row.stock} ${row.unit}`);\n  }\n  state.inventoryImports181.push({id:'import-'+crypto.randomUUID(),hash:draft.hash,fileName:draft.fileName,\n   count:fresh.length,at:new Date().toISOString()});\n });return afterOperation275(done,done=>{\n if(done){inventoryDraft181=null;document.getElementById('opsDialog92')?.close();showFeedback('Inventar lokal gespeichert','success',4500,`${fresh.length} Artikel übernommen.${cloudRuntime().enabled?' Der gemeinsame Datenabgleich läuft.':''}`)}\n;return done;});}\n"},{"src":"patch171.js","code":"'use strict';\nconst signupKey171='zapasio_new_company171';\nfunction emptyCompanyState171(){\n const fresh=clone(seed);\n fresh.tenantTemplate170=true;\n fresh.products={};fresh.mainArticles={};fresh.batches={};fresh.movements=[];\n fresh.suppliers=[];fresh.supplierProfiles=[];fresh.recipeGroups=[];\n fresh.categories=[];fresh.quickConfig={groups:[],standalone:[]};\n fresh.planning={caterings:[],recipes:{},shoppingDraft:[],loadStandards:[],supplierPriceLists:[]};\n fresh.dayTemplates=[];fresh.timeBlocks=[];fresh.dailyClosings=[];\n fresh.businessSettings={startLocation:'',defaultBufferMinutes:15};\n fresh.accessUsers=[];fresh.currentAccessUserId='';fresh.log=[];\n fresh.lastInventory='';fresh.lastInventoryISO='';\n return fresh;\n}\n// Existing seeded workspaces follow the original migration unchanged.\nconst migrateBefore171=migrate;\nmigrate=function(source){\n if(!source?.tenantTemplate170)return migrateBefore171(source);\n const names=['suppliers','supplierProfiles','recipeGroups','categories','quickConfig','planning',\n  'dayTemplates','timeBlocks','accessUsers','businessSettings'];\n const old=Object.fromEntries(names.map(k=>[k,seed[k]]));\n try{\n  seed.suppliers=[];seed.supplierProfiles=[];seed.recipeGroups=[];seed.categories=[];\n  seed.quickConfig={groups:[],standalone:[]};\n  seed.planning={caterings:[],recipes:{},shoppingDraft:[],loadStandards:[],supplierPriceLists:[]};\n  seed.dayTemplates=[];seed.timeBlocks=[];seed.accessUsers=[];\n  seed.businessSettings={startLocation:'',defaultBufferMinutes:15};\n  const result=migrateBefore171(source);\n  result.currentAccessUserId='';return result;\n }finally{Object.assign(seed,old)}\n};\n\nconst openCloudBefore171=openCloudSettings;\nopenCloudSettings=function(){\n openCloudBefore171();\n if(!cloudRuntime().user){\n  const box=document.querySelector('#opsDialog92 .opsBody92');\n  if(box&&!document.getElementById('registerCompany171')){\n   const button=document.createElement('button');button.type='button';button.className='soft';\n   button.id='registerCompany171';button.textContent='Neues Unternehmen registrieren';\n   button.addEventListener('click',openCompanyRegistration171);box.append(button);\n  }\n }\n};\nfunction openCompanyRegistration171(){\n openOpsPanel('Unternehmen registrieren',`<p>Lege zuerst deinen Admin-Zugang an. Danach kannst du eine\n  siebentägige Demo starten oder ein Paket wählen. Andere Unternehmen bleiben getrennt.</p>\n  <div class='opsGrid92'><label>Unternehmen<input id='newCompany171' autocomplete='organization'></label>\n  <label>Name<input id='newName171' autocomplete='name'></label>\n  <label>E-Mail<input id='newEmail171' type='email' autocomplete='email'></label>\n  <label>Passwort (mindestens 10 Zeichen)<input id='newPassword171' type='password' minlength='10' autocomplete='new-password'></label></div>\n  <button class='primary' onclick='registerCompany171()'>Konto anlegen</button>\n  <p id='signupStatus171' role='status'></p>`);\n}\nasync function registerCompany171(){\n const company=document.getElementById('newCompany171')?.value.trim(),\n  name=document.getElementById('newName171')?.value.trim(),\n  email=document.getElementById('newEmail171')?.value.trim().toLowerCase(),\n  password=document.getElementById('newPassword171')?.value||'',\n  note=document.getElementById('signupStatus171');\n if(!company||company.length>100||name.length<2||!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)||password.length<10){\n  note.textContent='Unternehmen, Namen, E-Mail und Passwort prüfen.';return;\n }\n try{\n  if(!cloudConfig())localStorage.setItem('gs_livetest_config92',JSON.stringify(CLOUD_INVITE_CONFIG));\n  note.textContent='Registrierung wird geprüft …';\n  const result=await cloudRequest('/auth/v1/signup',{email,password,data:{name}},false);\n  document.getElementById('newPassword171').value='';\n  if(!result.user?.id)throw Error('Konto konnte nicht angelegt werden');\n  localStorage.setItem(signupKey171,JSON.stringify({company,email,name}));\n  note.textContent='Konto angelegt. Bitte den Bestätigungslink in deiner E-Mail öffnen. Anschließend hier mit E-Mail und Passwort anmelden.';\n }catch(error){note.textContent=error.message}\n}\nasync function finishNewCompany171(data,pending){\n const runtime=cloudRuntime(),cfg=cloudConfig();\n runtime.token=data.access_token;runtime.refresh=data.refresh_token;\n runtime.expires=Date.now()+data.expires_in*1000;saveCloudSession119();\n let workspace=pending.workspace;\n if(!workspace){\n  workspace=await cloudRequest('/rest/v1/rpc/gs_create_workspace',{p_name:pending.company});\n  pending.workspace=workspace;\n  localStorage.setItem(signupKey171,JSON.stringify(pending));\n }\n cfg.workspace=workspace;localStorage.setItem('gs_livetest_config92',JSON.stringify(cfg));\n const result=await cloudRequest('/rest/v1/rpc/gs_read',{p_workspace:workspace});\n state=result.data?migrate(validBackupState(result.data)):emptyCompanyState171();\n runtime.enabled=true;runtime.user=result.member;runtime.user.id=data.user.id;\n runtime.user.name=pending.name;runtime.revision=result.revision;\n runtime.base=result.data;runtime.blocked=false;runtime.status='Neuer Betrieb · '+pending.company;\n localStorage.setItem('gs_livetest_frankfurt_v1',JSON.stringify(state));\n localStorage.removeItem(signupKey171);finishCloudLogin();openCommercial169();\n}\nconst signInBefore171=signInCloud;\nsignInCloud=async function(){\n const pending=safeReadStore(signupKey171);\n if(!pending)return signInBefore171();\n const email=document.getElementById('cloudEmail92')?.value.trim().toLowerCase();\n if(email!==pending.email)return signInBefore171();\n const note=document.getElementById('cloudMessage92');\n try{\n  note.textContent='Neuer Betrieb wird angelegt …';\n  const data=await cloudRequest('/auth/v1/token?grant_type=password',{\n    email,password:document.getElementById('cloudPassword92').value},false);\n  document.getElementById('cloudPassword92').value='';\n  await finishNewCompany171(data,pending);\n }catch(error){note.textContent=error.message}\n};\nconst startDemoBefore171=startDemo169;\nstartDemo169=async function(){\n await startDemoBefore171();\n // A new company starts with an empty document; seed data from another tenant\n // must never be copied into it.\n const runtime=cloudRuntime();\n if(runtime.user&&runtime.base===null&&state.tenantTemplate170){\n  try{const status=await cloudRequest('/rest/v1/rpc/gs_commercial_status',{\n    p_workspace:cloudConfig().workspace});\n   if(status.active)queueCloudSave('Leerer Betriebsstand angelegt')}\n  catch(error){showFeedback('Demo-Grunddaten nicht gespeichert','warn',5000,error.message)}\n }\n};\n"},{"src":"patch172.js","code":"'use strict';\nfunction routeStart172(){return (document.getElementById('planStartOverride172')?.value||'').trim()||\n  String(state.businessSettings?.startLocation||'').trim()}\nconst renderRouteBefore172=renderRouteControls148;\nrenderRouteControls148=function(){\n renderRouteBefore172();\n const tools=document.getElementById('routeTools148');\n if(tools&&!document.getElementById('planStartOverride172')){\n  const field=document.createElement('label');field.textContent='Startadresse für diesen Einsatz (optional)';\n  const input=document.createElement('input');input.id='planStartOverride172';\n  input.placeholder=state.businessSettings?.startLocation||'Unternehmensadresse verwenden';\n  input.addEventListener('change',()=>{routeResult148=null;updateCateringPreview()});\n  field.append(input);tools.prepend(field);\n }\n};\nconst cateringDraftBefore172=cateringDraftObject;\ncateringDraftObject=function(){const draft=cateringDraftBefore172();\n draft.startOverride172=document.getElementById('planStartOverride172')?.value.trim()||'';\n return draft};\nconst clearDraftBefore172=clearCateringDraft;\nclearCateringDraft=function(...args){const result=clearDraftBefore172(...args);\n const input=document.getElementById('planStartOverride172');if(input)input.value='';return result};\nconst recentRoutes172=new Map();\nconst editPlanBefore172=editCateringPlan;\neditCateringPlan=function(id){\n const result=editPlanBefore172(id),c=state.planning.caterings.find(x=>x.id===id);\n renderRouteControls148();\n const input=document.getElementById('planStartOverride172');if(input)input.value=c?.startOverride172||'';\n if(!c?.routeSource148||!c.date||!cloudRuntime().user)return result;\n const until=new Date(c.date+'T'+(c.readyTime||'12:00')+':00').getTime()-Date.now();\n const age=Date.now()-new Date(c.routeSource148.checkedAt||0).getTime();\n if(until>=0&&until<=24*3600000&&age>6*3600000&&\n   Date.now()-(recentRoutes172.get(id)||0)>2*3600000){\n  recentRoutes172.set(id,Date.now());\n  setTimeout(()=>{\n   if(editingCateringId===id&&document.getElementById('planAddress')?.value===c.address)\n    calculateRoute148();\n  },300);\n }\n return result;\n};\nrenderRouteControls148();\n"},{"src":"shopping173.js?v=88","code":"/* Shopping-list presentation: the existing needs, pack calculation, receipt and sync remain authoritative. */\nconst shoppingAreas173=[\n ['produce','Obst & Gemüse','produce'],['bakery','Brot & Backwaren','bread'],\n ['pantry','Trockenware & Konserven','jar'],['drinks','Getränke','bottle'],\n ['packaging','Verpackung & Non-Food','box'],['chilled','Kühlprodukte','fridge'],\n ['meat','Fleisch & Fisch','food'],['frozen','Tiefkühlkost','snow'],\n ['unassigned','Bereich zuordnen','shelf']\n];\nconst shoppingIconPaths173={\n produce:'M12 21c-5-2-8-6-7-10 2-4 5-3 7-2 2-1 5-2 7 2 1 4-2 8-7 10ZM12 9c0-4 2-6 5-6M12 9c-1-3-3-4-6-4',\n bread:'M4 11c0-4 3-7 8-7s8 3 8 7v8H4v-8ZM9 7v4m6-4v4',\n jar:'M5 5h14M7 5v15h10V5M5 2h14v3H5M9 11h6m-6 4h6',\n bottle:'M10 2h4v4l2 3v11H8V9l2-3V2Zm-2 9h8m-8 5h8',\n box:'M3 7 12 3l9 4-9 4-9-4Zm0 0v10l9 4 9-4V7m-9 4v10',\n fridge:'M6 3h12v18H6V3Zm0 9h12M9 7v3m0 5v3',\n food:'M3 3v8m3-8v8m3-8v8M6 11v10m8-18v18m0-9c7 0 7-9 0-9',\n snow:'M12 2v20M2 12h20M5 5l14 14M19 5 5 19M9 5l3 3 3-3M9 19l3-3 3 3',\n shelf:'M3 4h18v17H3V4Zm0 8h18M7 7h3m4 0h3M7 15h3m4 0h3'\n};\nfunction shoppingIcon173(key){const kind=shoppingAreas173.find(a=>a[0]===key)?.[2]||'shelf';return `<svg class=\"shoppingAreaIcon173\" viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><path d=\"${shoppingIconPaths173[kind]}\"/></svg>`}\nfunction shoppingAreaSuggestion173(article){const category=String(article?.overviewGroup||article?.cat||'').toLocaleLowerCase('de');const storage=String(article?.storage||'').toLocaleLowerCase('de');\n if(/tiefk[uü]hl|gefrier|frozen|freezer|\\btk\\b/.test(storage))return'frozen';\n if(/k[uü]hl|frisch|k[aä]lte|refrigerat/.test(storage))return'chilled';\n if(/trocken|un(?:ge)?k[uü]hl|raumtemperatur|haltbar|vorrat|konserv/.test(storage)){\n  if(/obst|gem[uü]se/.test(category)&&!/haltbar|konserv/.test(storage))return'produce';\n  if(/brot|back/.test(category))return'bakery';\n  if(/getr[aä]nk/.test(category))return'drinks';\n  if(/verpack|non.food|reinig|hygiene/.test(category))return'packaging';\n  return'pantry';\n }\n if(/tiefk[uü]hl|gefrier|\\btk\\b/.test(category))return'frozen';\n if(/obst|gem[uü]se/.test(category))return'produce';\n if(/brot|back/.test(category))return'bakery';\n if(/trocken|konserv/.test(category))return'pantry';\n if(/getr[aä]nk/.test(category))return'drinks';\n if(/verpack|non.food|reinig|hygiene/.test(category))return'packaging';\n if(/fleisch|fisch/.test(category))return'meat';\n if(/k[uü]hl/.test(category))return'chilled';\n return'unassigned';\n}\nfunction shoppingAreaFor173(article){return shoppingAreas173.some(a=>a[0]===article?.shoppingArea173)?article.shoppingArea173:shoppingAreaSuggestion173(article)}\nfunction shoppingAreaOptions173(value){return shoppingAreas173.map(([key,name])=>`<option value=\"${key}\" ${key===value?'selected':''}>${esc(name)}</option>`).join('')}\nfunction renderArticleShoppingArea173(article){const select=document.getElementById('aeShoppingArea173');if(!select)return;select.dataset.manual=article?.shoppingArea173?'true':'false';select.innerHTML=shoppingAreaOptions173(shoppingAreaFor173(article||{cat:document.getElementById('aeCategory')?.value,storage:document.getElementById('aeSubgroup')?.value}));select.onchange=()=>{select.dataset.manual='true'}}\nfunction updateShoppingAreaSuggestion173(){const select=document.getElementById('aeShoppingArea173');if(select&&select.dataset.manual!=='true')select.value=shoppingAreaSuggestion173({cat:document.getElementById('aeCategory')?.value,storage:document.getElementById('aeSubgroup')?.value})}\nfunction shoppingGroups173(channels){return channels.pickup.map(g=>{const configured=state.shoppingChecks?._areaOrders173?.[g.name]||[];const order=[...new Set([...configured,...shoppingAreas173.map(a=>a[0])])];const areas=shoppingAreas173.map(([key,label])=>({key,label,rows:g.rows.filter(r=>(r.manual174?r.area:shoppingAreaFor173(state.products?.[r.productCode]))===key)})).filter(a=>a.rows.length).sort((a,b)=>order.indexOf(a.key)-order.indexOf(b.key));for(const area of areas)area.rows.sort((a,b)=>Number(shoppingRowChecked(a))-Number(shoppingRowChecked(b))||a.name.localeCompare(b.name,'de'));return{...g,areas}})}\nfunction shoppingAreaReorder173(supplier,key,direction){if(!guardPermission('shopping','Bereichsreihenfolge'))return;const original=clone(state.shoppingChecks||{}),checks=state.shoppingChecks||(state.shoppingChecks={}),orders=checks._areaOrders173||(checks._areaOrders173={}),order=[...new Set([...(orders[supplier]||[]),...shoppingAreas173.map(a=>a[0])])],visible=shoppingGroups173(shoppingChannels()).find(g=>g.name===supplier)?.areas.map(a=>a.key)||[];const i=visible.indexOf(key),other=visible[i+direction];if(!other)return;const a=order.indexOf(key),b=order.indexOf(other);[order[a],order[b]]=[order[b],order[a]];orders[supplier]=order;if(!persistState('Laufweg gespeichert',false))state.shoppingChecks=original;renderSelfShopping()}\nfunction shoppingAreaSet173(code,key){if(!guardPermission('articles','Einkaufsbereich zuordnen'))return;const article=state.products?.[code];if(!article||!shoppingAreas173.some(a=>a[0]===key))return;const previous=article.shoppingArea173;article.shoppingArea173=key;if(!persistState('Einkaufsbereich gespeichert',false))article.shoppingArea173=previous;renderSelfShopping()}\nfunction shoppingSearch173(value){for(const input of document.querySelectorAll('.shoppingSearch173'))if(input.value!==value)input.value=value;for(const el of document.querySelectorAll('.shoppingCheckRow'))el.hidden=!el.textContent.toLocaleLowerCase('de').includes(value.toLocaleLowerCase('de'));for(const area of document.querySelectorAll('.shoppingArea173')){area.hidden=!!value&&![...area.querySelectorAll('.shoppingCheckRow')].some(row=>!row.hidden);if(value&&!area.hidden)area.open=true}for(const supplier of document.querySelectorAll('.shoppingSupplier173')){supplier.hidden=!!value&&![...supplier.querySelectorAll('.shoppingCheckRow')].some(row=>!row.hidden);if(value&&!supplier.hidden)supplier.open=true}}\nfunction shoppingPlural173(unit,count){const forms={Dose:['Dose','Dosen'],Packung:['Packung','Packungen'],Flasche:['Flasche','Flaschen'],Karton:['Karton','Kartons'],Becher:['Becher','Becher'],Glas:['Glas','Gläser'],Stück:['Stück','Stück'],Sack:['Sack','Säcke'],Kiste:['Kiste','Kisten'],Schachtel:['Schachtel','Schachteln'],Bund:['Bund','Bünde']};const pair=forms[unit];return pair?(count===1?pair[0]:pair[1]):unit}\nfunction shoppingDisplay173(row,plan){const label=String(plan?.label||'').trim();if(!plan?.ok)return{headline:row.name,pack:label};const pieces=label.split(/\\s+\\+\\s+/);if(pieces.length!==1)return{headline:`${row.name} · ${label}`,pack:''};const match=label.match(/^(\\d+(?:[.,]\\d+)?)\\s*(?:×\\s*)?(.+?)(?:\\s+à\\s+(.+))?$/);if(!match)return{headline:`${row.name} · ${label}`,pack:''};const count=Number(match[1].replace(',','.')),packName=match[2].trim(),size=match[3]||'',word=packName.split(/\\s+/)[0];const singular=Object.keys({Dose:1,Packung:1,Flasche:1,Karton:1,Becher:1,Glas:1,Stück:1,Sack:1,Kiste:1,Schachtel:1,Bund:1}).find(x=>x.toLocaleLowerCase('de')===word.toLocaleLowerCase('de')||shoppingPlural173(x,2).toLocaleLowerCase('de')===word.toLocaleLowerCase('de'));if(!singular||!Number.isInteger(count))return{headline:`${count} × ${row.name}`,pack:[packName,size&&`je ${size}`].filter(Boolean).join(' · ')};const descriptor=packName.slice(word.length).trim(),redundant=size.toLocaleLowerCase('de')===`1 ${singular}`.toLocaleLowerCase('de');return{headline:`${count} ${shoppingPlural173(singular,count)} ${row.name}`,pack:[descriptor,size&&!redundant&&`je ${size}`].filter(Boolean).join(' · ')}}\nfunction shoppingHeadline173(row,plan){const display=shoppingDisplay173(row,plan);return display.headline+(display.pack?' · '+display.pack:'')}\nfunction shoppingAreaRow173(row){const plan=shoppingPurchasePlan(row),display=shoppingDisplay173(row,plan),done=shoppingRowChecked(row),article=state.products?.[row.productCode],area=shoppingAreaFor173(article);return `<div class=\"shoppingCheckRow ${done?'isDone':''}\"><label class=\"shoppingTick173\"><input aria-label=\"${esc(row.name)} ${done?'wieder öffnen':'abhaken'}\" type=\"checkbox\" ${done?'checked':''} onchange=\"setShoppingRowChecked('${escJs(row.key)}',this.checked)\"><span class=\"shoppingNoPrint\">${done?'Erledigt':'Offen'}</span></label><span class=\"shoppingItem173\"><b class=\"shoppingItemName\">${esc(display.headline)}</b>${display.pack?`<span class=\"shoppingPack173\">${esc(display.pack)}</span>`:''}<small>${esc((row.reasons||[]).join(' · '))}</small>${area==='unassigned'&&article?`<select class=\"shoppingNoPrint\" aria-label=\"Einkaufsbereich für ${esc(row.name)}\" onchange=\"shoppingAreaSet173('${escJs(row.productCode)}',this.value)\">${shoppingAreaOptions173(area)}</select>`:''}</span><span class=\"shoppingQty\"><small>${esc(plan.detail)}</small>${!plan.ok&&row.productCode?`<button class=\"soft shoppingNoPrint\" onclick=\"openWarningArticle('${escJs(row.productCode)}','pack')\">Gebinde prüfen</button>`:''}</span></div>`}\nfunction shoppingAreaHtml173(g,area,index){const done=area.rows.filter(shoppingRowChecked).length;return `<details class=\"shoppingArea173\" data-area=\"${area.key}\" open><summary>${shoppingIcon173(area.key)}<span class=\"shoppingAreaTitle173\"><b>${esc(area.label)}</b><small>${area.rows.length} Artikel · ${done} erledigt · ${area.rows.length-done} offen</small></span><span class=\"shoppingNoPrint shoppingMove173\"><button type=\"button\" class=\"soft\" aria-label=\"${esc(area.label)} nach oben\" ${index===0?'disabled':''} onclick=\"event.preventDefault();shoppingAreaReorder173('${escJs(g.name)}','${area.key}',-1)\">↑</button><button type=\"button\" class=\"soft\" aria-label=\"${esc(area.label)} nach unten\" ${index===g.areas.length-1?'disabled':''} onclick=\"event.preventDefault();shoppingAreaReorder173('${escJs(g.name)}','${area.key}',1)\">↓</button></span></summary><div class=\"shoppingAreaBody173\">${area.rows.map(shoppingAreaRow173).join('')}</div></details>`}\nfunction shoppingBody173(channels){const groups=shoppingGroups173(channels),rows=groups.flatMap(g=>g.rows),done=rows.filter(shoppingRowChecked).length;return `<div class=\"shoppingTop173 shoppingNoPrint\"><strong>Noch offen: ${rows.length-done}</strong><span>${done} von ${rows.length} erledigt</span><input class=\"shoppingSearch173\" type=\"search\" aria-label=\"Einkaufsliste durchsuchen\" placeholder=\"Artikel suchen\" oninput=\"shoppingSearch173(this.value)\"></div>`+groups.map(g=>{const completed=g.rows.filter(shoppingRowChecked).length;return `<details class=\"shoppingStore shoppingSupplier173\" data-supplier=\"${esc(g.name)}\" open><summary class=\"shoppingSupplierSummary173\"><span><b>${esc(g.name)}</b><small>${g.rows.length} Artikel · ${completed} erledigt · ${g.rows.length-completed} offen</small></span></summary><div class=\"shoppingSupplierBody173\">${g.profile.address?`<p class=\"tiny\">${esc(g.profile.address)}</p>`:''}${g.areas.map((area,i)=>shoppingAreaHtml173(g,area,i)).join('')}${shoppingFinishButton(g)}</div></details>`}).join('')+(groups.length?'':'<p>Nichts zum selbst Einkaufen offen. Lieferantenbestellungen stehen unter „Bestellungen“.</p>')+(channels.review.length||channels.unlinked.length?'<p class=\"planWarn\">Weitere Positionen brauchen eine Lieferanten- oder Artikelzuordnung. Bitte „Bedarf prüfen“ öffnen.</p>':'')+`${typeof shoppingManualForm174==='function'?shoppingManualForm174():''}<p class=\"tiny\">Abhaken merkt den Einkauf vor. Der Wareneingang erfolgt beim Abschluss.</p>`+shoppingReceiptHistoryHtml()}\nrenderSelfShopping=function(){const el=document.getElementById('selfShoppingBody');if(!el)return;const inline=document.getElementById('selfShoppingInline'),active=document.activeElement,query=active?.classList?.contains('shoppingSearch173')?active.value:'',openSuppliers=new Set([...document.querySelectorAll('.shoppingSupplier173')].filter(x=>x.open).map(x=>x.dataset.supplier)),closedSuppliers=new Set([...document.querySelectorAll('.shoppingSupplier173')].filter(x=>!x.open).map(x=>x.dataset.supplier)),closedAreas=new Set([...document.querySelectorAll('.shoppingArea173')].filter(x=>!x.open).map(x=>x.closest('.shoppingSupplier173')?.dataset.supplier+'|'+x.dataset.area)),channels=shoppingChannels();el.innerHTML=shoppingBody173(channels);if(inline)inline.innerHTML=el.innerHTML;for(const root of [el,inline].filter(Boolean)){for(const supplier of root.querySelectorAll('.shoppingSupplier173')){if(closedSuppliers.has(supplier.dataset.supplier))supplier.open=false;else if(openSuppliers.has(supplier.dataset.supplier))supplier.open=true}for(const area of root.querySelectorAll('.shoppingArea173'))if(closedAreas.has(area.closest('.shoppingSupplier173')?.dataset.supplier+'|'+area.dataset.area))area.open=false}if(query){shoppingSearch173(query);(active.closest?.('#selfShoppingInline')?inline:el)?.querySelector('.shoppingSearch173')?.focus()}const mail=document.getElementById('shoppingMailText');if(mail)mail.value=selfShoppingText(channels)};\nselfShoppingText=function(channels=shoppingChannels()){let output='GRÜNSTOFF Einkaufsliste\\nStand: '+new Date().toLocaleString('de-AT')+'\\n\\n';for(const g of shoppingGroups173(channels)){output+=g.name+'\\n';for(const a of g.areas){output+=a.label+'\\n';for(const r of a.rows){const p=shoppingPurchasePlan(r);output+=(shoppingRowChecked(r)?'[x] ':'[ ] ')+shoppingHeadline173(r,p)+(p.ok?'':' · '+p.label)+' ('+p.detail+')\\n'}}output+='\\n'}return output+'Vorbestellungen sind separat in der App aufgeführt.'};\nprintSelfShopping=function(){if(!guardPermission('shopping','Einkaufsliste'))return;renderSelfShopping();document.querySelectorAll('#selfShoppingModal .shoppingSupplier173,#selfShoppingModal .shoppingArea173').forEach(x=>x.open=true);document.body.classList.add('shopping-print');const clear=()=>document.body.classList.remove('shopping-print');try{if(window.ZapasioAndroid?.printShopping173){window.addEventListener('focus',clear,{once:true});window.ZapasioAndroid.printShopping173()}else{window.addEventListener('afterprint',clear,{once:true});window.print()}}catch(e){clear();showFeedback('Druckdialog konnte nicht geöffnet werden','warn')}};\n"},{"src":"shopping174.js?v=87","code":"/* Einkauf-only entries live in the shopping permission's workspace data.\n   They deliberately have no product code and never become stock, purchase orders or recipes. */\nlet shoppingManualEdit174='';\nconst shoppingChannelsBefore174=shoppingChannels;\nshoppingChannels=function(data){const result=shoppingChannelsBefore174(data);for(const item of state.shoppingChecks?._manual174||[]){if(!item||!item.id||!item.name||!Number(item.qty))continue;const profile=supplierProfileFor(item.supplier),name=profile?.name||item.supplier||'Privat / anderer Markt';let group=result.pickup.find(g=>g.name===name);if(!group){group={name,method:'pickup',profile:profile?.method==='pickup'&&!profile.preorder?profile:{name,method:'pickup',address:''},rows:[]};result.pickup.push(group)}group.rows.push({key:'manual174:'+item.id,signature:JSON.stringify([item.id,item.name,item.qty,item.unit,item.supplier,item.area,item.private,item.note]),name:item.name,qty:Number(item.qty),unit:item.unit||'Stück',supplier:name,area:shoppingAreas173.some(a=>a[0]===item.area)?item.area:'unassigned',private:!!item.private,note:item.note||'',manual174:true})}result.pickup.sort((a,b)=>a.name.localeCompare(b.name,'de'));return result};\nconst shoppingPurchasePlanBefore174=shoppingPurchasePlan;\nshoppingPurchasePlan=function(row){if(row?.manual174)return{ok:true,label:formatQtySmart(row.qty,row.unit),detail:(row.private?'Privater Einkauf · ':'Freier Eintrag · ')+(row.note||'kein Lagerzugang')};return shoppingPurchasePlanBefore174(row)};\nconst shoppingFinishButtonBefore174=shoppingFinishButton;\nshoppingFinishButton=function(group){return shoppingFinishButtonBefore174({...group,rows:group.rows.filter(row=>!row.manual174)})};\nconst shoppingAreaRowBefore174=shoppingAreaRow173;\nshoppingAreaRow173=function(row){if(!row.manual174)return shoppingAreaRowBefore174(row);const done=shoppingRowChecked(row),plan=shoppingPurchasePlan(row),display=shoppingDisplay173(row,plan),id=row.key.slice('manual174:'.length);return `<div class=\"shoppingCheckRow shoppingManualRow174 ${done?'isDone':''}\"><label class=\"shoppingTick173\"><input aria-label=\"${esc(row.name)} ${done?'wieder öffnen':'abhaken'}\" type=\"checkbox\" ${done?'checked':''} onchange=\"setShoppingRowChecked('${escJs(row.key)}',this.checked)\"><span class=\"shoppingNoPrint\">${done?'Erledigt':'Offen'}</span></label><span class=\"shoppingItem173\"><b class=\"shoppingItemName\">${esc(display.headline)}</b>${display.pack?`<span class=\"shoppingPack173\">${esc(display.pack)}</span>`:''}<small>${row.private?'Privat · ':''}Nur Einkaufsliste, kein Lagerartikel${row.note?' · '+esc(row.note):''}</small><span class=\"shoppingManualActions174 shoppingNoPrint\"><button type=\"button\" class=\"soft\" onclick=\"shoppingManualEditStart174('${escJs(id)}')\">Bearbeiten</button><button type=\"button\" class=\"soft\" onclick=\"shoppingManualDeleteAsk174('${escJs(id)}')\">Entfernen</button></span></span></div>`};\nconst openShoppingReceiptBefore174=openShoppingReceipt;\nopenShoppingReceipt=function(supplier){const normal=shoppingChannels().pickup.find(g=>g.name===supplier)?.rows.some(r=>!r.manual174&&shoppingRowChecked(r));if(!normal){showFeedback('Kein Lagerartikel ausgewählt','info',3500,'Freie Einträge kannst du abhaken. Sie werden nicht ins Lager gebucht.');return}return openShoppingReceiptBefore174(supplier)};\nfunction shoppingManualOptions174(){const suppliers=(state.supplierProfiles||[]).filter(s=>s.method==='pickup'&&!s.preorder&&s.name).map(s=>s.name);return [...new Set(suppliers)]}\nfunction shoppingManualForm174(){const current=(state.shoppingChecks?._manual174||[]).find(x=>x.id===shoppingManualEdit174),supplier=current?.supplier||shoppingManualOptions174()[0]||'Privat / anderer Markt',list=shoppingManualOptions174();return `<details class=\"shoppingManualAdd174 shoppingNoPrint\" ${current?'open':''}><summary>+ Freien Artikel auf die Einkaufsliste setzen</summary><form onsubmit=\"return shoppingManualSave174(this)\" autocomplete=\"off\"><p>Für Dinge wie Kaffee oder privat gekauftes Salz. Dieser Eintrag bleibt ausschließlich auf der Einkaufsliste.</p><div class=\"shoppingManualGrid174\"><label>Was brauchst du?<input name=\"name\" required maxlength=\"160\" value=\"${esc(current?.name||'')}\" placeholder=\"z. B. Kaffee\"></label><label>Menge<input name=\"qty\" required inputmode=\"decimal\" value=\"${esc(current?.qty||'1')}\" placeholder=\"z. B. 2\"></label><label>Einheit<input name=\"unit\" required maxlength=\"32\" list=\"shoppingManualUnits174\" value=\"${esc(current?.unit||'Packung')}\"></label><label>Geschäft / Lieferant<input name=\"supplier\" required maxlength=\"100\" list=\"shoppingManualSuppliers174\" value=\"${esc(supplier)}\"></label><label>Einkaufsbereich<select name=\"area\">${shoppingAreaOptions173(current?.area||'unassigned')}</select></label><label>Hinweis (optional)<input name=\"note\" maxlength=\"160\" value=\"${esc(current?.note||'')}\" placeholder=\"z. B. ganze Bohnen\"></label></div><datalist id=\"shoppingManualUnits174\"><option value=\"Stück\"><option value=\"Packung\"><option value=\"kg\"><option value=\"g\"><option value=\"Liter\"><option value=\"ml\"><option value=\"Dose\"><option value=\"Flasche\"><option value=\"Karton\"></datalist><datalist id=\"shoppingManualSuppliers174\">${list.map(name=>`<option value=\"${esc(name)}\">`).join('')}<option value=\"Privat / anderer Markt\"></datalist><label class=\"shoppingManualPrivate174\"><input name=\"private\" type=\"checkbox\" ${current?.private?'checked':''}>Privater Einkauf</label><div class=\"shoppingManualActions174\"><button class=\"primary\" type=\"submit\">${current?'Änderung speichern':'Zur Liste hinzufügen'}</button>${current?'<button class=\"soft\" type=\"button\" onclick=\"shoppingManualCancel174()\">Abbrechen</button>':''}</div></form></details>`}\nfunction shoppingManualSave174(form){if(!guardPermission('shopping','Freien Einkaufsartikel speichern'))return false;const name=form.elements.name.value.trim(),unit=form.elements.unit.value.trim(),supplier=form.elements.supplier.value.trim(),note=form.elements.note.value.trim(),qty=Number(String(form.elements.qty.value).trim().replace(',','.')),area=form.elements.area.value,profile=supplierProfileFor(supplier);if(!name||!unit||!supplier||!Number.isFinite(qty)||qty<=0||qty>100000||!shoppingAreas173.some(a=>a[0]===area)){showFeedback('Angaben prüfen','warn',3500,'Name, positive Menge, Einheit, Geschäft und Bereich angeben.');return false}if(profile&&(profile.method!=='pickup'||profile.preorder)){showFeedback('Bestellweg prüfen','warn',4500,'Dieser Lieferant wird separat bestellt. Für den Einkauf vor Ort bitte ein anderes Geschäft wählen.');return false}const previous=clone(state.shoppingChecks||{});state.shoppingChecks??={};const list=state.shoppingChecks._manual174??(state.shoppingChecks._manual174=[]);const id=shoppingManualEdit174||('item-'+(globalThis.crypto?.randomUUID?.()||Date.now()+'-'+Math.random().toString(36).slice(2)));const next={id,name:name.slice(0,160),qty,unit:unit.slice(0,32),supplier:profile?.name||supplier.slice(0,100),area,private:!!form.elements.private.checked,note:note.slice(0,160)};const index=list.findIndex(x=>x.id===id);if(index<0)list.push(next);else list[index]=next;delete state.shoppingChecks['manual174:'+id];if(!persistState('Freier Einkaufsartikel gespeichert',false)){state.shoppingChecks=previous;return false}shoppingManualEdit174='';renderSelfShopping();return false}\nfunction shoppingManualEditStart174(id){if(!guardPermission('shopping','Einkaufsartikel bearbeiten'))return;shoppingManualEdit174=id;renderSelfShopping();const visible=[...document.querySelectorAll('.shoppingManualAdd174')].find(e=>e.getClientRects().length);if(visible){visible.open=true;visible.scrollIntoView({block:'nearest'});visible.querySelector('[name=\"name\"]')?.focus()}}\nfunction shoppingManualCancel174(){shoppingManualEdit174='';renderSelfShopping()}\nfunction shoppingManualDeleteAsk174(id){if(!guardPermission('shopping','Freien Einkaufsartikel entfernen'))return;const item=(state.shoppingChecks?._manual174||[]).find(x=>x.id===id);if(!item)return;askDeleteConfirm(`„${item.name}“ von der Einkaufsliste entfernen?`,()=>{const previous=clone(state.shoppingChecks||{});state.shoppingChecks._manual174=state.shoppingChecks._manual174.filter(x=>x.id!==id);delete state.shoppingChecks['manual174:'+id];if(!persistState('Eintrag entfernt',false))state.shoppingChecks=previous;if(shoppingManualEdit174===id)shoppingManualEdit174='';renderSelfShopping()},'Eintrag entfernen')}\n"},{"src":"patch175.js","code":"/* Rezeptzutaten: auch Treffer mitten im Artikelnamen während der Eingabe anbieten. */\nconst ingredientRowHtmlBefore175 = ingredientRowHtml;\ningredientRowHtml = function (...args) {\n  return ingredientRowHtmlBefore175(...args)\n    .replace(/ list=\"ingredientNames_[^\"]+\"/, '')\n    .replace('onkeydown=\"ingredientNameKey(event,this,', 'onkeydown=\"ingredientSearchKey175(event,this,')\n    .replace('<input type=\"hidden\" class=\"riSupplier\"', '<div class=\"ingredientSuggestions175\" role=\"listbox\" hidden></div><input type=\"hidden\" class=\"riSupplier\"');\n};\n\nconst updateIngredientMatchHintBefore175 = updateIngredientMatchHint;\nupdateIngredientMatchHint = function (input, gi, ri) {\n  updateIngredientMatchHintBefore175(input, gi, ri);\n  const menu = input.closest('.ingredientRow')?.querySelector('.ingredientSuggestions175');\n  if (!menu) return;\n  const query = mobileNormalize(input.value.trim());\n  const kind = input.closest('.ingredientRow')?.dataset.kind || 'food';\n  const hits = query.length < 2 ? [] : Object.entries(state.products || {})\n    .filter(([, article]) => (kind === 'packaging' ? isPackagingArticle(article) : !article.cat || article.cat === 'Lebensmittel') &&\n      mobileNormalize([article.name, ...(Array.isArray(article.synonyms) ? article.synonyms : [])].join(' ')).includes(query))\n    .sort((a, b) => {\n      const startsA = mobileNormalize(a[1].name).startsWith(query) ? 0 : 1;\n      const startsB = mobileNormalize(b[1].name).startsWith(query) ? 0 : 1;\n      return startsA - startsB || a[1].name.localeCompare(b[1].name, 'de');\n    }).slice(0, 8);\n  menu.innerHTML = hits.map(([code, article]) =>\n    `<button type=\"button\" role=\"option\" aria-selected=\"false\" data-code=\"${esc(code)}\" onmousedown=\"event.preventDefault()\" onclick=\"chooseIngredient175(this,${gi},${ri})\">${esc(article.name)}<small>${esc(code)}</small></button>`\n  ).join('');\n  menu.hidden = hits.length === 0;\n};\n\nfunction chooseIngredient175(button, gi, ri) {\n  const row = button.closest('.ingredientRow');\n  const code = button.dataset.code;\n  const article = state.products?.[code];\n  if (!row || !article) return;\n  const input = row.querySelector('.riName');\n  input.value = recipeProductBaseName(article);\n  updateIngredientMatchHintBefore175(input, gi, ri);\n  const ref = row.querySelector('.riRef');\n  if (![...ref.options].some(option => option.value === `product:${code}`)) return;\n  ref.value = `product:${code}`;\n  row.querySelector('.ingredientSuggestions175').hidden = true;\n  changeRecipeVariant(ref, gi, ri);\n}\n\nfunction ingredientSearchKey175(event, input, gi, ri) {\n  const menu = input.closest('.ingredientRow')?.querySelector('.ingredientSuggestions175');\n  const buttons = menu && !menu.hidden ? [...menu.querySelectorAll('button')] : [];\n  if (buttons.length && ['ArrowDown', 'ArrowUp'].includes(event.key)) {\n    event.preventDefault();\n    const selected = buttons.findIndex(button => button.getAttribute('aria-selected') === 'true');\n    const next = (selected + (event.key === 'ArrowDown' ? 1 : buttons.length - 1)) % buttons.length;\n    buttons.forEach((button, index) => button.setAttribute('aria-selected', String(index === next)));\n    return;\n  }\n  if (event.key === 'Escape' && menu) { menu.hidden = true; return; }\n  if (event.key === 'Enter' && buttons.length) {\n    event.preventDefault();\n    chooseIngredient175(buttons.find(button => button.getAttribute('aria-selected') === 'true') || buttons[0], gi, ri);\n    return;\n  }\n  ingredientNameKey(event, input, gi, ri);\n}\n\n/* Von „1 Portion“ zu „450 g je Portion“ wechseln, ohne 1 g Gesamtmenge zu erzeugen. */\nconst recipeEditorHtmlBefore175 = recipeEditorHtml;\nrecipeEditorHtml = function (...args) {\n  const gi = args[2], ri = args[3];\n  return recipeEditorHtmlBefore175(...args)\n    .replace('<div class=\"field\"><label>Verkaufspreis netto /', '<div class=\"field recipePriceField175\"><label>Verkaufspreis netto /')\n    .replace(/(id=\"recipeSellPrice_[^\"]+\"[^>]* oninput=\")scheduleRecipeAutosave\\(/,\n      '$1formatRecipePriceInput175(this);scheduleRecipeAutosave(')\n    .replace(/(<input id=\"recipeSellPrice_[^>]+)>/,\n      `$1 onblur=\"formatRecipePriceInput175(this,true);scheduleRecipeAutosave(${gi},${ri})\">`)\n    .replace(/(id=\"recipeYieldQty_[^\"]+\"[^>]* oninput=\")refreshRecipePortionPreview108\\(/,\n      '$1manualRecipeYield175(this);refreshRecipePortionPreview108(')\n    .replace(/(id=\"recipePortionQty_[^\"]+\"[^>]* oninput=\")refreshRecipePortionPreview108\\(/,\n      `$1syncPortionSize175(${gi},${ri});refreshRecipePortionPreview108(`)\n    .replace(/(id=\"recipePortionUnit_[^\"]+\"[^>]* onchange=\")refreshRecipePortionPreview108\\(/,\n      `$1syncPortionSize175(${gi},${ri});refreshRecipePortionPreview108(`);\n};\n\nfunction formatRecipePriceInput175(input, complete = false) {\n  const raw = input.value.replace(/[\\s€]/g, '');\n  const normalized = raw.includes(',') ? raw.replace(/\\./g, '') :\n    /\\.\\d{1,2}$/.test(raw) ? raw.replace('.', ',') : raw.replace(/\\./g, '');\n  const [whole, ...decimals] = normalized.replace(/[^\\d,]/g, '').split(',');\n  const text = (whole || '') + (decimals.length ? ',' + decimals.join('').slice(0, 2) : '');\n  input.value = complete && text ? (Number(text.replace(',', '.')) || 0).toFixed(2).replace('.', ',') : text;\n}\n\nconst toggleRecipePortioningBefore175 = toggleRecipePortioning108;\ntoggleRecipePortioning108 = function (gi, ri, checkbox) {\n  const qty = document.getElementById(`recipeYieldQty_${gi}_${ri}`);\n  const unit = document.getElementById(`recipeYieldUnit_${gi}_${ri}`);\n  const portionQty = document.getElementById(`recipePortionQty_${gi}_${ri}`);\n  const portionUnit = document.getElementById(`recipePortionUnit_${gi}_${ri}`);\n  if (checkbox.checked && unit?.value === 'Portionen' && qty && portionQty && portionUnit) {\n    const count = Number(qty.value);\n    const size = Number(portionQty.value);\n    if (count > 0 && size > 0) {\n      checkbox.dataset.autoPortionCount175 = String(count);\n      unit.value = portionUnit.value;\n      applyQtyRules(qty, unit.value, false);\n      qty.value = String(count * size);\n    }\n  } else if (!checkbox.checked && checkbox.dataset.autoPortionCount175 && qty && unit) {\n    unit.value = 'Portionen';\n    qty.value = checkbox.dataset.autoPortionCount175;\n    delete checkbox.dataset.autoPortionCount175;\n    applyQtyRules(qty, unit.value, false);\n  }\n  toggleRecipePortioningBefore175(gi, ri, checkbox);\n};\n\nfunction manualRecipeYield175(input) {\n  const checkbox = input.closest('.recipeEditor')?.querySelector('[id^=\"recipePortionEnabled_\"]');\n  if (checkbox) delete checkbox.dataset.autoPortionCount175;\n}\n\nfunction syncPortionSize175(gi, ri) {\n  const checkbox = document.getElementById(`recipePortionEnabled_${gi}_${ri}`);\n  if (!checkbox?.checked || !checkbox.dataset.autoPortionCount175) return;\n  const qty = document.getElementById(`recipeYieldQty_${gi}_${ri}`);\n  const unit = document.getElementById(`recipeYieldUnit_${gi}_${ri}`);\n  const portionQty = document.getElementById(`recipePortionQty_${gi}_${ri}`);\n  const portionUnit = document.getElementById(`recipePortionUnit_${gi}_${ri}`);\n  if (!qty || !unit || !portionQty || !portionUnit) return;\n  unit.value = portionUnit.value;\n  applyQtyRules(qty, unit.value, false);\n  qty.value = String(Number(checkbox.dataset.autoPortionCount175) * Number(portionQty.value));\n}\n"},{"src":"patch176.js","code":"/* Nicht gewählte optionale Beilagen gehören nicht in die Zutaten-Warnungen. */\nconst expandRecipeIntoBefore176 = expandRecipeInto;\nexpandRecipeInto = function (acc, recipeId, targetQty, targetUnit, sourceLabel = '', stack = []) {\n  const recipe = findRecipeById(recipeId)?.recipe;\n  if (!recipe?.ingredients?.some(ingredient => ingredient.optionalTopping === true))\n    return expandRecipeIntoBefore176(acc, recipeId, targetQty, targetUnit, sourceLabel, stack);\n  const original = recipe.ingredients;\n  const choices = stack.length ? {} : optionalExpansionContext138 || {};\n  recipe.ingredients = original.map((ingredient, index) => {\n    if (ingredient.optionalTopping !== true || Number(choices[optionalIngredientKey138(recipeId, index)]) > 0)\n      return ingredient;\n    // Platzhalter behält den Index für Fehlermeldungen anderer Zutaten bei.\n    return { kind: ingredient.kind, optionalTopping: true, qty: 0 };\n  });\n  try {\n    return expandRecipeIntoBefore176(acc, recipeId, targetQty, targetUnit, sourceLabel, stack);\n  } finally {\n    recipe.ingredients = original;\n  }\n};\n"},{"src":"patch177.js","code":"/* Einkaufspreis der ganzen Dose und Rezeptpreis nach Abtropfgewicht erklären. */\nconst updatePurchaseQuoteBefore177 = updatePurchaseQuote;\nupdatePurchaseQuote = function (context = 'article') {\n  const result = updatePurchaseQuoteBefore177(context);\n  if (context !== 'article' || !document.getElementById('aeDrainWeight')?.checked) return result;\n  const capture = drainCapture135();\n  const pack = chosenDrainPack135(capture);\n  const box = document.getElementById('aePriceCalculation');\n  if (!box || !pack || !(capture.amount > 0)) return result;\n  const usableKg = convertUnit(pack.usable, pack.usableUnit, 'kg');\n  const grossKg = convertUnit(pack.gross, pack.grossUnit, 'kg');\n  const grossPrice = grossKg > 0 ? `${quoteMoney(capture.amount / grossKg)} je kg Inhalt samt Flüssigkeit` : '';\n  const usablePrice = usableKg > 0 ? `${quoteMoney(capture.amount / usableKg)} je kg nutzbar` : '';\n  box.innerHTML = `<b>Einkauf: 1 ${esc(pack.name)} kostet ${quoteMoney(capture.amount)} netto.</b>` +\n    `<span>${esc(formatQtySmart(pack.gross, pack.grossUnit))} laut Etikett${grossPrice ? ` · ${grossPrice}` : ''}</span>` +\n    `<span>${esc(formatQtySmart(pack.usable, pack.usableUnit))} Abtropfgewicht${usablePrice ? ` · ${usablePrice}` : ''}</span>` +\n    `<small>Die ganze Dose kostet ${quoteMoney(capture.amount)}. Für Rezepte wird die tatsächlich verwendete nutzbare Menge berechnet; bei Verwendung des gesamten Abtropfgewichts sind das wieder ${quoteMoney(capture.amount)}.</small>`;\n  return result;\n};\n"},{"src":"recipe-vegan180.js","code":"/* Vegan applies to a recipe only when every food ingredient is explicitly confirmed. */\nfunction recipeIsVegan180(recipe,visiting=new Set()){\n if(!recipe||visiting.has(recipe.id))return false;\n const ingredients=(recipe.ingredients||[]).filter(ing=>ing.kind!=='packaging');\n if(!ingredients.length)return false;\n const next=new Set(visiting);next.add(recipe.id);\n return ingredients.every(ing=>{\n  if(ing.recipeId)return recipeIsVegan180(findRecipeById(ing.recipeId)?.recipe,next);\n  const code=ing.productCode||(ing.productId&&productCodeByInternalId(ing.productId));\n  if(code)return isVegan130(state.products?.[code]);\n  if(ing.mainArticleId){const members=mainMembers(ing.mainArticleId);return members.length>0&&members.every(([,article])=>isVegan130(article))}\n  if(ing.valuation==='average'){const codes=ingredientCandidates(ing);return codes.length>0&&codes.every(candidate=>isVegan130(state.products?.[candidate]))}\n  return false;\n });\n}\nconst cookbookCardSummaryBefore180=cookbookCardSummary;\ncookbookCardSummary=function(group,recipe,index){\n const html=cookbookCardSummaryBefore180(group,recipe,index);\n return recipeIsVegan180(recipe)?html.replace(`<h3>${esc(recipe.name)}</h3>`,`<h3>${esc(recipe.name)} <span class=\"recipeVegan180\" aria-label=\"Vegan\">Vegan</span></h3>`):html;\n};\n"},{"src":"photo179.js?v=188","code":"'use strict';\n/* Keep the fine print readable for on-device OCR. The small preview is only used for visual comparison. */\nfunction ocrData179(file){return new Promise((resolve,reject)=>{\n  const image=new Image(),url=URL.createObjectURL(file);\n  image.onload=()=>{try{const scale=Math.min(1,2000/Math.max(image.width,image.height)),canvas=document.createElement('canvas');\n    canvas.width=Math.max(1,Math.round(image.width*scale));canvas.height=Math.max(1,Math.round(image.height*scale));\n    canvas.getContext('2d').drawImage(image,0,0,canvas.width,canvas.height);\n    resolve(canvas.toDataURL('image/jpeg',.85));}catch(error){reject(error)}finally{URL.revokeObjectURL(url)}};\n  image.onerror=()=>{URL.revokeObjectURL(url);reject(Error('Bild konnte nicht geöffnet werden'))};image.src=url;\n})}\nfunction mhdSuggestion179(ocr){\n  const found=[...String(ocr||'').matchAll(/\\b([0-3]?\\d)[.\\/-]([01]?\\d)[.\\/-](20\\d{2})\\b/g)]\n    .map(match=>{const d=Number(match[1]),m=Number(match[2]),y=Number(match[3]),date=new Date(Date.UTC(y,m-1,d));\n      return date.getUTCFullYear()===y&&date.getUTCMonth()===m-1&&date.getUTCDate()===d?`${y}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}`:''}).filter(Boolean);\n  return new Set(found).size===1?found[0]:'';\n}\n// OCR may read the digits printed below a barcode when image decoding fails.\n// Only use complete GTINs with a correct GS1 check digit as article hints.\nfunction validGtin179(value){\n const code=String(value||'');\n if(!/^(?:\\d{8}|\\d{12,14})$/.test(code))return false;\n let sum=0;\n for(let i=code.length-2,weight=3;i>=0;i--,weight=weight===3?1:3)\n   sum+=Number(code[i])*weight;\n return (10-sum%10)%10===Number(code.at(-1));\n}\nfunction ocrBarcodeCandidates179(text){\n const candidates=new Set();\n for(const line of String(text||'').split(/\\r?\\n/)){\n  // Printed GTINs may be grouped with spaces. Do not join different lines.\n  for(const match of line.matchAll(/(?:^|[^\\d])((?:\\d[\\s-]?){7,13}\\d)(?!\\d)/g)){\n    const code=match[1].replace(/[\\s-]/g,'');\n    if(validGtin179(code))candidates.add(code);\n  }\n }\n return [...candidates].slice(0,5);\n}\nfunction photoCandidates179(unique){\n  // Visual hashes are unstable across front/side/back views and lighting.\n  // Keep plausible visual matches as explicit suggestions, never confirmations.\n  const primary=unique.filter(hit=>hit.source!=='photo');\n  // Mixed shelves scored around .62–.66 against unrelated reference photos.\n  // Those weak visual matches would hide the actual product in the first three.\n  const visual=unique.filter(hit=>hit.source==='photo'&&hit.score>=.72);\n  return [...primary,...visual].slice(0,3);\n}\nfunction savePhotoReference179(){\n  const draft=photoDraft144,a=draft?.code&&state.products?.[draft.code];\n  if(!a||!draft.captured179||!guardPermission('articles','Referenzfoto speichern'))return;\n  const before=clone(a.referencePhotos144||[]),refs=a.referencePhotos144??(a.referencePhotos144=[]);\n  if(refs.some(ref=>ref.data===draft.captured179)){showFeedback('Foto ist bereits als Referenz gespeichert','info');return}\n  if(refs.length>=6){showFeedback('Maximal sechs Referenzfotos pro Artikel','warn');return}\n  refs.push({id:'ref-'+Date.now()+'-'+Math.random().toString(36).slice(2),data:draft.captured179,packIndex:Number(draft.packIndex)||0});\n  if(!persistState('Referenzfoto gespeichert',false))a.referencePhotos144=before;\n  else showFeedback('Referenzfoto gespeichert','success');\n}\nconst photoRenderBefore179=renderPhoto144;\nrenderPhoto144=function(){\n  photoRenderBefore179();const draft=photoDraft144,body=document.getElementById('photoBody144');\n  if(!draft||!body)return;\n  if(draft.step==='confirm'&&draft.captured179&&hasPermission('articles')&&!body.querySelector('.photoReference179')){\n    const actions=body.querySelector('.scanActions146,.photoActionSection149');\n    if(actions)actions.insertAdjacentHTML('afterend','<button type=\"button\" class=\"soft photoReference179\" onclick=\"savePhotoReference179()\">Dieses Foto als Referenz bestätigen</button>');\n    else body.insertAdjacentHTML('beforeend','<button type=\"button\" class=\"soft photoReference179\" onclick=\"savePhotoReference179()\">Dieses Foto als Referenz bestätigen</button>');\n  }\n  if(draft.step==='mhd'&&draft.mhdSuggestion179){\n    const input=body.querySelector('#photoDate144');\n    if(input&&!body.querySelector('.photoMhdHint179')){\n      input.insertAdjacentHTML('afterend',`<small class=\"photoMhdHint179\">Auf dem Foto erkannt: ${esc(formatDate(draft.mhdSuggestion179))}. MHD bitte selbst vom Produkt ablesen und eingeben.</small>`);\n    }\n  }\n};\n"},{"src":"label183.js?v=422","code":"'use strict';\n/* Local label reading prepares an article draft. Nothing is persisted here. */\nfunction parseLabel183(text, decodedBarcode='') {\n  const raw=String(text||'').replace(/\\r/g,''), lines=raw.split('\\n').map(x=>x.trim()).filter(Boolean);\n  const companyLine422=x=>/\\b(?:gmbh|ges\\.?\\s*m\\.?\\s*b\\.?\\s*h|s\\.?r\\.?o\\.?|sp\\.?\\s*z\\.?\\s*o\\.?\\s*o\\.?)\\b/i.test(x)&&!/^\\d/.test(x);\n  const clean=lines.map(x=>x.replace(/\\s+/g,' ')).filter(x=>!companyLine422(x));\n  const packagingLine=clean.find(x=>/^(?:deckel\\s+für\\s+verpackungsbecher|lids?\\s+for\\s+packaging\\s+cups?|verpackungsbecher|(?:riffel[-\\s]*)?suppenbecher|(?:ribbed\\s+)?soup\\s+cups?)\\b/i.test(x));\n  const napkins=clean.some(x=>/\\b(?:servietten|serviettes|servilletas|napkins)\\b/i.test(x));\n  const packaging=!!packagingLine||napkins;\n  const diameter=packaging?raw.match(/[Ø⌀]\\s*(\\d{1,3}(?:[,.]\\d{1,2})?)\\s*(mm|cm)\\b/i):null;\n  const consumerInfo=clean.findIndex(x=>/information\\s+für\\s+den\\s+verbraucher/i.test(x));\n  const productBeforeInfo=consumerInfo>0?clean.slice(Math.max(0,consumerInfo-3),consumerInfo).reverse().find(x=>x.length>=5&&x.length<=90&&!/(?:gmbh|straße|strasse|\\d{4}\\s+\\w+|^AT\\s+\\d+)/i.test(x)):'';\n  // Some fronts put a short brand above a product name split across two lines.\n  const titleWord=x=>/^[\\p{Lu}ÄÖÜ][\\p{Lu}ÄÖÜ]{2,17}$/u.test(x||'');\n  const titlePart=x=>x.charAt(0)+x.slice(1).toLocaleLowerCase('de');\n  const brandedTitle=clean[0]?.length<=18&&/^[\\p{L} &.\\-]+$/u.test(clean[0])&&/[\\p{Ll}äöü]/u.test(clean[0])&&\n    titleWord(clean[1])&&titleWord(clean[2])\n    ?`${clean[0]} ${titlePart(clean[1])}${/^GEMÜSE$/i.test(clean[1])&&/^BRÜHE$/i.test(clean[2])?'':' '}${clean[2].toLocaleLowerCase('de')}`:'';\n  const brandProduct=!brandedTitle&&clean[0]?.length<=18&&/^[\\p{L} &.\\-]+$/u.test(clean[0])&&/[\\p{Ll}äöü]/u.test(clean[0])&&\n    /^[\\p{Lu}ÄÖÜ ]{5,55}$/u.test(clean[1]||'')\n    ?`${clean[0]} ${clean[1].split(' ').map(titlePart).join(' ')}`:'';\n  const napkinSize=napkins?raw.match(/\\b(\\d{2})\\s*[x×]\\s*(\\d{2})\\s*(?:cm)?\\b/i):null;\n  const napkinName=napkins?`${clean.find(x=>/garcia\\s+de\\s+pou/i.test(x))||''} Servietten${clean.some(x=>/double\\s*point/i.test(x))?' Double Point':''}${napkinSize?` ${napkinSize[1]} × ${napkinSize[2]} cm`:''}`.trim():'';\n  // Prefer a readable product heading over stray OCR fragments above the label.\n  const productHeading=clean.find(x=>/^[\\p{Lu}ÄÖÜ][\\p{Lu}ÄÖÜ -]{7,65}$/u.test(x)&&!/(?:GMBH|ZUTATEN|NÄHRWERTE|HERSTELLER|MINDESTENS|HALTBAR|ABTROPFGEWICHT|FÜLLGEWICHT|NETTOGEWICHT)/i.test(x))||'';\n  const baseName=packaging?(napkinName||clean.find(x=>/^(?:riffel[-\\s]*)?suppenbecher\\b/i.test(x))||clean.find(x=>/^deckel\\s+für\\s+verpackungsbecher\\b/i.test(x))||packagingLine):\n    productBeforeInfo||brandedTitle||brandProduct||productHeading||clean.find(x=>x.length>=5&&x.length<=90&&!/^(zutaten|ursprungsland|gekühlt|ungeöffnet|mindestens|haltbar|nach dem|packer|hersteller|bestellnummer|artikelnummer|nährwerte|brennwert|fett|energie|eiweiß|www\\.|\\d)/i.test(x)&&!/[\\d]{2}[.\\/-][\\d]{2}[.\\/-](?:20)?\\d\\d/.test(x))||'';\n  const name=diameter&&baseName&&!/[Ø⌀]/.test(baseName)?`${baseName} Ø ${diameter[1].replace('.',',')} ${diameter[2]}`:baseName;\n  // A count on packaging is the contents of a purchased pack, never starting stock.\n  const count=packaging?clean.find(x=>/^(?:\\d{1,4}\\s*(?:(?:stück|stk\\.?|pcs?\\.?|pieces)(?:\\s*\\/\\s*stick)?)?|(?:inhalt|menge|packungsinhalt)\\s*:\\s*\\d{1,4}\\s*(?:stück|stk\\.?))$/i.test(x)):null;\n  const packCount=count?Number(count.match(/\\d+/)?.[0]):0;\n  const articleNumber=packaging?(raw.match(/\\bArt\\.?\\s*[-.]?\\s*Nr\\.?\\s*:?\\s*(\\d{3,10})\\b/i)?.[1]||''):'';\n  const weightByDate=!packaging?raw.match(/(?:^|\\n)\\s*(\\d{1,3}[,.]\\d{3})\\s*kg\\s*(?:\\n[^\\n]{0,60})?\\n\\s*(?:mindestens\\s+haltbar\\s+bis|mhd)\\b/i):null;\n  const packWeightHint=weightByDate?Number(weightByDate[1].replace(',','.')):0;\n  const amount='(\\\\d{1,4}(?:[,.]\\\\d{1,3})?)\\\\s*(kg|g|ml|l)\\\\b';\n  const marked=raw.match(new RegExp('(?:Füllgewicht|Fuellgewicht|Inhalt|Nettomenge|Netto(?:gewicht|inhalt)?|Net weight)\\\\s*[:：]?\\\\s*(?:℮|e)?\\\\s*'+amount,'i'));\n  const drained=raw.match(new RegExp('(?:Abtropfgewicht|Abtropfmasse|drained\\\\s+weight)\\\\s*[:：]?\\\\s*'+amount,'i'));\n  // A standalone pack weight is safe only with the estimated sign. Nutrition tables often say g/100g.\n  const estimated=raw.match(new RegExp(amount+'\\\\s*(?:/r)?\\\\s*(?:℮|e)(?![a-z])','i'));\n  const prefixed=raw.match(new RegExp('(?:℮|e)\\\\s*'+amount,'i'));\n  // A standalone kilogram line on a pack front can lack the estimated-content sign.\n  // The three-decimal variable weight of an individual meat pack is excluded.\n  const standaloneKg=!packWeightHint?clean.find(x=>/^\\d{1,3}(?:[,.]\\d{1,2})?\\s*kg$/i.test(x)):'';\n  const plainKg=standaloneKg?.match(/^(\\d{1,3}(?:[,.]\\d{1,2})?)\\s*(kg)$/i);\n  const content=marked||estimated||(prefixed?[prefixed[0],prefixed[1],prefixed[2]]:null)||plainKg;\n  const candidate=String(decodedBarcode||'').trim().replace(/\\s/g,'');\n  const gs1=readGs1Label183(candidate)||readGs1Label183(raw);\n  const printed=typeof ocrBarcodeCandidates179==='function'?ocrBarcodeCandidates179(raw):[];\n  const decoded=gs1?.gtin||(typeof validGtin179==='function'&&validGtin179(candidate)?candidate:printed.length===1?printed[0]:'');\n  const variableBarcode=!!(packWeightHint&&/^2\\d{12}$/.test(decoded));\n  const barcode=variableBarcode?'':decoded;\n  const temperature=raw.match(/([+-]?\\d{1,2})\\s*(?:bis|–|-)\\s*([+-]?\\d{1,2})\\s*°\\s*C/i);\n  const supplier=raw.match(/(?:Packer|Hersteller|produziert\\s+von)\\s*:?\\s*([^\\n]+)/i)?.[1]?.trim()||lines.find(companyLine422)||'';\n  const origin=raw.match(/(?:Ursprungsland|Ursprung)\\s*:\\s*([^\\n]+)/i)?.[1]?.trim()||'';\n  const ingredients=raw.match(/Zutaten\\s*:\\s*([^\\n]+)(?:\\n(?!Ursprungsland|Packer|Hersteller|Ungeöffnet|Nach dem|haltbar\\s+bis)([^\\n]+))?/i);\n  const capturedWeight=content?{quantity:Number(content[1].replace(',','.')),unit:content[2].toLowerCase()==='l'?'Liter':content[2].toLowerCase()}:null;\n  const drainedWeight=drained?{quantity:Number(drained[1].replace(',','.')),unit:drained[2].toLowerCase()==='l'?'Liter':drained[2].toLowerCase()}:null;\n  const gs1Weight=gs1?.weightGrams?{quantity:gs1.weightGrams,unit:'g'}:null;\n  const suggestedWeight=capturedWeight||gs1Weight;\n  const inconsistent=!!(capturedWeight&&gs1Weight&&Math.abs((capturedWeight.unit==='kg'?capturedWeight.quantity*1000:capturedWeight.quantity)-gs1Weight.quantity)>1);\n  return {name,barcode,barcodeSource:gs1?.gtin?'GS1':barcode===candidate&&barcode?'scan':barcode?'druck':'',\n    packaging,packCount:packCount>0?packCount:0,articleNumber,packWeightHint,variableBarcode,\n    content:inconsistent?null:suggestedWeight,drained:drainedWeight,contentConflict:inconsistent,\n    temperature:temperature?`${temperature[1]} bis ${temperature[2]} °C`:'',\n    supplier,origin,ingredients:ingredients?[ingredients[1],ingredients[2]].filter(Boolean).join(' ').trim():'',\n    mhdPolicy:/\\b(?:mindestens\\s+haltbar|haltbar\\s+bis|zu\\s+verbrauchen\\s+bis)\\b/i.test(raw),\n    mhdDate:bestBeforeLabel183(raw,gs1?.bestBefore||'')};\n}\nfunction bestBeforeLabel183(text,gs1Date=''){\n  const dates=new Set(),long=typeof mhdSuggestion179==='function'?mhdSuggestion179(text):'';\n  if(long)dates.add(long);\n  if(/\\b(?:haltbar\\s+bis|mindestens\\s+haltbar|best\\s+before)\\b/i.test(text))for(const match of text.matchAll(/\\b([0-3]?\\d)[.\\/-]([01]?\\d)[.\\/-](\\d\\d)\\b/g)){\n    const day=Number(match[1]),month=Number(match[2]),year=2000+Number(match[3]);\n    const date=new Date(Date.UTC(year,month-1,day));\n    if(date.getUTCFullYear()===year&&date.getUTCMonth()===month-1&&date.getUTCDate()===day)dates.add(`${year}-${String(month).padStart(2,'0')}-${String(day).padStart(2,'0')}`);\n  }\n  if(gs1Date)dates.add(gs1Date);\n  return dates.size===1?[...dates][0]:'';\n}\nfunction readGs1Label183(value){\n  const text=String(value||'').replace(/\\s+/g,'');\n  // Parse only explicit application identifiers, never a run of untagged digits.\n  const native=text.match(/^\\]C101(\\d{14})(?:310(\\d)(\\d{6}))?(?:15(\\d{6}))?$/);\n  const gtin=native?.[1]||text.match(/\\(01\\)(\\d{14})(?!\\d)/)?.[1];\n  if(!gtin||typeof validGtin179!=='function'||!validGtin179(gtin))return null;\n  const weight=native?.[2]?[null,native[2],native[3]]:text.match(/\\(310(\\d)\\)(\\d{6})(?!\\d)/),date=native?.[4]?[null,native[4]]:text.match(/\\(15\\)(\\d{6})(?!\\d)/);\n  let bestBefore='';if(date){const yy=Number(date[1].slice(0,2)),mm=Number(date[1].slice(2,4)),dd=Number(date[1].slice(4,6)),check=new Date(Date.UTC(2000+yy,mm-1,dd));if(check.getUTCFullYear()===2000+yy&&check.getUTCMonth()===mm-1&&check.getUTCDate()===dd)bestBefore=`${2000+yy}-${String(mm).padStart(2,'0')}-${String(dd).padStart(2,'0')}`}\n  return {gtin,weightGrams:weight?Number(weight[2])/10**Number(weight[1])*1000:0,bestBefore};\n}\nlet labelRequest183=0,labelDraft183=null;\nfunction centerLabelImage183(file){return new Promise((resolve,reject)=>{\n  const image=new Image(),url=URL.createObjectURL(file);\n  image.onload=()=>{try{const canvas=document.createElement('canvas'),w=image.naturalWidth,h=image.naturalHeight;\n    canvas.width=1800;canvas.height=1100;\n    const context=canvas.getContext('2d');\n    context.drawImage(image,w*.07,h*.25,w*.84,h*.52,0,0,canvas.width,canvas.height);\n    resolve(canvas.toDataURL('image/jpeg',.89));\n  }catch(error){reject(error)}finally{URL.revokeObjectURL(url)}};\n  image.onerror=()=>{URL.revokeObjectURL(url);reject(Error('Foto konnte nicht geöffnet werden'))};image.src=url;\n})}\nfunction installLabel183(){\n  const form=document.querySelector('#articleModal .articleFormGrid');\n  if(!form||document.getElementById('labelInput183'))return;\n  const box=document.createElement('div');box.className='full labelScan183';\n  box.innerHTML='<button type=\"button\" class=\"soft\" id=\"labelButton183\">Etikett fotografieren und Angaben vorschlagen</button><input id=\"labelInput183\" type=\"file\" accept=\"image/*\" capture=\"environment\" hidden><div id=\"labelReview183\" role=\"status\" aria-live=\"polite\"></div>';\n  form.querySelector('.articleFormSectionHead')?.insertAdjacentElement('afterend',box);\n  box.querySelector('#labelButton183').onclick=()=>box.querySelector('#labelInput183').click();\n  box.querySelector('#labelInput183').onchange=e=>readLabel183(e.target.files?.[0]);\n}\nasync function readLabel183(file){\n  if(!file)return;\n  const request=++labelRequest183,codeAtStart=editingArticleCode;\n  const input=document.getElementById('labelInput183');if(input)input.value='';\n  const review=document.getElementById('labelReview183');review.textContent='Etikett wird lokal gelesen …';\n  try{\n    if(!file.type.startsWith('image/')||file.size>30*1024*1024)throw Error('Bitte ein Bild unter 30 MB wählen.');\n    const data=await ocrData179(file);\n    let [text,decoded]=await Promise.all([requestOcr147(data),zReadBarcode166(file).catch(()=>'')]);\n    if(text.trim().length<45&&typeof window.ZapasioOcr?.recognizeImageText==='function'){\n      const crop=await centerLabelImage183(file);\n      const retry=await requestOcr147(crop);\n      if(retry.trim().length>text.trim().length)text=retry;\n    }\n    if(request!==labelRequest183||editingArticleCode!==codeAtStart||document.getElementById('articleModal')?.style.display!=='flex')return;\n    if(!text&&!decoded){review.textContent='Keine Schrift oder Barcode erkannt. In dieser Umgebung ist möglicherweise keine Texterkennung verfügbar. Bitte Angaben selbst eintragen.';return}\n    labelDraft183=parseLabel183(text,decoded);labelDraft183.preview=data;\n    renderLabel183();\n  }catch(error){if(request===labelRequest183)review.textContent=`Etikett konnte nicht gelesen werden: ${error.message}`}\n}\nfunction renderLabel183(){\n  const box=document.getElementById('labelReview183'),d=labelDraft183;if(!box||!d)return;\n  const entries=[['name','Artikelname',d.name],['barcode','Barcode',d.barcode],['packaging','Artikelart',d.packaging?'Verpackung / Verbrauchsmaterial':''],['packCount','Stück je Packung',d.packCount?`${d.packCount} Stück`:''],['content','Packungsinhalt',d.content?`${d.content.quantity} ${d.content.unit}`:''],['supplier','Packer / Hersteller',d.supplier],['origin','Ursprung laut Etikett',d.origin],['ingredients','Zutaten laut Etikett',d.ingredients],['temperature','Kühlhinweis',d.temperature],['mhdPolicy','MHD bei Wareneingang abfragen',d.mhdPolicy?'Ja':'']].filter(x=>x[2]);\n  box.innerHTML=`<p><b>Erkannte Angaben prüfen</b> · ${d.barcodeSource==='GS1'?'GTIN aus GS1-Feld':d.barcodeSource==='druck'?'Barcode aus gedruckter Nummer':d.barcodeSource==='scan'?'Barcode gescannt':'Barcode offen'}</p>${d.preview?`<img class=\"labelPreview183\" src=\"${d.preview}\" alt=\"Fotografiertes Etikett zur Kontrolle\">`:''}${d.contentConflict?'<p class=\"muted\">Gedruckte Menge und GS1-Gewicht widersprechen sich. Packungsinhalt bitte selbst prüfen.</p>':''}${entries.length?`<div class=\"labelRows183\">${entries.map(([key,title,value])=>`<label><input type=\"checkbox\" data-label-field=\"${key}\" checked><span><b>${esc(title)}</b><small>${esc(String(value))}</small></span></label>`).join('')}</div><button type=\"button\" class=\"soft\" onclick=\"applyLabel183()\">Ausgewählte Vorschläge ins Formular übernehmen</button>`:'<p>Keine eindeutigen Stammdaten erkannt. Bitte von Hand eingeben.</p>'}${d.drained?`<p class=\"muted\">Abtropfgewicht: ${esc(d.drained.quantity)} ${esc(d.drained.unit)}. Getrennt vom Füllgewicht in der Gebindekarte prüfen.</p>`:''}${d.articleNumber?`<p class=\"muted\">Herstellerartikelnummer: ${esc(d.articleNumber)}. Bitte nicht mit dem Barcode verwechseln.</p>`:''}${d.packWeightHint?`<p class=\"muted\">Gewicht dieser Packung: ${esc(String(d.packWeightHint).replace('.',','))} kg. Nicht als festes Artikelgewicht oder Menge einer ganzen Lieferung übernehmen.</p>`:''}${d.variableBarcode?'<p class=\"muted\">Gewichtsabhängiger Barcode: nicht als fester Artikelcode übernehmen.</p>':''}<p class=\"muted\">${d.mhdDate?`Aufgedrucktes MHD ${esc(d.mhdDate)} betrifft nur diese Packung. `:''}Kein Bestand und keine Charge werden aus dem Foto gebucht. Angaben vor dem Speichern prüfen.</p>`;\n}\nfunction applyLabel183(){\n  const d=labelDraft183;if(!d||!document.getElementById('labelReview183'))return;\n  const selected=key=>!!document.querySelector(`#labelReview183 [data-label-field=\"${key}\"]:checked`);\n  if(selected('barcode')&&d.barcode&&!editingArticleCode){const conflict=barcodeConflict(d.barcode,'');if(conflict){showFeedback('Barcode bereits vergeben','warn',5000,barcodeConflictText(conflict));return false}}\n  if(selected('name')&&d.name)document.getElementById('aeName').value=d.name;\n  if(selected('packaging')&&d.packaging){\n    const categories=state.categories||[],packagingCategory=categories.find(c=>/verpack|verbrauch/i.test(c.name));\n    if(packagingCategory){document.getElementById('aeCategory').value=packagingCategory.name;articleCategoryChanged()}\n    const policy=document.getElementById('aeMhdPolicy144');\n    if(policy){policy.value='none';policy.dispatchEvent(new Event('change',{bubbles:true}))}\n  }\n  if(selected('packCount')&&d.packCount){\n      const unit=document.getElementById('aeUnit');if(unit){unit.value='Stück';unit.dispatchEvent(new Event('change',{bubbles:true}))}\n      const packs=document.getElementById('aePacks');\n      let row=packs?.querySelector('.packEditRow:not(.basePack)');\n      if(!row&&packs){addPackEditorRow();row=packs.querySelector('.packEditRow:not(.basePack)')}\n      if(row){row.querySelector('.aePackName').value='Packung';row.querySelector('.aePackQty').value=String(d.packCount);row.querySelector('.aePackUnit').value='Stück';row.querySelector('.aePackName').dispatchEvent(new Event('input',{bubbles:true}));row.querySelector('.aePackQty').dispatchEvent(new Event('input',{bubbles:true}))}\n  }\n  // Manufacturer item numbers are informative; they are not GTINs or Zapasio article codes.\n  if(selected('barcode')&&d.barcode){\n    const code=document.getElementById('aeCode');\n    if(code&&!code.readOnly){code.value=d.barcode;code.dispatchEvent(new Event('input',{bubbles:true}));}\n  }\n  if(selected('content')&&d.content){\n    const categories=state.categories||[],food=categories.find(c=>isFoodCategoryName(c.name));\n    if(food){document.getElementById('aeCategory').value=food.name;articleCategoryChanged()}\n    const qty=document.getElementById('aeContentQty'),unit=document.getElementById('aeContentUnit');\n    if(qty&&unit){qty.value=String(d.content.quantity);unit.value=d.content.unit;}\n  }\n  if(selected('supplier')&&d.supplier){const supplier=document.getElementById('aeSupplier');\n    const option=[...supplier.options].find(o=>o.value.toLocaleLowerCase('de')===d.supplier.toLocaleLowerCase('de'));\n    if(option)supplier.value=option.value;else showFeedback('Packer ist kein hinterlegter Lieferant','info',4500,'Lieferant bitte selbst auswählen.');\n  }\n  if(selected('mhdPolicy')&&d.mhdPolicy){const policy=document.getElementById('aeMhdPolicy144');if(policy){policy.value='required';policy.dispatchEvent(new Event('change',{bubbles:true}))}document.getElementById('aeMhd').checked=true;renderExistingMhdEditor()}\n  const origin=document.querySelector('#aeLmivFields [data-lmiv=\"origin\"]'),notes=document.querySelector('#aeLmivFields [data-lmiv=\"notes\"]');\n  if(selected('origin')&&d.origin&&origin)origin.value=d.origin;\n  if(notes){const extras=[];if(selected('ingredients')&&d.ingredients)extras.push(`Zutaten laut Etikett: ${d.ingredients}`);if(selected('temperature')&&d.temperature)extras.push(`Kühlung laut Etikett: ${d.temperature}`);if(extras.length)notes.value=[notes.value.trim(),...extras].filter(Boolean).join('\\n')}\n  // A packer's address is not a verified supplier or storage area; no allergen flags are inferred.\n  articleNameInputChanged();showFeedback('Vorschläge übernommen','success',3500,'Artikelangaben bitte prüfen und anschließend speichern.');return true;\n}\nconst saveArticleBefore183=saveArticleEditor;\nsaveArticleEditor=function(...args){\n  const name=document.getElementById('aeName');\n  const selectedName=document.querySelector('#labelReview183 [data-label-field=\"name\"]:checked');\n  if(!editingArticleCode&&document.getElementById('articleModal')?.style.display==='flex'&&\n      labelDraft183?.name&&selectedName&&!name?.value.trim()){\n    // The checked scan proposals are still only in the review panel. Apply them\n    // before normal validation, keeping the save and duplicate checks intact.\n    if(applyLabel183()===false)return;\n  }\n  return saveArticleBefore183(...args);\n};\nconst openArticleBefore183=openArticleEditor;\nopenArticleEditor=function(code=null){const result=openArticleBefore183(code);installLabel183();labelRequest183++;labelDraft183=null;const box=document.getElementById('labelReview183');if(box)box.textContent='';const button=document.getElementById('labelButton183');if(button)button.hidden=!!code;return result};\nconst closeArticleBefore183=closeArticleEditor;\ncloseArticleEditor=function(...args){labelRequest183++;labelDraft183=null;return closeArticleBefore183(...args)};\n"},{"src":"photo-storage184.js?v=1","code":"'use strict';\n// Keep image bytes small before they enter the article state or its local safety copies.\nfunction compactReference184(source){return new Promise((resolve,reject)=>{\n  const image=new Image(),url=typeof source==='string'?source:URL.createObjectURL(source);\n  image.onload=()=>{try{\n    const canvas=document.createElement('canvas'),context=canvas.getContext('2d');\n    if(!context)throw Error('Bildverarbeitung nicht verfügbar');\n    let data='';\n    for(const [edge,quality] of [[640,.56],[520,.5],[440,.45]]){\n      const scale=Math.min(1,edge/Math.max(image.naturalWidth,image.naturalHeight));\n      canvas.width=Math.max(1,Math.round(image.naturalWidth*scale));\n      canvas.height=Math.max(1,Math.round(image.naturalHeight*scale));\n      context.fillStyle='#fff';context.fillRect(0,0,canvas.width,canvas.height);\n      context.drawImage(image,0,0,canvas.width,canvas.height);\n      data=canvas.toDataURL('image/jpeg',quality);\n      if(data.length<=100000)break;\n    }\n    if(data.length>120000)throw Error('Dieses Bild bleibt für den Gerätespeicher zu groß');\n    resolve(data);\n  }catch(error){reject(error)}finally{if(typeof source!=='string')URL.revokeObjectURL(url)}};\n  image.onerror=()=>{if(typeof source!=='string')URL.revokeObjectURL(url);reject(Error('Foto konnte nicht gelesen werden'))};\n  image.src=url;\n})}\n\nlet referenceBusy184=false;\naddEditorRef144=async function(file){\n  if(!file||!hasPermission('articles')||referenceBusy184)return;\n  if(editorRefs144.length>=6)return showFeedback('Maximal 6 Referenzfotos','warn');\n  referenceBusy184=true;\n  try{\n    const data=await compactReference184(file),hash=await hash144(data);\n    editorRefs144.push({id:'ref-'+Date.now(),data,hash,createdAt:new Date().toISOString(),confirmed:true,packIndex:0});\n    renderRefs144();\n  }catch(error){showFeedback('Referenzfoto nicht übernommen','warn',5000,error.message)}\n  finally{referenceBusy184=false;const input=document.getElementById('refInput144');if(input)input.value=''}\n};\n\nconst installArticleBefore184=installArticle144;\ninstallArticle144=function(){\n  installArticleBefore184();\n  const refs=document.getElementById('refs144');\n  if(refs&&!document.getElementById('compactArticlePhotos184'))refs.parentElement.insertAdjacentHTML('beforeend',\n    '<button type=\"button\" class=\"soft\" id=\"compactArticlePhotos184\" onclick=\"compactEditorPhotos184()\">Vorhandene Fotos verkleinern</button><p class=\"tiny\">Bei vollem Gerätespeicher zuerst eine Sicherung herunterladen. Die bisherigen Bilder bleiben bis zum erfolgreichen Artikelspeichern erhalten.</p>');\n};\n\nasync function compactEditorPhotos184(){\n  if(!hasPermission('articles')||referenceBusy184)return;\n  if(articlePhotoPending)return showFeedback('Artikelfoto wird noch verarbeitet','warn');\n  referenceBusy184=true;\n  try{\n    const oldRefs=editorRefs144,oldMain=articlePhotoData;\n    const updated=await Promise.all(oldRefs.map(async ref=>{\n      if(!ref.data?.startsWith('data:image/'))return ref;\n      const data=await compactReference184(ref.data);\n      return data.length<ref.data.length?{...ref,data,hash:await hash144(data)}:ref;\n    }));\n    const compactMain=oldMain?.startsWith('data:image/')?await compactReference184(oldMain):oldMain;\n    const main=compactMain?.length<oldMain?.length?compactMain:oldMain;\n    const size=rows=>rows.reduce((n,r)=>n+(r.data?.length||0),0);\n    const saving=Math.max(0,size(oldRefs)+(oldMain?.length||0)-size(updated)-(main?.length||0));\n    editorRefs144=updated;articlePhotoData=main;\n    if(main!==oldMain)document.getElementById('aePhotoPreview').innerHTML=`<img src=\"${main}\" alt=\"Artikelfoto\">`;\n    renderRefs144();showFeedback('Fotos im Formular verkleinert','info',5000,`${Math.round(saving/1024)} KB weniger. Bitte Artikel speichern und den Erfolg prüfen.`);\n  }catch(error){showFeedback('Fotos unverändert','warn',5000,error.message)}\n  finally{referenceBusy184=false}\n}\n\nconst saveArticleBefore184=saveArticleEditor;\nsaveArticleEditor=function(){\n  if(referenceBusy184)return showFeedback('Fotos werden noch verarbeitet','warn',4000,'Bitte kurz warten und anschließend speichern.');\n  return saveArticleBefore184();\n};\n\nlet scanReferenceBusy184=false;\nsavePhotoReference179=async function(){\n  const draft=photoDraft144,article=draft?.code&&state.products?.[draft.code];\n  if(!article||!draft.captured179||scanReferenceBusy184||!guardPermission('articles','Referenzfoto speichern'))return;\n  const refs=article.referencePhotos144??(article.referencePhotos144=[]);\n  if(refs.length>=6)return showFeedback('Maximal sechs Referenzfotos pro Artikel','warn');\n  scanReferenceBusy184=true;\n  try{\n    const data=await compactReference184(draft.captured179);\n    if(photoDraft144!==draft||state.products?.[draft.code]!==article)return;\n    if(refs.some(ref=>ref.data===data))return showFeedback('Foto ist bereits als Referenz gespeichert','info');\n    const hash=await hash144(data);\n    if(photoDraft144!==draft||state.products?.[draft.code]!==article)return;\n    const before=clone(refs);\n    refs.push({id:'ref-'+Date.now()+'-'+Math.random().toString(36).slice(2),data,hash,packIndex:Number(draft.packIndex)||0});\n    if(!persistState('Referenzfoto gespeichert',false))article.referencePhotos144=before;\n    else showFeedback('Referenzfoto gespeichert','success');\n  }catch(error){showFeedback('Referenzfoto nicht gespeichert','warn',5000,error.message)}\n  finally{scanReferenceBusy184=false}\n};\n"},{"src":"photo182.js?v=275","code":"'use strict';\n/* Photo inventory sessions build on the existing single-product recognition flow.\n   Recognition never changes stock. Only an explicitly confirmed difference does. */\nlet photoInventory182=null;\nfunction photoInventoryKey182(code){return String(code||'')}\nfunction photoInventoryDiff182(stock,packQty,packCount){\n  const current=Number(stock)||0, observed=(Number(packQty)||0)*(Number(packCount)||0);\n  return {current,observed,diff:observed-current};\n}\nfunction aggregatePhotoInventory182(rows){\n  const map=new Map();\n  for(const row of rows||[]){\n    if(!row?.code)continue;const key=photoInventoryKey182(row.code);\n    const found=map.get(key)||{code:row.code,observedQty:0,observations:0,packages:[]};\n    const count=Number(row.packCount)||0,packQty=Number(row.packQty)||0;\n    found.observedQty+=count*packQty;found.observations+=1;\n    found.packages.push(`${count} × ${row.packName||'Gebinde'}`);map.set(key,found);\n  }\n  return [...map.values()];\n}\nfunction installPhotoInventory182(){\n  if(!document.getElementById('photoInventoryModal182'))document.body.insertAdjacentHTML('beforeend',`<div class=\"modal\" id=\"photoInventoryModal182\"><div class=\"card photoInventoryCard182\"><div class=\"modalHeader\"><div><h2>Foto-Inventur</h2><p class=\"muted\">Fotografieren, nur Abweichungen prüfen, erst danach korrigieren.</p></div><button class=\"close\" onclick=\"closePhotoInventory182()\">× Schließen</button></div><div id=\"photoInventoryBody182\"></div></div></div>`);\n  const anchors=[document.getElementById('photoBtn144'),document.getElementById('photoDesk144')].filter(Boolean);\n  for(const anchor of anchors)if(!anchor.parentElement.querySelector('.photoInventoryStart182'))anchor.insertAdjacentHTML('afterend','<button type=\"button\" class=\"soft photoInventoryStart182\" onclick=\"startPhotoInventory182()\">Foto-Inventur</button>');\n}\nfunction startPhotoInventory182(){\n  if(!guardPermission('scan','Foto-Inventur'))return;\n  if(photoInventory182?.sceneUrl)URL.revokeObjectURL(photoInventory182.sceneUrl);\n  installPhotoInventory182();photoInventory182={id:'pinv-'+Date.now(),startedAt:new Date().toISOString(),rows:[],coverageConfirmed:{}};\n  document.getElementById('photoInventoryModal182').style.display='flex';renderPhotoInventory182();\n}\nfunction closePhotoInventory182(){const modal=document.getElementById('photoInventoryModal182');if(modal)modal.style.display='none'}\nfunction captureForInventory182(){\n  if(!photoInventory182)return;closePhotoInventory182();startPhoto144();\n  if(photoDraft144)photoDraft144.inventorySession182=photoInventory182.id;\n}\nfunction sceneRect182(start,end,width,height){\n  const x=Math.max(0,Math.min(start.x,end.x)),y=Math.max(0,Math.min(start.y,end.y));\n  const w=Math.min(width,Math.max(start.x,end.x))-x,h=Math.min(height,Math.max(start.y,end.y))-y;\n  return {x:Math.round(x),y:Math.round(y),width:Math.round(w),height:Math.round(h)};\n}\nfunction chooseScene182(file){\n  if(!photoInventory182||!file)return;\n  if(!file.type.startsWith('image/')||file.size>30*1024*1024)return showFeedback('Bitte ein Foto unter 30 MB auswählen','warn');\n  if(photoInventory182.sceneUrl)URL.revokeObjectURL(photoInventory182.sceneUrl);\n  photoInventory182.sceneFile=file;photoInventory182.sceneUrl=URL.createObjectURL(file);\n  renderPhotoInventory182();\n}\nfunction scenePoint182(event,img){\n  const rect=img.getBoundingClientRect();\n  return {x:Math.max(0,Math.min(img.naturalWidth,(event.clientX-rect.left)*img.naturalWidth/rect.width)),\n    y:Math.max(0,Math.min(img.naturalHeight,(event.clientY-rect.top)*img.naturalHeight/rect.height))};\n}\nlet sceneSelection182=null,sceneBusy182=false;\nfunction beginScene182(event){\n  const img=document.getElementById('photoInventorySceneImage182');if(sceneBusy182||!img?.naturalWidth)return;\n  event.preventDefault();img.setPointerCapture(event.pointerId);\n  sceneSelection182={sessionId:photoInventory182?.id,start:scenePoint182(event,img)};\n  document.getElementById('photoInventorySceneBox182').style.display='none';\n}\nfunction moveScene182(event){\n  const img=document.getElementById('photoInventorySceneImage182'),box=document.getElementById('photoInventorySceneBox182');\n  if(!sceneSelection182||!img||!box)return;\n  const area=sceneRect182(sceneSelection182.start,scenePoint182(event,img),img.naturalWidth,img.naturalHeight);\n  Object.assign(box.style,{display:'block',left:`${100*area.x/img.naturalWidth}%`,top:`${100*area.y/img.naturalHeight}%`,width:`${100*area.width/img.naturalWidth}%`,height:`${100*area.height/img.naturalHeight}%`});\n}\nasync function endScene182(event){\n  if(sceneBusy182)return;\n  const selection=sceneSelection182;sceneSelection182=null;\n  const session=photoInventory182,img=document.getElementById('photoInventorySceneImage182');\n  if(!selection||!session||selection.sessionId!==session.id||!img?.naturalWidth)return;\n  const area=sceneRect182(selection.start,scenePoint182(event,img),img.naturalWidth,img.naturalHeight);\n  if(area.width<Math.max(50,img.naturalWidth*.045)||area.height<Math.max(50,img.naturalHeight*.045))return showFeedback('Bitte eine ganze Packung markieren','warn');\n  sceneBusy182=true;\n  try{\n    const bitmap=await createImageBitmap(session.sceneFile);\n    try{\n      if(session!==photoInventory182)return;\n      const canvas=document.createElement('canvas');canvas.width=Math.min(1200,area.width);canvas.height=Math.round(area.height*canvas.width/area.width);\n      canvas.getContext('2d').drawImage(bitmap,area.x,area.y,area.width,area.height,0,0,canvas.width,canvas.height);\n      const blob=await new Promise((resolve,reject)=>canvas.toBlob(b=>b?resolve(b):reject(Error('Bild konnte nicht zugeschnitten werden')),'image/jpeg',.88));\n      if(session!==photoInventory182)return;\n      closePhotoInventory182();startPhoto144();\n      if(!photoDraft144)return;\n      photoDraft144.inventorySession182=session.id;\n      photoDraft144.sceneRegion182=area;\n      await analysePhoto144(new File([blob],'produktausschnitt.jpg',{type:'image/jpeg'}));\n    }finally{bitmap.close?.()}\n  }catch(error){showFeedback('Ausschnitt konnte nicht geprüft werden','warn',5000,error.message);document.getElementById('photoInventoryModal182').style.display='flex'}\n  finally{sceneBusy182=false}\n}\nfunction addCurrentPhotoToInventory182(count=1,measuredQty=null){\n  const draft=photoDraft144,session=photoInventory182;if(!draft?.code||!session||draft.inventorySession182!==session.id)return;\n  const article=state.products?.[draft.code];if(!article)return;\n  const packs=getPacks(article,draft.code),pack=packs[draft.packIndex]||packs[0];\n  const rest=measuredQty!==null,packCount=rest?Number(measuredQty):Number(count);\n  if(!Number.isFinite(packCount)||packCount<=0||(!rest&&!Number.isInteger(packCount)))return showFeedback('Menge prüfen','warn');\n  const fingerprint=String(draft.captured179||'').slice(-180);\n  if(fingerprint&&session.rows.some(row=>row.fingerprint===fingerprint)){showFeedback('Dieses Foto ist bereits in der Inventur','info');return}\n  const row={code:draft.code,packIndex:Number(draft.packIndex)||0,packCount,\n    packName:rest?`gemessener Rest (${article.unit})`:pack[0],packQty:rest?1:packUsableQty(pack),\n    fingerprint,sceneRegion:draft.sceneRegion182||null,source:draft.candidates?.find(x=>x.code===draft.code)?.source||'confirmed'};\n  const add=()=>{\n    if(photoInventory182!==session||photoDraft144!==draft)return;\n    session.rows.push(row);session.coverageConfirmed[draft.code]=false;\n    closePhoto144();document.getElementById('photoInventoryModal182').style.display='flex';renderPhotoInventory182();showFeedback('Zur Foto-Inventur hinzugefügt','success');\n  };\n  if(session.rows.some(previous=>previous.code===draft.code))\n    askConfirm(`${article.name} wurde in dieser Inventur bereits erfasst. Zeigt dieses Foto wirklich zusätzliche Ware, die auf den bisherigen Fotos nicht zu sehen ist?\\n\\nBei einer anderen Ansicht derselben Packungen abbrechen.`,add,'Zusätzliche Ware erfassen','Doppelte Ansicht prüfen');\n  else add();\n}\nfunction addCurrentPhotoQuantity182(){\n  const value=prompt('Wie viele gleiche Packungen sind auf diesem Foto sicher sichtbar?','1');if(value===null)return;\n  const count=Number(String(value).replace(',','.'));if(!Number.isInteger(count)||count<1)return showFeedback('Anzahl prüfen','warn');\n  addCurrentPhotoToInventory182(count);\n}\nfunction addCurrentPhotoRest182(){\n  const article=state.products?.[photoDraft144?.code];if(!article)return;\n  const value=prompt(`Restmenge des angebrochenen Gebindes in ${article.unit} eingeben. Bitte messen oder wiegen; das Foto zeigt keine genaue Menge.`,'');\n  if(value===null)return;\n  const qty=Number(String(value).replace(',','.'));\n  if(!Number.isFinite(qty)||qty<=0)return showFeedback('Restmenge prüfen','warn');\n  addCurrentPhotoToInventory182(1,qty);\n}\nfunction removePhotoInventoryRow182(code){if(!photoInventory182)return;photoInventory182.rows=photoInventory182.rows.filter(row=>row.code!==code);delete photoInventory182.coverageConfirmed[code];renderPhotoInventory182()}\nfunction confirmPhotoInventoryCoverage182(code,checked){if(!photoInventory182)return;photoInventory182.coverageConfirmed[code]=!!checked;renderPhotoInventory182()}\nfunction renderPhotoInventory182(){\n  const body=document.getElementById('photoInventoryBody182');if(!body||!photoInventory182)return;\n  const rows=aggregatePhotoInventory182(photoInventory182.rows);\n  body.innerHTML=`<div class=\"photoInventoryTop182\"><b>${rows.length} Artikelposition${rows.length===1?'':'en'}</b><span>${photoInventory182.rows.length} bestätigte Foto-Beobachtung${photoInventory182.rows.length===1?'':'en'}</span></div>\n    ${rows.length?`<div class=\"photoInventoryRows182\">${rows.map(row=>{const article=state.products?.[row.code];if(!article)return'';const d=photoInventoryDiff182(article.stock,1,row.observedQty),same=Math.abs(d.diff)<1e-8,covered=!!photoInventory182.coverageConfirmed[row.code];return `<div class=\"photoInventoryRow182 ${same?'isSame':''}\"><div><b>${esc(article.name)}</b><small>${esc(row.packages.join(' + '))} · Foto: ${esc(formatQtySmart(d.observed,article.unit))}</small><label><input type=\"checkbox\" ${covered?'checked':''} onchange=\"confirmPhotoInventoryCoverage182('${escJs(row.code)}',this.checked)\"> Gesamten Bestand dieses Artikels an allen Lagerorten erfasst</label></div><div class=\"photoInventoryCompare182\"><small>Zapasio</small><b>${esc(formatQtySmart(d.current,article.unit))}</b></div><div class=\"photoInventoryCompare182\"><small>Differenz</small><b>${same?'passt':(d.diff>0?'+':'')+esc(formatQtySmart(d.diff,article.unit))}</b></div><div class=\"photoInventoryRowActions182\">${same?'<span class=\"photoInventoryOk182\">✓ passt</span>':`<button class=\"primary\" ${covered?'':'disabled'} onclick=\"confirmPhotoInventoryDiff182('${escJs(row.code)}')\">Korrektur prüfen</button>`}<button class=\"soft\" onclick=\"removePhotoInventoryRow182('${escJs(row.code)}')\">Entfernen</button></div></div>`}).join('')}</div>`:'<div class=\"emptyState\">Noch keine Produkte erfasst. Fotografiere Produkte nacheinander; sichere Treffer werden hier gesammelt.</div>'}\n    <div class=\"photoInventoryActions182\"><button class=\"primary\" onclick=\"captureForInventory182()\">+ Produkt fotografieren</button><button class=\"soft\" onclick=\"document.getElementById('photoInventorySceneInput182').click()\">Gesamtfoto mit mehreren Produkten</button><button class=\"soft\" onclick=\"closePhotoInventory182()\">Später weiter</button></div>\n    <input id=\"photoInventorySceneInput182\" type=\"file\" accept=\"image/*\" capture=\"environment\" hidden onchange=\"chooseScene182(this.files[0]);this.value=''\">\n    ${photoInventory182.sceneUrl?`<div class=\"photoInventoryScene182\"><p>Auf dem Gesamtfoto <b>ein einzelnes Produkt</b> mit dem Finger einrahmen. Danach Artikel bestätigen und die Zahl der tatsächlich sichtbaren gleichen Produkte eingeben. Für weitere Produkte zum Bild zurückkehren.</p><div style=\"position:relative;max-width:100%;width:max-content\"><img id=\"photoInventorySceneImage182\" src=\"${photoInventory182.sceneUrl}\" alt=\"Gesamtfoto zur Auswahl einzelner Produkte\" style=\"display:block;max-width:100%;max-height:65vh;touch-action:none\" onpointerdown=\"beginScene182(event)\" onpointermove=\"moveScene182(event)\" onpointerup=\"endScene182(event)\" onpointercancel=\"sceneSelection182=null\"><div id=\"photoInventorySceneBox182\" style=\"display:none;position:absolute;pointer-events:none;border:3px solid #ffb900;background:#ffb90033\"></div></div></div>`:''}\n    <p class=\"tiny\">Wichtig: Mehrere Ansichten desselben physischen Gebindes werden in diesem Teststand noch nicht automatisch zusammengeführt. Im Zweifel nur eine Ansicht als Menge erfassen.</p>`;\n}\nfunction confirmPhotoInventoryDiff182(code){\n  const row=aggregatePhotoInventory182(photoInventory182?.rows||[]).find(x=>x.code===code),article=state.products?.[code];if(!row||!article||!photoInventory182?.coverageConfirmed[code])return;\n  const d=photoInventoryDiff182(article.stock,1,row.observedQty);if(Math.abs(d.diff)<1e-8)return;\n  if(d.diff>0&&effectiveMhd144(article)==='required')return showFeedback('MHD für zusätzlichen Bestand manuell erfassen','warn',6000,'Bitte den Zugang mit MHD über den normalen Wareneingang buchen.');\n  const text=`${article.name}\\nZapasio: ${formatQtySmart(d.current,article.unit)}\\nFoto-Inventur: ${formatQtySmart(d.observed,article.unit)}\\nDifferenz: ${(d.diff>0?'+':'')+formatQtySmart(d.diff,article.unit)}\\n\\nBestand wirklich auf den bestätigten Foto-Bestand korrigieren?`;\n  const sessionId=photoInventory182.id;\n  askConfirm(text,()=>applyPhotoInventoryDiff182(code,d.current,d.observed,sessionId),'Bestand korrigieren','Foto-Inventur prüfen');\n}\nfunction applyPhotoInventoryDiff182(code,expectedStock,expectedObserved,expectedSessionId){\n  if(expectedSessionId!==undefined&&photoInventory182?.id!==expectedSessionId){\n    showFeedback('Foto-Inventur inzwischen gewechselt','warn',5000,'Bitte die aktuelle Sitzung erneut prüfen.');return false;\n  }\n  const row=aggregatePhotoInventory182(photoInventory182?.rows||[]).find(x=>x.code===code),article=state.products?.[code];if(!row||!article||!photoInventory182?.coverageConfirmed[code])return false;\n  const d=photoInventoryDiff182(article.stock,1,row.observedQty);if(Math.abs(d.diff)<1e-8)return true;\n  if(expectedStock!==undefined&&(Math.abs(d.current-expectedStock)>1e-8||Math.abs(d.observed-expectedObserved)>1e-8)){\n    showFeedback('Bestand oder Foto-Menge inzwischen geändert','warn',5000,'Differenz erneut prüfen.');renderPhotoInventory182();return false;\n  }\n  if(d.diff>0&&effectiveMhd144(article)==='required')return false;\n  const ok=operationTransaction('Inventurkorrektur gespeichert','scan',()=>{\n    article.stock=d.observed;\n    if(d.diff<0){consumeBatches(code,-d.diff);if(Number(article.consignmentQty168)>article.stock)article.consignmentQty168=article.stock}\n    addMovement('inventory',code,d.diff,{unitCost:Number(article.price)||0,source:'photo-inventory-confirmed',reason:'Bestätigte Foto-Inventur '+photoInventory182.id});\n    state.log.push(`${stamp()} | ${article.name} | ${(d.diff>0?'+':'')+d.diff} | bestätigte Foto-Inventur`);\n    state.lastInventory=new Date().toLocaleString('de-AT');state.lastInventoryISO=new Date().toISOString();\n  });return afterOperation275(ok,ok=>{\n  if(ok){photoInventory182.coverageConfirmed[code]=false;renderPhotoInventory182();showFeedback('Bestand korrigiert','success')}\n  return ok;\n;return ok;});}\nconst renderPhotoBefore182=renderPhoto144;\nrenderPhoto144=function(){\n  renderPhotoBefore182();const draft=photoDraft144,body=document.getElementById('photoBody144');\n  if(!draft||!body||draft.step!=='confirm'||!draft.inventorySession182||draft.inventorySession182!==photoInventory182?.id)return;\n  const actions=body.querySelector('.scanActions146,.photoActions144');if(actions&&!body.querySelector('.photoInventoryAdd182'))actions.insertAdjacentHTML('beforebegin','<div class=\"photoInventoryQuick182\"><button class=\"primary photoInventoryAdd182\" onclick=\"addCurrentPhotoToInventory182(1)\">1 volle Packung</button><button class=\"soft\" onclick=\"addCurrentPhotoQuantity182()\">Mehrere volle Packungen</button><button class=\"soft\" onclick=\"addCurrentPhotoRest182()\">Angebrochen: Restmenge eingeben</button></div>');\n};\nconst renderBefore182=render;\nrender=function(){const result=renderBefore182();installPhotoInventory182();return result};\nsetTimeout(installPhotoInventory182,0);\n"},{"src":"shopping-pdf178.js?v=88","code":"'use strict';\n/* Make a real PDF from the same grouped rows displayed in the shopping list. */\nfunction pdfText178(text){return String(text??'').replace(/[łŁ]/g,x=>x==='ł'?'l':'L').replace(/[ąĄćĆęĘńŃśŚźŹżŻ]/g,x=>({ą:'a',Ą:'A',ć:'c',Ć:'C',ę:'e',Ę:'E',ń:'n',Ń:'N',ś:'s',Ś:'S',ź:'z',Ź:'Z',ż:'z',Ż:'Z'})[x]).replace(/[\\u2011-\\u2015]/g,'-').replace(/[\\u2022]/g,'·').replace(/[^\\u0020-\\u007e\\u00a0-\\u00ff\\u20ac]/g,'?')}\nfunction pdfLines178(value,font,size,maxWidth){const lines=[];for(const paragraph of pdfText178(value).split('\\n')){let current='';for(const word of paragraph.split(/\\s+/)){if(!word)continue;const next=current?current+' '+word:word;if(font.widthOfTextAtSize(next,size)>maxWidth&&current){lines.push(current);current=word}else current=next}lines.push(current)}return lines}\nasync function createShoppingPdf178(channels=shoppingChannels()){\n if(typeof loadAsset254==='function')await loadAsset254('pdf');\n  const {PDFDocument,StandardFonts,rgb}=PDFLib,doc=await PDFDocument.create();\n  const regular=await doc.embedFont(StandardFonts.Helvetica),bold=await doc.embedFont(StandardFonts.HelveticaBold);\n  const green=rgb(.02,.24,.18),muted=rgb(.33,.40,.35),rule=rgb(.83,.88,.82),pale=rgb(.94,.97,.93);\n  const pages=[],pageWidth=595.28,pageHeight=841.89,margin=44,width=pageWidth-margin*2;\n  let page,y,logo=null;\n  try{const response=await fetch('assets/zapasio-main-approved-lossless424.webp');if(response.ok)logo=await doc.embedPng(await response.arrayBuffer())}catch(_){}\n  function newPage(){page=doc.addPage();page.setSize(pageWidth,pageHeight);pages.push(page);y=pageHeight-48;\n    if(logo){page.drawImage(logo,{x:margin,y:y-42,width:94,height:63});}\n    page.drawText('Einkaufsliste',{x:logo?margin+103:margin,y:y-17,size:22,font:bold,color:green});\n    page.drawText('Zum Mitnehmen · '+new Date().toLocaleDateString('de-AT'),{x:logo?margin+104:margin,y:y-34,size:10,font:regular,color:muted});\n    y-=72;page.drawLine({start:{x:margin,y},end:{x:pageWidth-margin,y},thickness:1,color:rule});y-=20;\n  }\n  function ensure(height){if(y-height<55)newPage()}\n  function drawText(value,x,size=12,weight=regular,color=green,maxWidth=width){const lines=pdfLines178(value,weight,size,maxWidth);for(const line of lines){ensure(size+6);page.drawText(line,{x,y,size,font:weight,color});y-=size+5}return lines.length}\n  newPage();\n  const groups=shoppingGroups173(channels);\n  if(!groups.length)drawText('Keine Artikel zum Einkauf vor Ort offen.',margin,12);\n  for(const group of groups){\n    ensure(72);page.drawRectangle({x:margin,y:y-30,width,height:38,color:pale});\n    page.drawText(pdfText178(group.name),{x:margin+13,y:y-21,size:17,font:bold,color:green});y-=55;\n    for(const area of group.areas){\n      ensure(58);drawText(area.label,margin+5,14,bold,green,width-10);y-=9;\n      for(const row of area.rows){\n        const display=shoppingDisplay173(row,shoppingPurchasePlan(row)),headline=pdfLines178(display.headline,bold,14,width-54),size=19+headline.length*20+(display.pack?19:0);\n        ensure(size+9);const top=y;\n        page.drawRectangle({x:margin+5,y:top-15,width:13,height:13,borderColor:muted,borderWidth:1});\n        if(shoppingRowChecked(row)){page.drawLine({start:{x:margin+7,y:top-9},end:{x:margin+10,y:top-12},thickness:1.5,color:green});page.drawLine({start:{x:margin+10,y:top-12},end:{x:margin+17,y:top-3},thickness:1.5,color:green})}\n        for(const line of headline){page.drawText(line,{x:margin+29,y:y-11,size:14,font:bold,color:green});y-=23}\n        if(display.pack){drawText(display.pack,margin+29,11,regular,muted,width-45)}\n        y-=9;page.drawLine({start:{x:margin+28,y},end:{x:pageWidth-margin,y},color:rule,thickness:.5});y-=17;\n      }\n      y-=10;\n    }\n    y-=10;\n  }\n  if(channels.review.length||channels.unlinked.length){ensure(52);drawText('Weitere Positionen brauchen eine Zuordnung in Zapasio.',margin,10,bold,muted)}\n  for(const [index,p] of pages.entries())p.drawText(`${index+1} / ${pages.length}`,{x:pageWidth-margin-25,y:29,size:9,font:regular,color:muted});\n  return await doc.save();\n}\nfunction shoppingPdfFile178(bytes){return new File([bytes],'Zapasio-Einkaufsliste-'+new Date().toISOString().slice(0,10)+'.pdf',{type:'application/pdf'})}\nfunction downloadShoppingPdf178(file){const link=document.createElement('a'),url=URL.createObjectURL(file);link.href=url;link.download=file.name;document.body.appendChild(link);link.click();link.remove();setTimeout(()=>URL.revokeObjectURL(url),60000)}\nasync function shareShoppingPdf178(){\n  if(!guardPermission('shopping','Einkaufsliste'))return;\n  try{\n    const file=shoppingPdfFile178(await createShoppingPdf178());\n    if(typeof window.ZapasioAndroid?.shareShoppingPdf178==='function'){\n      const bytes=new Uint8Array(await file.arrayBuffer());let binary='';\n      for(let offset=0;offset<bytes.length;offset+=8192)binary+=String.fromCharCode(...bytes.subarray(offset,offset+8192));\n      window.ZapasioAndroid.shareShoppingPdf178(btoa(binary));return;\n    }\n    if(navigator.canShare?.({files:[file]})&&navigator.share){await navigator.share({files:[file],title:'Zapasio Einkaufsliste'});return}\n    downloadShoppingPdf178(file);\n    showFeedback('PDF gespeichert','info',6000,'Die Datei liegt im Downloadordner und kann an eine E-Mail angehängt werden.');\n  }catch(error){if(error?.name!=='AbortError')showFeedback('PDF konnte nicht geteilt werden','warn',4500)}\n}\nasync function saveShoppingPdf178(){if(!guardPermission('shopping','Einkaufsliste'))return;try{downloadShoppingPdf178(shoppingPdfFile178(await createShoppingPdf178()))}catch(_){showFeedback('PDF konnte nicht erstellt werden','warn')}}\n"},{"src":"workflow178.js?v=408&release=411","code":"'use strict';\n/* Mobile layout only: planning, buying and settings use the existing calculations and permissions. */\nconst planningGroups178=[\n  {title:'Planen',sections:[['overview','Übersicht'],['entry','Einsatz'],['calc','Kalkulation']]},\n  {title:'Beschaffen',sections:[['shopping','Einkauf'],['suppliers','Lieferanten']]},\n  {title:'Durchführen',sections:[['preparation','Vorbereitung'],['load','Beladen'],['setup','Aufbau'],['onsite','Vor Ort']]}\n];\nfunction installPlanningNav178(){\n  const original=document.querySelector('#tabPlanning .planningSubnav');\n  if(!original||document.getElementById('planningNav178'))return;\n  const nav=document.createElement('nav');\n  nav.id='planningNav178';nav.setAttribute('aria-label','Bereiche des Einsatzes');\n  nav.innerHTML=planningGroups178.map(group=>`<details class=\"planningGroup178\"><summary>${esc(group.title)}<span class=\"planningCurrent178\"></span></summary><div class=\"planningLinks178\">${group.sections.map(([key,label])=>`<button type=\"button\" data-planning178=\"${key}\" onclick=\"setPlanningSection('${key}')\">${esc(label)}</button>`).join('')}</div></details>`).join('');\n  original.after(nav);\n  syncPlanningNav178();\n}\nfunction syncPlanningNav178(){\n  const nav=document.getElementById('planningNav178');if(!nav)return;\n  for(const group of nav.querySelectorAll('.planningGroup178')){\n    const active=group.querySelector(`[data-planning178=\"${planningSection}\"]`);\n    group.classList.toggle('selected178',!!active);\n    const current=group.querySelector('.planningCurrent178');\n    if(current)current.textContent=active?.textContent||'';\n    if(active)group.open=true;\n  }\n  for(const button of nav.querySelectorAll('button')){\n    const active=button.dataset.planning178===planningSection;\n    button.classList.toggle('active',active);\n    if(active)button.setAttribute('aria-current','page');else button.removeAttribute('aria-current');\n  }\n}\nconst planningSectionBefore178=setPlanningSection;\nsetPlanningSection=function(section){planningSectionBefore178(section);syncPlanningNav178()};\nconst planningRenderBefore178=renderPlanning;\nrenderPlanning=function(){const value=planningRenderBefore178();installPlanningNav178();syncPlanningNav178();\n  const header=document.querySelector('#planningOverviewBar .planningSummaryCard:first-child');\n  if(header&&!header.querySelector('.planningQuick178')){\n    const active=activePlanningEntry();\n    header.insertAdjacentHTML('beforeend',`<span class=\"planningQuick178\">${active?.date?esc(formatDate(active.date))+' · ':''}${active?.guests?esc(active.guests)+' Personen':''}</span>`);\n  }\n  return value;\n};\nconst shoppingForEventBefore178=renderShopping;\nrenderShopping=function(c){\n  const rows=procurementFor(c);if(!rows.length)return shoppingForEventBefore178(c);\n  const groups=new Map();\n  for(const row of rows){if(!groups.has(row.supplier))groups.set(row.supplier,[]);groups.get(row.supplier).push(row)}\n  return [...groups].map(([supplier,items])=>`<section class=\"eventSupplier178\"><h3>${esc(supplier)}</h3><div class=\"eventShoppingGrid178\">${items.map(r=>{\n    const match=supplierMatch(supplier,r.name,r.productCode);\n    const note=r.linked?`Lager: ${formatQtySmart(r.stock,r.unit)}`:r.reason||'Artikel zuordnen';\n    const extra=match?`Bestellnr. ${match.sku} · Wochenpreis ${moneyFmt(match.price)}`:r.linked&&supplier!=='Noch zuordnen'?'Lieferantenartikel noch bestätigen':'';\n    return `<article class=\"eventShoppingRow178\"><b>${esc(r.name)}</b><div class=\"eventShoppingAmounts178\"><span><small>Benötigt</small><strong>${esc(formatQtySmart(r.need,r.unit))}</strong></span><span><small>Bestellen</small><strong>${orderPackText(r,true)}</strong></span></div><small>${esc(note)}${extra?' · '+esc(extra):''}</small></article>`;\n  }).join('')}</div></section>`).join('');\n};\nfunction shoppingMode178(){return state.shoppingChecks?._viewMode178==='amateur'?'amateur':'pro'}\nfunction refreshShoppingMode178(){\n  for(const root of [document.getElementById('selfShoppingInline'),document.getElementById('selfShoppingBody')]){\n    root?.classList.toggle('shoppingAmateur178',shoppingMode178()==='amateur');\n  }\n  const input=document.getElementById('shoppingViewMode178');\n  if(input){const pro=shoppingMode178()==='pro';input.setAttribute('aria-checked',String(pro));input.querySelector('[data-shopping-mode-label]')?.replaceChildren(pro?'Pro':'Einfach');}\n}\nfunction setShoppingMode178(mode){\n  if(!guardPermission('shopping','Einkaufsansicht'))return;\n  const before=clone(state.shoppingChecks||{});\n  state.shoppingChecks??={};state.shoppingChecks._viewMode178=mode==='amateur'?'amateur':'pro';\n  if(!persistState('Einkaufsansicht gespeichert',false))state.shoppingChecks=before;\n  refreshShoppingMode178();\n}\nfunction installShoppingMode178(){\n  const tab=document.getElementById('tabSettings');if(!tab)return;\n  let card=document.getElementById('shoppingViewSetting178');\n  if(!card){card=document.createElement('section');card.id='shoppingViewSetting178';card.className='card';\n  card.innerHTML='<h2>Einkaufsansicht</h2><p>Einfach zeigt Artikel, Gebinde und Menge. Pro zeigt zusätzlich Bedarf und Herkunft.</p><button id=\"shoppingViewMode178\" type=\"button\" role=\"switch\" aria-label=\"Pro-Ansicht beim Einkauf\" aria-checked=\"false\" onclick=\"setShoppingMode178(shoppingMode178()===\\'pro\\'?\\'amateur\\':\\'pro\\')\"><span class=\"shoppingModeTrack408\" aria-hidden=\"true\"><span></span></span><span data-shopping-mode-label>Einfach</span></button>';}\n  const panel=document.getElementById('settingsPanel130_account');\n  card.hidden=!panel;(panel||tab).appendChild(card);refreshShoppingMode178();\n}\nconst selfShoppingBefore178=renderSelfShopping;\nrenderSelfShopping=function(){const value=selfShoppingBefore178();refreshShoppingMode178();return value};\nconst renderBefore178=render;\nrender=function(){const value=renderBefore178();installPlanningNav178();installShoppingMode178();refreshShoppingMode178();return value};\ninstallPlanningNav178();installShoppingMode178();\n/* A pack's usable content belongs next to its gross size in the read-only article view. */\nconst articleInfoBefore178=openArticleInfo127;\nopenArticleInfo127=function(code){\n  const result=articleInfoBefore178(code),a=state.products?.[code],modal=document.getElementById('articleInfoModal127');\n  if(!a?.drainWeightEnabled||!modal)return result;\n  const content=confirmedStockContent(a,'kg')||confirmedStockContent(a,'Liter');\n  const pack=[...modal.querySelectorAll('.articleInfoMetric')].find(metric=>metric.querySelector('span')?.textContent?.includes('Bevorzugte Bestellpackung'));\n  if(content&&pack&&!modal.querySelector('.articleUsable178'))\n    pack.insertAdjacentHTML('afterend',`<div class=\"articleInfoMetric articleUsable178\"><span>Nutzbarer Inhalt je ${esc(a.unit||'Packung')}</span><b>${esc(formatQtySmart(content.size,content.unit))} nach Abtropfen</b></div>`);\n  return result;\n};\n"},{"src":"offer181.js?v=392","code":"'use strict';\n/* Keep settings reachable while the workshop menu is scrolled; existing role checks stay in showTab. */\nfunction keepSettingsVisible181(){\n const footer=document.querySelector('.sidebarFooter');if(!footer)return;\n if(!document.getElementById('settingsQuick181')){\n  const button=document.createElement('button');button.id='settingsQuick181';button.type='button';\n  button.className='sideNavBtn';button.textContent='Einstellungen';\n  button.onclick=()=>showTab('settings');\n  footer.insertBefore(button,footer.querySelector('.sidebarStatus'));\n }\n const system=document.querySelector('#customSideNav .zNavGroup155[data-group=\"system\"]');\n if(system){const children=system.querySelector('.zNavChildren155');if(children)children.hidden=false}\n}\nconst offerRenderBefore181=render;\nrender=function(){const result=offerRenderBefore181();keepSettingsVisible181();return result};\nconst showNavGroupBefore181=showNavGroup155;\nshowNavGroup155=function(id,open){showNavGroupBefore181(id,open);keepSettingsVisible181()};\nconst renderPlanningBefore181=renderPlanning;\nrenderPlanning=function(){\n const result=renderPlanningBefore181(),plan=activePlanningEntry(),heading=document.querySelector('#planningDashboard .planningActiveTitle');\n if(plan?.customer170&&heading&&!heading.parentElement.querySelector('.offerCustomer181'))\n  heading.insertAdjacentHTML('afterend',`<div class=\"offerCustomer181\">Kunde: ${esc(plan.customer170)}${plan.customerContact170?' · '+esc(plan.customerContact170):''}${plan.offerEndDate170?' · bis '+esc(formatDate(plan.offerEndDate170)):''}</div>`);\n return result;\n};\nkeepSettingsVisible181();\n"},{"src":"document-bundle226.js?v=215","code":"'use strict';\nconst ZapasioDocuments226=(()=>{const modules={};const base=document.currentScript?.src||location.href;\nmodules['europe229']=(()=>{\n// Shared AT/DE/PL document conventions. All amounts remain evidence from the\n// document: no exchange-rate, tax-rate or package-to-stock guesses.\nfunction number229(raw){\n let s=String(raw??'').trim().replace(/[\\s\\u00a0\\u202f]/g,'');\n if(!s||!/^\\d+(?:[.,]\\d+)*$/.test(s))return null;\n if(s.includes(','))s=s.replace(/\\./g,'').replace(',','.');\n const value=Number(s);return Number.isFinite(value)?value:null;\n}\nconst unitPattern229='Stück|Stueck|Stiick|Stick|Stk\\\\.?|St\\\\.?|szt\\\\.?|sztuk(?:a|i)?|kg|g|ml|Liter|L|Flaschen?|Fl|but\\\\.?|Becher|Dosen?|Packungen?|Pack|Paket|Pkg\\\\.?|opak\\\\.?|ryza|Kartons?|kart\\\\.?|Stunden?|Std\\\\.?|godz\\\\.?|psch\\\\.?';\nfunction unit229(raw){\n const s=String(raw||'').toLowerCase().replace(/\\.$/,'');\n if(/^(?:st|stk|stück|stueck|stiick|stick|szt|sztuk[ai]?)$/.test(s))return 'Stück';\n if(/^(?:fl|flasche[n]?|but)$/.test(s))return 'Flasche';\n if(/^(?:pack|paket|pkg|packung[en]*|opak|ryza)$/.test(s))return 'Packung';\n if(/^(?:kart|karton[s]?)$/.test(s))return 'Karton';\n if(/^dose[n]?$/.test(s))return 'Dose';\n if(/^(?:std|stunde[n]?|godz)$/.test(s))return 'Stunde';\n if(s==='psch')return 'Pauschale';\n return ({kg:'kg',g:'g',ml:'ml',l:'Liter',liter:'Liter',becher:'Becher'})[s]||'';\n}\nfunction packageContent229(name){\n const values=[...String(name||'').matchAll(/(?:^|\\s)(?:(\\d{1,4})\\s*[x×]\\s*)?(\\d{1,6}(?:[.,]\\d{1,3})?)\\s*(kg|g|ml|l|liter)\\b(?!\\s*\\/)/gi)];\n if(values.length!==1)return null;\n const m=values[0],amount=number229(m[2]),count=number229(m[1]||'1');\n return amount>0&&count>0?{amount,unit:unit229(m[3]),count}:null;\n}\nfunction currency229(text){\n const t=String(text||''),codes=new Set([...t.matchAll(/\\b(EUR|PLN|CHF|GBP|USD|CZK|SEK|DKK|NOK|HUF|RON|BGN)\\b/gi)].map(m=>m[1].toUpperCase()));\n if(t.includes('€'))codes.add('EUR');if(/zł/i.test(t))codes.add('PLN');\n if(codes.size>1)return 'unbekannt';if(codes.size===1)return [...codes][0];\n return /\\bFaktura\\b|[$£]/i.test(t)?'unbekannt':'';\n}\nfunction documentDate229(raw){\n const s=String(raw||'').trim();let iso='';\n const named=s.match(/^(\\d{1,2})\\.?\\s+(Januar|Jänner|Februar|März|Maerz|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember)\\s+(20\\d{2})$/i);\n if(named){const months={januar:1,jänner:1,februar:2,märz:3,maerz:3,april:4,mai:5,juni:6,juli:7,august:8,september:9,oktober:10,november:11,dezember:12};iso=named[3]+'-'+String(months[named[2].toLowerCase()]).padStart(2,'0')+'-'+named[1].padStart(2,'0');}\n if(/^20\\d{2}-\\d{2}-\\d{2}$/.test(s))iso=s;\n else{const m=s.match(/^(\\d{1,2})[.\\/-](\\d{1,2})[.\\/-](20\\d{2})$/);if(m)iso=m[3]+'-'+m[2].padStart(2,'0')+'-'+m[1].padStart(2,'0')}\n if(!iso)return '';const [y,m,d]=iso.split('-').map(Number),date=new Date(Date.UTC(y,m-1,d));\n return date.getUTCFullYear()===y&&date.getUTCMonth()===m-1&&date.getUTCDate()===d?iso:'';\n}\nfunction europeanHeader229(text,kind){\n const t=String(text||'').split(/\\r?\\n/).slice(0,60).join('\\n');\n const invoice=kind==='invoice';\n const ref=invoice?/(?:rechnungs[- ]?(?:nr\\.?|nummer)|^\\s*rechnung(?:\\s+(?:nr\\.?|nummer))?|faktura\\s*(?:VAT\\s*)?(?:nr\\.?)?)\\s*[:#]?\\s*([A-Z0-9][A-Z0-9/._-]{1,35})/im:\n  /(?:lieferschein[- ]?(?:nr\\.?|nummer)|liefer[- ]?nr\\.?|\\bWZ(?:\\s*(?:nr\\.?|numer))?)\\s*[:#]?\\s*([A-Z0-9][A-Z0-9/._-]{1,35})/im;\n const match=t.match(ref);let reference=match?.[1]||'';\n const wz=!invoice?t.match(/\\bWZ[/-][A-Z0-9/._-]+/i)?.[0]:'';\n if(reference&&!/\\d/.test(reference))reference='';\n if(!invoice&&match&&/WZ/i.test(match[0]))reference='WZ'+(reference.startsWith('/')?'':'/')+reference;\n const datePattern='(20\\\\d{2}-\\\\d{2}-\\\\d{2}|\\\\d{1,2}[./-]\\\\d{1,2}[./-]20\\\\d{2}|\\\\d{1,2}\\\\.?\\\\s+(?:Januar|Jänner|Februar|März|Maerz|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember)\\\\s+20\\\\d{2})';\n const labels=invoice?'rechnungsdatum|erstellungsdatum|belegdatum|data wystawienia|z dnia':'lieferdatum|data wydania|data wystawienia';\n const raw=labels.split('|').map(label=>t.match(new RegExp('(?:'+label+')\\\\s*:?\\\\s*'+datePattern,'i'))?.[1]).find(Boolean)||t.match(new RegExp('\\\\bDatum\\\\s*:?\\\\s*'+datePattern,'i'))?.[1];\n return {reference:wz||reference,date:documentDate229(raw)};\n}\nfunction explicitSupplier229(text){\n const lines=String(text||'').split(/\\r?\\n/).map(s=>s.trim()).filter(Boolean).slice(0,35);\n const label=/^(?:Sprzedawca|Verkäufer|Rechnungssteller|Lieferant|Absender)\\s*:\\s*(.*)$/i;\n for(let i=0;i<lines.length;i++){\n  const m=lines[i].match(label);if(!m)continue;\n  // OCR flattens side-by-side Polish seller/buyer blocks into one line. Keep\n  // only the seller segment; never return a buyer name as part of the issuer.\n  const sellerOnly=String(m[1]||'').split(/(?:\\||\\[)?\\s*Nabyw\\p{L}*\\s*:/iu)[0];\n  const value=(sellerOnly||lines[i+1]||'').replace(/^Nazwa firmy\\s*:\\s*/i,'')\n   .replace(/[^\\p{L}\\p{N}.)]+$/u,'').replace(/\\bSP\\.\\s*Z\\s*[0O]\\.\\s*[0O]\\./i,'SP. Z O.O.').trim();\n  if(value.length>=3&&!/^(?:NIP|UID|Adres|Adresse|Nabywca)\\b/i.test(value))return value;\n }\n return '';\n}\nconst stop229=/^(?:Summe|Zwischensumme|Netto(?:summe)?|Gesamt(?:betrag|preis)?|Betrag ohne|Rabatt\\s*\\(|Razem|Podatek|według|Słownie|Uwagi dodatkowe|Zahlungs|Bestell|Lieferdatum|Seite\\b)/i;\nconst service229=/(?:\\b(?:Dienstleistung|Stunde|Montage|Einrichtung|Anfahrtspauschale|Versand|Pfand|schneiden|usługa|usluga|transport|kaucja)\\b)/i;\n\n// Quantity-before-SKU delivery tables. Read the summary table once; device\n// lists below it describe the same work and must not become extra deliveries.\nfunction quantityArticleDelivery296(text){\n const lines=String(text||'').split(/\\r?\\n/).map(s=>s.trim()).filter(Boolean);\n const heading=lines.findIndex(s=>/^Pos\\.?\\s+Menge\\s+Artikel[- ]Nr\\.?\\s+Benennung(?:\\s+Ger[aä]t(?:e)?liste\\s+Nr\\.?)?$/i.test(s));\n if(heading<0)return [];\n const devices=/Ger[aä]t(?:e)?liste/i.test(lines[heading]),out=[];\n const row=new RegExp('^(\\\\d{1,3})\\\\s+(\\\\d+(?:[.,]\\\\d+)?)\\\\s*('+unitPattern229+')\\\\s+([A-Z0-9][A-Z0-9/-]{2,24})\\\\s+(.+)$','i');\n const partial=new RegExp('^\\\\d{1,3}\\\\s+\\\\d+(?:[.,]\\\\d+)?\\\\s*(?:'+unitPattern229+')$','i');\n for(let i=heading+1;i<lines.length;i++){\n  if(/^(?:Die Details|Ger[aä]t(?:e)?liste|Datum,|Kontaktdaten|Seite\\b)/i.test(lines[i]))break;\n  let source=lines[i],combined=source;\n  // The source prints one wrapped summary row around an empty material\n  // field. Never join across another numbered row or an attachment heading.\n  if(partial.test(source)&&/^Material Nr\\.\\s*-$/i.test(lines[i+1]||'')&&/^[A-Z0-9][A-Z0-9/-]{2,24}\\s+\\p{L}/u.test(lines[i+2]||'')){\n   combined+=' '+lines[i+2];source+='\\n'+lines[i+1]+'\\n'+lines[i+2];i+=2;\n  }\n  const m=combined.match(row);if(!m)continue;\n  const quantity=number229(m[2]),unit=unit229(m[3]);\n  let name=m[5];\n  if(devices)name=name.replace(/\\s+\\d{3}(?:-\\d{3})?(?:,\\s*\\d{3}(?:-\\d{3})?)*$/,'');\n  if(!(quantity>0&&quantity<=1000000)||!unit||!/[\\p{L}]{2}/u.test(name))continue;\n  out.push({articleNumber:m[4],name,quantity:source.includes('[Menge prüfen]')?null:quantity,unit,\n   ...(source.includes('[Menge prüfen]')?{quantityUncertain:true}:{}),\n   price:null,priceMissing:true,content:packageContent229(name),\n   service:service229.test(name)||['Stunde','Pauschale'].includes(unit)||/\\b(?:Reparatur|Grundüberholungsarbeiten|Wartung)\\b/i.test(name),source});\n }\n return out;\n}\n\n// A paired gross/net table can wrap four monetary cells below one article.\n// Only consume adjacent monetary-only lines inside that article; never search\n// the footer or the next item for missing prices.\nfunction pairedPriceLines230(text){\n const lines=String(text||'').split(/\\r?\\n/).map(s=>s.replace(/\\|/g,' ').trim()).filter(Boolean);\n const heading=lines.findIndex(s=>/^(?:GTIN|EAN)\\s+Artikelbezeichnung\\s+Menge\\s+(?:USt|MwSt)\\.?$/i.test(s));\n if(heading<0)return [];\n const pairHeading=lines.slice(heading+1,heading+5).join(' ');\n if(!/^Preis pro Artikel brutto\\s*\\/\\s*netto,?\\s*(?:€|EUR) Gesamt brutto\\s*\\/\\s*netto,?\\s*(?:€|EUR)$/i.test(pairHeading))return [];\n const money='(?:\\\\d{1,3}(?:[ .]\\\\d{3})+|\\\\d+)[.,]\\\\d{2}';\n const cellsPattern=new RegExp('^'+money+'(?:\\\\s+'+money+')*$');\n const rowPattern=/^(\\d{8,14})\\s+(.+?)\\s+(\\d+(?:[.,]\\d+)?)\\s+(\\d{1,2}(?:[.,]\\d+)?)\\s*%(?:\\s+(.*))?$/;\n const out=[];\n for(let i=heading+5;i<lines.length;i++){\n  if(stop229.test(lines[i]))break;\n  const m=lines[i].match(rowPattern);if(!m)continue;\n  const quantity=number229(m[3]),vatRate=number229(m[4]);\n  if(!(quantity>0&&quantity<=1000000)||!/[\\p{L}]{2}/u.test(m[2]))continue;\n  const source=[lines[i]],raw=[];let malformed=false;\n  const collect=value=>{if(!value)return;if(!cellsPattern.test(value)){malformed=true;return}raw.push(...value.match(new RegExp(money,'g')).map(number229))};\n  collect(m[5]);\n  while(i+1<lines.length&&cellsPattern.test(lines[i+1])){source.push(lines[++i]);collect(lines[i])}\n  const [gross,net,totalGross,totalNet]=raw;\n  const consistent=!malformed&&raw.length===4&&vatRate<=100&&\n   Math.abs(quantity*gross-totalGross)<=.03&&Math.abs(quantity*net-totalNet)<=.03&&\n   Math.abs(net*(1+vatRate/100)-gross)<=.03&&Math.abs(totalNet*(1+vatRate/100)-totalGross)<=.03;\n  out.push({articleNumber:m[1],name:m[2],quantity,unit:'',unitUncertain:true,\n   price:consistent?net:null,grossPrice:consistent?gross:null,priceMissing:!consistent,\n   vatRate,currency:currency229(text),content:packageContent229(m[2]),service:service229.test(m[2]),source:source.join('\\n')});\n }\n return out;\n}\n\n// Table headings determine numeric-column roles. Arithmetic verifies prices,\n// but never supplies a missing quantity or a missing tax rate.\nfunction europeanLines229(text,kind='invoice'){\n if(kind==='delivery_note'){const quantityArticle=quantityArticleDelivery296(text);if(quantityArticle.length)return quantityArticle;}\n const paired=kind==='invoice'?pairedPriceLines230(text):[];if(paired.length)return paired;\n const original=String(text||'').split(/\\r?\\n/).map(s=>s.replace(/\\|/g,' ').trim()).filter(Boolean);\n const heading=original.findIndex(s=>/(?:Bezeichnung|Artikel|Beschreibung|Nazwa)/i.test(s)&&/(?:Menge|Anzahl|[Il1]lo[śs][ćc¢é])/i.test(s));\n if(heading<0)return [];\n const head=original[heading],currency=currency229(text),out=[];\n const qtyFirst=/^Menge\\s+Einheit/i.test(head),polish=/Cena\\s+(?:jedn\\.?\\s*)?netto/i.test(head)&&/VAT/i.test(head);\n const priceBeforeQuantity=/Einzelpreis(?:\\s+(?:netto|brutto))?\\s+Menge\\s+Gesamtpreis/i.test(head);\n const reversePL=/Indeks\\s+J\\.?m\\.?\\s+[Il1]lo[śs][ćc¢é]/i.test(head),discount=/Rabatt/i.test(head);\n const vatColumn=/USt\\.?|MwSt\\.?|VAT/i.test(head),net=/netto|netosumme/i.test(text)||qtyFirst||/Einzelpreis\\s+USt/i.test(head)||polish;\n const body=original.slice(heading+1);\n for(let i=0;i<body.length;i++){\n  let line=body[i];if(stop229.test(line))break;\n  // Join wrapped names only inside a table and never across the next position.\n  if(!qtyFirst&&!reversePL&&/^\\d{1,3}\\s+(?:[A-Z]{1,6}-[A-Z0-9-]+\\s+)?\\p{L}/u.test(line)&&!new RegExp('\\\\s\\\\d+(?:[.,]\\\\d+)?\\\\s+(?:'+unitPattern229+')(?=\\\\s|$)','i').test(line)){\n   for(let n=0;n<3&&i+1<body.length;n++){\n    const next=body[i+1];if(stop229.test(next)||/^\\d{1,3}\\s+(?:[A-Z]{1,6}-)/.test(next))break;\n    line+=' '+body[++i];\n    if(new RegExp('\\\\d+(?:[.,]\\\\d+)?\\\\s+(?:'+unitPattern229+')(?=\\\\s|$)','i').test(next))break;\n   }\n  }\n  let sku='',name='',qty=null,unit='',tail='',precedingPrice=null;\n  if(reversePL){\n   const m=line.match(new RegExp('^\\\\d{1,3}\\\\s+(.+?)\\\\s+([A-Z0-9]+-[A-Z0-9-]+)\\\\s+('+unitPattern229+')\\\\s+(\\\\d+(?:[.,]\\\\d+)?)(?:\\\\s|$)','i'));\n   if(!m)continue;[,name,sku]=m;unit=unit229(m[3]);qty=number229(m[4]);\n  }else if(qtyFirst){\n   const m=line.match(new RegExp('^(\\\\d+(?:[.,]\\\\d+)?)\\\\s+('+unitPattern229+')\\\\s+(.+?)\\\\s+(\\\\d{4,12})\\\\s+(.+)$','i'));\n   if(!m)continue;qty=number229(m[1]);unit=unit229(m[2]);name=m[3];sku=m[4];tail=m[5];\n  }else{\n   const candidates=[...line.matchAll(new RegExp('(\\\\d+(?:[.,]\\\\d+)?)\\\\s+('+unitPattern229+')(?=\\\\s|$)\\\\s*','gi'))];\n   const m=candidates.find(c=>c.index>0&&/^(?:[\\d\\s.,%€]|EUR|PLN|zł)*$/i.test(line.slice(c.index+c[0].length)));\n   if(!m)continue;name=line.slice(0,m.index).trim();qty=number229(m[1]);unit=unit229(m[2]);tail=line.slice(m.index+m[0].length);\n   if(priceBeforeQuantity){\n    const p=name.match(/^(.*?)\\s+((?:\\d{1,3}(?:[ .]\\d{3})+|\\d+)[.,]\\d{2})$/);\n    if(p){name=p[1];precedingPrice=number229(p[2])}\n   }\n   const id=name.match(/^(?:\\d{1,3}\\s+)?(\\d{4,12}|[A-Z]{1,6}-[A-Z0-9-]+)\\s+(.+)$/i);\n   if(id){sku=id[1];name=id[2]}else if(/^(?:Lp\\.?|Nr\\.?|Pos\\.?)\\s/i.test(head))name=name.replace(/^\\d{1,3}\\s+/,'');\n  }\n  if(!(qty>0&&qty<=1000000)||name.length<3||!/[\\p{L}]{2}/u.test(name))continue;\n  // A weight followed by more descriptive text belongs to the product name,\n  // not to the delivered quantity. Unsupported layouts stay unresolved.\n  if(tail&&!/^[\\d\\s.,%€]+(?:EUR|PLN|zł)?/i.test(tail)&&!reversePL)continue;\n  let price=null,vatRate=null,discountRate=null,grossPrice=null,unclassifiedPrice=null;\n  const clean=tail.replace(/EUR|PLN|zł|€/gi,'').trim();\n  const money=[...clean.matchAll(/(?:\\d{1,3}(?:[ .]\\d{3})+|\\d+)[.,]\\d{2}(?!\\d)/g)].map(m=>number229(m[0]));\n  const percent=[...clean.matchAll(/(\\d{1,2}(?:[.,]\\d+)?)\\s*%/g)].map(m=>number229(m[1]));\n  if(priceBeforeQuantity){\n   if(precedingPrice!==null&&money.length===1&&Math.abs(qty*precedingPrice-money[0])<=.03){\n    if(/Einzelpreis\\s+netto/i.test(head))price=precedingPrice;\n    else if(/Einzelpreis\\s+brutto/i.test(head))grossPrice=precedingPrice;\n    else unclassifiedPrice=precedingPrice;\n   }\n  }else if(polish){\n   const m=clean.match(/^((?:\\d{1,3}(?:[ .]\\d{3})+|\\d+)[.,]\\d{2})\\s+(\\d{1,2})(?:\\s*%)?\\s+/);\n   if(m&&money.length===4){\n    const candidate=number229(m[1]),rate=number229(m[2]),total=money[1],tax=money[2],gross=money[3];\n    if(Math.abs(qty*candidate-total)<=.03&&Math.abs(total+tax-gross)<=.03&&Math.abs(total*rate/100-tax)<=.03){price=candidate;vatRate=rate}\n   }\n  }else if(money.length===2){\n   const rate=discount&&percent.length===1?percent[0]:0,candidate=money[0]*(1-rate/100);\n   if(rate>=0&&rate<=100&&Math.abs(qty*candidate-money[1])<=.03){\n    if(net)price=Math.round(candidate*1000000)/1000000;\n    if(discount)discountRate=rate;else if(vatColumn&&percent.length===1)vatRate=percent[0];\n   }\n  }\n  out.push({articleNumber:sku,name,quantity:line.includes('[Menge prüfen]')?null:qty,unit,price,vatRate,currency,\n   ...(priceBeforeQuantity?{grossPrice,unclassifiedPrice,priceMissing:price===null&&grossPrice===null}:{}),\n   ...(line.includes('[Menge prüfen]')?{quantityUncertain:true}:{}),\n   ...(discountRate!==null?{discountRate}:{}),content:packageContent229(name),\n   service:service229.test(name)||['Stunde','Pauschale'].includes(unit),source:line});\n }\n return out;\n}\n\nreturn {number229,unit229,packageContent229,currency229,documentDate229,europeanHeader229,explicitSupplier229,quantityArticleDelivery296,pairedPriceLines230,europeanLines229};})();\nmodules['inbound-document182']=(()=>{\nconst {europeanHeader229,explicitSupplier229}=modules['europe229'];\n// Incoming documents have different roles: a delivery note proves delivered\n// quantities, an invoice adds prices. Never derive a price from a blank column.\nfunction classifyInboundDocument182(text){\n const lines=String(text||'').split(/\\r?\\n/).map(x=>x.trim()).filter(Boolean);\n const head=lines.slice(0,45).join('\\n');\n // A type label may precede the actual document kind. These documents do not\n // prove a received purchase and must never enter the invoice stock flow.\n // Keep references such as \"Bezug: Angebot A-123\" allowed on a real invoice.\n if(lines.slice(0,20).some(line=>/^(?:(?:dokument(?:art|typ)?|belegart|typ|art)\\s*[:：-]\\s*)?(?:Angebot|Proforma(?:[- ]?Rechnung)?|Gutschrift|Stornorechnung|Credit\\s+Note|Faktura\\s+(?:pro\\s*forma|koryguj[aą]ca)|Korekta\\s+faktury|Oferta)(?:\\s|$)/i.test(line)))return 'unknown';\n if(/\\bFaktura\\b/i.test(head))return 'invoice';\n // Numbered invoice headings outrank delivery references, including duplicated\n // visible/hidden PDF text. A \"Rechnung zu Lieferschein\" reference is not enough.\n if(lines.slice(0,35).some(line=>/^(?:Rechnung\\s+)?(?:Sammelrechnung|Rechnung|Invoice)\\s*[:：]\\s*(?:RE[-/])?[A-Z0-9][A-Z0-9/-]*\\d[A-Z0-9/-]*(?:\\s|$)/i.test(line)))return 'invoice';\n const delivery=/\\b(?:lieferschein|delivery\\s+note|WZ)\\b|wydani[ae]\\s+zewn[eę]trzn/i.test(head);\n const invoice=/\\b(?:sammelrechnung|rechnung|invoice)\\s*(?:nr\\.?|nummer|#|[:：])?/i.test(head);\n // OCR may read columns out of order: a standalone invoice title outranks a\n // reference to a delivery note elsewhere on the same invoice.\n if(lines.slice(0,35).some(line=>/^(?:sammelrechnung|rechnung|invoice)(?:\\s*(?:nr\\.?|nummer|#|[:：])\\s*[\\w/-]+)?$/i.test(line)))return 'invoice';\n if(invoice&&(!delivery||head.search(/\\brechnung\\b/i)<head.search(/\\blieferschein\\b/i)))return 'invoice';\n if(delivery)return 'delivery_note';\n // A cash-register receipt also proves a purchase. It enters the invoice\n // review, where every line must still be classified before stock booking.\n if(/\\b(?:kassen\\s*(?:id|nr)|trans\\s*[:.]|bediener)\\b/i.test(head)&&\n    /\\b(?:bar|karte|gesamt|zwisch\\.?\\s*summ)\\b/i.test(head))return 'invoice';\n return 'unknown';\n}\nfunction inboundSupplier182(text){\n const explicit=explicitSupplier229(text);if(explicit)return explicit;\n const issuer=String(text||'').split(/\\r?\\n/).slice(0,35).join('\\n');\n const orient=issuer.match(/Orient\\s+Marketingservice\\s+GmbH[.,]?\\s*N(?:fg|ig)[.,]?\\s*KG/i);\n if(orient&&/orient(?:marketing)?\\s*[.,]?\\s*at\\b/i.test(issuer))return 'Orient Marketingservice GmbH. Nfg. KG';\n if(/\\bMETRO\\s+Cash\\s*&\\s*Carry\\b/i.test(issuer)&&/\\bwww\\.?\\s*metro[.,]\\s*at\\b/i.test(issuer))return 'METRO Cash & Carry Österreich GmbH';\n if(/\\bMETRO\\b/i.test(issuer)&&/\\bCarry\\b[\\s\\S]{0,45}\\bGmbH\\b/i.test(issuer)&&/\\bmetro[.,]\\s*at\\b/i.test(issuer))return 'METRO Cash & Carry Österreich GmbH';\n const rawHead=String(text||'').split(/\\r?\\n/).slice(0,35);\n const roleLine=line=>/^\\s*(?:Firma|Kunde|Warenempfänger|Rechnungsempfänger|Nabywca|Odbiorca)\\s*:?(?:\\s|$)/i.test(line);\n const recipients=new Set(rawHead.filter((line,index)=>/^\\s*(?:Herrn|Frau|z\\.?\\s*H\\.)\\b/i.test(rawHead[index+1]||'')||rawHead.slice(Math.max(0,index-3),index).some(roleLine)||rawHead.slice(index+1,index+3).some(roleLine)).map(line=>line.trim()));\n const head=rawHead.filter(line=>!recipients.has(line.trim()));\n let supplier=head.map(x=>x.trim()).find(x=>/\\b(?:GmbH|GmbH\\s*&\\s*Co\\.?\\s*KG|OG|e\\.U\\.|AG)\\b/i.test(x)&&\n  !/^(?:Firma|Kunde|Lieferant|Rechnung|Lieferschein|Nabywca|Odbiorca)\\b|^(?:vielen Dank\\b|Danke\\b|Wir\\s|Ihr Einkauf\\b|Sie\\s|Bitte\\s)/i.test(x))||'';\n supplier=supplier.match(/^(.+?\\b(?:GmbH\\s*&\\s*Co\\.?\\s*KG|GmbH|OG|AG|KG|e\\.U\\.))/i)?.[1]||supplier;\n if(/bioschaefer[.\\s]+at/i.test(text)&&/\\b(?:blo|bio)\\s+schaf?er\\b/i.test(supplier))return 'Bio Schäfer GmbH';\n if(/\\b(?:www\\.)?\\s*metro\\.\\s*at\\b/i.test(text)&&/\\b(?:wetro|metro)\\b/i.test(supplier))return 'METRO Cash & Carry Österreich GmbH';\n if(!supplier&&/\\bg\\s*u\\s*t\\s*s\\s*j\\s*a\\s*h\\s*r\\b/i.test(text))return 'Bäckerei Gutsjahr';\n if(!supplier&&/\\bg\\s*u\\s*t\\s*s\\s*j\\s*a\\s*h\\s*r\\s*e\\b/i.test(text)&&/\\b61508568\\b/.test(text))return 'Bäckerei Gutsjahr';\n return supplier;\n}\nfunction invoiceHeader182(text){\n const lines=String(text||'').split(/\\r?\\n/).map(x=>x.trim()).filter(Boolean);\n const head=lines.slice(0,45).join('\\n');\n const receiptNumber=head.match(/(?:^|\\s)#\\s*(\\d{3,10})\\b/)?.[1]||head.match(/\\bTRANS\\s*[:：]\\s*(\\d{4,12})\\b/i)?.[1]||'';\n const numberedTitle=head.match(/(?:^|\\n)(?:Rechnung\\s+)?(?:Sammelrechnung|Rechnung|Invoice)\\s*[:：]\\s*((?:RE[-/])?[A-Z0-9][A-Z0-9/-]*\\d[A-Z0-9/-]*)(?:\\s|$)/i)?.[1]||'';\n const reference=numberedTitle||head.match(/\\bRechnungs(?:nummer|[- ]?nr\\.?)\\s*[:：#]?\\s*([A-Z0-9][A-Z0-9/-]{2,30})\\b/i)?.[1]||head.match(/\\b(?:beleg\\w*|rechnungs?(?:nummer|nr\\.?))\\s*[:：#]?\\s*([A-Z]{0,3}20\\d{2}[-/]?\\d{4,}(?:\\/20\\d{6})?)\\b/i)?.[1]||\n  head.match(/\\brechnung\\s*(?:nr\\.?|nummer|#)?\\s*[:：#]?\\s*([A-Z]{0,3}20\\d{2}[-/]?\\d{4,}(?:\\/20\\d{6})?|\\d{6,}(?:\\/20\\d{6})?)\\b/i)?.[1]||(receiptNumber?'BON-'+receiptNumber:'');\n const rawDate=head.match(/(?:^|[\\s])(?:rechnungsdatum|erstellungsdatum|belegdatum)\\s*[:：]?\\s*(\\d{1,2}[.]\\d{1,2}[.]20\\d{2})/i)?.[1]||head.match(/(?:^|[\\s])Datum\\s*[:：]\\s*(\\d{1,2}[.]\\d{1,2}[.]20\\d{2})/i)?.[1]||'';\n const receiptDate=reference.startsWith('BON-')?head.match(/\\b(\\d{1,2})[.\\/-](\\d{1,2})[.\\/-](20\\d{2})\\b/):null;\n const referenceDate=reference.match(/\\/(20\\d{2})(\\d{2})(\\d{2})$/);\n const date=referenceDate?`${referenceDate[1]}-${referenceDate[2]}-${referenceDate[3]}`:rawDate?rawDate.split('.').reverse().join('-'):receiptDate?`${receiptDate[3]}-${receiptDate[2].padStart(2,'0')}-${receiptDate[1].padStart(2,'0')}`:'';\n const extra=europeanHeader229(text,'invoice');\n return {supplier:inboundSupplier182(text),reference:String(text).includes('[Belegnummer prüfen]')?'':reference||extra.reference,date:String(text).includes('[Belegdatum prüfen]')?'':validDate182(date)?date:extra.date};\n}\nfunction canReplaceInvoicePlaceholder182(entry,detected){\n return !!entry&&detected==='delivery_note'&&entry.status==='Prüfung ausstehend'&&\n  !entry.linkedDeliveryId;\n}\nfunction deliveryHeader182(text){\n const lines=String(text||'').split(/\\r?\\n/).map(x=>x.trim()).filter(Boolean);\n const near=lines.slice(0,35).join('\\n').replace(/Belegnurnmer/gi,'Belegnummer');\n const reference=near.match(/\\bLS[-\\s/]?(\\d{5,})\\b/i)?.[0]?.replace(/\\s/g,'')||near.match(/(?:belegnummer|lieferschein(?:nummer|[- ]?nr\\.?))\\s*[:：]?\\s*(\\d{4,}(?:[-/]\\d{3,})?)\\b/i)?.[1]||'';\n const rawDate=near.match(/\\blieferdatum\\s*[:：]?\\s*(\\d{1,2}[.]\\d{1,2}[.]20\\d{2})/i)?.[1]||near.match(/\\bdatum\\s*[:：]?\\s*(\\d{1,2}[.]\\d{1,2}[.]20\\d{2})/i)?.[1]||'';\n const date=rawDate?validDate182(rawDate.split('.').reverse().join('-'))?rawDate.split('.').reverse().join('-'):'':'';\n const order=near.match(/\\b(?:bestell[-\\s]*nr\\.?|bestellnummer)\\s*[:：]?\\s*([\\w/-]+)/i)?.[1]||'';\n const extra=europeanHeader229(text,'delivery_note');\n return {reference:String(text).includes('[Belegnummer prüfen]')?'':reference||extra.reference,date:String(text).includes('[Belegdatum prüfen]')?'':date||extra.date,order};\n}\nfunction validDate182(value){\n if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(String(value||'')))return false;\n const [year,month,day]=value.split('-').map(Number),check=new Date(Date.UTC(year,month-1,day));\n return check.getUTCFullYear()===year&&check.getUTCMonth()===month-1&&check.getUTCDate()===day;\n}\n// Only a distinct article row can suggest a delivered quantity. Totals, prices\n// and packaging weights must never turn into stock movements.\nfunction deliveryRows182(text){\n return String(text||'').split(/\\r?\\n/).map(line=>{\n  const match=line.trim().match(/^(\\d{4,9})\\s+(.{4,90}?)\\s+(\\d{1,5}(?:[.,]\\d{1,3})?)\\s+(kg|g|st(?:ück|k)?\\.?|fl(?:aschen)?)\\b/i);\n  if(!match||/^(?:gesamt|netto|brutto|betrag)/i.test(match[2]))return null;\n  return {articleNumber:match[1],name:match[2].trim(),quantity:Number(match[3].replace(',','.')),unit:match[4]};\n }).filter(x=>x&&Number.isFinite(x.quantity)&&x.quantity>0);\n}\nfunction validateDelivery182(input,products,prior=[]){\n const supplier=String(input.supplier||'').trim(),reference=String(input.reference||'').trim(),date=String(input.date||'').trim();\n if(!supplier||!reference||!validDate182(date))throw Error('Lieferant, Lieferscheinnummer und gültiges Datum angeben');\n const key=deliveryKey182(supplier,reference,date);\n if(prior.some(x=>sameInboundIdentity254(x,{supplier,reference,date,key})||sameInboundFile192(x.fileHash,input.fileHash)))throw Error('Dieser Lieferschein oder dieses Foto wurde bereits gebucht');\n if(!Array.isArray(input.items)||!input.items.length)throw Error('Mindestens eine Position angeben');\n const seen=new Map();\n const items=input.items.map((row,index)=>{const code=String(row.code||''),a=products?.[code],qty=Number(row.qty),mhd=String(row.mhd||'');\n  if(!a)throw Error('Jede Position einem vorhandenen Artikel zuordnen');\n  const requiresMhd=a.mhdPolicy144==='required'||(!['optional','none'].includes(a.mhdPolicy144)&&a.mhd===true);\n  const noMhd=a.mhdPolicy144==='none'||(!a.mhdPolicy144&&a.mhd===false);\n  if(noMhd&&mhd)throw Error('Position '+(index+1)+': Dieser Artikel hat kein MHD');\n  const lot=code+'|'+mhd;\n  if(seen.has(lot))throw Error('Positionen '+(seen.get(lot)+1)+' und '+(index+1)+': gleicher Artikel „'+a.name+'“, '+(mhd?'MHD '+mhd.split('-').reverse().join('.'):'ohne MHD')+'. MHD je Abholung prüfen; sonst Menge zusammenfassen.');seen.set(lot,index);\n  if(!Number.isFinite(qty)||qty<=0||qty>1000000)throw Error('Eine gültige gelieferte Menge eingeben');\n  if(/^(stück|stk\\.?|flasche[n]?|becher|dose[n]?)$/i.test(String(a.unit||'').trim())&&!Number.isInteger(qty))throw Error('Für Stückware nur ganze Mengen eingeben');\n  if(requiresMhd&&!validDate182(mhd))throw Error('MHD für jeden MHD Artikel angeben');\n  if(mhd&&!validDate182(mhd))throw Error('MHD prüfen');\n  return {code,qty,mhd};});\n return {supplier,reference,date,key,items};\n}\nfunction deliveryKey182(supplier,reference,date){\n return [supplier,reference,date].map(x=>String(x||'').toLocaleLowerCase('de').normalize('NFKD')\n  .replace(/[\\u0300-\\u036f]/g,'').replace(/[^a-z0-9]+/g,'')).join(':');\n}\nfunction sameInboundFile192(a,b){\n const empty='e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';\n return !!a&&!!b&&a!==empty&&b!==empty&&a===b;\n}\nfunction sameInboundSupplier192(a,b){\n const normalize=value=>String(value||'').toLocaleLowerCase('de').normalize('NFKD').replace(/[\\u0300-\\u036f]/g,'').replace(/ß/g,'ss').replace(/\\b(gmbh|og|ag|kg)\\b/g,'').replace(/[^a-z0-9]+/g,' ').trim();\n const first=normalize(a),second=normalize(b);\n return !!first&&!!second&&(first===second||(Math.min(first.length,second.length)>=4&&(first.startsWith(second+' ')||second.startsWith(first+' '))));\n}\n\n// A document identity does not change when its date is corrected or it is photographed again.\nfunction sameInboundIdentity254(a,b){\n const supplier=x=>String(x||'').toLocaleLowerCase('de').normalize('NFKD').replace(/[\\u0300-\\u036f]/g,'').replace(/ß/g,'ss').replace(/\\b(gmbh|og|ag|kg)\\b/g,'').replace(/[^a-z0-9]+/g,'');\n const reference=x=>String(x||'').toLocaleLowerCase('de').replace(/[^a-z0-9]+/g,'');\n if(a?.supplier&&a?.reference&&b?.supplier&&b?.reference)return supplier(a.supplier)===supplier(b.supplier)&&reference(a.reference)===reference(b.reference);\n const prefix=x=>String(x?.key||'').split(':').slice(0,2).join(':');return !!prefix(a)&&prefix(a)===prefix(b);\n}\n\nreturn {classifyInboundDocument182,inboundSupplier182,invoiceHeader182,canReplaceInvoicePlaceholder182,deliveryHeader182,validDate182,deliveryRows182,validateDelivery182,deliveryKey182,sameInboundFile192,sameInboundSupplier192,sameInboundIdentity254};})();\nmodules['inbound-lines185']=(()=>{\nconst {europeanLines229,packageContent229}=modules['europe229'];\nconst {validDate182}=modules['inbound-document182'];\n\nfunction deliveryLines185(text){\n const rows=[];\n const originals=String(text||'').split(/\\r?\\n/);\n for(const [index,original] of originals.entries()){\n  const line=original.replace(/\\|/g,' ').trim();\n  const start=line.match(/^(?:\\d{1,3}[.)]?\\s+)?(\\d{4,10})\\s+(.{5,180})$/);\n  if(!start)continue;\n  const rawTail=start[2];let tail=rawTail.replace(/\\b\\d{1,2}[.]\\d{1,2}[.]20\\d{2}\\b/g,' ');\n  const matches=[...tail.matchAll(/\\b(\\d{1,6}(?:[.,]\\d{1,3})?)\\s+(F[lI1](?:aschen?)?|St(?:k\\.?|ück)?|Becher|Dosen?|Pkg\\.?|Packungen?|Kartons?|kg|g|Liter|L)\\b/gi)];\n  // Some invoices print a delivered litre quantity directly after the date\n  // as \"20L\". Require the date immediately before it so a bottle size in\n  // the product name (such as 330ml) cannot become a delivered quantity.\n  for(const match of rawTail.matchAll(/\\b\\d{1,2}[.]\\d{1,2}[.]20\\d{2}\\s+(\\d{1,6}(?:[.,]\\d{1,3})?)L\\b/gi)){\n   const quantity=match[1],index=tail.indexOf(quantity+'L');\n   if(index>=0)matches.push({0:quantity+'L',1:quantity,2:'L',index});\n  }\n  matches.sort((a,b)=>a.index-b.index);\n  // The last quantity with a warehouse unit is generally the delivered\n  // quantity; an earlier weight can be the content of a single package.\n  let chosen=matches.at(-1),qty=chosen?Number(chosen[1].replace(',','.')):0;\n  let unit=chosen?.[2]||'';\n  if(!chosen){\n   const sanLucar=tail.match(/\\b(\\d{1,5}(?:[.,]\\d{1,3})?)\\s*\\/\\s*\\d{1,5}(?:[.,]\\d{1,3})?\\s+\\d{1,5}(?:[.,]\\d{1,3})?\\b/);\n   if(sanLucar&&/\\bbecher\\b/i.test(tail)){qty=Number(sanLucar[1].replace(',','.'));unit='Becher';chosen={index:sanLucar.index}}\n  }\n  if(!chosen||!Number.isFinite(qty)||qty<=0||qty>1000000)continue;\n  let name=tail.slice(0,chosen.index).replace(/^[\\s—–_\\-]+/,'').replace(/(\\d)\\s*m[!I1](?=\\s|$|[,.])/g,'$1ml').replace(/\\s+/g,' ').replace(/\\s+\\d{1,5}(?:[.,]\\d{1,3})?\\s*\\/\\s*$/,'').trim();\n  if(name.length<4||/^(?:netto|gesamt|zwischensumme|betrag|mwst)/i.test(name))continue;\n  const continuation=originals[index+1]?.replace(/\\|/g,' ').trim()||'';\n  if(/^(?:(?:mittel|groß|klein|Inhalt)\\s+)?\\d+(?:[.,]\\d+)?\\s*(?:kg|g|ml|Liter|L)\\s*$/i.test(continuation))name+=' '+continuation;\n  rows.push({content:packageContent229(name),articleNumber:start[1],name,quantity:line.includes('[Menge prüfen]')?null:qty,...(line.includes('[Menge prüfen]')?{quantityUncertain:true}:{}),unit:stockUnit185(unit),source:line});\n }\n const more=europeanLines229(text,'delivery_note');\n for(const row of more){if(!rows.some(r=>r.articleNumber&&r.articleNumber===row.articleNumber||r.name===row.name))rows.push(row)}\n const merged=rows.map(row=>({...more.find(e=>e.articleNumber&&e.articleNumber===row.articleNumber),...row}));\n // An explicit total weight can reject a contradictory OCR quantity, but\n // cannot supply a replacement quantity or allocate weight among products.\n const weight=String(text||'').match(/\\bGesamtgewicht\\s*:\\s*(\\d+(?:[.,]\\d{1,3})?)/i);\n if(weight){const total=Number(weight[1].replace(',','.')),kg=merged.filter(r=>r.unit==='kg'&&Number.isFinite(r.quantity));\n  if(total>0&&kg.reduce((sum,r)=>sum+r.quantity,0)>total+0.001)for(const row of kg){row.quantity=null;row.quantityUncertain=true;row.source+=' [Menge prüfen]';}}\n return merged;\n}\n\nfunction stockUnit185(unit){\n if(/^(?:f[li1]|flaschen?)$/i.test(unit))return 'Flasche';\n if(/^(?:st|stk\\.?|stück)$/i.test(unit))return 'Stück';\n if(/^becher$/i.test(unit))return 'Becher';\n if(/^dosen?$/i.test(unit))return 'Dose';\n if(/^(?:pkg\\.?|packungen?)$/i.test(unit))return 'Packung';\n if(/^kartons?$/i.test(unit))return 'Karton';\n return unit.toLowerCase()==='kg'?'kg':unit.toLowerCase()==='g'?'g':/^(?:l|liter)$/i.test(unit)?'Liter':'Stück';\n}\n\nfunction normalized185(text){return String(text||'').toLocaleLowerCase('de').normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g,'').replace(/ß/g,'ss').replace(/[^a-z0-9]+/g,' ').trim()}\n\n// Confirmed document aliases live on the tenant's own products and are saved\n// atomically with stock booking. Never store a reusable quantity, price or MHD.\nfunction learningKey234(row,supplier){\n const source=normalized185(supplier),sku=String(row.articleNumber||row.sku||'').trim().toUpperCase();\n const name=normalized185(row.sourceName||row.name),unit=normalized185(row.sourceUnit||row.unit);\n if(!source||!unit||(!sku&&name.length<3))return '';\n return JSON.stringify([source,sku?'sku':'name',sku||name,unit]);\n}\nfunction learnedMatch234(row,products,supplier){\n const key=learningKey234(row,supplier);if(!key)return {known:false,code:''};\n const matches=Object.entries(products||{}).filter(([,a])=>(a.inboundLearning234||[]).some(x=>x.key===key));\n return {known:matches.length>0,code:matches.length===1?matches[0][0]:''};\n}\nfunction rememberInbound234(products,supplier,drafts,items,reference,at=new Date().toISOString()){\n const groups=new Map();\n drafts.forEach((draft,index)=>{\n  const code=items[index]?.code;if(!products[code])return;\n  const key=learningKey234(draft,supplier);if(!key)return;\n  if(!groups.has(key))groups.set(key,new Map());\n  groups.get(key).set(code,draft);\n });\n for(const [key,targets] of groups){\n  const previous=Object.entries(products).filter(([,a])=>(a.inboundLearning234||[]).some(x=>x.key===key)).map(([code])=>code);\n  for(const a of Object.values(products))if(a.inboundLearning234)a.inboundLearning234=a.inboundLearning234.filter(x=>x.key!==key);\n  for(const [code,draft] of targets){\n   const article=products[code];article.inboundLearning234??=[];\n   const record={key,supplier:String(supplier).trim(),sku:String(draft.sku||draft.articleNumber||'').trim(),sourceName:String(draft.sourceName||draft.name||'').trim(),sourceUnit:String(draft.sourceUnit||draft.unit||''),confirmedName:String(draft.name||article.name),reference:String(reference||''),at};\n   article.inboundLearning234.push(record);article.inboundLearning234=article.inboundLearning234.slice(-100);\n   article.inboundLearningHistory234??=[];\n   article.inboundLearningHistory234.push({...record,previousCodes:previous});article.inboundLearningHistory234=article.inboundLearningHistory234.slice(-30);\n  }\n }\n}\n\n\nfunction fingerprintHash203(text){let hash=2166136261;for(const ch of String(text)){hash^=ch.codePointAt(0);hash=Math.imul(hash,16777619)}return (hash>>>0).toString(36)}\nfunction inboundRowFingerprint203(row,supplier=''){\n const price=Number.isFinite(Number(row?.price))?Number(row.price).toFixed(4):'';\n const qty=Number.isFinite(Number(row?.quantity))?Number(row.quantity).toFixed(4):'';\n const source=normalized185(row?.source||row?.sourceName||row?.name||'');\n const raw=[normalized185(supplier),source,String(row?.articleNumber||row?.sku||'').trim().toUpperCase(),String(row?.barcode||'').replace(/\\s/g,''),qty,normalized185(row?.unit||''),price].join('|');\n return 'pos-'+fingerprintHash203(raw);\n}\nfunction prepareInboundPositions203(rows,supplier=''){\n const seenSemantic=new Map(),occurrence=new Map(),out=[];\n for(const row of rows||[]){\n  const semantic=[String(row?.articleNumber||'').trim().toUpperCase(),String(row?.barcode||'').replace(/\\s/g,''),normalized185(row?.name||''),Number.isFinite(Number(row?.quantity))?Number(row.quantity).toFixed(4):'',normalized185(row?.unit||''),Number.isFinite(Number(row?.price))?Number(row.price).toFixed(4):''].join('|');\n  const previous=seenSemantic.get(semantic),source=normalized185(row?.source||'');\n  // Different parser paths may describe the same semantic row with different source text.\n  // Keep one of those, but preserve a truly repeated printed row when its source text is identical.\n  if(previous&&previous.source!==source)continue;\n  const fp=inboundRowFingerprint203(row,supplier),n=(occurrence.get(fp)||0)+1;occurrence.set(fp,n);\n  const prepared={...row,positionFingerprint203:fp,positionId203:fp+'-'+n};out.push(prepared);\n  if(!previous)seenSemantic.set(semantic,{source});\n }\n return out;\n}\nfunction articleContent203(article){\n const c=article?.stockContent;if(c&&Number(c.qty)>0&&c.unit)return {amount:Number(c.qty),unit:String(c.unit),count:1};\n return null;\n}\nfunction contentCompatible203(row,article){\n if(!row?.content)return true;const own=articleContent203(article);if(!own)return false;\n const a=Number(row.content.amount)*Number(row.content.count||1),b=Number(own.amount)*Number(own.count||1);\n if(!Number.isFinite(a)||!Number.isFinite(b)||a<=0||b<=0)return false;\n const converted=(from,to,value)=>{if(from===to)return value;const mass={g:1,kg:1000},vol={ml:1,l:1000,liter:1000};const f=normalized185(from),t=normalized185(to);if(mass[f]&&mass[t])return value*mass[f]/mass[t];if(vol[f]&&vol[t])return value*vol[f]/vol[t];return null};\n const value=converted(row.content.unit,own.unit,a);return value!==null&&Math.abs(value-b)<=Math.max(.001,b*.01);\n}\nfunction possibleDeliveryMatches203(row,products,supplier=''){\n const source=normalized185(supplier),name=normalized185(row?.name||'');if(name.length<3)return [];\n const compatible=unit=>normalized185(unit)===normalized185(row.unit)||normalized185(unit)==='stuck'&&['Flasche','Becher','Dose'].includes(row.unit);\n const words=new Set(name.split(' ').filter(x=>x.length>2));\n return Object.entries(products||{}).filter(([,a])=>compatible(a.unit)&&contentCompatible203(row,a)).map(([code,a])=>{const candidate=normalized185(a.name),other=new Set(candidate.split(' ').filter(x=>x.length>2)),shared=[...words].filter(x=>other.has(x)).length,ratio=shared/Math.max(1,Math.min(words.size,other.size));const supplierText=normalized185(a.supplier||'');return {code,name:a.name,ratio,supplierSame:!!source&&!!supplierText&&(source.includes(supplierText)||supplierText.includes(source))}}).filter(x=>x.ratio>=.6).sort((a,b)=>Number(b.supplierSame)-Number(a.supplierSame)||b.ratio-a.ratio).slice(0,4);\n}\n\nfunction matchDelivery185(row,products,supplier){\n const sku=String(row.articleNumber||''),source=normalized185(supplier);\n const compatible=unit=>normalized185(unit)===normalized185(row.unit)||\n  normalized185(unit)==='stuck'&&['Flasche','Becher','Dose'].includes(row.unit);\n const supplierIsSame=value=>{const known=normalized185(value);return !!known&&!!source&&\n  (known===source||(known.length>=5&&source.includes(known))||(source.length>=5&&known.includes(source)))};\n const candidates=Object.entries(products||{}).filter(([,a])=>compatible(a.unit));\n // 1. Known supplier + supplier article number has priority over every public/global code.\n const linked=sku?candidates.filter(([,a])=>\n  (a.supplierArticles||[]).some(x=>x.confirmed&&String(x.sku||'')===sku&&supplierIsSame(x.supplier))||\n  String(a.supplierArticleNumber181||'')===sku&&supplierIsSame(a.supplier)):[];\n if(linked.length===1)return linked[0][0];\n if(linked.length>1)return '';\n // 2. Exact EAN/barcode. Pack barcodes with a factor >1 need manual unit confirmation.\n const barcode=String(row.barcode||'').replace(/\\s/g,'');\n if(barcode){\n  const owners=Object.entries(products||{}).filter(([code,a])=>code===barcode||String(a.barcode||'')===barcode||(a.packs||[]).some(p=>String(p[2]||'')===barcode));\n  if(owners.length!==1)return '';\n  const [code,a]=owners[0],pack=(a.packs||[]).find(p=>String(p[2]||'')===barcode);\n  return compatible(a.unit)&&(!pack||Number(pack[1])===1)?code:'';\n }\n // 3. A mapping explicitly confirmed by this tenant.\n const learned=learnedMatch234(row,products,supplier);\n if(learned.known)return learned.code&&compatible(products[learned.code].unit)?learned.code:'';\n // 4. Exact normalized product name only when printed package content is compatible.\n const name=normalized185(row.name);\n const exact=candidates.filter(([,a])=>normalized185(a.name)===name&&contentCompatible203(row,a));\n return exact.length===1?exact[0][0]:'';\n}\n\nfunction validateDeliveryDraft185(rows,products){\n if(!Array.isArray(rows)||!rows.length)throw Error('Mindestens eine Position prüfen');\n const result=rows.map((r,i)=>{\n  const code=String(r.code||''),product=products?.[code],isNew=code==='__new__';\n  if(!isNew&&!product)throw Error('Position '+(i+1)+': Artikel auswählen');\n  const name=String(isNew?r.name:product.name||'').trim(),unit=String(isNew?r.unit:product.unit||'').trim();\n  const qty=Number(String(r.qty||'').replace(',','.')),mhd=String(r.mhd||'').trim();\n  if(name.length<3||!unit)throw Error('Position '+(i+1)+': Artikelname und Lagereinheit prüfen');\n  if(!Number.isFinite(qty)||qty<=0||qty>1000000)throw Error('Position '+(i+1)+': gelieferte Menge prüfen');\n  if(/^(?:stück|flasche|becher|dose|packung|karton)$/i.test(unit)&&!Number.isInteger(qty))throw Error('Position '+(i+1)+': ganze Stückzahl eingeben');\n  const policy=isNew?String(r.mhdPolicy||'required'):(product.mhdPolicy144||((product.mhd===true)?'required':'none'));\n  if(policy==='none'&&mhd)throw Error('Position '+(i+1)+': Dieser Artikel hat kein MHD');\n  if(policy==='required'&&!validDate182(mhd))throw Error('Position '+(i+1)+': MHD vom Produkt eingeben');\n  if(mhd&&!validDate182(mhd))throw Error('Position '+(i+1)+': MHD prüfen');\n  const category=isNew?String(r.category||'Lebensmittel'):(product.cat||'');\n  if(isNew&&!['Lebensmittel','Verbrauchsartikel','Sonstiges'].includes(category))throw Error('Position '+(i+1)+': Artikelart prüfen');\n  if(isNew&&!['required','optional','none'].includes(policy))throw Error('Position '+(i+1)+': MHD-Regel prüfen');\n  return {code,name,unit,qty,mhd,mhdPolicy:policy,category,sku:String(r.sku||'').trim()};\n });\n const seen=new Map();\n for(const [index,row] of result.entries()){const id=(row.code==='__new__'?row.sku||normalized185(row.name):row.code)+'|'+row.mhd;\n  if(seen.has(id)){const first=seen.get(id),date=row.mhd?row.mhd.split('-').reverse().join('.'):'ohne MHD';\n   throw Error('Positionen '+(first+1)+' und '+(index+1)+': gleicher Artikel'+(row.sku?' ('+row.sku+')':'')+', '+(row.mhd?'MHD ':'')+date+'. MHD je Abholung prüfen; sonst Menge zusammenfassen.');\n  }seen.set(id,index)}\n return result;\n}\n\nreturn {deliveryLines185,stockUnit185,learnedMatch234,rememberInbound234,inboundRowFingerprint203,prepareInboundPositions203,possibleDeliveryMatches203,matchDelivery185,validateDeliveryDraft185};})();\nmodules['invoice-data191']=(()=>{\nconst {europeanLines229,packageContent229,currency229}=modules['europe229'];\n// Read explicit invoice rows. Never interpret a monetary amount as a stock quantity.\nconst xmlText191=value=>String(value||'').replace(/&(?:amp|lt|gt|quot|apos|#(?:x[\\da-f]+|\\d+));/gi,entity=>{\n const named={amp:'&',lt:'<',gt:'>',quot:'\"',apos:\"'\"},key=entity.slice(1,-1).toLowerCase();\n if(named[key])return named[key];\n const code=key.startsWith('#x')?parseInt(key.slice(2),16):key.startsWith('#')?parseInt(key.slice(1),10):0;\n return code>0&&code<=0x10ffff?String.fromCodePoint(code):entity;\n});\nconst xmlField191=(xml,tag)=>xmlText191(xml.match(new RegExp('<(?:[\\\\w-]+:)?'+tag+'\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\/(?:[\\\\w-]+:)?'+tag+'>','i'))?.[1]?.trim()||'');\nconst xmlSection191=(xml,tag)=>xml.match(new RegExp('<(?:[\\\\w-]+:)?'+tag+'\\\\b[^>]*>([\\\\s\\\\S]*?)<\\\\/(?:[\\\\w-]+:)?'+tag+'>','i'))?.[1]||'';\nconst xmlNum191=value=>{const n=Number(value);return Number.isFinite(n)?n:NaN};\nconst invoiceUnit191=code=>({H87:'Stück',C62:'Stück',KGM:'kg',GRM:'g',LTR:'Liter',MLT:'ml'})[String(code||'').toUpperCase()]||'';\nfunction invoiceDate191(raw){return /^20\\d{6}$/.test(raw)?`${raw.slice(0,4)}-${raw.slice(4,6)}-${raw.slice(6,8)}`:''}\n\nfunction invoiceXml191(xml){\n if(!/<(?:[\\w-]+:)?CrossIndustryInvoice\\b/i.test(xml||''))return null;\n const doc=xmlSection191(xml,'ExchangedDocument'),seller=xmlSection191(xml,'SellerTradeParty');\n const reference=xmlField191(doc,'ID'),date=invoiceDate191(xmlField191(xmlSection191(doc,'IssueDateTime'),'DateTimeString'));\n const supplier=xmlField191(seller,'Name'),lines=[];\n for(const match of xml.matchAll(/<(?:[\\w-]+:)?IncludedSupplyChainTradeLineItem\\b[^>]*>([\\s\\S]*?)<\\/(?:[\\w-]+:)?IncludedSupplyChainTradeLineItem>/gi)){\n  const body=match[1],name=xmlField191(xmlSection191(body,'SpecifiedTradeProduct'),'Name');\n  const delivery=xmlSection191(body,'SpecifiedLineTradeDelivery'),quantityText=delivery.match(/<(?:[\\w-]+:)?BilledQuantity\\b([^>]*)>([^<]*)<\\//i);\n  const unit=invoiceUnit191(quantityText?.[1].match(/\\bunitCode\\s*=\\s*[\"']([^\"']+)/i)?.[1]);\n  const quantity=xmlNum191(quantityText?.[2]),sku=xmlField191(xmlSection191(body,'SpecifiedTradeProduct'),'SellerAssignedID');\n  const price=xmlNum191(xmlField191(xmlSection191(body,'NetPriceProductTradePrice'),'ChargeAmount'));\n  const tax=xmlSection191(xmlSection191(body,'SpecifiedLineTradeSettlement'),'ApplicableTradeTax');\n  const rawRate=xmlField191(tax,'RateApplicablePercent'),rate=rawRate!==''?xmlNum191(rawRate):null;\n  const currency=xmlField191(xml,'InvoiceCurrencyCode');\n  const total=xmlNum191(xmlField191(xmlSection191(body,'SpecifiedTradeSettlementLineMonetarySummation'),'LineTotalAmount'));\n  if(name.length<3||name.length>180||!(quantity>0&&quantity<=1000000))continue;\n  const verifiedPrice=price>0&&Number.isFinite(total)&&Math.abs(price*quantity-total)<.03?price:null;\n  lines.push({articleNumber:sku,name,quantity,unit,price:verifiedPrice,vatRate:rate!==null&&rate>=0&&rate<=100?rate:null,currency,content:packageContent229(name),source:'Strukturierte Rechnungsposition'});\n }\n return {supplier,reference,date,lines,source:'XML in PDF'};\n}\n\nfunction invoiceTextLines191(text){\n const reviewed=rows=>rows.map(row=>String(row.source||'').includes('[Menge prüfen]')?{...row,quantity:null,quantityUncertain:true,price:null,priceMissing:true}:row);\n const retail=unitContentInvoice297(text);if(retail.length)return reviewed(retail);\n const metro=metroInvoiceLines295(text);\n if(metro.length)return reviewed(metro);\n const collected=invoiceCollectedLines233(text);\n if(collected.length)return reviewed(collected);\n const rows=[];\n const table=/\\bBeschreibung\\s+Menge\\s+Einzelpreis\\b/i.test(text||'');\n const weightedTable=/Artikel(?:Nr\\.?|bezeichnung)|Liefermenge/i.test(text||'');\n const receipt=/\\b(?:kassen\\s*(?:id|nr)|trans\\s*[:.]|bediener)\\b/i.test(text||'');\n for(const original of String(text).split(/\\r?\\n/)){\n  const line=original.replace(/\\|/g,' ').trim();\n  const weighted=weightedTable?line.match(/^(\\d{4,10})\\s+(.{3,140}?)\\s+(\\d{1,6}(?:[.,]\\d{1,3})?)\\s+(kg|g|Stück|Stk\\.?|Liter)\\s+(\\d{1,6}[.,]\\d{2})\\s+(\\d{1,8}[.,]\\d{2})$/i):null;\n  if(weighted){const quantity=Number(weighted[3].replace(',','.')),price=Number(weighted[5].replace(',','.')),total=Number(weighted[6].replace(',','.'));if(quantity>0)rows.push({articleNumber:weighted[1],name:weighted[2],quantity,unit:weighted[4],price:Math.abs(quantity*price-total)<.03?price:null,priceMissing:Math.abs(quantity*price-total)>=.03,source:line});continue}\n  const m=table?line.match(/^(.{3,140}?)\\s+(\\d{1,6}(?:[.,]\\d{1,3})?)\\s+(Stück|Stick|Stiick|Stk\\.?|Flasche|Becher|Dose|Packung|Karton|kg|g|Liter|L)\\s+(\\d{1,6}[.,]\\d{2})\\s+(?:EUR|€)\\s+\\d{1,2}(?:[.,]\\d+)?%\\s+(\\d{1,8}[.,]\\d{2})\\s+(?:EUR|€)$/i):null;\n  if(!m){\n   // Till lines use gross prices and often print a separate service row.\n   // Keep it visible for exclusion; never turn a till price into a net EK.\n   const p=receipt?line.trim().match(/^(\\d{1,5})\\s*[x×]\\s*(?:(\\d{1,5})\\s+)?(.{3,110}?)\\s+(?:p\\s+)?(\\d{1,6}[.,]\\d{2})\\s+(\\d{1,8}[.,]\\d{2})(?:\\s+[^\\d\\s]{1,3})?$/i):null;\n   if(!p){\n    // Column-based OCR can read an explicit item count without its prices.\n    // Retain that evidence; never borrow a detached amount from another line.\n    const partial=receipt?line.trim().match(/^(\\d{1,5})\\s*[x×]\\s*(?:(\\d{1,5})\\s+)?(.{3,110})$/i):null;\n    if(!partial)continue;\n    const name=partial[3].replace(/\\s*p\\s*\\d+[.,]\\d{2}\\s*$/i,'').trim();\n    const quantity=Number(partial[1])*Number(partial[2]||1);\n    if(!/[a-zäöüß]{3}/i.test(name)||/\\d+[.,]\\d{2}/.test(name)||!(quantity>0&&quantity<=1000000))continue;\n    rows.push({articleNumber:'',name,quantity,unit:'Stück',price:null,grossPrice:null,priceMissing:true,source:line.trim(),service:/\\b(?:schneiden|zustellung|liefergebühr|pfand|service)\\b/i.test(name)});\n    continue;\n   }\n   const quantity=Number(p[1])*Number(p[2]||1),grossPrice=Number(p[4].replace(',','.')),total=Number(p[5].replace(',','.'));\n   if(!(quantity>0&&quantity<=1000000)||Math.abs(quantity*grossPrice-total)>.03)continue;\n   rows.push({articleNumber:'',name:p[3].trim(),quantity,unit:'Stück',price:null,grossPrice,source:line.trim(),service:/\\b(?:schneiden|zustellung|liefergebühr|pfand|service)\\b/i.test(p[3])});\n   continue;\n  }\n  const quantity=Number(m[2].replace(',','.')),price=Number(m[4].replace(',','.')),total=Number(m[5].replace(',','.'));\n  if(!(quantity>0&&quantity<=1000000))continue;\n  const unit=/^(?:st|stück)/i.test(m[3])?'Stück':m[3]==='L'?'Liter':m[3];\n  rows.push({articleNumber:'',name:m[1].trim(),quantity,unit,price:Math.abs(price*quantity-total)<.03?price:null,source:line.trim()});\n }\n const old=rows.length?rows:invoiceColumnLines226(text),european=europeanLines229(text,'invoice');\n const selected=european.length>old.length?european:old.map(row=>({...european.find(e=>e.name===row.name&&e.quantity===row.quantity),...row}));\n return reviewed(selected.map(row=>({...row,content:packageContent229(row.name),currency:row.currency||currency229(text)})));\n}\n\n// Separate delivered count, pack contents, and the explicitly named net price.\n// Lost decimal separators remain unknown; arithmetic cannot reconstruct them.\nfunction unitContentInvoice297(text){\n if(!/\\bVK[- ]Preis\\b/i.test(text||'')||!/Ohne\\b[\\s\\S]{0,45}MwSt/i.test(text||'')||!/Einheit[_\\s]+Inhalt/i.test(text||''))return [];\n const out=[];\n for(const original of String(text||'').split(/\\r?\\n/)){\n  const line=original.replace(/[_|]/g,' ').replace(/^\\s*[@©]\\s*/,'').trim();\n  let m=line.match(/^(?:(\\d{1,3})\\s+)?([A-Z]{1,5}[A-Z0-9]{2,12})\\s+(.+?)\\s+(\\d{1,6})\\s+(Stk\\.?)\\s+(\\d{1,4})\\s+(.+)$/i);\n  if(!m){const partial=line.match(/^(?:(\\d{1,3})\\s+)?([A-Z]{1,5}[A-Z0-9]{2,12})\\s+(.+?)\\s+(Stk\\.?)\\s+(\\d{1,4})\\s+(.+)$/i);if(partial)m=[partial[0],partial[1],partial[2],partial[3],null,partial[4],partial[5],partial[6]];}\n  if(!m||!/[a-zäöü]{3}/i.test(m[3]))continue;\n  const quantity=m[4]===null?null:Number(m[4]),inside=Number(m[6]);if(!((quantity===null||quantity>0&&quantity<=1000000)&&inside>0))continue;\n  const cells=m[7].match(/^(\\d+[.,]\\d{2,3})\\s+(\\d+[.,]\\d{2,3})\\s+(?:(\\d+[.,]\\d{1,2})\\s+)?(\\d{1,2})\\s+(\\d+[.,]\\d{2})\\s*$/);\n  const number=s=>Number(s.replace(',','.'));let price=null,vatRate=null;\n  if(cells&&!cells[3]){\n   const net=number(cells[2]),total=number(cells[5]),rate=Number(cells[4]);\n   if(quantity!==null&&inside===1&&Math.abs(quantity*net-total)<=.015&&rate<=100){price=net;vatRate=rate;}\n  }\n  out.push({articleNumber:m[2],name:m[3].trim(),quantity,unit:inside===1?'Stück':'',unitUncertain:inside!==1,...(quantity===null?{quantityUncertain:true}:{}),price,priceMissing:price===null,vatRate,content:packageContent229(m[3]),currency:currency229(text),source:original.trim()});\n }\n return out;\n}\n\n// Native OCR may return each table column as a separate top-to-bottom block.\n// Require explicit column headings and identical name/quantity counts. Partial\n// price columns cannot be zipped onto products because a missing cell shifts them.\nfunction invoiceColumnLines226(text){\n const lines=String(text||'').split(/\\r?\\n/).map(line=>line.trim()).filter(Boolean);\n const description=lines.findIndex(line=>/^Beschreibung$/i.test(line));\n const quantity=lines.findIndex((line,i)=>i>description&&/^Menge$/i.test(line));\n const price=lines.findIndex((line,i)=>i>quantity&&/^Einzelpreis$/i.test(line));\n if(description<0||quantity<=description||price<=quantity)return [];\n let names=lines.slice(description+1,quantity);\n const stop=names.findIndex(line=>/^(?:Zahlungsarten|BANK|Netto|Gesamt|Summe|Zahlungsbedingungen)/i.test(line));\n if(stop>=0)names=names.slice(0,stop);\n names=names.filter(line=>!/^\\W*(?:mindestbest|\\*\\*)/i.test(line));\n if(!names.length||names.some(name=>name.length<3||name.length>140||!/[a-zäöüß]{3}/i.test(name)))return [];\n const quantities=lines.slice(quantity+1,price).map(line=>line.match(/^(\\d{1,6}(?:[.,]\\d{1,3})?)\\s+(Stück|Stick|Stiick|Stk\\.?|Flasche|Becher|Dose|Packung|Karton|kg|g|Liter|L)$/i));\n if(quantities.length!==names.length||quantities.some(value=>!value))return [];\n const priceEnd=lines.findIndex((line,i)=>i>price&&/^(?:USt\\.?|MwSt\\.?|Gesamtpreis)$/i.test(line));\n const prices=lines.slice(price+1,priceEnd<0?lines.length:priceEnd).map(line=>line.match(/^(\\d{1,6}[.,]\\d{2})\\s*(?:EUR|€)$/i));\n const totalStart=lines.findIndex((line,i)=>i>price&&/^Gesamtpreis$/i.test(line));\n const totals=totalStart<0?[]:lines.slice(totalStart+1,totalStart+1+names.length).map(line=>line.match(/^(\\d{1,8}[.,]\\d{2})\\s*(?:EUR|€)$/i));\n return names.map((name,i)=>{\n  const amount=Number(quantities[i][1].replace(',','.')),rawUnit=quantities[i][2];\n  const unit=/^st/i.test(rawUnit)?'Stück':rawUnit==='L'?'Liter':rawUnit;\n  let unitPrice=null;\n  if(prices.length===names.length&&prices.every(Boolean)&&totals.length===names.length&&totals.every(Boolean)){\n   const p=Number(prices[i][1].replace(',','.')),total=Number(totals[i][1].replace(',','.'));\n   if(Math.abs(amount*p-total)<.03)unitPrice=p;\n  }\n  return {name,quantity:amount,unit,articleNumber:'',price:unitPrice,priceMissing:unitPrice===null,source:name+' · '+quantities[i][0]};\n }).filter(row=>row.quantity>0&&row.quantity<=1000000);\n}\n\n// Collective invoices retain each delivery row; never deduplicate by SKU.\nfunction invoiceCollectedLines233(text){\n const lines=String(text||'').split(/\\r?\\n/).map(x=>x.trim());\n if(!/Menge\\s*\\/\\s*Inhalt/i.test(text)||!/E-Preis/i.test(text)||!/Nettosumme/i.test(text))return [];\n const rows=[];let delivery='';\n const num=value=>Number(value.replace(',','.'));\n for(let i=0;i<lines.length;i++){\n  const shipment=lines[i].match(/^LIEFERSCHEIN\\s+(\\S+)\\s+(\\d{2}\\.\\d{2}\\.\\d{4})/i);\n  if(shipment){delivery=shipment[1]+' · '+shipment[2];continue}\n  const m=lines[i].match(/^(\\d{4,10})\\s+(.+?)\\s+(\\d+[.,]\\d+)\\s*\\/\\s*(\\d+[.,]\\d+)\\s+(\\d+[.,]\\d+)\\s+(\\d+[.,]\\d+)\\s+(\\d+[.,]\\d+)$/);\n  if(!m)continue;\n  let name=m[2];if(/^(?:mittel|klein|groß)\\s+\\d+\\s*(?:g|kg|ml|l)$/i.test(lines[i+1]||''))name+=' '+lines[++i];\n  const quantity=num(m[3]),price=num(m[6]),total=num(m[7]);\n  if(!(quantity>0)||Math.abs(quantity*price-total)>.03)return [];\n  const unit=name.match(/\\b(Becher|Flasche|Dose|Packung|Karton)\\b/i)?.[1]||'';\n  rows.push({articleNumber:m[1],name,quantity,unit,unitUncertain:!unit,price,priceBasis:'net',content:packageContent229(name),currency:currency229(text),deliveryLabel:delivery,source:delivery?'Abholung '+delivery:m[0]});\n }\n const net=text.match(/Nettosumme\\s*:\\s*(\\d+[.,]\\d{2})/i);\n if(!net||!rows.length||Math.abs(rows.reduce((sum,r)=>sum+r.quantity*r.price,0)-num(net[1]))>.03)return [];\n return rows;\n}\n\n// METRO: explicit EAN + supplier article number and fixed trailing columns.\n// Invoice quantity is kolli count multiplied by the printed units per kolli.\nfunction metroInvoiceLines295(text){\n if(!/\\bEAN\\b/i.test(text||'')||!/\\bKOLLI\\b/i.test(text||'')||!/\\bMENGE\\b/i.test(text||''))return [];\n const rows=[];\n for(const original of String(text).split(/\\r?\\n/)){\n  const line=original.replace(/[«»]/g,' ').replace(/(\\d)[.,]\\s+(?=\\d)/g,'$1,').trim();\n  const m=line.match(/^(\\d{13})\\s+(\\d{6})[.,]\\s*(\\d)\\s+(.+?)\\s+(\\d+[.,]\\d{2,3})\\s+(\\d+)\\s+(\\d+[.,]\\d{2,3})\\s+(\\d+)\\s+(\\d+(?:[.,]\\d{1,3})?)\\s+([A-Z0-9]{1,3})(?:[:.]?\\s|$)/i);\n  if(!m||!/[a-zäöü]{3}/i.test(m[4]))continue;\n  const number=x=>Number(x.replace(',','.')),each=number(m[5]),inside=Number(m[6]),kolli=number(m[7]),count=Number(m[8]),total=number(m[9]),quantity=inside*count;\n  if(!(inside>0&&count>0&&quantity<=1000000))continue;\n  const verified=/[.,]\\d{2}$/.test(m[9])&&Math.abs(each*inside-kolli)<.015&&Math.abs(kolli*count-total)<.025;\n  rows.push({articleNumber:m[2]+'.'+m[3],barcode:m[1],name:m[4].trim(),quantity,unit:'Packung',price:verified?each:null,priceMissing:!verified,content:packageContent229(m[4]),currency:'EUR',source:original.trim()});\n }\n return rows;\n}\n\nreturn {invoiceXml191,invoiceTextLines191,unitContentInvoice297,invoiceColumnLines226,invoiceCollectedLines233,metroInvoiceLines295};})();\nmodules['document170']=(()=>{\n// Reads text without changing or uploading the original document.\nconst decodeXml170=s=>s.replace(/&#x([0-9a-f]+);/gi,(_,n)=>String.fromCodePoint(parseInt(n,16)))\n .replace(/&#(\\d+);/g,(_,n)=>String.fromCodePoint(Number(n)))\n .replace(/&(?:amp|lt|gt|quot|apos);/g,x=>({\"&amp;\":\"&\",\"&lt;\":\"<\",\"&gt;\":\">\",\"&quot;\":\"\\\"\",\"&apos;\":\"'\"})[x]);\nconst cellText170=xml=>decodeXml170([...xml.matchAll(/<(?:w:t|t)(?:\\s[^>]*)?>([\\s\\S]*?)<\\/(?:w:t|t)>/g)]\n .map(x=>x[1]).join(''));\nasync function zipFile170(zip,path){\n const entry=zip.file(path);\n if(!entry)return '';\n if(entry._data?.uncompressedSize>12000000)throw Error('Dokumentteil zu groß');\n return await entry.async('string');\n}\nasync function fromWord170(bytes){\n const zip=await globalThis.JSZip.loadAsync(bytes);\n const xml=await zipFile170(zip,'word/document.xml');\n if(!xml)throw Error('Word-Datei enthält keinen lesbaren Text');\n const text=[...xml.matchAll(/<w:p(?:\\s[^>]*)?>([\\s\\S]*?)<\\/w:p>/g)]\n  .map(x=>cellText170(x[1])).filter(Boolean).join('\\n');\n return {text,needsOcr:false};\n}\nasync function fromExcel170(bytes){\n const zip=await globalThis.JSZip.loadAsync(bytes);\n const sharedXml=await zipFile170(zip,'xl/sharedStrings.xml');\n const strings=[...sharedXml.matchAll(/<si(?:\\s[^>]*)?>([\\s\\S]*?)<\\/si>/g)].map(x=>cellText170(x[1]));\n const sheets=Object.keys(zip.files).filter(k=>/^xl\\/worksheets\\/sheet\\d+\\.xml$/.test(k)).sort((a,b)=>\n  Number(a.match(/\\d+(?=\\.xml)/)[0])-Number(b.match(/\\d+(?=\\.xml)/)[0]));\n if(!sheets.length)throw Error('Keine Tabellenblätter gefunden');\n const out=[];\n for(const sheet of sheets.slice(0,20)){\n  const xml=await zipFile170(zip,sheet);out.push(sheet.replace(/.*\\//,'')+':');\n  for(const row of xml.matchAll(/<row\\b[^>]*>([\\s\\S]*?)<\\/row>/g)){\n   const cells=[];\n   for(const cell of row[1].matchAll(/<c\\b([^>]*)>([\\s\\S]*?)<\\/c>/g)){\n    const value=cell[2].match(/<v>([\\s\\S]*?)<\\/v>/)?.[1];\n    const content=/\\bt=\"s\"/.test(cell[1])?strings[Number(value)]||'':\n      /\\bt=\"inlineStr\"/.test(cell[1])?cellText170(cell[2]):decodeXml170(value||'');\n    if(content.trim())cells.push(content.trim());\n   }\n   if(cells.length)out.push(cells.join(' · '));\n  }\n }\n return {text:out.join('\\n'),needsOcr:false};\n}\n// PDF text items are often emitted in drawing order, not reading order.\n// Keep visual rows and columns together before applying offer rules.\nfunction pdfLayoutRows170(parts,pageNumber=1){\n const positioned=(parts||[]).filter(part=>String(part.str||'').trim()).map(part=>({\n  text:String(part.str).trim(),x:Number(part.transform?.[4])||0,\n  y:Number(part.transform?.[5])||0,width:Math.max(0,Number(part.width)||0)\n })).sort((a,b)=>b.y-a.y||a.x-b.x);\n const rows=[];\n for(const part of positioned){\n  let row=rows.find(row=>Math.abs(row.y-part.y)<=2);\n  if(!row){row={y:part.y,parts:[]};rows.push(row)}\n  row.parts.push(part);\n }\n return rows.sort((a,b)=>b.y-a.y).map(row=>{\n  const cells=row.parts.sort((a,b)=>a.x-b.x);\n  let text='',end=-Infinity;\n  for(const cell of cells){\n   const gap=cell.x-end;\n   if(text)text+=gap>12?'   ':gap>1.5?' ':' ';\n   text+=cell.text;end=Math.max(end,cell.x+cell.width);\n  }\n  return {page:pageNumber,text:text.trim()};\n });\n}\nfunction ensureOfferPdfDomMatrix203(){\n if(typeof globalThis.DOMMatrix!=='undefined')return;\n globalThis.DOMMatrix=class DOMMatrix203{\n  constructor(init){const v=Array.isArray(init)||ArrayBuffer.isView(init)?Array.from(init):[];this.a=v[0]??1;this.b=v[1]??0;this.c=v[2]??0;this.d=v[3]??1;this.e=v[4]??0;this.f=v[5]??0;}\n  multiply(o={}){return new globalThis.DOMMatrix([this.a*(o.a??1)+this.c*(o.b??0),this.b*(o.a??1)+this.d*(o.b??0),this.a*(o.c??0)+this.c*(o.d??1),this.b*(o.c??0)+this.d*(o.d??1),this.a*(o.e??0)+this.c*(o.f??0)+this.e,this.b*(o.e??0)+this.d*(o.f??0)+this.f]);}\n  multiplySelf(o){Object.assign(this,this.multiply(o));return this;}\n  preMultiplySelf(o){Object.assign(this,new globalThis.DOMMatrix([o?.a??1,o?.b??0,o?.c??0,o?.d??1,o?.e??0,o?.f??0]).multiply(this));return this;}\n  translateSelf(x=0,y=0){return this.multiplySelf(new globalThis.DOMMatrix([1,0,0,1,x,y]));}\n  scaleSelf(x=1,y=x){return this.multiplySelf(new globalThis.DOMMatrix([x,0,0,y,0,0]));}\n  transformPoint(p={}){return {x:this.a*(p.x??0)+this.c*(p.y??0)+this.e,y:this.b*(p.x??0)+this.d*(p.y??0)+this.f};}\n };\n}\n\nasync function fromPdf170(bytes){\n ensureOfferPdfDomMatrix203();\n const pdfjs=await import('./assets/pdf226.js');\n pdfjs.GlobalWorkerOptions.workerSrc=new URL('./assets/pdf.worker226.js',base).href;\n const task=pdfjs.getDocument({data:new Uint8Array(bytes),useSystemFonts:true});\n const pdf=await task.promise;\n if(pdf.numPages>30)throw Error('Mehr als 30 PDF-Seiten; bitte Angebot aufteilen');\n const lines=[],sourceRows=[];let needsOcr=false;\n for(let i=1;i<=pdf.numPages;i++){\n  const page=await pdf.getPage(i);\n  const parts=(await page.getTextContent()).items;\n  if(parts.map(x=>x.str||'').join('').trim().length<24){\n   if(typeof globalThis.requestOcr147==='function'&&typeof document!=='undefined'){\n    const viewport=page.getViewport({scale:1.6}),canvas=document.createElement('canvas');\n    canvas.width=Math.ceil(viewport.width);canvas.height=Math.ceil(viewport.height);\n    await page.render({canvasContext:canvas.getContext('2d'),viewport}).promise;\n    const ocr=await globalThis.requestOcr147(canvas.toDataURL('image/jpeg',.82));\n    if(ocr?.trim())lines.push('Seite '+i+' (OCR):\\n'+ocr);\n    else{needsOcr=true;lines.push('Seite '+i+': OCR erforderlich')}\n   }else{needsOcr=true;lines.push('Seite '+i+': OCR erforderlich')}\n  }else{\n   const rows=pdfLayoutRows170(parts,i);\n   sourceRows.push(...rows);\n   lines.push(rows.map(row=>row.text).join('\\n'));\n  }\n }\n await pdf.destroy();\n return {text:lines.join('\\n'),needsOcr,sourceRows};\n}\nasync function readOfferFile170(file){\n if(!file||file.size>8*1024*1024)throw Error('Bitte ein Angebot bis 8 MB auswählen');\n const bytes=await file.arrayBuffer();\n const hash=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',bytes)),\n  x=>x.toString(16).padStart(2,'0')).join('');\n const name=file.name.toLowerCase();\n if(!/\\.(pdf|docx|xlsx)$/.test(name))throw Error('Bitte PDF, Word (.docx) oder Excel (.xlsx) auswählen');\n if(name.endsWith('.pdf')&&!new TextDecoder().decode(bytes.slice(0,5)).startsWith('%PDF-'))\n  throw Error('Datei ist kein gültiges PDF');\n if(!name.endsWith('.pdf')&&typeof globalThis.loadAsset254==='function')await globalThis.loadAsset254('zip');\n if(!name.endsWith('.pdf')&&(!globalThis.JSZip||new Uint8Array(bytes)[0]!==80))\n  throw Error('Word- oder Excel-Datei ist nicht lesbar');\n const result=name.endsWith('.pdf')?await fromPdf170(bytes):\n  name.endsWith('.docx')?await fromWord170(bytes):await fromExcel170(bytes);\n if(result.text.length>120000)throw Error('Angebot enthält zu viel Text');\n return {...result,hash,name:file.name};\n}\n\nreturn {pdfLayoutRows170,readOfferFile170};})();\nmodules['inbound-pdf183']=(()=>{\nconst {pdfLayoutRows170}=modules['document170'];\nconst {classifyInboundDocument182}=modules['inbound-document182'];\nconst {invoiceXml191}=modules['invoice-data191'];\n\nfunction inboundPdfDeadline188(task,ms,label,onTimeout){\n let timer;\n return Promise.race([task,new Promise((_,reject)=>{timer=setTimeout(()=>{try{onTimeout?.()}catch{}reject(Error(label+' antwortet nicht. Bitte Datei erneut auswählen.'))},ms)})]).finally(()=>clearTimeout(timer));\n}\n\nfunction inboundFileKind183(file){\n const name=String(file?.name||'').toLowerCase();\n if(file?.type==='application/pdf'||name.endsWith('.pdf'))return 'pdf';\n if(file?.type?.startsWith('image/'))return 'image';\n return 'unsupported';\n}\n\nfunction inboundNameHint183(name){\n const stem=String(name||'').replace(/\\.[^.]+$/,'');\n if(/\\b(?:rechnung|invoice)\\b/i.test(stem))return 'invoice';\n if(/\\b(?:lieferschein|delivery[ _-]?note)\\b/i.test(stem))return 'delivery_note';\n return 'unknown';\n}\n\nfunction ensurePdfDomMatrix203(){\n if(typeof globalThis.DOMMatrix!=='undefined')return;\n globalThis.DOMMatrix=class DOMMatrix203{\n  constructor(init){const v=Array.isArray(init)||ArrayBuffer.isView(init)?Array.from(init):[];this.a=v[0]??1;this.b=v[1]??0;this.c=v[2]??0;this.d=v[3]??1;this.e=v[4]??0;this.f=v[5]??0;}\n  multiply(o={}){return new globalThis.DOMMatrix([this.a*(o.a??1)+this.c*(o.b??0),this.b*(o.a??1)+this.d*(o.b??0),this.a*(o.c??0)+this.c*(o.d??1),this.b*(o.c??0)+this.d*(o.d??1),this.a*(o.e??0)+this.c*(o.f??0)+this.e,this.b*(o.e??0)+this.d*(o.f??0)+this.f]);}\n  multiplySelf(o){Object.assign(this,this.multiply(o));return this;}\n  preMultiplySelf(o){Object.assign(this,new globalThis.DOMMatrix([o?.a??1,o?.b??0,o?.c??0,o?.d??1,o?.e??0,o?.f??0]).multiply(this));return this;}\n  translateSelf(x=0,y=0){return this.multiplySelf(new globalThis.DOMMatrix([1,0,0,1,x,y]));}\n  scaleSelf(x=1,y=x){return this.multiplySelf(new globalThis.DOMMatrix([x,0,0,y,0,0]));}\n  transformPoint(p={}){return {x:this.a*(p.x??0)+this.c*(p.y??0)+this.e,y:this.b*(p.x??0)+this.d*(p.y??0)+this.f};}\n };\n}\n\nasync function readInboundPdf183(file,ocr,{onDocument,onProgress}={}){\n if(!file||inboundFileKind183(file)!=='pdf')throw Error('Bitte eine PDF-Datei auswählen');\n if(file.size>12*1024*1024)throw Error('PDF darf höchstens 12 MB groß sein');\n const bytes=await inboundPdfDeadline188(file.arrayBuffer(),20000,'Datei');\n if(new TextDecoder().decode(bytes.slice(0,5))!=='%PDF-')throw Error('Datei ist kein gültiges PDF');\n ensurePdfDomMatrix203();\n const pdfjs=await inboundPdfDeadline188(import('./assets/pdf226.js'),20000,'PDF-Modul');\n pdfjs.GlobalWorkerOptions.workerSrc=new URL('./assets/pdf.worker226.js',base).href;\n // PDF.js transfers its buffer to the worker. Preserve the original for hashing.\n const loading=pdfjs.getDocument({data:new Uint8Array(bytes).slice(),useSystemFonts:true});\n const pdf=await inboundPdfDeadline188(loading.promise,25000,'PDF',()=>{void loading.destroy().catch(()=>{})});\n try{\n  if(pdf.numPages>30)throw Error('PDF hat mehr als 30 Seiten; bitte aufteilen');\n  if(onDocument)await inboundPdfDeadline188(onDocument(pdf),25000,'PDF-Vorschau');\n  let invoiceData=null;\n  try{\n   const attachments=await inboundPdfDeadline188(pdf.getAttachments(),5000,'Rechnungsanlage');\n   for(const [filename,attachment] of Object.entries(attachments||{})){\n    if(!/\\.xml$/i.test(filename)||!attachment.content||attachment.content.length>2*1024*1024)continue;\n    invoiceData=invoiceXml191(new TextDecoder().decode(attachment.content));\n    if(invoiceData?.lines.length)break;\n   }\n  }catch{} // The PDF text remains usable if its optional XML attachment is damaged.\n  const sections=[],unread=[],ocrErrors=[];\n  for(let i=1;i<=pdf.numPages;i++){\n   onProgress?.(`Seite ${i} von ${pdf.numPages} wird gelesen …`);\n   const page=await inboundPdfDeadline188(pdf.getPage(i),20000,'PDF-Seite');\n   let text=pdfLayoutRows170((await inboundPdfDeadline188(page.getTextContent(),20000,'PDF-Text')).items,i).map(x=>x.text).join('\\n');\n   if(text.trim().length<24||(i===1&&typeof ocr==='function'&&classifyInboundDocument182(text)==='unknown')){\n    if(typeof ocr==='function'){\n     const viewport=page.getViewport({scale:Math.min(2.3,1800/page.getViewport({scale:1}).width)});\n     const canvas=document.createElement('canvas');canvas.width=Math.ceil(viewport.width);canvas.height=Math.ceil(viewport.height);\n     const render=page.render({canvasContext:canvas.getContext('2d'),viewport});\n     await inboundPdfDeadline188(render.promise,25000,'PDF-Bild',()=>render.cancel());\n     onProgress?.(`Texterkennung: Seite ${i} von ${pdf.numPages} …`);\n     try{\n      const scanned=String(await ocr(canvas.toDataURL('image/jpeg',.82))||'');\n      if(scanned.trim().length>text.trim().length||classifyInboundDocument182(scanned)!=='unknown')text=scanned;\n     }catch(error){ocrErrors.push('Seite '+i+': '+String(error?.message||'Erkennung fehlgeschlagen').slice(0,160));onProgress?.(`Texterkennung auf Seite ${i} nicht möglich. Die Seite kann manuell geprüft werden.`)}\n    }\n    if(text.trim().length<24)unread.push(i);\n   }\n   sections.push('Seite '+i+'\\n'+text);\n  }\n  return {pdf,text:sections.join('\\n'),unread,ocrErrors,invoiceData,pages:pdf.numPages,bytes};\n }catch(error){await pdf.destroy();throw error}\n}\n\nasync function renderInboundPdfPage183(pdf,number){\n if(!pdf||number<1||number>pdf.numPages)throw Error('PDF-Seite nicht vorhanden');\n const page=await pdf.getPage(number),natural=page.getViewport({scale:1});\n const viewport=page.getViewport({scale:Math.min(1.8,1200/natural.width)});\n const canvas=document.createElement('canvas');canvas.width=Math.ceil(viewport.width);canvas.height=Math.ceil(viewport.height);\n const render=page.render({canvasContext:canvas.getContext('2d'),viewport});\n await inboundPdfDeadline188(render.promise,25000,'PDF-Vorschau',()=>render.cancel());\n return canvas.toDataURL('image/jpeg',.76);\n}\n\nreturn {inboundFileKind183,inboundNameHint183,readInboundPdf183,renderInboundPdfPage183};})();\nmodules['document-reader225']=(()=>{\n// Local document preparation and reading order. No supplier-specific geometry.\nconst {invoiceTextLines191}=modules['invoice-data191'];\nconst {deliveryLines185}=modules['inbound-lines185'];\nconst {deliveryHeader182,invoiceHeader182,classifyInboundDocument182,inboundSupplier182}=modules['inbound-document182'];\nconst documentTextScore225=text=>invoiceTextLines191(text).reduce((sum,row)=>sum+(Number.isFinite(row.price)||Number.isFinite(row.grossPrice)?3:1),0)+deliveryLines185(text).length;\nfunction selectDocumentText225(data){\n const raw=String(data?.text||''),layout=documentRows225(data?.tsv).join('\\n');\n // Choose one reading order, never concatenate both sets of item rows.\n return layout&&documentTextScore225(layout)>documentTextScore225(raw)?layout:raw;\n}\nfunction quantityEvidence225(data,row){\n const sku=String(row?.articleNumber||'').trim();if(!sku)return null;\n const words=[];\n for(const line of String(data?.tsv||'').split(/\\r?\\n/)){\n  const c=line.split('\\t');if(c[0]!=='5'||c.length<12)continue;\n  const [x,y,w,h]=c.slice(6,10).map(Number),confidence=Number(c[10]),text=c.slice(11).join('\\t').trim();\n  if(text&&[x,y,w,h,confidence].every(Number.isFinite)&&w>0&&h>0)words.push({text,x,y,w,h,confidence,centerY:y+h/2,page:c[1]});\n }\n for(const anchor of words.filter(word=>word.text===sku)){\n  const sameRow=words.filter(word=>word.page===anchor.page&&word.x>anchor.x+anchor.w&&Math.abs(word.centerY-anchor.centerY)<=Math.max(12,anchor.h));\n  const unit=sameRow.filter(word=>/^(?:F[lI1](?:aschen?)?|St(?:k\\.?|ück)?|Becher|Dosen?|Pkg\\.?|Packungen?|Kartons?|kg|g|Liter|L)$/i.test(word.text)).sort((a,b)=>a.x-b.x).at(-1);\n  if(!unit)continue;\n  const amount=sameRow.filter(word=>word.x<unit.x&&/^\\d{1,6}(?:[.,]\\d{1,3})?$/.test(word.text)).sort((a,b)=>a.x-b.x).at(-1);\n  if(amount)return {quantity:Number(amount.text.replace(',','.')),confidence:amount.confidence};\n }\n return null;\n}\nfunction confidentTableFusion225(row,other,chosenRows,chosenData,otherData){\n if(!row||!other||row.unit!==other.unit||!Number.isFinite(row.quantity)||!Number.isFinite(other.quantity))return false;\n const position=String(row.source||'').match(/^(\\d{1,3})[.)]?\\s+(\\d{4,10})\\b/);\n if(!position||position[2]!==String(row.articleNumber)||String(other.quantity)!==position[1]+String(row.quantity))return false;\n if((chosenRows||[]).filter(candidate=>candidate.unit===row.unit&&candidate.quantity===row.quantity).length<3)return false;\n const chosen=quantityEvidence225(chosenData,row),alternative=quantityEvidence225(otherData,other);\n return !!chosen&&!!alternative&&chosen.quantity===row.quantity&&alternative.quantity===other.quantity&&chosen.confidence>=alternative.confidence+8;\n}\n// Skip receipt retries only when every priced position agrees with the printed subtotal.\nfunction receiptComplete215(text){\n if(!/\\b(?:kassen|bediener|zwisch)/i.test(text)||/\\[(?:Menge|Beleg).*prüfen\\]/i.test(text))return false;\n const match=String(text).match(/(?:ZWISCH[.,]?\\s*SUMM(?:E)?|ZWISCHENSUMME|SUBTOTAL)\\s*[:=]?\\s*(\\d+[.,]\\d{2})\\b/i);\n if(!match)return false;\n const rows=invoiceTextLines191(text);if(rows.length<2||rows.some(r=>!Number.isFinite(r.quantity)||r.quantity<=0||!Number.isFinite(r.grossPrice)))return false;\n const sum=rows.reduce((n,r)=>n+Math.round(r.quantity*r.grossPrice*100),0),total=Math.round(Number(match[1].replace(',','.'))*100);\n return total>0&&sum===total;\n}\nasync function readDocument225(worker,source,prepare=prepareDocumentImage225){\n const image=await prepare(source),output={text:true,tsv:true};\n const firstData=(await worker.recognize(image,{tessedit_pageseg_mode:'3'},output)).data;\n let text=selectDocumentText225(firstData);\n // Sparse detection recovers receipt text near folds/backgrounds; coordinate\n // reconstruction puts its scattered words back into printed rows.\n if(/\\blieferschein\\b/i.test(text)){\n  const first=text,alternativeData=(await worker.recognize(image,{tessedit_pageseg_mode:'6'},output)).data,alternative=selectDocumentText225(alternativeData);\n  const quality=value=>deliveryLines185(value).length*10+(deliveryHeader182(value).reference?2:0)+(deliveryHeader182(value).date?1:0);\n  const useAlternative=quality(alternative)>=quality(text);if(useAlternative)text=alternative;\n  const chosenData=useAlternative?alternativeData:firstData,otherData=useAlternative?firstData:alternativeData;\n  const chosenRows=deliveryLines185(text),other=deliveryLines185(useAlternative?first:alternative);\n  const disputed=chosenRows.filter(row=>{const old=other.find(candidate=>candidate.articleNumber===row.articleNumber&&candidate.quantity!==row.quantity);return old&&!confidentTableFusion225(row,old,chosenRows,chosenData,otherData)}).map(row=>row.articleNumber);\n  if(disputed.length)text=text.split('\\n').map(line=>disputed.some(sku=>line.includes(sku))?line+' [Menge prüfen]':line).join('\\n');\n  if(!inboundSupplier182(text)){const raw=String((await worker.recognize(source,{tessedit_pageseg_mode:'3'},output)).data?.text||'');const supplier=inboundSupplier182(raw);if(supplier)text=supplier+'\\n'+text;}\n }else if(/\\b(?:kassen|bediener|zwisch)/i.test(text)&&!/\\brechnung\\b/i.test(text)&&!receiptComplete215(text)){\n  const alternative=selectDocumentText225((await worker.recognize(image,{tessedit_pageseg_mode:'11'},output)).data);\n  if(documentTextScore225(alternative)>documentTextScore225(text))text=alternative;\n }\n return recoverDocument297(worker,source,image,text,firstData,output);\n}\n\n// Recovery is bounded to two additional OCR passes. Pick one complete reading\n// order; never concatenate its item rows with a second interpretation.\nasync function recoverDocument297(worker,source,image,text,firstData,output){\n const kind=classifyInboundDocument182(text),invoice=kind==='invoice';\n const rows=invoice?invoiceTextLines191(text):deliveryLines185(text);\n const header=invoice?invoiceHeader182(text):deliveryHeader182(text);\n const receipt=/\\b(?:kassen|bediener|zwisch)/i.test(text)&&!/\\brechnung\\b/i.test(text);\n const codes=new Set([...text.matchAll(/\\b\\d{13}\\b/g)].map(m=>m[0]));\n if(receipt||rows.length>=3&&header.reference&&header.date&&inboundSupplier182(text)&&codes.size<=rows.length)return text;\n const candidates=[{text,data:firstData}];\n for(const [input,mode] of [[image,'11'],[source,'6']]){\n  try{const data=(await worker.recognize(input,{tessedit_pageseg_mode:mode},output)).data;candidates.push({text:selectDocumentText225(data),data});}catch{/* A recovery failure preserves the original review, never invents fields. */}\n }\n const type=classifyInboundDocument182(candidates[0].text);\n const getRows=t=>type==='invoice'?invoiceTextLines191(t):deliveryLines185(t);\n const getHeader=t=>type==='invoice'?invoiceHeader182(t):deliveryHeader182(t);\n const score=t=>getRows(t).reduce((s,r)=>s+(Number.isFinite(r.quantity)?2:0)+(Number.isFinite(r.price)?2:0)+1,0)*10+(getHeader(t).reference?2:0)+(getHeader(t).date?1:0)+(inboundSupplier182(t)?1:0);\n const selected=candidates.reduce((best,c)=>score(c.text)>score(best.text)?c:best,candidates[0]);\n const selectedRows=getRows(selected.text),disputed=[];\n for(const row of selectedRows){\n  if(!Number.isFinite(row.quantity)||!row.articleNumber)continue;\n  for(const other of candidates.filter(c=>c!==selected)){\n   const old=getRows(other.text).find(r=>r.articleNumber===row.articleNumber&&r.unit===row.unit&&Number.isFinite(r.quantity)&&r.quantity!==row.quantity);\n   if(old&&!confidentTableFusion225(row,old,selectedRows,selected.data,other.data))disputed.push(row.articleNumber);\n  }\n }\n text=selected.text.split('\\n').map(line=>disputed.some(sku=>line.includes(sku))?line+' [Menge prüfen]':line).join('\\n');\n const headers=candidates.map(c=>getHeader(c.text));\n if(new Set(headers.map(h=>h.reference).filter(Boolean)).size>1)text+='\\n[Belegnummer prüfen]';\n if(new Set(headers.map(h=>h.date).filter(Boolean)).size>1)text+='\\n[Belegdatum prüfen]';\n if(!inboundSupplier182(text)){const supplier=candidates.map(c=>inboundSupplier182(c.text)).find(Boolean);if(supplier)text=supplier+'\\n'+text;}\n return text;\n}\nfunction normalizeDocumentPixels225(pixels,width,height){\n const stride=width+1,integral=new Float64Array(stride*(height+1));\n const gray=new Uint8Array(width*height);\n for(let y=0;y<height;y++){\n  let sum=0;\n  for(let x=0;x<width;x++){\n   const n=y*width+x,i=n*4;\n   const value=Math.round(.299*pixels[i]+.587*pixels[i+1]+.114*pixels[i+2]);\n   gray[n]=value;sum+=value;integral[(y+1)*stride+x+1]=integral[y*stride+x+1]+sum;\n  }\n }\n const radius=Math.max(16,Math.round(width/18));\n for(let y=0;y<height;y++)for(let x=0;x<width;x++){\n  const x0=Math.max(0,x-radius),x1=Math.min(width,x+radius+1),y0=Math.max(0,y-radius),y1=Math.min(height,y+radius+1);\n  const background=(integral[y1*stride+x1]-integral[y0*stride+x1]-integral[y1*stride+x0]+integral[y0*stride+x0])/((x1-x0)*(y1-y0));\n  const value=Math.min(255,Math.round(gray[y*width+x]/Math.max(1,background)*230)),i=(y*width+x)*4;\n  pixels[i]=pixels[i+1]=pixels[i+2]=value;pixels[i+3]=255;\n }\n return pixels;\n}\nfunction prepareDocumentImage225(source){return new Promise((resolve,reject)=>{\n const image=new Image();\n image.onload=async()=>{try{\n  const scale=Math.min(1,2000/Math.max(image.naturalWidth,image.naturalHeight));\n  const canvas=document.createElement('canvas');canvas.width=Math.round(image.naturalWidth*scale);canvas.height=Math.round(image.naturalHeight*scale);\n  const ctx=canvas.getContext('2d',{willReadFrequently:true});ctx.drawImage(image,0,0,canvas.width,canvas.height);\n  const data=ctx.getImageData(0,0,canvas.width,canvas.height);\n  let worker;\n  try{if(typeof Worker==='undefined')throw Error('Kein Worker');worker=new Worker(new URL('./document-pixels254.js',base),{type:'module'});const pixels=await new Promise((ok,no)=>{const timer=setTimeout(()=>no(Error('Bildaufbereitung dauert zu lange')),15000);worker.onmessage=event=>{clearTimeout(timer);event.data.error?no(Error(event.data.error)):ok(new Uint8ClampedArray(event.data.buffer))};worker.onerror=()=>{clearTimeout(timer);no(Error('Bildaufbereitung nicht verfügbar'))};worker.postMessage({buffer:data.data.buffer,width:canvas.width,height:canvas.height},[data.data.buffer])});ctx.putImageData(new ImageData(pixels,canvas.width,canvas.height),0,0)}catch{const fallback=ctx.getImageData(0,0,canvas.width,canvas.height);normalizeDocumentPixels225(fallback.data,canvas.width,canvas.height);ctx.putImageData(fallback,0,0)}finally{worker?.terminate()}\n  resolve(canvas.toDataURL('image/png'));\n }catch(error){reject(error)}};\n image.onerror=()=>reject(Error('Belegfoto konnte nicht aufbereitet werden'));image.src=source;\n})}\nconst median225=values=>{const list=[...values].sort((a,b)=>a-b);return list.length?list[Math.floor(list.length/2)]:0};\nfunction documentRows225(tsv){\n const words=[];\n for(const line of String(tsv||'').split(/\\r?\\n/)){\n  const c=line.split('\\t');if(c[0]!=='5'||c.length<12)continue;\n  const [x,y,w,h]=c.slice(6,10).map(Number),text=c.slice(11).join('\\t').trim();\n  if(!text||![x,y,w,h].every(Number.isFinite)||w<=0||h<=0)continue;\n  words.push({text,x,y,w,h,page:c[1],line:c.slice(1,5).join(':'),center:x+w/2});\n }\n const output=[];\n for(const page of [...new Set(words.map(w=>w.page))]){\n  const pageWords=words.filter(w=>w.page===page),heights=median225(pageWords.map(w=>w.h));\n  const lines=new Map();for(const word of pageWords){if(!lines.has(word.line))lines.set(word.line,[]);lines.get(word.line).push(word)}\n  const slopes=[];\n  for(const line of lines.values()){\n   const sorted=line.sort((a,b)=>a.x-b.x),first=sorted[0],last=sorted.at(-1);\n   if(sorted.length>=3&&last.center-first.center>heights*8){const slope=((last.y+last.h)-(first.y+first.h))/(last.center-first.center);if(Math.abs(slope)<.15)slopes.push(slope)}\n  }\n  const slope=median225(slopes),rows=[];\n  for(const word of pageWords)word.baseline=word.y+word.h-slope*word.center;\n  for(const word of pageWords.sort((a,b)=>a.baseline-b.baseline||a.x-b.x)){\n   const row=rows.at(-1);\n   if(row&&Math.abs(word.baseline-row.baseline)<=Math.max(3,heights*.5)){\n    row.words.push(word);row.baseline=median225(row.words.map(w=>w.baseline));\n   }else rows.push({baseline:word.baseline,words:[word]});\n  }\n  output.push(...rows.map(row=>row.words.sort((a,b)=>a.x-b.x).map(w=>w.text).join(' ')));\n }\n return output;\n}\n\nreturn {documentTextScore225,selectDocumentText225,confidentTableFusion225,receiptComplete215,readDocument225,recoverDocument297,normalizeDocumentPixels225,prepareDocumentImage225,documentRows225};})();\nreturn modules;})();\nfunction getDocumentModule226(name){const value=ZapasioDocuments226[name];if(!value)throw Error(\"Belegmodul nicht verfügbar: \"+name);return value;}\n"},{"src":"inbound-ocr184.js?v=215","code":"'use strict';\n// Beleg OCR runs on the device. The bundled browser worker has no CDN calls.\nlet inboundBrowserWorker184=null;\nfunction inboundOcrDeadline187(work,ms){\n let timer;\n return Promise.race([work,new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error('Texterkennung antwortet nicht')),ms)})]).finally(()=>clearTimeout(timer));\n}\nasync function requestInboundOcr184(image,onDiagnostic){\n // ML Kit's getText() groups columns into separate blocks. Receipt parsers\n // need the printed row order, so use the bundled document engine on Android\n // as well as in the browser. The native bridge is only a failure fallback.\n try{\n  const text=await requestInboundBrowserOcr184(image);\n  if(text.trim()){onDiagnostic?.({engine:'document225',warning:''});return text}\n  throw Error('Auf dem Foto wurde kein Text gelesen');\n }catch(error){\n  const native=typeof window.ZapasioOcr?.recognizeImageText==='function'||typeof window.ZapasioAndroid?.recognizeImageText==='function';\n  if(native){const text=await inboundOcrDeadline187(Promise.resolve().then(()=>requestOcr147(image)),40000);if(String(text).trim()){onDiagnostic?.({engine:'android',warning:String(error?.message||error||'Belegerkennung konnte nicht gestartet werden').slice(0,180)});return text}}\n  throw error;\n }\n}\nfunction startInboundWorker215(){\n if(!inboundBrowserWorker184){\n  inboundBrowserWorker184=(async()=>{\n   if(typeof loadAsset254==='function')await loadAsset254('ocr');\n   if(typeof window.Tesseract?.createWorker!=='function')throw Error('Texterkennung im Browser konnte nicht geladen werden');\n   const root=new URL('./assets/ocr/',document.querySelector('script[src*=\"inbound-ocr184.js\"]')?.src||location.href).href;\n   const worker=await window.Tesseract.createWorker('eng',1,{workerPath:root+'worker.min.js',corePath:root+'tesseract-core-lstm.wasm.js',langPath:root,gzip:true,cacheMethod:'write',workerBlobURL:false});\n   try{await worker.setParameters({tessedit_pageseg_mode:'3'});return worker}catch(error){await worker.terminate();throw error}\n  })().catch(error=>{inboundBrowserWorker184=null;throw error});\n }\n return inboundBrowserWorker184;\n}\nfunction warmInboundOcr215(){return startInboundWorker215().catch(()=>null);}\nasync function requestInboundBrowserOcr184(image){\n const pending=startInboundWorker215();\n // Image preparation can run while the OCR language and WASM are loading.\n const prepared=Promise.resolve().then(()=>getDocumentModule226('document-reader225')).then(async reader=>({reader,image:typeof reader.prepareDocumentImage225==='function'?await reader.prepareDocumentImage225(image):image}));\n try{\n  const [worker,ready]=await inboundOcrDeadline187(Promise.all([pending,prepared]),25000);\n  return await inboundOcrDeadline187(ready.reader.readDocument225(worker,image,async()=>ready.image),60000);\n }catch(error){\n  if(pending===inboundBrowserWorker184){inboundBrowserWorker184=null;pending.then(lateWorker=>lateWorker.terminate()).catch(()=>{});}\n  throw error;\n }\n}\n"},{"src":"patch182.js?v=217","code":"// Recognition evidence stays with the document; it never changes stock units.\nfunction inboundEvidence229(proposal){\n return {currency:proposal?.currency||'',vatRate:Number.isFinite(proposal?.vatRate)?proposal.vatRate:null,\n  content:proposal?.content||null,unclassifiedPrice:Number.isFinite(proposal?.unclassifiedPrice)?proposal.unclassifiedPrice:null,grossPrice:Number.isFinite(proposal?.grossPrice)?proposal.grossPrice:null,unitUncertain:!!proposal?.unitUncertain,unitPrice:Number.isFinite(proposal?.price)?proposal.price:null,\n  unit:proposal?.unit||'',discountRate:Number.isFinite(proposal?.discountRate)?proposal.discountRate:null};\n}\nfunction inboundEvidenceText229(evidence){\n const parts=[];\n if(evidence.unitUncertain)parts.push('Lagereinheit im Beleg nicht angegeben. Bitte auswählen.');\n if(Number.isFinite(evidence.unclassifiedPrice))parts.push('Einzelpreis laut Beleg: '+evidence.unclassifiedPrice.toFixed(2).replace('.',',')+' '+(evidence.currency||'')+' · Brutto/Netto nicht eindeutig. Preis oben mit passender Preisart ergänzen.');\n if(evidence.content){const c=evidence.content;parts.push('Packungsinhalt laut Beleg: '+(c.count>1?c.count+' × ':'')+String(c.amount).replace('.',',')+' '+c.unit)}\n if(evidence.vatRate!==null)parts.push('USt. laut Position: '+String(evidence.vatRate).replace('.',',')+' %');\n if(evidence.discountRate>0)parts.push('Positionsrabatt: '+evidence.discountRate+' % · Preis nach Rabatt');\n if(evidence.currency&&evidence.currency!=='EUR')parts.push('Belegwährung: '+evidence.currency+(evidence.unitPrice!==null?' · Einzelpreis: '+evidence.unitPrice:'')+'. Einkaufspreis in Euro ergänzen oder offen lassen.');\n return parts.join(' · ');\n}\n'use strict';\n// Separate incoming-document actions. OCR only suggests metadata; it never books stock.\nlet inboundEngine182=null,inboundLines185=null,inboundDraft182=null,inboundPdf183=null,inboundSelectedFile183=null,inboundReadSerial183=0;\nlet inboundCameraStream186=null;\nfunction inboundPickerHtml186(context){\n const fileId=context==='auto'?'inboundAutoFile183':'inboundFile182';\n const cameraId=context==='auto'?'inboundAutoCamera183':'inboundCamera183';\n return `<div id=\"inboundDrop186\" class=\"inboundDrop186\">\n  <span class=\"inboundDropIcon186\" aria-hidden=\"true\">▤</span>\n  <strong>Beleg hier hineinziehen</strong><span>PDF oder Bild, höchstens 12 MB</span>\n  <button type=\"button\" class=\"soft\" onclick=\"document.getElementById('${fileId}').click()\">Datei auswählen</button>\n </div><div class=\"inboundPickerActions186\">\n  <button type=\"button\" class=\"soft inboundCameraButton186\" onclick=\"startInboundCamera186('${context}')\">◉ ${context==='auto'?'Bon oder Beleg fotografieren':'Foto erneut aufnehmen'}</button>\n  <button type=\"button\" class=\"soft inboundMobileFile186\" onclick=\"document.getElementById('${fileId}Photo').click()\">Vorhandenes Foto wählen</button>\n  <button type=\"button\" class=\"soft inboundMobileFile186\" onclick=\"document.getElementById('${fileId}Pdf').click()\">PDF wählen</button>\n  <input class=\"inboundHidden186\" id=\"${fileId}Photo\" type=\"file\" accept=\"image/*\" tabindex=\"-1\" aria-label=\"Vorhandenes Foto wählen\">\n  <input class=\"inboundHidden186\" id=\"${fileId}Pdf\" type=\"file\" accept=\"application/pdf,.pdf\" tabindex=\"-1\" aria-label=\"PDF wählen\">\n  <input class=\"inboundHidden186\" id=\"${fileId}\" type=\"file\" accept=\"image/*,application/pdf,.pdf\" tabindex=\"-1\" aria-label=\"PDF oder Bild auswählen\">\n  <input class=\"inboundHidden186\" id=\"${cameraId}\" type=\"file\" accept=\"image/*\" capture=\"environment\" tabindex=\"-1\" aria-label=\"Kamera des Handys öffnen\">\n </div><div id=\"inboundCameraLive186\" hidden></div>`;\n}\nfunction installInboundPicker186(context){\n const fileId=context==='auto'?'inboundAutoFile183':'inboundFile182';\n const cameraId=context==='auto'?'inboundAutoCamera183':'inboundCamera183';\n const read=context==='auto'?autoReadInbound183:readInbound182;\n for(const id of [fileId,cameraId,fileId+'Photo',fileId+'Pdf'])document.getElementById(id).addEventListener('change',event=>{\n  const file=event.target.files?.[0],result=file?read(file):undefined;event.target.value='';return result;\n });\n const drop=document.getElementById('inboundDrop186');\n for(const type of ['dragenter','dragover'])drop.addEventListener(type,event=>{\n  if(!event.dataTransfer?.types?.includes('Files'))return;\n  event.preventDefault();event.dataTransfer.dropEffect='copy';drop.classList.add('dragActive186');\n });\n drop.addEventListener('dragleave',event=>{if(!drop.contains(event.relatedTarget))drop.classList.remove('dragActive186')});\n drop.addEventListener('drop',event=>{\n  event.preventDefault();event.stopPropagation();drop.classList.remove('dragActive186');\n  const files=event.dataTransfer?.files;\n  if(files?.length===1)return read(files[0]);\n  else document.getElementById(context==='auto'?'inboundAutoStatus183':'inboundStatus182').textContent='Bitte genau einen Beleg ablegen.';\n });\n const dialog=document.getElementById('opsDialog92');\n if(dialog){\n  dialog.ondragover=event=>{if(event.dataTransfer?.types?.includes('Files'))event.preventDefault()};\n  dialog.ondrop=event=>{event.preventDefault();if(event.dataTransfer?.files?.length===1)return read(event.dataTransfer.files[0])};\n  dialog.addEventListener('close',stopInboundCamera186,{once:true});\n }\n}\nfunction stopInboundCamera186(){\n inboundCameraStream186?.getTracks().forEach(track=>track.stop());inboundCameraStream186=null;\n const panel=document.getElementById('inboundCameraLive186');if(panel){panel.hidden=true;panel.textContent=''}\n}\nasync function startInboundCamera186(context){\n stopInboundCamera186();\n const cameraId=context==='auto'?'inboundAutoCamera183':'inboundCamera183';\n if(/Android|iPhone|iPad|iPod/i.test(navigator.userAgent||'')){\n  document.getElementById(cameraId)?.click();return;\n }\n const status=document.getElementById(context==='auto'?'inboundAutoStatus183':'inboundStatus182');\n if(!navigator.mediaDevices?.getUserMedia){status.textContent='Auf diesem Gerät ist keine Kamera im Browser verfügbar. Bitte eine Datei auswählen.';return}\n try{\n  const stream=await navigator.mediaDevices.getUserMedia({video:{facingMode:{ideal:'environment'}},audio:false});\n  const panel=document.getElementById('inboundCameraLive186');\n  if(!panel){stream.getTracks().forEach(track=>track.stop());return}\n  inboundCameraStream186=stream;panel.hidden=false;\n  panel.innerHTML='<video autoplay playsinline muted aria-label=\"Livebild der Kamera\"></video><div><button type=\"button\" class=\"primary\" onclick=\"captureInboundCamera186(\\''+context+'\\')\">Foto verwenden</button> <button type=\"button\" class=\"soft\" onclick=\"stopInboundCamera186()\">Abbrechen</button></div>';\n  const video=panel.querySelector('video');video.srcObject=stream;await video.play();\n  status.textContent='Kamera geöffnet. Beleg vollständig ins Bild nehmen und Foto verwenden.';\n }catch(error){stopInboundCamera186();status.textContent='Kamera konnte nicht geöffnet werden. Zugriff prüfen oder eine Datei auswählen.'}\n}\nasync function captureInboundCamera186(context){\n const video=document.querySelector('#inboundCameraLive186 video');\n if(!video?.videoWidth){const status=document.getElementById(context==='auto'?'inboundAutoStatus183':'inboundStatus182');if(status)status.textContent='Kamera lädt noch. Bitte kurz warten.';return}\n const canvas=document.createElement('canvas'),scale=Math.min(1,2000/video.videoWidth);\n canvas.width=Math.round(video.videoWidth*scale);canvas.height=Math.round(video.videoHeight*scale);\n canvas.getContext('2d').drawImage(video,0,0,canvas.width,canvas.height);\n const blob=await new Promise(resolve=>canvas.toBlob(resolve,'image/jpeg',.9));\n stopInboundCamera186();\n if(!blob)return;\n const file=new File([blob],'Beleg-Foto-'+Date.now()+'.jpg',{type:'image/jpeg'});\n if(context==='auto')await autoReadInbound183(file);else await readInbound182(file);\n}\nfunction installInbound182(){\n const host=document.querySelector('#tabLager .pageHeader');\n if(!host||document.getElementById('inboundActions182'))return;\n const actions=document.createElement('div');actions.id='inboundActions182';actions.innerHTML=`<button type=\"button\" class=\"primary\" onclick=\"openInboundAuto183()\">Beleg erfassen · Foto oder PDF</button>`;\n host.insertAdjacentElement('afterend',actions);\n}\nconst previousRender182=render;\nrender=function(){const value=previousRender182();installInbound182();return value};\ninstallInbound182();\nasync function prepareInbound183(file,{onProgress,onPreview}={}){\n const reader=await getDocumentModule226('inbound-pdf183'),fileKind=reader.inboundFileKind183(file);\n if(fileKind==='unsupported'||file.size>12*1024*1024)throw Error('Bitte ein Foto oder eine PDF-Datei mit höchstens 12 MB auswählen');\n if(fileKind==='pdf'){\n  let previewUrl='';\n  const pdfResult=await reader.readInboundPdf183(file,requestInboundOcr184,{\n   onProgress,\n   onDocument:async pdf=>{\n    onProgress?.('PDF geöffnet. Vorschau wird erstellt …');\n    previewUrl=await reader.renderInboundPdfPage183(pdf,1);\n    onPreview?.(previewUrl,pdf.numPages);\n   }\n  });\n  try{return {reader,fileKind,pdfResult,bytes:pdfResult.bytes,ocr:pdfResult.text,dataUrl:previewUrl,ocrIncomplete:pdfResult.unread.length>0,ocrError:pdfResult.ocrErrors?.join(' · ')||'',invoiceData:pdfResult.invoiceData||null}}\n  catch(error){await pdfResult.pdf.destroy();throw error}\n }\n const bytes=await file.arrayBuffer(),dataUrl=await compress144(file);\n onPreview?.(dataUrl,1);onProgress?.('Foto wird lokal gelesen …');\n let ocr='',ocrIncomplete=false,ocrError='',ocrDiagnostic=null;\n try{\n  // The 900 px preview loses the small print on till receipts.\n  ocr=await requestInboundOcr184(await ocrData179(file),info=>{ocrDiagnostic=info});\n  const receiptLines=await getDocumentModule226('invoice-data191');\n  const documentReader215=await getDocumentModule226('document-reader225');\n  if(!documentReader215.receiptComplete215?.(ocr)&&(!receiptLines.invoiceTextLines191(ocr).length||/\\b(?:kassen|bediener|trans|zwisch)|#\\s*\\d{3,}|\\bbar\\s+\\d+[.,]\\d{2}/i.test(ocr))){\n   onProgress?.('Kassenbon wird genauer gelesen …');\n   const crop=await receiptCrop183(file);\n   if(crop){let retryDiagnostic=null;const retry=await requestInboundOcr184(crop,info=>{retryDiagnostic=info});\n    const score=text=>receiptLines.invoiceTextLines191(text).reduce((sum,row)=>sum+(Number.isFinite(row.grossPrice)||Number.isFinite(row.price)?2:1),0);\n    const initialRows=score(ocr);\n    const retryRows=score(retry);\n    if(retryRows>initialRows){\n     // Header details from the first read may be clearer. Keep its header,\n     // but use one set of line items so OCR retries cannot double the stock.\n     const firstItem=ocr.search(/^\\s*\\d{1,5}\\s*[x×]/im);\n     const header=firstItem>=0?ocr.slice(0,firstItem):ocr;\n     ocr=header+'\\n'+retry;\n     ocrDiagnostic=retryDiagnostic;\n    }\n   }\n  }\n }catch(error){ocrIncomplete=true;ocrError=String(error?.message||'Erkennung fehlgeschlagen').slice(0,160)}\n return {reader,fileKind,pdfResult:null,bytes,dataUrl,ocr,ocrIncomplete,ocrError,ocrDiagnostic};\n}\nfunction receiptCrop183(file){return new Promise(resolve=>{\n const image=new Image(),url=URL.createObjectURL(file);\n image.onload=()=>{try{\n  const width=image.naturalWidth,height=image.naturalHeight,probe=document.createElement('canvas');\n  probe.width=600;probe.height=Math.max(1,Math.round(600*height/width));\n  const ctx=probe.getContext('2d',{willReadFrequently:true});ctx.drawImage(image,0,0,probe.width,probe.height);\n  const pixels=ctx.getImageData(0,0,probe.width,probe.height).data,top=Math.round(probe.height*.25),bottom=Math.round(probe.height*.7),ratios=[];\n  for(let x=0;x<probe.width;x++){\n   let bright=0,total=0;\n   for(let y=top;y<bottom;y+=4){const i=(y*probe.width+x)*4;if((pixels[i]+pixels[i+1]+pixels[i+2])/3>125)bright++;total++}\n   ratios.push(bright/total);\n  }\n  const columns=ratios.map((_,x)=>{const from=Math.max(0,x-8),to=Math.min(ratios.length,x+9);let sum=0;for(let n=from;n<to;n++)sum+=ratios[n];return sum/(to-from)>.55});\n  let left=0,right=0,start=-1;\n  for(let x=0;x<=columns.length;x++){if(columns[x]&&start<0)start=x;if((!columns[x]||x===columns.length)&&start>=0){if(x-start>right-left){left=start;right=x}start=-1}}\n  if(right-left<probe.width*.28){resolve('');return}\n  const x0=Math.max(0,(left/probe.width-.03)*width),x1=Math.min(width,(right/probe.width+.03)*width);\n  const output=document.createElement('canvas');output.width=1600;output.height=Math.round((height*.9)/(x1-x0)*1600);\n  output.getContext('2d').drawImage(image,x0,height*.06,x1-x0,height*.9,0,0,output.width,output.height);\n  resolve(output.toDataURL('image/jpeg',.9));\n }catch{resolve('')}finally{URL.revokeObjectURL(url)}};\n image.onerror=()=>{URL.revokeObjectURL(url);resolve('')};image.src=url;\n})}\nlet inboundAutoPrepared183=null,inboundAutoFile183=null,inboundAutoSerial183=0;\nfunction openInboundAuto183(){\n if(!guardPermission('stockAdd','Belege und Lagerzugänge'))return;\n if(typeof warmInboundOcr215==='function')warmInboundOcr215();\n stopInboundCamera186();\n inboundAutoSerial183++;\n inboundAutoPrepared183?.pdfResult?.pdf.destroy();inboundAutoPrepared183=null;inboundAutoFile183=null;\n openOpsPanel('Beleg erfassen',`<div class=\"inbound182\"><p>Rechnung, Kassenbon oder Lieferschein fotografieren oder eine vorhandene Datei wählen. Die Dokumentart wird anhand des Inhalts geprüft.</p>\n ${inboundPickerHtml186('auto')}\n <p id=\"inboundAutoStatus183\" role=\"status\">Noch kein Beleg gewählt.</p><div id=\"inboundAutoPreview183\"></div><div id=\"inboundAutoChoice183\"></div></div>`);\n installInboundPicker186('auto');\n}\nfunction homeCaptureReceipt183(){\n openInboundAuto183();\n // This call stays in the original tap handler so Android may open the camera.\n if(document.getElementById('inboundAutoStatus183'))void startInboundCamera186('auto');\n}\nasync function autoReadInbound183(file){\n if(!file)return;\n const serial=++inboundAutoSerial183,status=document.getElementById('inboundAutoStatus183');\n inboundAutoPrepared183?.pdfResult?.pdf.destroy();inboundAutoPrepared183=null;inboundAutoFile183=null;\n document.getElementById('inboundAutoPreview183').textContent='';document.getElementById('inboundAutoChoice183').textContent='';\n status.textContent='Beleg wird lokal geprüft …';\n try{\n  const prepared=await prepareInbound183(file,{\n   onProgress:message=>{if(serial===inboundAutoSerial183&&status.isConnected)status.textContent=message},\n   onPreview:url=>{if(serial!==inboundAutoSerial183||!status.isConnected)return;\n    const preview=document.getElementById('inboundAutoPreview183');preview.innerHTML='';\n    const img=document.createElement('img');img.src=url;img.alt='Gewählter Beleg';preview.appendChild(img);\n   }\n  });\n  if(serial!==inboundAutoSerial183||!document.getElementById('inboundAutoStatus183')){await prepared.pdfResult?.pdf.destroy();return}\n  inboundAutoPrepared183=prepared;inboundAutoFile183=file;\n  const preview=document.getElementById('inboundAutoPreview183');if(!preview.querySelector('img')){const img=document.createElement('img');img.src=prepared.dataUrl;img.alt='Gewählter Beleg';preview.appendChild(img)}\n  if(prepared.pdfResult?.pages>1){const nav=document.createElement('div');nav.id='inboundAutoPages183';nav.innerHTML=`<button type=\"button\" class=\"soft\" onclick=\"autoShowInboundPage183(-1)\">Vorherige Seite</button> <span id=\"inboundAutoPageNumber183\">Seite 1 von ${prepared.pdfResult.pages}</span> <button type=\"button\" class=\"soft\" onclick=\"autoShowInboundPage183(1)\">Nächste Seite</button>`;document.getElementById('inboundAutoPreview183').appendChild(nav)}\n  const engine=await getDocumentModule226('inbound-document182'),kind=prepared.invoiceData?.reference?'invoice':engine.classifyInboundDocument182(prepared.ocr),hint=prepared.reader.inboundNameHint183(file.name);\n  const choice=document.getElementById('inboundAutoChoice183');\n  if(kind==='unknown'){\n   status.textContent=(prepared.ocrIncomplete?'Texterkennung unvollständig'+(prepared.ocrError?' ('+prepared.ocrError+')':'')+'. ':'')+'Dokumentart aus dem Inhalt nicht sicher erkannt. Bitte die Überschrift im Beleg prüfen'+(hint!=='unknown'?' (Dateiname deutet auf '+(hint==='invoice'?'Rechnung':'Lieferschein')+' hin).':'.');\n   choice.innerHTML=`${prepared.ocrIncomplete?'<button type=\"button\" class=\"soft\" onclick=\"autoReadInbound183(inboundAutoFile183)\">Texterkennung erneut versuchen</button> ':''}<button type=\"button\" class=\"soft\" onclick=\"autoChooseInbound183('invoice')\">Als Rechnung prüfen</button> <button type=\"button\" class=\"soft\" onclick=\"autoChooseInbound183('delivery_note')\">Als Lieferschein prüfen</button>`;\n  }else{\n   status.textContent=kind==='invoice'?'Rechnung oder Kassenbon erkannt. Die Belegprüfung wird geöffnet.':'Lieferschein erkannt. Die Wareneingangsprüfung wird geöffnet.';\n   await autoChooseInbound183(kind);\n  }\n }catch(error){if(serial===inboundAutoSerial183)status.textContent='Beleg konnte nicht geprüft werden: '+error.message}\n}\nasync function autoShowInboundPage183(delta){\n const pdf=inboundAutoPrepared183?.pdfResult?.pdf,label=document.getElementById('inboundAutoPageNumber183');if(!pdf||!label)return;\n const current=Number(label.textContent.match(/\\d+/)?.[0])||1,page=Math.max(1,Math.min(pdf.numPages,current+delta));if(page===current)return;\n try{const reader=await getDocumentModule226('inbound-pdf183'),url=await reader.renderInboundPdfPage183(pdf,page);\n  if(pdf===inboundAutoPrepared183?.pdfResult?.pdf&&document.getElementById('inboundAutoPageNumber183')===label){document.querySelector('#inboundAutoPreview183 img').src=url;label.textContent=`Seite ${page} von ${pdf.numPages}`}\n }catch(error){document.getElementById('inboundAutoStatus183').textContent='Seite konnte nicht angezeigt werden: '+error.message}\n}\nasync function autoChooseInbound183(kind){\n if(!inboundAutoPrepared183||!inboundAutoFile183)return;\n const prepared=inboundAutoPrepared183,file=inboundAutoFile183;\n inboundAutoPrepared183=null;inboundAutoFile183=null;\n await openInbound182(kind);await readInbound182(file,prepared);\n}\nasync function openInbound182(kind){\n if(!guardPermission('stockAdd','Belege und Lagerzugänge'))return;\n if(typeof warmInboundOcr215==='function')warmInboundOcr215();\n stopInboundCamera186();\n inboundEngine182=await getDocumentModule226('inbound-document182');\n inboundLines185=await getDocumentModule226('inbound-lines185');\n if(inboundPdf183){inboundPdf183.destroy();inboundPdf183=null}\n inboundSelectedFile183=null;inboundReadSerial183++;\n inboundDraft182={kind,fileHash:'',fileName:'',sourceKind:'',pages:1,ocr:'',detected:'unknown',imageUrl:''};\n const delivery=kind==='delivery_note';\n openOpsPanel(delivery?'Lieferschein buchen':'Rechnung prüfen',`<div class=\"inbound182\">\n  <details id=\"inboundPhotoDetails225\" open><summary>Belegfoto ansehen oder ändern</summary>${inboundPickerHtml186('review')}\n  <div id=\"inboundPreview182\"></div><div id=\"inboundPages183\"></div></details><p id=\"inboundStatus182\" role=\"status\">Noch kein Beleg gewählt.</p>\n  ${delivery?'':'<details id=\"inboundHeader191\"><summary>Rechnungsdaten prüfen</summary>'}<div class=\"inboundFields182\"><label>Lieferant<input id=\"inboundSupplier182\" oninput=\"refreshInvoiceLinks192()\" onchange=\"suggestLearnedInbound234()\" autocomplete=\"organization\" placeholder=\"Lieferant\"></label>\n  <label>${delivery?'Lieferscheinnummer':'Rechnungsnummer'}<input id=\"inboundRef182\" placeholder=\"Nummer laut Beleg\"></label>\n  <label>Datum<input id=\"inboundDate182\" type=\"date\"></label>\n  ${delivery?'<label>Bestellnummer (optional)<input id=\"inboundOrder182\"></label>':''}</div>${delivery?'':'</details>'}\n  ${delivery?`<h3>Artikel prüfen</h3><div id=\"inboundSuggestions182\" role=\"status\"></div><div id=\"inboundRows182\"></div><button type=\"button\" class=\"soft\" onclick=\"addInboundRow182()\">Position hinzufügen</button>\n  <label class=\"inboundConfirm182\"><input id=\"inboundConfirm182\" type=\"checkbox\"> Artikel, Mengen und MHD geprüft</label>\n  <button type=\"button\" class=\"primary\" onclick=\"confirmDelivery182()\">In den Bestand übernehmen</button>`:\n  `<h3>Artikel prüfen</h3><details><summary>Mehrere Abholungen?</summary><p>Pro MHD eine eigene Zeile verwenden. Das MHD von der Ware ablesen. Bereits gebuchte Abholungen nicht erneut übernehmen.</p></details><div id=\"inboundSuggestions182\" role=\"status\"></div><div id=\"inboundRows182\"></div>\n   <button type=\"button\" class=\"soft\" onclick=\"addInboundRow182()\">Produkt ergänzen</button>\n   <p class=\"muted\">Nur noch nicht gebuchte Ware übernehmen.</p>\n   <label class=\"inboundConfirm182\"><input id=\"invoiceAllRowsChecked191\" type=\"checkbox\"> Alle Belegpositionen geprüft</label>\n   <button type=\"button\" class=\"primary\" onclick=\"confirmInvoiceStock190()\">In den Bestand übernehmen</button>\n   <details class=\"inboundInvoiceStock190\"><summary>Bereits gebucht? Rechnung nur zuordnen</summary>\n    <label>Gebuchten Lieferschein auswählen<select id=\"inboundLinked182\" onchange=\"updateInvoiceLink191()\"><option value=\"\">Keine Zuordnung</option></select></label>\n    <label class=\"inboundConfirm182\"><input id=\"inboundConfirm182\" type=\"checkbox\"> Beleg und Nummer geprüft</label>\n    <button type=\"button\" class=\"soft\" onclick=\"confirmInvoice182()\">Rechnung ohne Lagerzugang speichern</button>\n   </details>`}\n   <details><summary>Bisherige ${delivery?'Lieferscheine':'Rechnungsbelege'} ansehen</summary><div id=\"inboundHistory182\"></div></details></div>`);\n installInboundPicker186('review');\n renderInboundHistory182(kind);\n addInboundRow182();\n}\nasync function readInbound182(file,prepared=null){\n if(!file)return;\n const status=document.getElementById('inboundStatus182');if(!status)return;\n const serial=++inboundReadSerial183;\n if(inboundPdf183){inboundPdf183.destroy();inboundPdf183=null}\n inboundDraft182.fileHash='';inboundDraft182.imageUrl='';inboundDraft182.fileName='';inboundDraft182.sourceKind='';inboundDraft182.pages=1;inboundSelectedFile183=null;\n document.getElementById('inboundPreview182').textContent='';document.getElementById('inboundPages183').textContent='';\n document.getElementById('inboundSwitch183')?.remove();\n document.getElementById('inboundReadDetails224')?.remove();\n const reader=prepared?.reader||await getDocumentModule226('inbound-pdf183');\n const fileKind=reader.inboundFileKind183(file);\n if(fileKind==='unsupported'||file.size>12*1024*1024){status.textContent='Bitte ein Foto oder eine PDF-Datei mit höchstens 12 MB auswählen.';return}\n status.textContent=fileKind==='pdf'?'PDF-Seiten werden lokal gelesen …':'Foto wird lokal geprüft …';\n let pdfResult=null;\n try{\n  const parsed=prepared||await prepareInbound183(file,{\n   onProgress:message=>{if(serial===inboundReadSerial183&&status.isConnected)status.textContent=message},\n   onPreview:url=>{if(serial!==inboundReadSerial183||!status.isConnected)return;\n    const host=document.getElementById('inboundPreview182');host.innerHTML='';\n    const img=document.createElement('img');img.src=url;img.alt='Ausgewählter Beleg zur Sichtprüfung';host.appendChild(img);\n   }\n  });\n  pdfResult=parsed.pdfResult;\n  const {bytes,dataUrl,ocr}=parsed;\n  if(serial!==inboundReadSerial183||!inboundDraft182||!document.getElementById('inboundStatus182')){await pdfResult?.pdf.destroy();return}\n  if(!bytes?.byteLength)throw Error('Dateiinhalt fehlt. Bitte den Beleg erneut auswählen');\n  const hash=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',bytes))).map(x=>x.toString(16).padStart(2,'0')).join('');\n  inboundDraft182.fileHash=hash;inboundDraft182.fileName=file.name;inboundDraft182.sourceKind=fileKind;inboundDraft182.pages=pdfResult?.pages||1;inboundDraft182.ocr=ocr;\n  const detected=parsed.invoiceData?.reference?'invoice':inboundEngine182.classifyInboundDocument182(ocr),hint=reader.inboundNameHint183(file.name);\n  inboundDraft182.detected=detected==='unknown'?hint:detected;\n  inboundSelectedFile183=file;\n  if(pdfResult){inboundPdf183=pdfResult.pdf;inboundDraft182.unreadPages=pdfResult.unread;inboundDraft182.pages=pdfResult.pages}\n  inboundDraft182.imageUrl=dataUrl;\n  const preview=document.getElementById('inboundPreview182');preview.innerHTML='';\n  const img=document.createElement('img');img.alt='Ausgewählter Beleg zur Sichtprüfung';img.src=dataUrl;preview.appendChild(img);\n  if(pdfResult){\n   const pages=document.getElementById('inboundPages183');\n   pages.innerHTML=`<button type=\"button\" class=\"soft\" onclick=\"showInboundPdfPage183(-1)\">Vorherige Seite</button> <span id=\"inboundPageNumber183\">Seite 1 von ${pdfResult.pages}</span> <button type=\"button\" class=\"soft\" onclick=\"showInboundPdfPage183(1)\">Nächste Seite</button><p class=\"muted\">Gespeichert wird eine Vorschau der ersten Seite. Bitte die Original-PDF aufbewahren und alle Seiten vor der Bestätigung prüfen.</p>${pdfResult.pages>1?`<label class=\"inboundConfirm182\"><input id=\"inboundAllPagesChecked247\" type=\"checkbox\"> Alle ${pdfResult.pages} PDF-Seiten vollständig geprüft</label>`:''}`;\n   if(pdfResult.unread.length)pages.insertAdjacentHTML('beforeend',`<p class=\"planWarn\">Text auf Seite ${pdfResult.unread.join(', ')} nicht lesbar. Diese Seiten manuell prüfen.</p>`);\n  }\n  if(inboundDraft182.kind==='delivery_note'){\n   const header=inboundEngine182.deliveryHeader182(ocr);\n   if(header.reference)document.getElementById('inboundRef182').value=header.reference;\n   if(header.date)document.getElementById('inboundDate182').value=header.date;\n   if(header.order)document.getElementById('inboundOrder182').value=header.order;\n   const rawSuggestions=inboundLines185.deliveryLines185(ocr).filter(row=>!row.service),supplierNow=inboundEngine182.inboundSupplier182(ocr),suggestions=inboundLines185.prepareInboundPositions203(rawSuggestions,supplierNow),host=document.getElementById('inboundSuggestions182');\n   if(host)host.textContent=suggestions.length?`${suggestions.length} Positionen vorgeschlagen. Namen, Stückzahlen und Lagereinheiten prüfen; fehlende Zeilen ergänzen.`:'Keine Position sicher gelesen. Positionen aus dem Beleg unten ergänzen.';\n   if(suggestions.length){document.getElementById('inboundRows182').textContent='';for(const row of suggestions)addInboundRow182(row)}\n   inboundDraft182.itemCount=suggestions.length;\n  }else{\n   const header=inboundEngine182.invoiceHeader182(ocr);\n   const xml=parsed.invoiceData;\n   if(xml?.reference||header.reference)document.getElementById('inboundRef182').value=xml?.reference||header.reference;\n   if(xml?.date||header.date)document.getElementById('inboundDate182').value=xml?.date||header.date;\n  const textReader=await getDocumentModule226('invoice-data191');\n   const tableRows=textReader.invoiceTextLines191(ocr);\n   const rawSuggestions=xml?.lines?.length?xml.lines:tableRows.length?tableRows:inboundLines185.deliveryLines185(ocr),supplierNow=xml?.supplier||inboundEngine182.inboundSupplier182(ocr),suggestions=inboundLines185.prepareInboundPositions203(rawSuggestions,supplierNow),host=document.getElementById('inboundSuggestions182');\n   if(host)host.textContent='Positionen werden ins Formular übertragen …';\n   if(!suggestions.length&&host)host.insertAdjacentHTML('afterend',`<details id=\"inboundReadDetails224\"><summary>Gelesenen Text anzeigen</summary><pre style=\"white-space:pre-wrap;overflow-wrap:anywhere\">${esc(ocr||'Es wurde kein Text erkannt.')}</pre></details>`);\n   if(suggestions.length){document.getElementById('inboundRows182').textContent='';for(const row of suggestions)addInboundRow182(row)}\n   inboundDraft182.itemCount=suggestions.length;\n   if(host)host.textContent=suggestions.length?`${suggestions.length} Positionen ins Formular übernommen. ${suggestions.some(row=>row.priceMissing)?'Mindestens ein Preis ist nicht sicher zugeordnet. ':''}Mengen und MHD prüfen.`:'Keine Produktposition erkannt. Aus dem Beleg wurden keine Produkte übernommen.';\n  }\n  const supplier=parsed.invoiceData?.supplier||inboundEngine182.inboundSupplier182(ocr);\n  if(inboundDraft182.itemCount)document.getElementById('inboundPhotoDetails225').open=false;\n  if(supplier)document.getElementById('inboundSupplier182').value=supplier;\n  const recognizedRows=typeof document.querySelectorAll==='function'?Array.from(document.querySelectorAll('#inboundRows182 .inboundRow182')||[]):Array.from(document.getElementById('inboundRows182')?.children||[]),unassigned=recognizedRows.filter(row=>row.querySelector('.inboundProduct182')?.value==='__new__').length;\n  const recognition=document.getElementById('inboundSuggestions182');if(recognition&&recognizedRows.length)recognition.insertAdjacentHTML('beforeend',`<div class=\"inboundRecognitionState204\">${recognizedRows.length} erkannt · ${unassigned} erkannt, aber noch nicht zugeordnet.</div>`);\n  if(inboundDraft182.kind==='invoice')refreshInvoiceLinks192();\n  if(inboundDraft182.kind==='invoice')document.getElementById('inboundHeader191').open=!(supplier&&document.getElementById('inboundRef182').value&&document.getElementById('inboundDate182').value);\n  const type=inboundDraft182.detected;\n  status.textContent=type==='unknown'?'Dokumenttyp nicht sicher erkannt. Bitte alle Seiten und die Überschrift selbst prüfen.':type==='invoice'?'Als Rechnung erkannt. Lieferscheinbuchung ist damit gesperrt.':'Als Lieferschein erkannt. Alle Angaben vor Buchung prüfen.';\n  if(inboundDraft182.kind==='invoice'&&type==='invoice')status.textContent=/\\b(?:kassen\\s*(?:id|nr)|bediener|trans\\s*[:.])\\b/i.test(ocr)?'Kassenbon erkannt. Ware zuordnen, Dienstleistungen ausschließen, Menge und MHD prüfen.':'Rechnung erkannt. Artikel und Mengen prüfen, MHD angeben und bestätigen.';\n  if(inboundDraft182.kind==='invoice'&&!inboundDraft182.itemCount)status.textContent='Beleg eingelesen, aber keine Produktposition erkannt. Die Produktfelder wurden nicht automatisch ausgefüllt.';\n  if(parsed.ocrIncomplete)status.textContent+=' Texterkennung unvollständig'+(parsed.ocrError?' ('+parsed.ocrError+')':'')+'. Bitte alle Angaben direkt mit dem Beleg vergleichen.';\n  if(parsed.ocrDiagnostic?.warning)status.textContent+=' Das neue Belegmodul konnte nicht verwendet werden; die einfache Handy-Erkennung wurde genutzt. Ursache: '+parsed.ocrDiagnostic.warning;\n  if(type==='unknown'&&inboundDraft182.kind==='delivery_note')status.insertAdjacentHTML('afterend',`<label id=\"inboundUnknownType190\" class=\"inboundConfirm182\"><input type=\"checkbox\" id=\"inboundUnknownConfirm190\"> Auf dem Original steht ausdrücklich „Lieferschein“. Falls dort „Rechnung“ steht, zur Rechnungsprüfung wechseln.</label>`);\n  if(detected==='unknown'&&hint!=='unknown')status.textContent+=' Dokumenttyp nur aus dem Dateinamen vermutet.';\n  const oldInvoice=(state.inboundInvoices182||[]).find(x=>inboundEngine182.sameInboundFile192(x.fileHash,hash));\n  if(type==='delivery_note'&&inboundDraft182.kind==='delivery_note'&&oldInvoice&&\n   inboundEngine182.canReplaceInvoicePlaceholder182(oldInvoice,type)){\n   status.textContent+=' Dieser Beleg wurde früher als Rechnung vorgemerkt. Bei der bestätigten Lieferscheinbuchung wird nur diese ungeprüfte Vormerkung ersetzt.';\n  }\n  if(type!=='unknown'&&type!==inboundDraft182.kind)status.insertAdjacentHTML('afterend',`<button id=\"inboundSwitch183\" class=\"soft\" type=\"button\" onclick=\"switchInboundKind183()\">${type==='invoice'?'Zur Rechnungsprüfung wechseln':'Zum Lieferschein wechseln'}</button>`);\n }catch(error){if(pdfResult?.pdf&&!inboundPdf183)await pdfResult.pdf.destroy();if(serial===inboundReadSerial183){status.textContent='Beleg konnte nicht geprüft werden: '+error.message;const host=document.getElementById('inboundSuggestions182');if(host)host.textContent='Übernahme nicht abgeschlossen. Es wurde kein Bestand gebucht.'}}\n}\nasync function showInboundPdfPage183(delta){\n if(!inboundPdf183)return;\n const label=document.getElementById('inboundPageNumber183');if(!label)return;\n const current=Number(label.textContent.match(/\\d+/)?.[0])||1,page=Math.max(1,Math.min(inboundPdf183.numPages,current+delta));\n if(page===current)return;\n try{const pdf=inboundPdf183,reader=await getDocumentModule226('inbound-pdf183'),url=await reader.renderInboundPdfPage183(pdf,page);\n  const image=document.querySelector('#inboundPreview182 img');if(image&&document.getElementById('inboundPageNumber183')===label&&pdf===inboundPdf183){image.src=url;label.textContent=`Seite ${page} von ${pdf.numPages}`}\n }catch(error){document.getElementById('inboundStatus182').textContent='Seite konnte nicht angezeigt werden: '+error.message}\n}\nasync function switchInboundKind183(){\n const file=inboundSelectedFile183,kind=inboundDraft182?.detected;\n if(!file||!['invoice','delivery_note'].includes(kind))return;\n await openInbound182(kind);await readInbound182(file);\n}\nfunction addInboundRow182(proposal=null){\n const host=document.getElementById('inboundRows182');if(!host)return;\n if(inboundDraft182?.kind==='invoice'){addInvoiceRow191(host,proposal);return}\n const row=document.createElement('div');row.className='inboundRow182';\n const supplier=document.getElementById('inboundSupplier182')?.value||inboundEngine182.inboundSupplier182(inboundDraft182?.ocr||'');\n const match=proposal?inboundLines185.matchDelivery185(proposal,state.products,supplier):'';\n const options=Object.entries(state.products||{}).sort((x,y)=>x[1].name.localeCompare(y[1].name,'de')).map(([code,a])=>`<option value=\"${esc(code)}\">${esc(a.name)} · ${esc(a.unit||'Einheit')}</option>`).join('');\n row.dataset.learnedSupplier234=proposal&&inboundLines185.learnedMatch234(proposal,state.products,supplier).code?supplier:'';row.dataset.sku=proposal?.articleNumber||'';row.dataset.sourceName234=proposal?.name||'';row.dataset.sourceUnit234=proposal?.unit||'';\n row.dataset.positionId203=proposal?.positionId203||'';row.dataset.positionFingerprint203=proposal?.positionFingerprint203||'';\n const possible=proposal&&!match?inboundLines185.possibleDeliveryMatches203(proposal,state.products,supplier):[];\n const evidence=inboundEvidence229(proposal);row.dataset.evidence229=JSON.stringify(evidence);\n row.innerHTML=`${proposal?`<p class=\"inboundSource185\"><b>Belegzeile ${esc(proposal.articleNumber)}</b> · ${esc(proposal.name)} · ${esc(String(proposal.quantity).replace('.',','))} ${esc(proposal.unit)}</p>`:''}\n  ${inboundEvidenceText229(evidence)?`<p class=\"inboundSource185\">${esc(inboundEvidenceText229(evidence))}</p>`:''}\n  ${possible.length?`<p class=\"inboundMatchHint204\"><b>Möglicherweise bereits vorhanden:</b> ${possible.map(x=>esc(x.name)).join(' · ')}. Bitte auswählen, es wird nichts automatisch neu angelegt.</p>`:''}\n  <label>Artikel zuordnen<select class=\"inboundProduct182\"><option value=\"\">Artikel auswählen</option>${options}<option value=\"__new__\">Neuen Artikel aus Beleg anlegen</option></select></label>\n  <div class=\"inboundNew185\"><label>Artikelname<input class=\"inboundName185\" value=\"${esc(proposal?.name||'')}\" placeholder=\"Name laut Beleg\"></label>\n  <label>Lagereinheit<select class=\"inboundUnit185\">${['Stück','Flasche','Becher','Dose','Packung','Karton','Liter','ml','kg','g'].map(u=>`<option value=\"${u}\" ${proposal?.unit===u?'selected':''}>${u}</option>`).join('')}</select></label>\n  <label>Artikelart<select class=\"inboundCategory185\"><option>Lebensmittel</option><option>Verbrauchsartikel</option><option>Sonstiges</option></select></label>\n  <label>MHD-Regel<select class=\"inboundPolicy185\"><option value=\"required\">MHD erforderlich</option><option value=\"none\">Kein MHD</option><option value=\"optional\">MHD optional</option></select></label></div>\n  <label>${proposal?.quantityUncertain?'Menge widersprüchlich gelesen · bitte eingeben':'Gelieferte Menge'}<input class=\"inboundQty182\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" value=\"${proposal?.quantity||''}\"></label>\n  <label class=\"inboundMhdLabel185\">MHD vom Produkt<input class=\"inboundMhd182\" type=\"date\"></label>\n  <button type=\"button\" class=\"soft\" aria-label=\"Position entfernen\">Entfernen</button>`;\n const select=row.querySelector('.inboundProduct182');select.value=match||'__new__';\n select.addEventListener('change',()=>{row.dataset.manualAssignment234='yes';updateInboundRow185(row)});\n row.refreshAssignment234=()=>updateInboundRow185(row);\n row.querySelector('.inboundCategory185').addEventListener('change',()=>{\n  row.querySelector('.inboundPolicy185').value=row.querySelector('.inboundCategory185').value==='Lebensmittel'?'required':'none';\n  updateInboundRow185(row);\n });\n row.querySelector('.inboundPolicy185').addEventListener('change',()=>updateInboundRow185(row));\n row.querySelector('button').addEventListener('click',()=>row.remove());host.appendChild(row);\n updateInboundRow185(row);\n}\nfunction updateInboundRow185(row){\n const code=row.querySelector('.inboundProduct182').value,newArticle=code==='__new__';\n row.querySelector('.inboundNew185').hidden=!newArticle;\n const product=state.products?.[code],policy=newArticle?row.querySelector('.inboundPolicy185').value:\n  product?.mhdPolicy144||((product?.mhd===true)?'required':'none');\n const label=row.querySelector('.inboundMhdLabel185');label.hidden=policy==='none';\n const mhd=row.querySelector('.inboundMhd182');mhd.required=policy==='required';\n if(policy==='none')mhd.value='';\n}\nfunction renderInboundHistory182(kind){\n const host=document.getElementById('inboundHistory182');if(!host)return;\n const entries=kind==='delivery_note'?state.deliveryNotes182||[]:state.inboundInvoices182||[];\n host.textContent='';\n if(!entries.length){host.textContent='Noch keine Belege erfasst.';return}\n entries.slice().reverse().slice(0,30).forEach(entry=>{\n  const item=document.createElement('div');item.className='inboundHistoryItem182';\n  const source=entry.sourceKind==='pdf'?` · PDF ${Number(entry.pageCount)||1} Seite${Number(entry.pageCount)===1?'':'n'} · Original nicht gespeichert`:entry.sourceKind==='image'?' · Komprimierte Bildvorschau':'';\n  const label=document.createElement('span');label.textContent=`${entry.supplier} · ${entry.reference} · ${entry.date} · ${entry.status||'Lager gebucht'}${(entry.items||[]).length?' · '+entry.items.map(item=>`${state.products[item.code]?.name||item.code}: ${item.qty} ${state.products[item.code]?.unit||''}`).join(', '):' · Kein Lagerzugang'}${source}`;\n  const button=document.createElement('button');button.type='button';button.className='soft';button.textContent=entry.sourceKind==='pdf'?'Vorschau Seite 1 ansehen':'Belegvorschau ansehen';button.disabled=!entry.image;\n  button.onclick=()=>{const preview=document.getElementById('inboundPreview182');if(!preview)return;preview.textContent='';const img=document.createElement('img');img.alt='Gespeicherter Beleg '+entry.reference;img.src=entry.image;preview.appendChild(img);preview.scrollIntoView({block:'nearest'})};\n  item.append(label,button);host.appendChild(item);\n });\n}\nfunction inboundSourceMeta247(){return {sourceFileName:String(inboundDraft182?.fileName||''),sourceKind:inboundDraft182?.sourceKind||'',pageCount:Number(inboundDraft182?.pages)||1,originalStored:false}}\nfunction requireInboundPagesChecked247(){\n const pages=Number(inboundDraft182?.pages)||1;\n if(inboundDraft182?.sourceKind==='pdf'&&pages>1&&!document.getElementById('inboundAllPagesChecked247')?.checked)throw Error(`Alle ${pages} PDF-Seiten vollständig prüfen und bestätigen`);\n}\nfunction inboundCommon182({invoiceStock=false}={}){\n if(!inboundDraft182?.fileHash)throw Error('Zuerst einen Beleg auswählen und prüfen');\n requireInboundPagesChecked247();\n const priorInvoice=inboundDraft182.kind==='delivery_note'?(state.inboundInvoices182||[]).find(x=>inboundEngine182.sameInboundFile192(x.fileHash,inboundDraft182.fileHash)):null;\n if(priorInvoice&&!inboundEngine182.canReplaceInvoicePlaceholder182(priorInvoice,inboundDraft182.detected))throw Error('Dieser Beleg ist bereits als Rechnung erfasst und kann nicht automatisch umgestellt werden');\n if(inboundDraft182.kind==='invoice'&&(state.deliveryNotes182||[]).some(x=>inboundEngine182.sameInboundFile192(x.fileHash,inboundDraft182.fileHash)))throw Error('Dieser Lieferschein wurde bereits als Lagerzugang gebucht');\n if(!invoiceStock&&!document.getElementById('inboundConfirm182')?.checked)throw Error('Prüfung erst bestätigen');\n if(inboundDraft182.kind==='delivery_note'&&inboundDraft182.detected==='invoice')throw Error('Der Beleg wurde als Rechnung erkannt. Bitte den Rechnungseinstieg verwenden');\n if(inboundDraft182.kind==='delivery_note'&&inboundDraft182.detected==='unknown'&&!document.getElementById('inboundUnknownConfirm190')?.checked)throw Error('Die Überschrift Lieferschein auf dem Original ausdrücklich bestätigen. Eine Rechnung in der Rechnungsprüfung buchen.');\n if(inboundDraft182.kind==='invoice'&&inboundDraft182.detected==='delivery_note')throw Error('Der Beleg wurde als Lieferschein erkannt. Bitte den Lieferscheineinstieg verwenden');\n const supplier=document.getElementById('inboundSupplier182').value.trim(),reference=document.getElementById('inboundRef182').value.trim(),date=document.getElementById('inboundDate182').value;\n if(!supplier||!reference||!date)throw Error('Lieferant, Dokumentnummer und Datum angeben');\n return {supplier,reference,date,fileHash:inboundDraft182.fileHash};\n}\nfunction confirmDelivery182(){\n try{\n  const common=inboundCommon182();\n  const draft=[...document.querySelectorAll('#inboundRows182 .inboundRow182')].map(el=>({\n   code:el.querySelector('.inboundProduct182').value,\n   qty:el.querySelector('.inboundQty182').value,mhd:el.querySelector('.inboundMhd182').value||(typeof receiptMhd242==='function'?receiptMhd242(state.products?.[el.querySelector('.inboundProduct182').value]||{},''):''),\n   name:el.querySelector('.inboundName185').value,unit:el.querySelector('.inboundUnit185').value,\n   category:el.querySelector('.inboundCategory185').value,mhdPolicy:el.querySelector('.inboundPolicy185').value,\n   sourceName:el.dataset.sourceName234||'',sourceUnit:el.dataset.sourceUnit234||'',sku:el.dataset.sku||'',evidence229:el.dataset.evidence229?JSON.parse(el.dataset.evidence229):null\n  }));\n  const rows=inboundLines185.validateDeliveryDraft185(draft,state.products);\n  if(rows.some(r=>r.code==='__new__')&&!canCreateArticles())throw Error('Für neue Artikel fehlt die Berechtigung. Bitte einen berechtigten Benutzer anmelden.');\n  if(!inboundEngine182.validDate182(common.date))throw Error('Datum am Lieferschein prüfen');\n  const key=inboundEngine182.deliveryKey182(common.supplier,common.reference,common.date);\n  if((state.deliveryNotes182||[]).some(x=>inboundEngine182.sameInboundIdentity254(x,{...common,key})||inboundEngine182.sameInboundFile192(x.fileHash,common.fileHash)))throw Error('Dieser Lieferschein wurde bereits gebucht');\n  const order=document.getElementById('inboundOrder182').value.trim();\n  let bookedCount=0,createdCount=0;\n  const saved=operationTransaction('Lieferschein als Lagerzugang gebucht','stockAdd',()=>{\n   const earlierInvoice=(state.inboundInvoices182||[]).find(x=>inboundEngine182.sameInboundFile192(x.fileHash,common.fileHash));\n   if(earlierInvoice){\n    if(!inboundEngine182.canReplaceInvoicePlaceholder182(earlierInvoice,inboundDraft182.detected)||\n     (state.deliveryNotes182||[]).some(x=>(x.linkedInvoiceIds||[]).includes(earlierInvoice.id)))throw Error('Die Rechnung ist bereits zugeordnet oder geprüft. Bitte zuerst die Zuordnung klären.');\n    state.inboundInvoices182=state.inboundInvoices182.filter(x=>x.id!==earlierInvoice.id);\n    state.log.push(`${stamp()} | Vormerkung Rechnung ${earlierInvoice.reference} durch Lieferschein ${common.reference} ersetzt`);\n   }\n   if(rows.some(r=>r.code==='__new__')&&!canCreateArticles())throw Error('Berechtigung zum Anlegen neuer Artikel fehlt');\n   state.deliveryNotes182??=[];state.batches??={};\n   const generated=new Map(),items=rows.map(r=>{\n    if(r.code!=='__new__')return{code:r.code,qty:r.qty,mhd:r.mhd};\n    const id=(r.sku||r.name.toLocaleLowerCase('de'))+'|'+r.unit;\n    let code=generated.get(id);\n    if(!code){\n     code=nextInternalBarcode();\n     const article={name:r.name,cat:r.category,overviewGroup:r.category,storage:'Allgemein',\n      stock:0,min:0,price:0,priceQuoteQty:1,priceQuoteUnit:r.unit,\n      supplier:common.supplier,unit:r.unit,mhd:r.mhdPolicy==='required',mhdPolicy144:r.mhdPolicy,\n      packs:[[r.unit,1,code,false,1,1,r.unit,1,r.unit]],photo:'',referencePhotos144:[],\n      internalId:'prod-'+crypto.randomUUID(),supplierArticles:r.sku?[{supplier:common.supplier,sku:r.sku,name:r.name,confirmed:true}]:[],\n      supplierArticleNumber181:r.sku,priceHistory:[],mainArticleId:'',mainFactor:1,prepLossPct:0,\n      drainWeightEnabled:false,stockContent:null};\n     state.products[code]=article;state.batches[code]=[];generated.set(id,code);createdCount++;\n     state.log.push(`${stamp()} | ${r.name} | Artikel aus Lieferschein ${common.reference} angelegt | Anfangsbestand 0`);\n    }\n    return{code,qty:r.qty,mhd:r.mhd};\n   });\n   const checked=inboundEngine182.validateDelivery182({...common,items},state.products,state.deliveryNotes182);\n   const id='dn-'+crypto.randomUUID();\n   for(const item of checked.items){const a=state.products[item.code];a.stock=(Number(a.stock)||0)+item.qty;\n    if(item.mhd)addBatch(item.code,item.qty,item.mhd);\n    addMovement('in',item.code,item.qty,{unitCost:0,supplier:checked.supplier,reason:'Lieferschein '+checked.reference+' · Einkaufspreis offen'});\n    state.log.push(`${stamp()} | ${a.name} | Lieferschein ${checked.reference} | +${item.qty} ${a.unit}`);\n   }\n   inboundLines185.rememberInbound234(state.products,common.supplier,draft,checked.items,common.reference);\n   bookedCount=checked.items.length;\n   state.deliveryNotes182.push({id,key:checked.key,fileHash:common.fileHash,image:inboundDraft182.imageUrl,...inboundSourceMeta247(),\n    supplier:checked.supplier,reference:checked.reference,date:checked.date,order,\n    items:checked.items.map((item,index)=>({...item,documentEvidence229:draft[index].evidence229})),at:new Date().toISOString(),linkedInvoiceIds:[],replacedInvoiceId:earlierInvoice?.id||''});\n  });return afterOperation275(saved,saved=>{\n  if(saved){inboundDraft182=null;document.getElementById('opsDialog92')?.close();showFeedback('Lieferschein gebucht','success',5000,`${bookedCount} Positionen, ${createdCount} neue Artikel. Einkaufspreis bleibt offen.`)}\n ;return saved;});}catch(error){showFeedback('Lieferschein prüfen','warn',6000,error.message)}\n}\nfunction confirmInvoice182(){\n try{\n  const common=inboundCommon182(),key=inboundEngine182.deliveryKey182(common.supplier,common.reference,common.date);\n  const linkedId=document.getElementById('inboundLinked182').value;\n  if((state.inboundInvoices182||[]).some(x=>inboundEngine182.sameInboundIdentity254(x,{...common,key})||inboundEngine182.sameInboundFile192(x.fileHash,common.fileHash)))throw Error('Diese Rechnung oder dieses Foto wurde bereits erfasst');\n  const saved=operationTransaction('Rechnungsbeleg geprüft','stockAdd',()=>{\n   if((state.deliveryNotes182||[]).some(x=>inboundEngine182.sameInboundFile192(x.fileHash,common.fileHash)))throw Error('Beleg bereits als Lieferschein gebucht');\n   state.inboundInvoices182??=[];state.deliveryNotes182??=[];\n   if(state.inboundInvoices182.some(x=>inboundEngine182.sameInboundIdentity254(x,{...common,key})||inboundEngine182.sameInboundFile192(x.fileHash,common.fileHash)))throw Error('Diese Rechnung wurde bereits erfasst');\n   const linked=linkedId?state.deliveryNotes182.find(x=>x.id===linkedId):null;\n   if(linkedId&&!linked)throw Error('Lieferschein nicht mehr vorhanden');\n   if(linked&&!inboundEngine182.sameInboundSupplier192(common.supplier,linked.supplier))throw Error('Rechnung und Lieferschein müssen vom selben Lieferanten stammen');\n   const id='inv-'+crypto.randomUUID();state.inboundInvoices182.push({id,key,fileHash:common.fileHash,image:inboundDraft182.imageUrl,...inboundSourceMeta247(),supplier:common.supplier,reference:common.reference,date:common.date,linkedDeliveryId:linked?.id||'',at:new Date().toISOString(),status:'Prüfung ausstehend'});\n   if(linked){linked.linkedInvoiceIds??=[];linked.linkedInvoiceIds.push(id)}\n  });return afterOperation275(saved,saved=>{\n  if(saved){inboundDraft182=null;document.getElementById('opsDialog92')?.close();showFeedback('Rechnungsbeleg vorgemerkt','success',5000,'Kein Lagerzugang und keine Buchhaltungsbuchung ausgelöst. Positionen und Preise sind noch zu prüfen.')}\n ;return saved;});}catch(error){showFeedback('Rechnung prüfen','warn',6000,error.message)}\n}\n\nfunction suggestLearnedInbound234(){\n const supplier=document.getElementById('inboundSupplier182')?.value||'';\n for(const row of document.querySelectorAll('#inboundRows182 .inboundRow182')){\n  if(row.dataset.manualAssignment234==='yes')continue;\n  const proposal={articleNumber:row.dataset.sku,name:row.dataset.sourceName234,unit:row.dataset.sourceUnit234};\n  const learned=inboundLines185.learnedMatch234(proposal,state.products,supplier);\n  if(!learned.known){if(row.dataset.learnedSupplier234){row.querySelector('.inboundProduct182').value='__new__';row.querySelector('.inboundName185').value=row.dataset.sourceName234||'';row.dataset.learnedSupplier234='';row.refreshAssignment234?.()}continue;}\n  const code=inboundLines185.matchDelivery185(proposal,state.products,supplier);\n  const select=row.querySelector('.inboundProduct182');\n  select.value=code||'__new__';row.dataset.learnedSupplier234=code?supplier:'';if(!code)row.querySelector('.inboundName185').value=row.dataset.sourceName234||'';row.refreshAssignment234?.();\n }\n}\n"},{"src":"workflow185.js?v=1","code":"'use strict';\n/* Inventory has one home on mobile and desktop. Move the existing count form,\n   so its inputs and stock rules are not duplicated. */\nlet inventoryFold185=null, inventoryOrigin185=null, inventoryAfter185=null;\nfunction installInventory185(){\n  const fold=inventoryFold185||document.querySelector('#tabLager .inventoryFold');\n  if(!fold)return;\n  if(!inventoryFold185){inventoryFold185=fold;inventoryOrigin185=fold.parentNode;inventoryAfter185=fold.nextSibling}\n  const mobile=document.getElementById('mobileScreenInventory');\n  const desktop=document.getElementById('tabInventory185');\n  const destination=matchMedia('(max-width:820px)').matches?mobile?.querySelector('.inventoryManual185'):desktop?.querySelector('.inventoryManual185');\n  if(destination&&fold.parentNode!==destination)destination.appendChild(fold);\n  if(!destination&&fold.parentNode!==inventoryOrigin185)inventoryOrigin185.insertBefore(fold,inventoryAfter185);\n  document.querySelectorAll('.photoInventoryStart182').forEach(button=>button.remove());\n  const group=document.querySelector('#customSideNav .zNavGroup155[data-group=\"stock\"] .zNavChildren155');\n  if(group&&!document.getElementById('navInventory185')){\n    const button=document.createElement('button');button.type='button';button.id='navInventory185';button.className='sideNavBtn zSubNav155';\n    button.textContent='Inventur';button.onclick=()=>openInventoryDesktop185();\n    group.appendChild(button);\n  }\n}\nfunction renderInventory185(){\n  installInventory185();\n  const status=state.lastInventory?'Letzte Inventur: '+state.lastInventory:'Noch keine abgeschlossene Inventur';\n  for(const node of document.querySelectorAll('.inventoryLast185'))node.textContent=status;\n  const count=photoInventory182?.rows?.length||0,hasDraft=!!(count||photoInventory182?.sceneUrl);\n  for(const node of document.querySelectorAll('.inventoryDraft185'))node.textContent=count?`${count} Foto-Beobachtung${count===1?'':'en'} offen`:hasDraft?'Ein Foto ist zur Prüfung geöffnet':'Noch keine Foto-Beobachtungen';\n  for(const node of document.querySelectorAll('.inventoryResume185'))node.hidden=!hasDraft;\n}\nfunction openInventory185(){\n  if(!hasPermission('scan')&&!hasPermission('inventory'))return guardPermission('inventory','Inventur');\n  mobileShow('inventory');renderInventory185();\n}\nfunction openInventoryDesktop185(){\n  if(!hasPermission('scan')&&!hasPermission('inventory'))return guardPermission('inventory','Inventur');\n  showTab('inventory185');renderInventory185();\n}\nfunction startInventoryPhoto185(){\n  if(!guardPermission('scan','Foto-Inventur'))return;\n  if(photoInventory182?.rows?.length||photoInventory182?.sceneUrl)return askConfirm('Die offene Foto-Inventur durch eine neue ersetzen? Noch nicht bestätigte Beobachtungen gehen verloren.',startPhotoInventory182,'Neu beginnen','Foto-Inventur');\n  startPhotoInventory182();\n}\nfunction resumeInventoryPhoto185(){\n  if(!photoInventory182)return startInventoryPhoto185();\n  if(!guardPermission('scan','Foto-Inventur'))return;\n  installPhotoInventory182();document.getElementById('photoInventoryModal182').style.display='flex';renderPhotoInventory182();\n}\nconst originalShowTab185=showTab;\nshowTab=function(which,...rest){\n  const panel=document.getElementById('tabInventory185');\n  if(which==='inventory185'){\n    document.querySelectorAll('.tabPanel').forEach(el=>el.classList.toggle('active',el===panel));\n    document.querySelectorAll('#customSideNav .sideNavBtn.active').forEach(el=>el.classList.remove('active'));\n    document.getElementById('navInventory185')?.classList.add('active');\n    if(matchMedia('(max-width:820px)').matches)document.body.classList.add('mobile-detail-mode');return;\n  }\n  panel?.classList.remove('active');return originalShowTab185(which,...rest);\n};\nconst originalMobileShow185=mobileShow;\nmobileShow=function(which='today'){\n  const result=originalMobileShow185(which);\n  if(which==='inventory'){\n    document.querySelector('.mobileBottomNav [data-mobiletab=\"more\"]')?.classList.add('active');\n    renderInventory185();\n  }\n  return result;\n};\nconst originalBuildNav185=buildWorkshopNav155;\nbuildWorkshopNav155=function(){const result=originalBuildNav185();installInventory185();return result};\nconst originalInstallPhotoInventory185=installPhotoInventory182;\ninstallPhotoInventory182=function(){\n  const result=originalInstallPhotoInventory185();\n  document.querySelectorAll('.photoInventoryStart182').forEach(button=>button.remove());\n  return result;\n};\nconst originalRender185=render;\nrender=function(){const result=originalRender185();renderInventory185();return result};\nwindow.addEventListener('resize',()=>installInventory185());\n\n/* Review the existing manual count before calling its booking function. */\nconst saveManualInventoryBefore185=finishInventory;\nfinishInventory=function(){\n  if(!guardPermission('inventory','Inventur'))return;\n  const inputs=[...document.querySelectorAll('.invInput')];\n  const snapshot=inputs.map(input=>[input,input.value]);\n  const rows=inputs.filter(input=>input.value.trim()!=='');\n  if(!rows.length){showFeedback('Bitte mindestens einen Artikel zählen','warn');return}\n  const review=[];\n  for(const input of rows){\n    const article=state.products[input.dataset.code],raw=input.value.trim();\n    if(!article||!/^\\d+(?:[,.]\\d+)?$/.test(raw)){showFeedback('Inventurmenge prüfen','warn',4500,'Nur nichtnegative Zahlen eingeben.');return}\n    const observed=Number(raw.replace(',','.'));\n    if(!Number.isFinite(observed)||(isWholeQuantityUnit(article.unit)&&!Number.isInteger(observed))){showFeedback('Inventurmenge prüfen','warn',4500,`${article.name}: gültige Menge in ${article.unit} eingeben.`);return}\n    const current=Number(article.stock)||0;\n    if(observed>current&&effectiveMhd144(article)==='required'){\n      showFeedback('MHD für Mehrbestand fehlt','warn',6000,`${article.name}: zusätzliche Ware mit MHD einzeln prüfen und erfassen.`);return;\n    }\n    review.push({code:input.dataset.code,input,raw,old:current,observed,name:article.name,unit:article.unit});\n  }\n  const changed=review.filter(row=>row.old!==row.observed);\n  const detail=changed.slice(0,5).map(row=>`${row.name}: ${formatQtySmart(row.old,row.unit)} → ${formatQtySmart(row.observed,row.unit)}`).join('\\n');\n  const message=`${review.length} Artikel gezählt, ${changed.length} Abweichung${changed.length===1?'':'en'}.\\n${detail}${changed.length>5?`\\n… und ${changed.length-5} weitere`:''}\\n\\nBestände nach diesen Zählungen speichern?`;\n  askConfirm(message,()=>{\n    if(review.some(row=>state.products[row.code]?.stock!==row.old)||snapshot.some(([input,value])=>input.value!==value)){\n      showFeedback('Inventur hat sich geändert','warn',4500,'Bitte Mengen erneut prüfen.');return;\n    }\n    for(const row of review)row.input.value=String(row.observed);\n    saveManualInventoryBefore185();renderInventory185();\n  },'Inventur speichern','Zählung prüfen');\n};\n\n/* Buying precedes picking; a shortage must never look like a ready checklist. */\nfunction taskBoard185(c){\n  if(!c)return {shopping:[],ready:[],waiting:[]};\n  const prep=ensurePreparation(c),shopping=procurementFor(c),missing=new Map(shopping.filter(x=>x.productCode).map(x=>[x.productCode,x]));\n  const auto=preparationAutoTasks(c).filter(t=>!prep.done[t.id]);\n  const manual=(prep.manual||[]).filter(t=>!t.done).map(t=>({...t,kind:'manual'}));\n  const ready=[],waiting=[];\n  for(const task of [...auto,...manual]){\n    const code=task.id.startsWith('ingredient:')?task.id.slice('ingredient:'.length):'';\n    const shortage=code?missing.get(code):null;\n    if(shortage||task.missingArticle){waiting.push({...task,shortage});continue}\n    if((task.section==='Produktion'||task.section==='Unterrezepte herstellen')&&shopping.length){waiting.push({...task,shortage:null});continue}\n    ready.push(task);\n  }\n  const order=task=>task.section==='Zutaten bereitstellen'?0:task.kind==='manual'?1:task.section==='Unterrezepte herstellen'?2:3;\n  ready.sort((a,b)=>order(a)-order(b));\n  return {shopping,ready,waiting};\n}\nfunction taskPurchase185(item){return `<button type=\"button\" class=\"zTaskDetail166 zTaskLink166\" onclick=\"openPurchasingOverview('list')\"><span><strong>Einkaufen: ${esc(item.name)}</strong><small>${esc(formatQtySmart(item.qty,item.unit))} fehlen · ${esc(item.supplier||'Lieferant prüfen')}</small></span><span aria-hidden=\"true\">›</span></button>`}\nfunction taskReady185(t,c){\n  const ingredient=t.section==='Zutaten bereitstellen';\n  const heading=(t.label||'Aufgabe')+(ingredient?' bereitstellen':'');\n  if(t.kind==='manual'&&!/\\b(vorbereiten|herstellen|bereitstellen|einräumen|beladen|einladen|prüfen|reinigen)\\b/i.test(heading))\n    return `<button class=\"zTaskDetail166 zTaskLink166\" onclick=\"mobileOpenPlanning('preparation')\"><span><strong>Aufgabe klären: ${esc(heading)}</strong><small>Manuell eingetragen</small></span><span aria-hidden=\"true\">›</span></button>`;\n  const detail=[t.meta,c.event||'Einsatz'].filter(Boolean).join(' · ');\n  return `<label class=\"zTaskRow158 zTaskDetail166\"><input type=\"checkbox\" aria-label=\"${esc(heading)} erledigt\" onchange=\"${t.kind==='manual'?'togglePrepManual':'setPrepDone'}('${escJs(c.id)}','${escJs(t.id)}',this.checked)\"><span><strong>${esc(heading)}</strong><small>${esc(detail)}</small></span></label>`;\n}\nfunction taskWaiting185(t){\n  const text=t.shortage?`${formatQtySmart(t.shortage.qty,t.shortage.unit)} fehlen`:\n    t.missingArticle?'Artikelzuordnung fehlt':'Vorbereitung nach dem Einkauf prüfen';\n  return `<div class=\"inventoryWaiting185\"><b>${esc(t.label||'Aufgabe')}</b><small>${esc(text)}</small></div>`;\n}\nconst homeBefore185=renderMobileToday;\nrenderMobileToday=function(){\n  homeBefore185();\n  const panel=document.querySelector('.zMobileTasks158'),c=mobileWorkingPlan()||zData158().active;\n  if(!panel||!c)return;\n  const board=taskBoard185(c),purchases=board.shopping.slice(0,2),slots=Math.max(1,3-purchases.length);\n  panel.innerHTML=`<div class=\"zPanelHead158\"><h2>Als Nächstes</h2><button class=\"soft\" onclick=\"mobileShow('tasks')\">Alle Aufgaben</button></div>\n    ${purchases.map(taskPurchase185).join('')}${board.shopping.length>2?`<button class=\"soft\" onclick=\"openPurchasingOverview('list')\">${board.shopping.length-2} weitere Einkaufspositionen</button>`:''}\n    ${board.ready.slice(0,slots).map(t=>taskReady185(t,c)).join('')}\n    ${board.waiting.length?`<button class=\"soft inventoryWaitingLink185\" onclick=\"mobileShow('tasks')\">${board.waiting.length} Aufgabe${board.waiting.length===1?'':'n'} warten auf Ware oder Zuordnung</button>`:''}\n    ${!board.shopping.length&&!board.ready.length&&!board.waiting.length?'<p class=\"zEmpty158\">Für diesen Einsatz ist aktuell nichts offen.</p>':''}`;\n};\nconst tasksBefore185=renderMobileTasks;\nrenderMobileTasks=function(){\n  tasksBefore185();const c=mobileWorkingPlan(),host=document.getElementById('mobileTasksContent');\n  if(!c||!host)return;\n  const board=taskBoard185(c),details=document.createElement('section');details.className='taskSequence185';\n  details.innerHTML=`<div class=\"mobileWorkCard\"><h2>1. Fehlende Ware</h2>${board.shopping.length?board.shopping.map(taskPurchase185).join(''):'<p>Für die verknüpften Artikel ist kein Einkauf offen.</p>'}</div>\n    <div class=\"mobileWorkCard\"><h2>2. Jetzt möglich</h2>${board.ready.length?board.ready.map(t=>taskReady185(t,c)).join(''):'<p>Derzeit keine sofort ausführbaren Vorbereitungsaufgaben.</p>'}</div>\n    ${board.waiting.length?`<div class=\"mobileWorkCard\"><h2>3. Danach prüfen</h2>${board.waiting.map(taskWaiting185).join('')}</div>`:''}`;\n  host.querySelector('.mobileTaskList')?.before(details);\n};\ninstallInventory185();renderInventory185();\nif(matchMedia('(max-width:820px)').matches)renderMobileToday();\n"},{"src":"stock-withdrawal187.js?v=1","code":"'use strict';\nlet zConfirmedUnassigned187 = null;\nlet zBookedUnassigned187 = 0;\nfunction zOwnedLots187(code, commission = false) {\n  return (state.batches?.[code] || []).filter(batch => !!batch.consignment168 === !!commission)\n    .reduce((sum, batch) => sum + Math.max(0, Number(batch.qty) || 0), 0);\n}\nconst zBookBefore187 = book;\nbook = function() {\n  const article = state.products?.[current];\n  const qty = Number(document.getElementById('qty')?.value);\n  const mult = Number(document.getElementById('pack')?.value) || 1;\n  const total = qty * mult;\n  const commission = !!document.getElementById('zCommission168')?.checked;\n  if (mode !== 'out' || !article?.mhd || !Number.isFinite(total) || total <= 0 ||\n      (isWholeQuantityUnit(article.unit) && !Number.isInteger(qty))) return zBookBefore187();\n  const available = commission ? zCommissionQty168(article)\n    : Math.max(0, Number(article.stock) - zCommissionQty168(article));\n  const assigned = zOwnedLots187(current, commission);\n  const missing = Math.max(0, total - assigned);\n  if (missing <= 1e-8 || total > available + 1e-8) return zBookBefore187();\n  const code = current, stock = Number(article.stock);\n  askConfirm(`${formatQtySmart(missing, article.unit)} haben keine zugeordnete MHD-Charge. Die Entnahme wird mit diesem Hinweis protokolliert. Bestand: ${formatQtySmart(available, article.unit)}, davon mit MHD: ${formatQtySmart(assigned, article.unit)}. Trotzdem entnehmen?`, () => {\n    if (current !== code || Number(state.products?.[code]?.stock) !== stock ||\n        zOwnedLots187(code, commission) !== assigned) {\n      showFeedback('Bestand hat sich geändert', 'warn', 4000, 'Bitte die Entnahme erneut prüfen.');\n      return;\n    }\n    zConfirmedUnassigned187 = {code, total, commission};\n    try { return zBookBefore187(); }\n    finally { zConfirmedUnassigned187 = null; }\n  }, 'Entnahme buchen', 'MHD-Zuordnung fehlt');\n};\nconst zConsumeBefore187 = consumeBatches;\nconsumeBatches = function(code, qty, commission = false) {\n  const approved = zConfirmedUnassigned187;\n  if (!approved || approved.code !== code || approved.total !== qty || approved.commission !== commission)\n    return zConsumeBefore187(code, qty, commission);\n  const article = state.products?.[code];\n  const batches = state.batches?.[code] || [];\n  const assigned = zOwnedLots187(code, commission);\n  const missing = Math.max(0, qty - assigned);\n  // finishBook has already reduced stock. Check the stock that existed before this booking.\n  const previousStock = Number(article?.stock) + qty;\n  const previousCommission = Number(article?.consignmentQty168 || 0) + (commission ? qty : 0);\n  const availableBefore = commission ? previousCommission : previousStock - previousCommission;\n  if (!article || missing > Math.max(0, availableBefore - assigned) + 1e-8)\n    throw Error('Bestand und MHD-Chargen haben sich geändert. Bitte erneut prüfen.');\n  let left = qty;\n  for (const batch of batches.sort((a,b) => (a.mhd || '9999').localeCompare(b.mhd || '9999'))) {\n    if (!!batch.consignment168 !== commission || left <= 1e-8) continue;\n    const taken = Math.min(left, Math.max(0, Number(batch.qty) || 0));\n    batch.qty -= taken;\n    if (Number(batch.packSize) > 0) batch.packCount = Math.max(0, Math.ceil((batch.qty - 1e-9) / Number(batch.packSize)));\n    left -= taken;\n  }\n  (state.batches ??= {})[code] = batches.filter(batch => Number(batch.qty) > 1e-8);\n  if (missing > 1e-8) {\n    zBookedUnassigned187 = missing;\n    state.log.push(`${stamp()} | ${article.name} | ${formatQtySmart(missing, article.unit)} ohne zugeordnete MHD-Charge entnommen`);\n  }\n};\nconst zFinishBefore187 = finishBook;\nfinishBook = function(total) {\n  zBookedUnassigned187 = 0;\n  zFinishBefore187(total);\n  const missing = zBookedUnassigned187;\n  zBookedUnassigned187 = 0;\n  if (missing > 0 && document.getElementById('modal')?.style.display === 'none') {\n    const unit = state.products?.[current]?.unit || 'Stück';\n    showFeedback('Entnahme gespeichert', 'warn', 6500,\n      `${formatQtySmart(missing, unit)} ohne zugeordnete MHD-Charge gebucht. Bitte die restlichen Chargen prüfen.`);\n  }\n};\n"},{"src":"barcode187.js?v=241","code":"'use strict';\n/* Add a manufacturer barcode without changing the stable article key. */\nlet barcodeTarget187 = null;\nfunction decoratePackBarcodes187() {\n  for (const row of document.querySelectorAll('#aePacks .purchasePackCard')) {\n    const field = row.querySelector('.packBarcodeField');\n    if (!field || field.querySelector('.barcodePhoto187')) continue;\n    const button = document.createElement('button');\n    button.type = 'button';\n    button.className = 'soft barcodePhoto187';\n    button.textContent = 'Barcode fotografieren';\n    button.onclick = () => capturePackBarcode187(row);\n    field.appendChild(button);\n  }\n  const status = document.getElementById('aeCodeStatus');\n  const existing = document.getElementById('addManufacturerBarcode187');\n  if (existing) existing.hidden = !editingArticleCode;\n  if (status && editingArticleCode && !document.getElementById('addManufacturerBarcode187')) {\n    const button = document.createElement('button');\n    button.type = 'button';\n    button.className = 'soft';\n    button.id = 'addManufacturerBarcode187';\n    button.textContent = 'Originalbarcode ergänzen';\n    button.onclick = () => {\n      const packs=document.getElementById('aePacks');\n      let row=packs?.querySelector('.originalBarcode241');\n      if(!row)addPackEditorRow();\n      row=row||[...document.querySelectorAll('#aePacks .purchasePackCard')].at(-1);\n      if (!row) return;\n      packs.classList.add('barcodeEditing241');\n      row.classList.add('originalBarcode241');\n      row.querySelector('.aePackName').value = 'Originalpackung';\n      row.querySelector('.packCardTitle').textContent = 'Originalpackung';\n      row.querySelector('.aePackOrderable').checked = false;\n      decoratePackBarcodes187();\n      row.scrollIntoView({block:'center',behavior:'smooth'});\n      row.querySelector('.aePackQty').focus();\n      showFeedback('Packungsinhalt prüfen','info',5000,'Packungsinhalt und Barcode ergänzen. Danach Artikel speichern.');\n    };\n    status.insertAdjacentElement('afterend',button);\n  }\n}\nfunction capturePackBarcode187(row) {\n  if (!row?.isConnected) return;\n  barcodeTarget187 = {row, article:editingArticleCode};\n  let input = document.getElementById('packBarcodeFile187');\n  if (!input) {\n    input = document.createElement('input');\n    input.id = 'packBarcodeFile187';\n    input.type = 'file';\n    input.accept = 'image/*';\n    input.setAttribute('capture','environment');\n    input.hidden = true;\n    document.body.appendChild(input);\n    input.onchange = async () => {\n      const target = barcodeTarget187;\n      const file = input.files?.[0];\n      input.value = '';\n      if (!file || !target) return;\n      const button = target.row.querySelector('.barcodePhoto187');\n      if (button) {button.disabled = true;button.textContent = 'Barcode wird gelesen …';}\n      try {\n        const value = String(await zReadBarcode166(file)).trim().toUpperCase();\n        if (document.getElementById('articleModal')?.style.display !== 'flex' ||\n            !target.row.isConnected || editingArticleCode !== target.article) return;\n        if (!value) return showFeedback('Barcode nicht lesbar','warn',4500,'Nummer unter dem Strichcode bitte manuell eingeben.');\n        const field = target.row.querySelector('.aePackBarcode');\n        field.value = value;\n        if (!checkPackBarcodeLive(field)) {\n          showFeedback('Barcode bereits vergeben','warn',5000,field.title);\n          return;\n        }\n        showFeedback('Barcode erkannt','success',4000,value + ' – Packungsinhalt prüfen und Artikel speichern.');\n      } catch (_) {\n        showFeedback('Barcode nicht lesbar','warn',4500,'Nummer unter dem Strichcode bitte manuell eingeben.');\n      } finally {\n        if (button?.isConnected) {button.disabled = false;button.textContent = 'Barcode fotografieren';}\n      }\n    };\n  }\n  input.click();\n}\nconst renderPackEditorBefore187 = renderPackEditor;\nrenderPackEditor = function(...args) {const result = renderPackEditorBefore187(...args);decoratePackBarcodes187();return result;};\nconst addPackEditorRowBefore187 = addPackEditorRow;\naddPackEditorRow = function(...args) {const result = addPackEditorRowBefore187(...args);decoratePackBarcodes187();return result;};\nconst openArticleEditorBefore187 = openArticleEditor;\nopenArticleEditor = function(...args) {const result = openArticleEditorBefore187(...args);decoratePackBarcodes187();return result;};\n"},{"src":"android-back188.js?v=239","code":"'use strict';\n// Android's system Back key asks the page before navigating WebView history.\n// Only visible Zapasio dialogs are handled here; stock is never changed.\nwindow.zapasioHandleBack = function() {\n  if (document.getElementById('appConfirm')?.classList.contains('show')) {\n    closeAppConfirm(false); return true;\n  }\n  if (document.getElementById('appPrompt')?.classList.contains('show')) {\n    closeAppPrompt(false); return true;\n  }\n  const panel=document.getElementById('opsDialog92');\n  if(panel?.open){\n    // A native dialog is above all normal page overlays, including photo forms.\n    if(document.body?.classList.contains('cloudLocked92'))return true;\n    askConfirm('Fenster schließen? Nicht gespeicherte Eingaben gehen verloren.',\n      ()=>panel.close(),'Schließen','Eingaben verwerfen?');\n    return true;\n  }\n  if (document.getElementById('photoModal144')?.style.display === 'flex' && photoDraft144) {\n    const draft=photoDraft144;\n    const previous={summary:effectiveMhd144(state.products[draft.code])==='none'?'quantity':'mhd',\n      mhd:'quantity',quantity:'confirm',confirm:'choose',choose:'capture'};\n    if (previous[draft.step]) {draft.step=previous[draft.step];renderPhoto144();return true;}\n    if (draft.inventorySession182 && typeof photoInventory182!=='undefined' &&\n        photoInventory182?.id===draft.inventorySession182) {\n      closePhoto144();\n      document.getElementById('photoInventoryModal182').style.display='flex';\n      renderPhotoInventory182();\n      return true;\n    }\n    closePhoto144();return true;\n  }\n  if (document.getElementById('photoInventoryModal182')?.style.display==='flex') {\n    closePhotoInventory182();return true;\n  }\n  if (document.getElementById('articleModal')?.style.display==='flex') {\n    askConfirm('Artikelbearbeitung schließen? Nicht gespeicherte Eingaben gehen verloren.',\n      ()=>closeArticleEditor(),'Schließen','Artikelbearbeitung');\n    return true;\n  }\n  if(document.getElementById('shoppingReceiptModal')?.style.display==='flex'){\n    askConfirm('Einkauf schließen? Nicht gespeicherte Eingaben gehen verloren.',\n      ()=>closeShoppingReceipt(),'Schließen','Eingaben verwerfen?');return true;\n  }\n  if(document.getElementById('modal')?.style.display==='flex'){\n    askConfirm('Buchung abbrechen?',()=>closeModal(),'Abbrechen','Buchung');return true;\n  }\n  return false;\n};\n"},{"src":"drink-calculation189.js?v=386","code":"'use strict';\nlet drinkMode189='recipe';\nfunction isBeverageCategory386(a){return /getrank|beverage|napoj|drinks?/i.test(((a?.overviewGroup||'')+' '+(a?.cat||'')).normalize('NFKD').replace(/[\\u0300-\\u036f]/g,''))}\nfunction drinkProductOptions189(){\n const products=Object.entries(state.products||{}).sort((a,b)=>String(a[1].name||'').localeCompare(String(b[1].name||''),'de'));\n const isDrink=([,a])=>isBeverageCategory386(a);\n const option=([code,a])=>`<option value=\"${esc(code)}\">${esc(a.name)} · ${esc(a.unit||'Einheit')}</option>`;\n return `<option value=\"\">Bitte auswählen</option><option value=\"__free__\">Getränk ohne Lagerartikel</option>`+\n  (products.some(isDrink)?`<optgroup label=\"Getränke im Lager\">${products.filter(isDrink).map(option).join('')}</optgroup>`:'');\n}\nfunction drinkInputs189(){return `<div class=\"field\"><label for=\"drinkProduct189\">Getränk</label><select id=\"drinkProduct189\" onchange=\"drinkProductChanged189()\">${drinkProductOptions189()}</select></div>\n <div id=\"drinkFree189\" class=\"field\" hidden><label for=\"drinkName189\">Getränkename</label><input id=\"drinkName189\" placeholder=\"z. B. Hauslimonade\" oninput=\"updateFoodCalc143()\"></div>\n <div class=\"drinkCostInputs189\"><div class=\"field moneyInput143\"><label for=\"drinkPrice189\">EK netto je Lagereinheit</label><input id=\"drinkPrice189\" inputmode=\"decimal\" placeholder=\"0,00\" oninput=\"updateFoodCalc143()\"></div>\n <div class=\"field\"><label for=\"drinkAmount189\">Lagermenge je Getränk <span id=\"drinkUnit189\"></span></label><input id=\"drinkAmount189\" inputmode=\"decimal\" value=\"1\" oninput=\"updateFoodCalc143()\"></div></div>\n <div class=\"field moneyInput143\"><label for=\"drinkGross189\">Brutto-VK je Getränk</label><input id=\"drinkGross189\" inputmode=\"decimal\" placeholder=\"0,00\" oninput=\"updateFoodCalc143()\"></div>\n <p class=\"muted drinkInfo189\">Beispiel: Bei einem Artikel mit Lagereinheit Liter für 0,33 l pro Glas hier 0,33 eingeben. Der EK wird nur für diese Berechnung verwendet.</p>`}\nfunction drinkProductChanged189(){\n const code=document.getElementById('drinkProduct189')?.value,a=state.products?.[code],free=code==='__free__';\n document.getElementById('drinkFree189').hidden=!free;\n document.getElementById('drinkUnit189').textContent=a?'('+String(a.unit||'Einheit')+')':'';\n document.getElementById('drinkPrice189').value=a&&Number(a.price)>0?String(a.price).replace('.',','):'';\n document.getElementById('drinkAmount189').value='1';\n updateFoodCalc143();\n}\nfunction setDrinkMode189(mode){\n drinkMode189=mode==='drink'?'drink':'recipe';\n document.getElementById('foodRecipeArea189').hidden=drinkMode189==='drink';\n document.getElementById('foodDrinkArea189').hidden=drinkMode189!=='drink';\n document.getElementById('foodQtyLabel189').textContent=drinkMode189==='drink'?'Anzahl verkaufter Getränke':'Portionen';\n document.getElementById('foodPortions145').value='1';\n document.getElementById('foodNetPer145').value='';\n document.getElementById('foodNetPer145').readOnly=drinkMode189==='drink';\n const saleLabel=document.getElementById('foodNetPer145').closest('.field')?.querySelector('label');\n if(saleLabel)saleLabel.textContent=drinkMode189==='drink'?'Netto-VK je Getränk (berechnet)':'Netto-VK pro Portion';\n document.getElementById('foodOther145').value='';\n document.getElementById('foodTarget145').value='';\n const rate=taxCountry145().rates.find(([,name])=>/getränk|getrank/i.test(name))?.[0]||taxCountry145().rates[0][0];\n setFoodVat145(drinkMode189==='drink'?rate:(Number(business130().vat)||taxCountry145().rates[0][0]));\n for(const button of document.querySelectorAll('.drinkModeButton189')){button.setAttribute('aria-pressed',String(button.dataset.mode===drinkMode189))}\n updateFoodCalc143();\n}\nconst renderFoodBefore189=renderFoodCalc143;\nrenderFoodCalc143=function(){\n renderFoodBefore189();\n const card=document.querySelector('#calc143_food .calcCard143');if(!card)return;\n card.querySelector('h2').textContent='Wareneinsatz für Speisen und Getränke';\n card.querySelector('p').textContent='Rezeptkosten oder den Einkaufspreis eines Getränks berechnen.';\n const oldRecipe=card.querySelector('#foodRecipe145')?.closest('.field');\n if(oldRecipe){\n  const recipe=oldRecipe.outerHTML;\n  oldRecipe.outerHTML=`<div class=\"drinkModes189\" role=\"group\" aria-label=\"Art der Kalkulation\"><button class=\"soft drinkModeButton189\" type=\"button\" data-mode=\"recipe\" onclick=\"setDrinkMode189('recipe')\" aria-pressed=\"true\">Rezept</button><button class=\"soft drinkModeButton189\" type=\"button\" data-mode=\"drink\" onclick=\"setDrinkMode189('drink')\" aria-pressed=\"false\">Getränk</button></div><div id=\"foodRecipeArea189\">${recipe}</div><div id=\"foodDrinkArea189\" hidden>${drinkInputs189()}</div>`;\n  const label=card.querySelector('#foodPortions145')?.closest('.field')?.querySelector('label');if(label)label.id='foodQtyLabel189';\n }\n const saleLabel=document.getElementById('foodNetPer145')?.closest('.field')?.querySelector('label');if(saleLabel)saleLabel.textContent='Netto-VK je Portion / Getränk';\n if(drinkMode189==='drink')setDrinkMode189('drink');\n};\nconst foodModelBefore189=foodModel145;\nfunction syncDrinkNet189(){\n const grossField=document.getElementById('drinkGross189'),netField=document.getElementById('foodNetPer145');\n if(!grossField||!netField)return;\n const gross=localeNumber144(grossField.value),vat=localeNumber144(document.getElementById('foodVat145')?.value);\n netField.value=gross>0&&vat>=0?String(Math.round(gross/(1+vat/100)*1e6)/1e6).replace('.',','):'';\n}\nfoodModel145=function(){\n const m=foodModelBefore189();if(drinkMode189!=='drink')return m;\n const code=document.getElementById('drinkProduct189')?.value,a=state.products?.[code],free=code==='__free__';\n if(!a&&!free)return {...m,f:null,drink:true,drinkValid:false};\n const name=free?document.getElementById('drinkName189')?.value.trim()||'Getränk ohne Lagerartikel':a.name;\n const price=localeNumber144(document.getElementById('drinkPrice189')?.value),amount=localeNumber144(document.getElementById('drinkAmount189')?.value);\n const valid=price>0&&amount>0;\n const goods=valid?m.portions*price*amount:0,targetNet=goods+m.other+Math.max(0,localeNumber144(document.getElementById('foodTarget145')?.value));\n return {...m,drink:true,drinkValid:valid,drinkName:name,drinkUnit:a?.unit||'Einheit',drinkAmount:amount,\n  f:{recipe:{name}},meta:{complete:valid},unitCost:valid?price*amount:0,goods,\n  quote:m.netTotal&&valid?goods/m.netTotal*100:0,result:m.netTotal-goods-m.other,\n  targetNet,targetPer:targetNet/m.portions,targetGross:targetNet*(1+m.vat/100),targetGrossPer:targetNet/m.portions*(1+m.vat/100)};\n};\nconst updateFoodBefore189=updateFoodCalc143;\nupdateFoodCalc143=function(){\n if(drinkMode189==='drink')syncDrinkNet189();\n updateFoodBefore189();if(drinkMode189!=='drink')return;\n const m=foodModel145(),out=document.getElementById('foodRecipeResult145');if(!out)return;\n if(!m.f){out.textContent='Getränk auswählen oder Getränk ohne Lagerartikel wählen.';return}\n if(!m.drinkValid){out.innerHTML='<b>EK oder Menge fehlt</b><br>Netto-Einkaufspreis je Lagereinheit und verwendete Lagermenge je Getränk eingeben.';\n  document.getElementById('foodMetrics145').innerHTML='<div class=\"calcHint143\">Ohne Einkaufspreis wird kein Gewinn oder Wareneinsatz berechnet.</div>';\n  document.getElementById('foodTargetResult145').textContent='EK und Menge eingeben.';return}\n out.innerHTML=`<b>${esc(m.drinkName)}</b><br>${fmt145(m.goods)} für ${m.portions.toLocaleString('de-AT')} Getränke<br><small>${fmt145(m.unitCost)} je Getränk · ${esc(String(m.drinkAmount).replace('.',','))} ${esc(m.drinkUnit)} pro Getränk</small>`;\n};\nif(document.getElementById('calc143_food')?.classList.contains('active'))renderFoodCalc143();\n"},{"src":"price228.js?v=1","code":"'use strict';\nfunction priceControls228(id,basis='net',vat=''){\n return `<span class=\"priceBasis228\" style=\"display:flex;gap:8px;align-items:center;margin-top:6px\"><select id=\"${id}Basis\" aria-label=\"Netto oder Brutto\" onchange=\"document.getElementById('${id}Vat').hidden=this.value!=='gross'\" style=\"width:auto;min-height:40px\"><option value=\"net\" ${basis==='net'?'selected':''}>Netto</option><option value=\"gross\" ${basis==='gross'?'selected':''}>Brutto</option></select><input id=\"${id}Vat\" aria-label=\"Umsatzsteuer in Prozent\" placeholder=\"USt. %\" type=\"number\" inputmode=\"decimal\" min=\"0\" max=\"100\" step=\"any\" value=\"${vat}\" ${basis==='gross'?'':'hidden'} style=\"width:100px\"></span>`;\n}\nfunction netPrice228(raw,basis='net',vat=''){\n const value=String(raw??'').trim();if(!value)return null;\n const amount=Number(value.replace(',','.'));if(!Number.isFinite(amount)||amount<=0)throw Error('Preis größer als 0 eingeben oder offen lassen.');\n if(basis==='net')return amount;\n const rate=Number(String(vat).replace(',','.'));if(String(vat).trim()===''||!Number.isFinite(rate)||rate<0||rate>100)throw Error('Für den Bruttopreis den Umsatzsteuersatz vom Beleg eingeben.');\n return Math.round(amount/(1+rate/100)*1000000)/1000000;\n}\nfunction readPrice228(id){return netPrice228(document.getElementById(id)?.value,document.getElementById(id+'Basis')?.value||'net',document.getElementById(id+'Vat')?.value??'')}\n"},{"src":"invoice-stock190.js?v=213-204","code":"'use strict';\nfunction confirmInvoiceStock190(){\n try{\n  const selectedLink=document.getElementById('inboundLinked182')?.value;\n  if(selectedLink)throw Error('Ein Lieferschein ist zur Zuordnung ausgewählt. Zum Buchen der Rechnung bitte Keine Zuordnung wählen. Es wurde noch nichts gebucht.');\n  const common=inboundCommon182({invoiceStock:true});\n  if(!inboundEngine182.validDate182(common.date))throw Error('Datum der Rechnung prüfen');\n  const key=inboundEngine182.deliveryKey182(common.supplier,common.reference,common.date);\n  if((state.deliveryNotes182||[]).some(x=>inboundEngine182.sameInboundIdentity254(x,{...common,key})||inboundEngine182.sameInboundFile192(x.fileHash,common.fileHash)))throw Error('Der Beleg ist bereits als Lieferschein gebucht');\n  const elements=[...document.querySelectorAll('#inboundRows182 .inboundRow182')];\n  if(!document.getElementById('invoiceAllRowsChecked191')?.checked)throw Error('Alle Positionen des Originals prüfen, auch private Käufe, Pfand und fehlende Zeilen');\n  if(!elements.length)throw Error('Mindestens eine Rechnungsposition prüfen');\n  for(const [index,el] of elements.entries()){\n   const disposition=el.querySelector('.invoiceDisposition191')?.value;\n   if(!['stock','exclude'].includes(disposition))throw Error('Produkt '+(index+1)+': Bitte entscheiden, ob diese Belegzeile ins Lager gehört');\n   if(disposition==='exclude')continue;\n   if(el.dataset?.priceId){try{const price=readPrice228(el.dataset.priceId);el.dataset.grossPrice=document.getElementById(el.dataset.priceId+'Basis')?.value==='gross'?String(Number(document.getElementById(el.dataset.priceId).value.replace(',','.'))||''):'';el.dataset.price=price===null?'':String(price)}catch(error){throw Error('Produkt '+(index+1)+': '+error.message)}}\n   if(el.dataset?.price!==''&&el.dataset?.price!==undefined&&(!Number.isFinite(Number(el.dataset.price))||Number(el.dataset.price)<=0))throw Error('Produkt '+(index+1)+': Einkaufspreis muss größer als 0 sein oder leer bleiben');\n   const known=state.products?.[el.querySelector('.inboundProduct182').value],mhdInput=el.querySelector('.inboundMhd182');if(known&&mhdInput&&!mhdInput.value&&typeof receiptMhd242==='function')mhdInput.value=receiptMhd242(known,'');\n   const choice=el.querySelector('.invoiceMhdChoice191');\n   if(choice&&!choice.value)throw Error('Produkt '+(index+1)+': Mindesthaltbarkeitsdatum mit Ja oder Nein bestätigen');\n   if(choice?.value==='yes'&&el.querySelector('.invoiceSameAnswer213')?.value!=='no'&&!el.querySelector('.inboundMhd182').value)throw Error('Produkt '+(index+1)+': Ablaufdatum oder verbleibende Tage eingeben');\n   if(choice?.value==='no'&&known&&(known.mhdPolicy144==='required'||(!['none','optional'].includes(known.mhdPolicy144)&&known.mhd)))throw Error('Produkt '+(index+1)+': Für diesen bestehenden Artikel ist ein MHD erforderlich');\n  }\n  const included=elements.filter(el=>el.querySelector('.invoiceDisposition191')?.value==='stock');\n  if(!included.length)throw Error('Keine erhaltene Ware für den Lagerzugang ausgewählt');\n  const excluded=elements.filter(el=>el.querySelector('.invoiceDisposition191')?.value==='exclude')\n   .map(el=>({name:String(el.querySelector('.inboundName185')?.value||'').trim(),sku:el.dataset.sku||''}));\n  const draft=included.map(el=>{let lots=null;try{if(typeof invoiceMhdLots213==='function')lots=invoiceMhdLots213(el);}catch(error){throw Error('Produkt '+(elements.indexOf(el)+1)+': MHD: '+error.message)}return ({\n   lots,\n   code:el.querySelector('.inboundProduct182').value,\n   qty:el.querySelector('.inboundQty182').value,mhd:lots?.[0]?.mhd||el.querySelector('.inboundMhd182').value,\n   name:el.querySelector('.inboundName185').value,unit:el.querySelector('.inboundUnit185').value,\n   category:el.querySelector('.inboundCategory185').value,mhdPolicy:el.querySelector('.inboundPolicy185').value,\n   sourceName:el.dataset.sourceName234||'',sourceUnit:el.dataset.sourceUnit234||'',sku:el.dataset.sku||'',documentEvidence229:el.dataset.evidence229?JSON.parse(el.dataset.evidence229):null,vatRate:el.dataset.priceId&&document.getElementById(el.dataset.priceId+'Vat')?.value!==''?Number(document.getElementById(el.dataset.priceId+'Vat')?.value):null,price:el.dataset.price===''?null:Number(el.dataset.price),grossPrice:el.dataset.grossPrice?Number(el.dataset.grossPrice):null\n  });});\n  const rows=inboundLines185.validateDeliveryDraft185(draft,state.products);\n  if(rows.some(r=>r.code==='__new__')&&!canCreateArticles())throw Error('Zum Anlegen neuer Artikel fehlt die Berechtigung');\n  const prior=(state.inboundInvoices182||[]).find(x=>inboundEngine182.sameInboundIdentity254(x,{...common,key})||inboundEngine182.sameInboundFile192(x.fileHash,common.fileHash));\n  if(prior&&(prior.status!=='Prüfung ausstehend'||prior.linkedDeliveryId))throw Error('Diese Rechnung ist bereits gebucht oder einem Lieferschein zugeordnet');\n  let bookedCount=0,createdCount=0,bookedNames=[];\n  const saved=operationTransaction('Rechnung als Lagerzugang gebucht','stockAdd',()=>{\n   state.inboundInvoices182??=[];state.deliveryNotes182??=[];state.batches??={};state.log??=[];\n   if(state.deliveryNotes182.some(x=>inboundEngine182.sameInboundIdentity254(x,{...common,key})||inboundEngine182.sameInboundFile192(x.fileHash,common.fileHash)))throw Error('Lieferung wurde schon per Lieferschein gebucht');\n   const existing=state.inboundInvoices182.find(x=>inboundEngine182.sameInboundIdentity254(x,{...common,key})||inboundEngine182.sameInboundFile192(x.fileHash,common.fileHash));\n   if(existing&&(existing.status!=='Prüfung ausstehend'||existing.linkedDeliveryId))throw Error('Rechnung wurde schon gebucht oder zugeordnet');\n   if(rows.some(r=>r.code==='__new__')&&!canCreateArticles())throw Error('Berechtigung zum Anlegen neuer Artikel fehlt');\n   const generated=new Map(),items=rows.map((r,index)=>{\n    const price=draft[index].price;\n    if(r.code!=='__new__')return {code:r.code,qty:r.qty,mhd:r.mhd,price,lots:draft[index].lots};\n    const identity=(r.sku||r.name.toLocaleLowerCase('de'))+'|'+r.unit;\n    let code=generated.get(identity);\n    if(!code){\n     code=nextInternalBarcode();\n     state.products[code]={name:r.name,cat:r.category,overviewGroup:r.category,storage:'Allgemein',\n      stock:0,min:0,price:0,priceQuoteQty:1,priceQuoteUnit:r.unit,\n      supplier:common.supplier,unit:r.unit,mhd:r.mhdPolicy==='required',mhdPolicy144:r.mhdPolicy,\n      packs:[[r.unit,1,code,false,1,1,r.unit,1,r.unit]],photo:'',referencePhotos144:[],\n      internalId:'prod-'+crypto.randomUUID(),supplierArticles:r.sku?[{supplier:common.supplier,sku:r.sku,name:r.name,confirmed:true}]:[],\n      supplierArticleNumber181:r.sku,priceHistory:[],mainArticleId:'',mainFactor:1,prepLossPct:0,\n      drainWeightEnabled:false,stockContent:null};\n     state.batches[code]=[];generated.set(identity,code);createdCount++;\n     state.log.push(`${stamp()} | ${r.name} | Artikel aus Rechnung ${common.reference} angelegt | Anfangsbestand 0`);\n    }\n    return {code,qty:r.qty,mhd:r.mhd,price,lots:draft[index].lots};\n   });\n   const checked=inboundEngine182.validateDelivery182({...common,items},state.products,[]);\n   for(const [index,item] of checked.items.entries()){\n    const article=state.products[item.code];article.stock=(Number(article.stock)||0)+item.qty;\n    const lots=items[index].lots;if(lots?.length){for(const lot of lots)addBatch(item.code,lot.qty,lot.mhd);}else if(item.mhd)addBatch(item.code,item.qty,item.mhd);\n    const price=Number(items[index].price)>0?Number(items[index].price):0;\n    if(price){article.price=price;article.priceQuoteQty=1;article.priceQuoteUnit=article.unit;article.supplier=common.supplier;\n     if(typeof recordPurchasePrice==='function')recordPurchasePrice(item.code,price,common.supplier,'Rechnung '+common.reference,{qty:item.qty,packName:article.unit,packQty:1})}\n    addMovement('in',item.code,item.qty,{unitCost:price,supplier:common.supplier,reason:'Rechnung '+common.reference+(price?'':' · Einkaufspreis offen')});\n    state.log.push(`${stamp()} | ${article.name} | Rechnung ${common.reference} | +${item.qty} ${article.unit}`);\n   }\n   inboundLines185.rememberInbound234(state.products,common.supplier,draft,checked.items,common.reference);\n   bookedCount=checked.items.length;bookedNames=checked.items.map(item=>`${state.products[item.code].name}: ${item.qty} ${state.products[item.code].unit}`);\n   const entry=existing||{id:'inv-'+crypto.randomUUID(),at:new Date().toISOString()};\n   Object.assign(entry,{key,fileHash:common.fileHash,image:inboundDraft182.imageUrl,...inboundSourceMeta247(),supplier:common.supplier,\n    reference:common.reference,date:common.date,linkedDeliveryId:'',status:'Lager gebucht',\n    items:checked.items.map((item,index)=>({...item,...(items[index].lots?.length?{lots:items[index].lots}:{}),unitPrice:items[index].price||null,unitPriceGross:draft[index].grossPrice||null,vatRate:Number.isFinite(draft[index].vatRate)?draft[index].vatRate:null,documentEvidence229:draft[index].documentEvidence229})),excludedItems:excluded,stockBookedAt:new Date().toISOString()});\n   if(!existing)state.inboundInvoices182.push(entry);\n  });return afterOperation275(saved,saved=>{\n  if(saved){inboundDraft182=null;document.getElementById('opsDialog92')?.close();\n   showFeedback('Rechnung und Ware erfasst','success',5000,`${bookedCount} Positionen eingebucht, ${createdCount} neue Artikel. ${bookedNames.join('; ')}`)}\n ;return saved;});}catch(error){\n  const dialog=document.getElementById('opsDialog92');if(dialog&&!dialog.open)dialog.showModal?.();\n  const position=error.message.match(/(?:Produkt|Position) (\\d+)/)?.[1];\n  const row=position?[...document.querySelectorAll('#inboundRows182 .inboundRow182')][Number(position)-1]:null;\n  const inline=row?.querySelector('.invoiceRowError226');\n  if(inline){inline.textContent=error.message;inline.hidden=false}\n  const field=row?.querySelector(/MHD|Mindesthaltbarkeitsdatum|Ablaufdatum/i.test(error.message)?'.invoiceMhdChoice191':'.inboundQty182');\n  field?.setAttribute?.('aria-invalid','true');field?.scrollIntoView?.({block:'center'});field?.focus?.({preventScroll:true});\n  if(!row&&/Lieferant|Dokumentnummer|Datum/.test(error.message)){const header=document.getElementById('inboundHeader191');if(header)header.open=true;const missing=['inboundSupplier182','inboundRef182','inboundDate182'].map(id=>document.getElementById(id)).find(field=>field&&!field.value);missing?.focus?.();missing?.scrollIntoView?.({block:'center'});}\n  if(!row){const status=document.getElementById('inboundStatus182');if(status){status.textContent=error.message;status.scrollIntoView?.({block:'center'})}}\n  showFeedback('Rechnung prüfen','warn',6500,error.message);\n }\n}\n"},{"src":"invoice-simple191.js?v=213-204","code":"'use strict';\n\nfunction invoicePriceBasis204(proposal,product=null){\n const content=proposal?.content||(product?.stockContent&&Number(product.stockContent.qty)>0?{amount:Number(product.stockContent.qty),unit:product.stockContent.unit,count:1}:null);\n const unit=product?.unit||proposal?.unit||(proposal?.unitUncertain?'Einheit':'Stück');\n if(content&&Number(content.amount)>0&&content.unit&&/^(?:Stück|Packung|Dose|Becher|Flasche)$/i.test(unit)){\n  const amount=Number(content.amount)*Number(content.count||1),text=Number.isInteger(amount)?String(amount):String(amount).replace('.',',');\n  const measure=String(content.unit).toLowerCase()==='liter'?'l':String(content.unit);\n  const container=/^(?:Dose|Becher|Flasche|Packung)$/i.test(unit)?unit:'Packung';return `${text}-${measure}-${container}`;\n }\n return unit;\n}\nfunction refreshInvoiceLinks192(){\n const select=document.getElementById('inboundLinked182');if(!select)return;\n const supplier=document.getElementById('inboundSupplier182')?.value||'',previous=select.value;\n const matches=(state.deliveryNotes182||[]).filter(note=>inboundEngine182.sameInboundSupplier192(supplier,note.supplier));\n select.innerHTML='<option value=\"\">Keine Zuordnung</option>'+matches.map(note=>`<option value=\"${esc(note.id)}\">${esc(note.supplier)} · ${esc(note.reference)} · ${esc(note.date)}</option>`).join('');\n select.value=matches.some(note=>note.id===previous)?previous:'';\n select.disabled=!matches.length;\n updateInvoiceLink191();\n}\nfunction updateInvoiceLink191(){\n const linked=!!document.getElementById('inboundLinked182')?.value;\n const button=document.querySelector('button[onclick=\"confirmInvoiceStock190()\"]');\n if(button){button.disabled=linked;button.title=linked?'Dieser Lieferschein hat den Bestand bereits gebucht':''}\n}\nfunction invoiceMhdDate191(raw){\n const input=String(raw||'').trim();if(!input)return '';\n if(/^\\d{4}-\\d\\d-\\d\\d$/.test(input))return input;\n const european=input.match(/^(\\d\\d?)\\.(\\d\\d?)\\.(\\d{4})$/);\n if(european)return `${european[3]}-${european[2].padStart(2,'0')}-${european[1].padStart(2,'0')}`;\n if(/^\\d{1,5}$/.test(input)){\n  const days=Number(input);if(days<1||days>3650)return '';\n  const date=new Date();date.setHours(12,0,0,0);date.setDate(date.getDate()+days);\n  return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')}`;\n }\n return '';\n}\nfunction addInvoiceRow191(host,proposal=null){\n const evidence=inboundEvidence229(proposal),foreign=evidence.currency&&evidence.currency!=='EUR';\n if(foreign)proposal={...proposal,price:null,grossPrice:null,priceMissing:true};\n const row=document.createElement('div');row.className='inboundRow182 invoiceRow191';\n const supplier=document.getElementById('inboundSupplier182')?.value||inboundEngine182.inboundSupplier182(inboundDraft182?.ocr||'');\n const match=proposal?.unit?inboundLines185.matchDelivery185(proposal,state.products,supplier):'';\n const possible=proposal&&!match&&proposal?.unit?inboundLines185.possibleDeliveryMatches203(proposal,state.products,supplier):[];\n const options=Object.entries(state.products||{}).sort((a,b)=>a[1].name.localeCompare(b[1].name,'de'))\n  .map(([code,a])=>`<option value=\"${esc(code)}\">${esc(a.name)} · ${esc(a.unit||'Einheit')}</option>`).join('');\n const articleOptions=[...Object.entries(state.products||{})].sort((a,b)=>a[1].name.localeCompare(b[1].name,'de'));\n row.dataset.learnedSupplier234=proposal&&inboundLines185.learnedMatch234(proposal,state.products,supplier).code?supplier:'';row.dataset.sku=proposal?.articleNumber||'';row.dataset.sourceName234=proposal?.name||'';row.dataset.sourceUnit234=proposal?.unit||'';row.dataset.evidence229=JSON.stringify(evidence);\n row.dataset.positionId203=proposal?.positionId203||'';row.dataset.positionFingerprint203=proposal?.positionFingerprint203||'';\n row.dataset.price=Number.isFinite(proposal?.price)&&proposal.price>0?String(proposal.price):'';\n row.dataset.barcode295=proposal?.barcode||'';\n row.dataset.priceId='invoicePrice-'+crypto.randomUUID();\n row.dataset.grossPrice=Number.isFinite(proposal?.grossPrice)&&proposal.grossPrice>0?String(proposal.grossPrice):'';\n row.innerHTML=`${proposal?.deliveryLabel?`<p class=\"inboundSource185\">Abholung ${esc(proposal.deliveryLabel)}</p>`:''}<label class=\"invoiceName226\">Produkt auf dem Beleg<input class=\"inboundName185\" value=\"${esc(proposal?.name||'')}\" placeholder=\"z. B. Mischbrot\"></label>\n  <label class=\"invoiceQty226\">Menge<input class=\"inboundQty182\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" value=\"${proposal?.quantity||''}\" placeholder=\"Stückzahl\"></label>\n  <label class=\"invoiceMhd226\">Mindesthaltbarkeitsdatum vorhanden?<select class=\"invoiceMhdChoice191\"><option value=\"\" selected disabled>MHD auswählen</option><option value=\"yes\">Ja</option><option value=\"no\">Nein</option></select></label>\n  <label class=\"invoicePrice191\"><span class=\"invoicePriceCaption241\">Einkaufspreis (€ je <span class=\"invoicePriceUnit227\">${esc(invoicePriceBasis204(proposal))}</span>)</span><input id=\"${row.dataset.priceId}\" class=\"invoicePriceInput227\" type=\"text\" inputmode=\"decimal\" placeholder=\"Preis eingeben oder offen lassen\" value=\"${esc(row.dataset.price?Number(row.dataset.price).toFixed(2).replace('.',','):'')}\">${priceControls228(row.dataset.priceId,proposal?.grossPrice&&!proposal?.price?'gross':'net',Number.isFinite(proposal?.vatRate)?proposal.vatRate:'')}<small>Preis je Lagereinheit. Bei Brutto den Steuersatz ergänzen.</small></label>\n  ${Number.isFinite(proposal?.grossPrice)?`<p class=\"invoicePrice191\">Laut Beleg: ${esc(proposal.grossPrice.toFixed(2).replace('.',','))} € brutto je ${esc(proposal.unit||'Einheit')}.</p>`:''}\n  ${inboundEvidenceText229(evidence)?`<p class=\"invoicePrice191\">${esc(inboundEvidenceText229(evidence))}</p>`:''}\n  ${proposal?.priceMissing?'<p class=\"invoicePrice191\">Preis nicht sicher zugeordnet. Bitte oben ergänzen oder offen lassen.</p>':''}\n  <label class=\"invoiceMhdInput191\" hidden>Wie lange noch haltbar? <input class=\"invoiceMhdRaw191\" placeholder=\"Datum oder Tage ab heute\" inputmode=\"text\"><small class=\"invoiceMhdResult191\"></small></label>\n  <input class=\"inboundMhd182\" type=\"hidden\"><input class=\"inboundPolicy185\" type=\"hidden\" value=\"required\">\n  <p class=\"invoiceRowError226\" role=\"alert\" hidden></p>\n  ${possible.length?`<p class=\"inboundMatchHint204\"><b>Möglicherweise bereits vorhanden:</b> ${possible.map(x=>esc(x.name)).join(' · ')}. Bitte Lagerartikel auswählen; es wird nichts automatisch neu angelegt.</p>`:''}\n  <details class=\"invoiceAssignment226\" ${proposal?.unitUncertain?'open':''}><summary>${proposal?.service?'Nicht ins Lager · ändern':match?'Artikelzuordnung ändern':'Neuer Artikel · Angaben ändern'}</summary>\n  <label>Was ist diese Position?<select class=\"invoiceDisposition191\"><option value=\"\">Bitte wählen</option><option value=\"stock\">Ware ins Lager</option><option value=\"exclude\">Nicht ins Lager (z. B. Leistung oder Pfand)</option></select></label>\n  <label>Vorhandenen Artikel suchen<input class=\"invoiceArticleSearch191\" type=\"search\" placeholder=\"z. B. Brot\" autocomplete=\"off\"></label>\n  <label>Welcher Lagerartikel?<select class=\"inboundProduct182\">${options}<option value=\"__new__\">Neuen Artikel anlegen</option></select></label>\n  <details class=\"invoiceExtra191\" ${proposal?.unitUncertain?'open':''}><summary>Angaben für neuen Artikel</summary><div class=\"invoiceExtraFields191\"><label>Lagereinheit<select class=\"inboundUnit185\">${proposal?.unitUncertain?'<option value=\"\" selected>Einheit wählen</option>':''}${['Stück','Flasche','Becher','Dose','Packung','Karton','Liter','ml','kg','g'].map(u=>`<option ${proposal?.unit===u?'selected':''}>${u}</option>`).join('')}</select></label>\n   <label>Artikelart<select class=\"inboundCategory185\"><option>Lebensmittel</option><option>Verbrauchsartikel</option><option>Sonstiges</option></select></label></div></details>\n  </details>\n  <button type=\"button\" class=\"soft invoiceRemove191\">Produkt entfernen</button>`;\n if(proposal?.barcode){\n  const help=document.createElement('p');help.className='invoicePrice191';\n  help.textContent='EAN: '+proposal.barcode+(match?' · Im eigenen Artikelstamm gefunden.':' · Zuordnung prüfen.');\n  if(!match&&validGtin179(proposal.barcode)){\n   const button=document.createElement('button');button.type='button';button.className='soft';button.textContent='Öffentliche Produktdaten prüfen';\n   button.onclick=async()=>{\n    button.disabled=true;\n    try{\n     const data=await cloudRequest('/functions/v1/gs-product-lookup',{barcode:proposal.barcode});\n     if(!row.isConnected)return;\n     const result=document.createElement('span');\n     const same=String(data?.barcode||'').padStart(14,'0')===proposal.barcode.padStart(14,'0');\n     result.textContent=data?.found&&same?' Open Food Facts: '+(data.name||'Name fehlt')+(data.brand?' · '+data.brand:'')+(data.content?' · '+data.content.quantity+' '+data.content.unit:'')+'. Hersteller und Packung prüfen.':' Kein passender Datenbanktreffer. Angaben vom Beleg prüfen.';\n     help.append(result);if(data?.found&&same){const link=document.createElement('a');link.href='https://world.openfoodfacts.org/product/'+encodeURIComponent(proposal.barcode);link.target='_blank';link.rel='noopener noreferrer';link.textContent=' Quelle · ODbL';help.append(link)}button.remove();\n    }catch(error){button.textContent='Abfrage nicht möglich · erneut versuchen';button.disabled=false}\n   };help.append(button);\n  }\n  row.prepend(help);\n }\n row.querySelector('.inboundName185').value=proposal?.name||'';\n row.querySelector('.inboundQty182').value=proposal?.quantity>0?String(proposal.quantity):'';\n if(proposal?.unitUncertain)row.querySelector('.inboundUnit185').value='';\n const priceInput=row.querySelector('.invoicePriceInput227');\n priceInput.value=row.dataset.price?Number(row.dataset.price).toFixed(2).replace('.',','):row.dataset.grossPrice?Number(row.dataset.grossPrice).toFixed(2).replace('.',','):'';\n priceInput.addEventListener('input',()=>{row.dataset.price=priceInput.value.trim().replace(',','.');});\n const select=row.querySelector('.inboundProduct182');select.value=match||'__new__';\n const disposition=row.querySelector('.invoiceDisposition191');\n disposition.value=proposal?.service?'exclude':'stock';\n const choice=row.querySelector('.invoiceMhdChoice191'),raw=row.querySelector('.invoiceMhdRaw191');\n const search=row.querySelector('.invoiceArticleSearch191');\n search.addEventListener('input',()=>{\n  const query=search.value.trim().toLocaleLowerCase('de'),selected=select.value;\n  const candidates=articleOptions.filter(([code,a])=>code===selected||a.name.toLocaleLowerCase('de').includes(query)).slice(0,query?40:articleOptions.length);\n  select.innerHTML=candidates.map(([code,a])=>`<option value=\"${esc(code)}\">${esc(a.name)} · ${esc(a.unit||'Einheit')}</option>`).join('')+'<option value=\"__new__\">Neuen Artikel anlegen</option>';\n  select.value=selected;\n });\n function refresh(){\n  row.querySelector('.invoiceRowError226').hidden=true;\n  row.querySelector('.invoiceMhdChoice191').removeAttribute?.('aria-invalid');\n  const product=state.products?.[select.value],newProduct=select.value==='__new__';\n  const noMhd=product&&(product.mhdPolicy144==='none'||(!product.mhdPolicy144&&product.mhd===false));\n  if(noMhd){choice.value='no';raw.value=''}\n  row.querySelector('.invoiceMhd226').hidden=!!noMhd;\n  row.querySelector('.invoicePriceUnit227').textContent=invoicePriceBasis204({...proposal,unit:row.querySelector('.inboundUnit185').value||proposal?.unit},product);\n  row.querySelector('.invoiceExtra191').hidden=!newProduct;\n  for(const field of row.querySelectorAll('input,select')){\n   if(field!==disposition)field.disabled=disposition.value==='exclude';\n  }\n  row.querySelector('.invoiceMhdInput191').hidden=choice.value!=='yes';\n  row.querySelector('.inboundPolicy185').value=choice.value==='no'?'none':'required';\n  const iso=choice.value==='yes'?invoiceMhdDate191(raw.value):'';\n  row.querySelector('.inboundMhd182').value=iso;\n  row.querySelector('.invoiceMhdResult191').textContent=raw.value?(iso?`MHD: ${iso.split('-').reverse().join('.')}`:'Datum oder Anzahl Tage prüfen'):'';\n  row.querySelector('.inboundName185').readOnly=!newProduct;\n  if(product)row.querySelector('.inboundName185').value=product.name;\n  if(typeof refreshInvoiceMhd213==='function')refreshInvoiceMhd213(row);\n }\n row.querySelector('.inboundQty182').addEventListener('input',refresh);\n row.querySelector('.inboundUnit185').addEventListener('change',refresh);\n row.refreshAssignment234=refresh;select.addEventListener('change',()=>{row.dataset.manualAssignment234='yes';refresh()});disposition.addEventListener('change',refresh);choice.addEventListener('change',refresh);raw.addEventListener('input',refresh);\n row.querySelector('.invoiceRemove191').addEventListener('click',()=>row.remove());host.appendChild(row);refresh();\n}\n"},{"src":"purchase-content193.js?v=1","code":"'use strict';\nfunction priceContent193(context,stockUnit){\n if(!isCountLikeUnit(stockUnit))return null;\n const content=context==='article'?{\n  qty:Number(String(document.getElementById('aeContentQty')?.value||'').replace(',','.')),\n  unit:document.getElementById('aeContentUnit')?.value,\n  stockUnit:document.getElementById('aeUnit')?.value\n }:p()?.stockContent;\n if(!content||!(Number(content.qty)>0)||!Number.isFinite(Number(content.qty))||\n  normalizeUnit(content.stockUnit||stockUnit)!==normalizeUnit(stockUnit))return null;\n if(convertUnit(1,content.unit,'kg')===null&&convertUnit(1,content.unit,'Liter')===null&&normalizeUnit(content.unit)!=='stk')return null;\n if(normalizeUnit(content.unit)==='stk'&&!Number.isSafeInteger(Number(content.qty)))return null;\n return {qty:Number(content.qty),unit:content.unit,stockUnit};\n}\nfunction priceBaseQty193(qty,inputUnit,stockUnit,content){\n const direct=convertUnit(qty,inputUnit,stockUnit);if(direct!==null)return direct;\n if(!content||!isCountLikeUnit(stockUnit))return null;\n const amount=convertUnit(qty,inputUnit,content.unit);\n return amount===null?null:amount/content.qty;\n}\nconst compatiblePriceUnitsBefore193=compatiblePriceUnits131;\ncompatiblePriceUnits131=function(stockUnit,context='article'){\n const usual=compatiblePriceUnitsBefore193(stockUnit),content=priceContent193(context,stockUnit);\n const units=content?[stockUnit,...(normalizeUnit(content.unit)==='stk'?['Stück']:convertUnit(1,content.unit,'kg')!==null?['g','kg']:['ml','Liter'])]:usual;\n return units.filter((unit,index)=>units.findIndex(other=>normalizeUnit(other)===normalizeUnit(unit))===index);\n};\nconst priceQuoteWithUnitBefore193=priceQuoteWithUnit131;\npriceQuoteWithUnit131=function(amountId,qtyId,unitId,stockUnit){\n const context=amountId==='aePrice'?'article':'booking',content=priceContent193(context,stockUnit);\n const inputUnit=document.getElementById(unitId)?.value||stockUnit;\n if(!content||convertUnit(1,inputUnit,stockUnit)!==null)return priceQuoteWithUnitBefore193(amountId,qtyId,unitId,stockUnit);\n const inputQty=Number(String(document.getElementById(qtyId)?.value||'').replace(',','.'));\n const baseQty=priceBaseQty193(inputQty,inputUnit,stockUnit,content);\n if(baseQty===null)return {error:`${inputUnit} passt nicht zum eingetragenen Inhalt ${content.unit}.`};\n const quote=purchaseQuote(document.getElementById(amountId)?.value||'',inputQty,inputUnit);\n if(quote.error)return quote;\n return {...quote,quantity:baseQty,baseQty,inputQty,inputUnit,unit:stockUnit,unitPrice:quote.amount/baseQty,contentBridge193:content};\n};\nconst articleQuoteValueBefore193=articleQuoteValue;\narticleQuoteValue=function(article){\n const content=article?.stockContent;\n if(content?.qty>0&&isCountLikeUnit(article.unit)&&normalizeUnit(content.stockUnit||article.unit)===normalizeUnit(article.unit)&&(convertUnit(1,content.unit,'kg')!==null||convertUnit(1,content.unit,'Liter')!==null||normalizeUnit(content.unit)==='stk'&&Number.isSafeInteger(Number(content.qty))))return {quantity:Number(content.qty),amount:Number(article.price)||0};\n return articleQuoteValueBefore193(article);\n};\nconst purchaseQuoteHtmlBefore193=purchaseQuoteHtml131;\npurchaseQuoteHtml131=function(quote){\n if(!quote.contentBridge193||quote.error||quote.missing)return purchaseQuoteHtmlBefore193(quote);\n return `<b>${esc(formatQtySmart(quote.inputQty,quote.inputUnit,{exact:true}))} kosten ${moneyFmt(quote.amount)}</b>`+\n  `<span>1 ${esc(quote.unit)} enthält ${esc(formatQtySmart(quote.contentBridge193.qty,quote.contentBridge193.unit,{exact:true}))} und kostet ${quoteMoney(quote.unitPrice)} netto.</span>`+\n  `<span>1 ${esc(quote.inputUnit)} kostet ${quoteMoney(quote.amount/quote.inputQty)} netto.</span>`;\n};\nfunction automaticContainerPrice400(context='article'){\n const article=context==='article',stockUnit=article?document.getElementById('aeUnit')?.value:p()?.unit;\n const content=priceContent193(context,stockUnit),qty=document.getElementById(article?'aePriceForQty':'priceForQty'),unit=document.getElementById(article?'aePriceUnit131':'priceUnit131');\n if(article&&typeof priceBasisManual452!=='undefined'&&priceBasisManual452){if(qty)qty.readOnly=false;if(unit)unit.disabled=false;return null}\n const active=!!(content&&qty&&unit&&!(article&&document.getElementById('aeDrainWeight')?.checked));\n if(qty)qty.readOnly=article?false:active;if(unit)unit.disabled=article?false:active;\n if(!active)return null;\n let count=1;if(!article){const selector=document.getElementById('pack'),packs=getPacks(p(),current),pack=packs[Math.max(0,selector?.selectedIndex||0)];count=pack?packUsableQty(pack):1;if(!(count>0))count=1;}\n fillPriceUnitPicker131(context,content.unit);qty.value=String((normalizeUnit(content.unit)===normalizeUnit(stockUnit)?1:content.qty)*count);unit.value=content.unit;\n return {...content,count};\n}\nconst updatePurchaseQuoteBefore193=updatePurchaseQuote;\nupdatePurchaseQuote=function(context='article'){\n const content=automaticContainerPrice400(context),result=updatePurchaseQuoteBefore193(context),article=context==='article';\n if(content&&article){const hint=document.getElementById('aePriceHint');if(hint)hint.textContent=`Der Einkaufspreis gilt automatisch für 1 ${content.stockUnit} mit ${formatQtySmart(content.qty,content.unit,{exact:true})}. Bestand und Inventur werden in ${content.stockUnit} geführt.`;}\n return result;\n};\nfor(const event of ['input','change'])document.addEventListener(event,e=>{\n if(['aeContentQty','aeContentUnit'].includes(e.target?.id)){updatePurchaseQuote('article');if(typeof improveArticleBridgeUi135==='function')improveArticleBridgeUi135();}\n});\n"},{"src":"product-guide194.js?v=422","code":"'use strict';\nlet productGuide194=null,guideStream194=null,articleGuideSource194=null,articleGuideBookingBarcode194='',articleGuideMhd422='';\nconst guideViews194=['barcode','front','back'];\nfunction guideText194(value){\n return String(value||'').toLocaleLowerCase('de').normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/ß/g,'ss').replace(/ł/g,'l');\n}\nfunction localBarcode194(code){\n const exact=findScanCode(code);if(exact)return exact;\n if(!validGtin179(code))return null;\n const canonical=code.padStart(14,'0');\n for(const [productCode,article] of Object.entries(state.products||{})){\n  const packs=getPacks(article,productCode);\n  for(const [packIndex,pack] of packs.entries()){\n   const value=String(pack[2]||'');if(validGtin179(value)&&value.padStart(14,'0')===canonical)return {productCode,packIndex,pack};\n  }\n }\n return null;\n}\nfunction guideRelated194(d){\n if(!d.existing)return [];\n const owner=state.products[d.existing.productCode],front=d.photos.front?.parsed;\n const words=guideText194([owner?.name,front?.name,d.lookup?.name].filter(Boolean).join(' ')).match(/[a-z]{4,}/g)||[];\n const terms=new Set(words.filter(word=>!['sprudel','flasche','stuck','packung','naturtrub','produkt'].includes(word)));\n if(!terms.size)return [];\n return Object.entries(state.products||{}).filter(([code])=>code!==d.existing.productCode).map(([code,article])=>({code,article,score:[...terms].filter(word=>guideText194(article.name).includes(word)).length})).filter(item=>item.score>0).sort((a,b)=>b.score-a.score||String(a.article.name).localeCompare(String(b.article.name),'de')).slice(0,5);\n}\nfunction guideCandidates194(d){\n const packaging=guidePackaging194(d);\n const queries=[d.review?.name,packaging.name,d.photos.front?.parsed?.name,d.photos.back?.parsed?.name,d.lookup?.name]\n  .filter(Boolean).map(name=>(guideText194(name).match(/[a-z]{4,}/g)||[])\n   .filter(w=>!['saps','gmbh','produkt','stuck','packung','naturtrub','riffel','braun','kraft'].includes(w))).filter(words=>words.length);\n if(!queries.length)return [];\n return Object.entries(state.products||{}).map(([code,article])=>{\n  const label=guideText194(article.name);\n  const score=Math.max(...queries.map(words=>{\n   const matches=words.filter(w=>label.includes(w)).length;\n   return matches>=Math.min(packaging.packaging?1:2,words.length)?matches:0;\n  }));\n  return {code,article,score};\n }).filter(x=>x.score>0).sort((a,b)=>b.score-a.score||a.article.name.localeCompare(b.article.name,'de')).slice(0,5);\n}\nfunction useGuideProduct194(code){\n const d=productGuide194;if(!d||!state.products[code])return;\n // A similar name is only a candidate. The user explicitly chooses the article.\n endGuide194();closeArticleEditor(false);\n if(typeof mobileShow==='function'&&matchMedia('(max-width:820px)').matches)mobileShow('search');\n mobileSelectProduct(code);\n}\nfunction guideRelatedHtml194(d){const related=guideRelated194(d);return related.length?`<div class=\"guideRelated194\"><b>Weitere ähnliche Artikel zur Prüfung</b><p>Name und Packungsinhalt vergleichen.</p>${related.map(item=>`<p>${esc(item.article.name)} · ${esc(String(item.article.stock??0))} ${esc(item.article.unit||'')} <button type=\"button\" class=\"soft\" data-code=\"${esc(item.code)}\" onclick=\"inspectGuideArticle194(this.dataset.code)\">Artikel ansehen</button></p>`).join('')}</div>`:''}\nfunction inspectGuideArticle194(code){if(!productGuide194||!state.products[code])return;askConfirm('Diesen Artikel zur Prüfung öffnen? Die aktuelle Fotoaufnahme wird beendet; ihr Barcode wird nicht automatisch zugeordnet.',()=>openArticleEditor(code),'Artikel ansehen','Möglicher Treffer')}\nfunction guideContent194(draft){\n const sources=[draft.lookup?.content,...guideViews194.map(view=>draft.photos[view]?.parsed?.content)].filter(Boolean);\n if(!sources.length)return {value:null,conflict:false};\n const first=sources[0],conflict=sources.some(x=>{const quantity=convertUnit(x.quantity,x.unit,first.unit);return quantity===null||Math.abs(quantity-first.quantity)>Math.max(.001,first.quantity*.005)});\n return {value:conflict?null:first,conflict};\n}\nfunction guidePackaging194(draft){\n const labels=[draft.photos.front?.parsed,draft.photos.back?.parsed,draft.photos.barcode?.parsed].filter(x=>x?.packaging);\n const counts=[...new Set(labels.map(x=>x.packCount).filter(x=>Number.isInteger(x)&&x>0))];\n return {packaging:labels.length>0,packCount:counts.length===1?counts[0]:0,conflict:counts.length>1,\n  name:labels.map(x=>x.name).find(x=>/riffel[-\\s]*suppenbecher|suppenbecher|verpackungsbecher|deckel|servietten/i.test(x||''))||''};\n}\nfunction guideAlive194(draft){return productGuide194===draft&&document.getElementById('articleModal')?.style.display==='flex'}\nfunction stopGuideCamera194(){guideStream194?.getTracks().forEach(track=>track.stop());guideStream194=null}\nfunction endGuide194(){if(typeof inventoryBarcodeIntent245!=='undefined')inventoryBarcodeIntent245=false;stopGuideCamera194();productGuide194=null;const box=document.getElementById('guideBox194');if(box)box.remove()}\nfunction startProductGuide194(){\n if(editingArticleCode)return;endGuide194();labelRequest183++;labelDraft183=null;\n articleGuideBookingBarcode194='';articleGuideMhd422='';\n const review=document.getElementById('labelReview183');if(review)review.textContent='';\n productGuide194={step:0,photos:{},barcode:'',lookup:null,existing:null,busy:false,lookupBusy:false,message:'',serial:0,lookupSerial:0};\n const box=document.createElement('section');box.id='guideBox194';box.className='guide194';\n document.getElementById('labelButton183').insertAdjacentElement('afterend',box);renderGuide194();\n}\nfunction renderGuide194(){\n const d=productGuide194,box=document.getElementById('guideBox194');if(!d||!box)return;\n stopGuideCamera194();\n const titles=['Barcode','Vorderseite','Etikett / Rückseite','Angaben prüfen'],view=guideViews194[d.step],photo=d.photos[view];\n const instructions=['Barcode scannen oder Nummer eingeben.','Vorderseite fotografieren. Name gut lesbar.','Etikett mit Inhalt und MHD fotografieren.'];\n box.innerHTML=`<div class=\"guideHeading194\"><strong>${d.step+1} / 4 · ${titles[d.step]}</strong><button type=\"button\" class=\"soft\" onclick=\"endGuide194()\">Fotos abbrechen</button></div>\n  <p>${instructions[d.step]||'Name und Packungsinhalt prüfen.'}</p>\n  <p class=\"guideStatus194\" role=\"status\">${esc(d.message||'')}</p>\n  ${d.step<3?`${photo?`<img class=\"guidePreview194\" src=\"${photo.data}\" alt=\"${titles[d.step]} des Produkts\">`:''}\n   <div class=\"guideActions194\"><button type=\"button\" class=\"primary\" onclick=\"guideCamera194()\" ${d.busy?'disabled':''}>${d.step===0?(photo?'Barcode erneut scannen':'Barcode scannen'):(photo?'Foto erneut aufnehmen':'Foto aufnehmen')}</button><button type=\"button\" class=\"soft\" onclick=\"document.getElementById('guideFile194').click()\" ${d.busy?'disabled':''}>Foto auswählen</button></div>\n   <input id=\"guideFile194\" type=\"file\" accept=\"image/*\" hidden><input id=\"guideCameraFile194\" type=\"file\" accept=\"image/*\" capture=\"environment\" hidden><div id=\"guideCameraPanel194\"></div>\n   ${d.step===0?`<label>Barcode<input id=\"guideBarcode194\" ${d.lookupBusy?'disabled':''} inputmode=\"numeric\" value=\"${esc(d.barcode)}\" placeholder=\"Nummer unter dem Barcode\"></label><button type=\"button\" class=\"soft\" onclick=\"lookupGuideBarcode194()\" ${d.lookupBusy||d.busy?'disabled':''}>${d.lookupBusy?'Datenbank wird abgefragt …':'Produktdaten abfragen'}</button><details><summary>Zur Barcode-Suche</summary><p>Suche im eigenen Lager und bei Open Food Facts. Nur der Barcode wird übermittelt.</p><a href=\"https://www.gs1.org/services/verified-by-gs1\" target=\"_blank\" rel=\"noopener noreferrer\">Bei GS1 nachschlagen</a></details>`:''}\n   ${d.lookup?guideSourceHtml194(d.lookup,!d.existing&&!d.busy&&!d.lookupBusy):''}\n   ${d.existing?`<p><b>Barcode gehört bereits zu: ${esc(state.products[d.existing.productCode]?.name)}</b> · ${esc(String(state.products[d.existing.productCode]?.stock??0))} ${esc(state.products[d.existing.productCode]?.unit||'')}</p><button type=\"button\" class=\"primary\" data-code=\"${esc(d.existing.productCode)}\" onclick=\"useGuideProduct194(this.dataset.code)\">Artikel öffnen</button><button type=\"button\" class=\"soft\" onclick=\"useExistingGuide194()\">Fotos zum Artikel ergänzen</button>${guideRelatedHtml194(d)}`:''}\n   <div class=\"guideActions194\"><button type=\"button\" class=\"soft\" onclick=\"guideStep194(-1)\" ${d.step===0||d.busy?'disabled':''}>Zurück</button><button type=\"button\" id=\"guideNext194\" class=\"primary\" onclick=\"guideStep194(1)\" ${d.busy||d.lookupBusy||d.step===1&&!photo||d.step===0&&!d.barcode?'disabled':''}>Weiter</button>${d.step===0?'<button type=\"button\" class=\"soft\" onclick=\"guideSkipBarcode194()\">Kein Barcode vorhanden</button>':''}${d.step===2?'<span class=\"tiny\">Foto optional.</span>':''}</div>`:guideReviewHtml194(d)}`;\n for(const id of ['guideFile194','guideCameraFile194']){const input=document.getElementById(id);if(input)input.onchange=event=>{const file=event.target.files?.[0];event.target.value='';void readGuidePhoto194(file)}}\n const barcode=document.getElementById('guideBarcode194');if(barcode)barcode.oninput=()=>{d.barcode=barcode.value.replace(/[\\s-]/g,'');d.lookup=null;d.existing=null;d.lookupSerial++;const next=document.getElementById('guideNext194');if(next)next.disabled=!d.barcode};\n}\nfunction guideSourceHtml194(data,canAccept=false){\n const url=data.sourceUrl?'https://world.openfoodfacts.org/product/'+encodeURIComponent(data.barcode):'https://world.openfoodfacts.org';\n return `<div class=\"guideLookup194 ${data.found?'found':'missing'}\" role=\"status\">\n  <strong>${data.found?'Produktdaten gefunden':'Kein Datenbanktreffer'}</strong>\n  ${data.found?`<p class=\"guideLookupName194\">${esc(data.name||'Produkt ohne Namen')}${data.brand?' · '+esc(data.brand):''}${data.quantityText?' · '+esc(data.quantityText):''}</p>`:'<p>Angaben aus dem Foto ergänzen.</p>'}\n  ${data.found&&canAccept?'<button type=\"button\" class=\"primary\" onclick=\"acceptGuideLookup241()\">Produktdaten übernehmen</button>':''}\n  <small>Quelle: <a href=\"${url}\" target=\"_blank\" rel=\"noopener noreferrer\">Open Food Facts</a> · ODbL</small>\n </div>`;\n}\nfunction acceptGuideLookup241(){\n const d=productGuide194;\n if(!d||d.busy||d.lookupBusy||!d.lookup?.found||d.existing||!validGtin179(d.barcode))return;\n if(String(d.lookup.barcode||'').padStart(14,'0')!==d.barcode.padStart(14,'0'))return;\n d.step=3;\n const content=guideContent194(d),packaging=guidePackaging194(d);\n // Conflicting evidence and earlier manual corrections still need the review screen.\n if(content.conflict||packaging.packaging||d.review||['front','back'].some(view=>{\n  const label=d.photos[view]?.parsed;\n  return label?.name&&label.name!==d.lookup.name;\n })){renderGuide194();return}\n applyGuide194({name:d.lookup.name||'',quantity:d.lookup.content?.quantity??'',unit:d.lookup.content?.unit||'g'});\n}\nfunction guideReviewHtml194(d){\n const content=guideContent194(d),front=d.photos.front?.parsed,back=d.photos.back?.parsed;\n const packaging=guidePackaging194(d);\n const label=back||front||{},candidates=guideCandidates194(d),dates422=[...new Set(guideViews194.map(view=>d.photos[view]?.parsed?.mhdDate).filter(Boolean))];\n const name=d.review?.name??(packaging.name||d.lookup?.name||front?.name||back?.name||document.getElementById('aeName')?.value.trim()||'');\n const quantity=d.review?.quantity??content.value?.quantity??'',unit=d.review?.unit||content.value?.unit||'g';\n const html=`<div class=\"guideThumbs194\">${guideViews194.filter(view=>d.photos[view]).map(view=>`<img src=\"${d.photos[view].data}\" alt=\"${view==='front'?'Vorderseite':view==='barcode'?'Barcode':'Etikett'}\">`).join('')}</div>\n  ${content.conflict?'<p class=\"warnText\">Packungsgrößen widersprechen sich. Bitte die tatsächliche Größe vom Etikett eintragen.</p>':''}\n  ${d.lookup?guideSourceHtml194(d.lookup):''}\n  ${front?.name&&d.lookup?.name&&front.name!==d.lookup.name?`<p class=\"tiny\">Auf der Vorderseite gelesen: ${esc(front.name)}. Prüfe, ob der Datenbanktreffer wirklich passt.</p>`:''}\n  <label>Artikelname<input id=\"guideName194\" value=\"${esc(name)}\"></label>\n  <label>Barcode<input id=\"guideReviewBarcode194\" value=\"${esc(d.barcode)}\" readonly></label>\n  ${packaging.packaging?`<p>Lagereinheit: <b>Stück</b>. Volumen ist kein Stückbestand.</p>\n  ${packaging.conflict?'<p class=\"warnText\">Die Stückzahlen auf den Fotos widersprechen sich. Bitte am Gebinde prüfen.</p>':''}\n  <label>Stück je Packung<input id=\"guidePackCount194\" type=\"number\" min=\"1\" step=\"1\" inputmode=\"numeric\" value=\"${esc(String(d.review?.packCount??packaging.packCount??''))}\" placeholder=\"Falls lesbar\"></label>`:\n  `<div class=\"guideContent194\"><label>Inhalt einer Packung<input id=\"guideQty194\" inputmode=\"decimal\" value=\"${esc(String(quantity))}\" placeholder=\"Falls lesbar\"></label><label>Einheit<select id=\"guideUnit194\">${['g','kg','ml','Liter','Stück'].map(x=>`<option ${x===unit?'selected':''}>${x}</option>`).join('')}</select></label></div>`}\n  ${label.drained?`<p><b>Abtropfgewicht:</b> ${esc(label.drained.quantity)} ${esc(label.drained.unit)}. Nicht der Packungsinhalt.</p>`:''}\n  ${label.supplier?`<p><b>Hersteller laut Etikett:</b> ${esc(label.supplier)}. Lieferant separat wählen.</p>`:''}\n  ${dates422.length===1?`<p><b>MHD dieser Packung:</b> ${esc(dates422[0])}. Beim Buchen prüfen.</p>`:dates422.length>1?'<p class=\"warnText\">Unterschiedliche MHD auf den Fotos. Datum beim Einlagern prüfen.</p>':''}\n  ${candidates.length?`<div class=\"guideRelated194 guideExisting422\" role=\"status\"><b>Ähnlicher Artikel vorhanden</b>${candidates.map(x=>`<p><b>${esc(x.article.name)}</b> · ${esc(String(x.article.stock??0))} ${esc(x.article.unit||'')}<br><button type=\"button\" class=\"soft\" data-code=\"${esc(x.code)}\" onclick=\"useGuideProduct194(this.dataset.code)\">Vorhandenen Artikel verwenden</button></p>`).join('')}<details><summary>Mehr erfahren</summary><p>Hersteller und Packungsgröße vergleichen. Beim Wechsel wird die Aufnahme beendet. Der neue Barcode wird nicht zugeordnet. Ein ähnlicher Name allein bedeutet keinen identischen Artikel. Einen gemeinsamen Gesamtvorrat kannst du später ausdrücklich zuordnen.</p></details></div>`:''}\n  <p class=\"tiny\">Noch kein Bestand gebucht.</p>\n  <div class=\"guideActions194\"><button type=\"button\" class=\"soft\" onclick=\"guideStep194(-1)\">Zurück</button><button type=\"button\" class=\"primary\" onclick=\"applyGuide194()\">${candidates.length?'Als neue Variante anlegen':'Weiter'}</button></div>`;\n const marker='<div class=\"guideRelated194 guideExisting422\"';const pos=html.indexOf(marker);if(pos<0)return html;const finish=html.indexOf('</details></div>',pos)+'</details></div>'.length;return html.slice(pos,finish)+html.slice(0,pos)+html.slice(finish);\n}\nfunction guideStep194(delta){\n const d=productGuide194;if(!d||d.busy||d.lookupBusy)return;const oldStep=d.step;\n if(d.step===3)d.review={name:document.getElementById('guideName194')?.value||'',quantity:document.getElementById('guideQty194')?.value||'',unit:document.getElementById('guideUnit194')?.value||'g',packCount:document.getElementById('guidePackCount194')?.value||''};\n if(delta>0&&d.step===0&&d.barcode&&!validGtin179(d.barcode)){d.message='Barcode oder Prüfziffer prüfen, erneut fotografieren oder Kein Barcode vorhanden wählen.';renderGuide194();return}\n if(delta>0&&d.step===0&&!d.barcode){d.message='Bitte den Barcode eingeben oder „Kein Barcode vorhanden“ wählen.';renderGuide194();return}\n if(delta>0&&d.step===1&&!d.photos.front){d.message='Bitte zuerst die Vorderseite aufnehmen.';renderGuide194();return}\n d.step=Math.max(0,Math.min(3,d.step+delta));d.message='';renderGuide194();\n if(oldStep===0&&d.step===1&&d.barcode&&!d.existing&&!d.lookup)void lookupGuideBarcode194();\n}\nfunction guideSkipBarcode194(){const d=productGuide194;if(!d||d.busy)return;d.lookupSerial++;d.lookupBusy=false;d.barcode='';d.lookup=null;d.existing=null;d.step=1;d.message='';renderGuide194()}\nasync function guideCamera194(){\n if(!productGuide194||productGuide194.busy)return;\n if(/Android|iPhone|iPad|iPod/i.test(navigator.userAgent||'')){document.getElementById('guideCameraFile194')?.click();return}\n const d=productGuide194,step=d.step;\n try{\n  stopGuideCamera194();const stream=await navigator.mediaDevices.getUserMedia({video:{facingMode:{ideal:'environment'}},audio:false});\n  if(!guideAlive194(d)||d.step!==step){stream.getTracks().forEach(track=>track.stop());return}guideStream194=stream;\n  const panel=document.getElementById('guideCameraPanel194');panel.innerHTML='<video autoplay playsinline muted></video><button type=\"button\" class=\"primary\" onclick=\"captureGuideCamera194()\">Dieses Foto verwenden</button>';\n  const video=panel.querySelector('video');video.srcObject=stream;await video.play();\n }catch{if(guideAlive194(d)){d.message='Kamera nicht verfügbar. Zugriff erlauben oder ein vorhandenes Foto öffnen.';renderGuide194()}}\n}\nasync function captureGuideCamera194(){\n const d=productGuide194,step=d?.step;\n const video=document.querySelector('#guideCameraPanel194 video');if(!video?.videoWidth)return;\n const canvas=document.createElement('canvas'),scale=Math.min(1,2000/video.videoWidth);canvas.width=Math.round(video.videoWidth*scale);canvas.height=Math.round(video.videoHeight*scale);canvas.getContext('2d').drawImage(video,0,0,canvas.width,canvas.height);\n const blob=await new Promise(resolve=>canvas.toBlob(resolve,'image/jpeg',.9));stopGuideCamera194();if(blob&&guideAlive194(d)&&d.step===step)await readGuidePhoto194(new File([blob],'produkt.jpg',{type:'image/jpeg'}));\n}\nfunction guideDeadline194(promise,ms){let timer;return Promise.race([promise,new Promise((_,reject)=>{timer=setTimeout(()=>reject(Error('Erkennung dauert zu lange')),ms)})]).finally(()=>clearTimeout(timer))}\nasync function readGuidePhoto194(file){\n const d=productGuide194;if(!file||!d||d.busy||d.step>2)return;\n if(!file.type.startsWith('image/')||file.size>30*1024*1024){d.message='Bitte ein Foto unter 30 MB auswählen.';renderGuide194();return}\n const view=guideViews194[d.step],serial=++d.serial;d.busy=true;d.message='Foto wird gelesen …';renderGuide194();\n try{\n  const [data,ocrData]=await Promise.all([compactReference184(file),ocrData179(file)]);\n  const results=await Promise.allSettled([guideDeadline194(requestInboundOcr184(ocrData),45000),guideDeadline194(zReadBarcode166(file),12000)]);\n  if(!guideAlive194(d)||serial!==d.serial)return;\n  const text=results[0].status==='fulfilled'?results[0].value:'',decoded=results[1].status==='fulfilled'?results[1].value:'';\n  const parsed=parseLabel183(text,decoded),hash=await hash144(data);\n  if(!guideAlive194(d)||serial!==d.serial)return;\n  d.photos[view]={data,hash,parsed};d.message=text||decoded?'Foto übernommen. Angaben werden zur Prüfung gesammelt.':'Foto übernommen. Text nicht sicher lesbar; Angaben später selbst ergänzen.';\n  if(view==='barcode'){\n   d.lookupSerial++;d.lookup=null;d.existing=null;d.barcode=parsed.barcode||'';\n   if(!d.barcode)d.message='Barcode nicht lesbar. Nummer eingeben, erneut fotografieren oder ohne Barcode fortfahren.';\n  }\n }catch(error){if(guideAlive194(d))d.message='Foto konnte nicht übernommen werden: '+error.message}\n finally{if(guideAlive194(d)&&serial===d.serial){d.busy=false;renderGuide194();if(view==='barcode'&&d.barcode)void lookupGuideBarcode194()}}\n}\nasync function lookupGuideBarcode194(){\n const d=productGuide194;if(!d||d.lookupBusy)return;\n const input=document.getElementById('guideBarcode194');if(input)d.barcode=input.value.replace(/[\\s-]/g,'');\n const code=d.barcode,serial=++d.lookupSerial;d.lookup=null;d.existing=null;\n if(!validGtin179(code)){d.message='Bitte eine gültige Barcodenummer eingeben.';renderGuide194();return}\n d.existing=localBarcode194(code);\n if(d.existing){d.message='Barcode im eigenen Artikelstamm gefunden. Kein neuer Artikel nötig.';renderGuide194();return}\n d.lookupBusy=true;d.message='Barcode wird bei Open Food Facts abgefragt …';renderGuide194();\n try{\n  const result=await cloudRequest('/functions/v1/gs-product-lookup',{barcode:code});\n  if(!guideAlive194(d)||d.lookupSerial!==serial||d.barcode!==code)return;\n  d.lookup=result;d.message=result.found?'Produktdaten gefunden. Name und Packungsgröße am Produkt prüfen.':'Kein Datenbanktreffer. Fotos und manuelle Angaben können weiterverwendet werden.';\n }catch(error){if(guideAlive194(d)&&d.lookupSerial===serial)d.message=error.message}\n finally{if(guideAlive194(d)&&d.lookupSerial===serial){d.lookupBusy=false;renderGuide194()}}\n}\nfunction appendGuideReferences194(d,packIndex=0){\n const extra=guideViews194.filter(view=>d.photos[view]&&!editorRefs144.some(ref=>ref.data===d.photos[view].data));\n if(editorRefs144.length+extra.length>6)throw Error('Maximal sechs Referenzfotos. Bitte zuerst alte Referenzen im Formular entfernen.');\n for(const view of guideViews194){const photo=d.photos[view];if(!photo||editorRefs144.some(ref=>ref.data===photo.data))continue;\n  if(editorRefs144.length>=6)throw Error('Maximal sechs Referenzfotos. Bitte zuerst alte Referenzen im Formular entfernen.');\n  editorRefs144.push({id:'ref-'+crypto.randomUUID(),data:photo.data,hash:photo.hash,createdAt:new Date().toISOString(),confirmed:true,packIndex,view194:view,barcode194:d.barcode});\n }\n renderRefs144();\n}\nfunction applyGuide194(accepted=null){\n const d=productGuide194;if(!d||d.step!==3||d.busy)return;\n const packaging=guidePackaging194(d),packRaw=document.getElementById('guidePackCount194')?.value.trim()||'';\n const name=accepted?String(accepted.name).trim():document.getElementById('guideName194')?.value.trim()||'',qtyRaw=accepted?String(accepted.quantity):document.getElementById('guideQty194')?.value.trim()||'',qty=Number(qtyRaw.replace(',','.')),unit=accepted?.unit||document.getElementById('guideUnit194')?.value||'g';\n if(!name){d.message='Bitte den Artikelnamen ergänzen.';d.review={name,quantity:qtyRaw,unit};renderGuide194();return}\n const hit=d.barcode&&localBarcode194(d.barcode);if(hit){d.existing=hit;d.message='Dieser Barcode gehört bereits zu '+state.products[hit.productCode].name+'. Bitte den vorhandenen Artikel verwenden.';d.step=0;renderGuide194();return}\n const conflictingPhoto=['front','back'].find(view=>{const code=d.photos[view]?.parsed?.barcode;return d.barcode&&code&&code.padStart(14,'0')!==d.barcode.padStart(14,'0')});\n if(conflictingPhoto){d.message='Auf den Fotos wurden unterschiedliche Barcodes erkannt. Bitte prüfen, dass alle Aufnahmen dasselbe Produkt zeigen.';d.step=guideViews194.indexOf(conflictingPhoto);renderGuide194();return}\n if(qtyRaw&&(!(qty>0)||!Number.isFinite(qty))){d.message='Packungsinhalt prüfen.';d.review={name,quantity:qtyRaw,unit};renderGuide194();return}\n if(packaging.packaging&&packRaw&&(!Number.isSafeInteger(Number(packRaw))||Number(packRaw)<1)){d.message='Stück je Packung als ganze Zahl prüfen.';d.review={name,packCount:packRaw};renderGuide194();return}\n const additional=guideViews194.filter(view=>d.photos[view]&&!editorRefs144.some(ref=>ref.data===d.photos[view].data));\n if(editorRefs144.length+additional.length>6){d.message='Bitte zuerst ältere Referenzfotos entfernen. Es passen höchstens sechs Fotos zum Artikel.';renderGuide194();return}\n document.getElementById('aeName').value=name;\n articleGuideBookingBarcode194=d.barcode||'';\n if(d.barcode&&!packaging.packaging){const field=document.getElementById('aeCode');field.value=d.barcode;field.dispatchEvent(new Event('input',{bubbles:true}))}\n else if(packaging.packaging){const field=document.getElementById('aeCode');if(field){field.value='';field.dispatchEvent(new Event('input',{bubbles:true}))}}\n if(packaging.packaging){\n  const categories=state.categories||[],category=categories.find(c=>/verpack|verbrauch/i.test(c.name));\n  if(category){document.getElementById('aeCategory').value=category.name;articleCategoryChanged()}\n  const stockUnit=document.getElementById('aeUnit');if(stockUnit){stockUnit.value='Stück';stockUnit.dispatchEvent(new Event('change',{bubbles:true}))}\n  const policy=document.getElementById('aeMhdPolicy144');if(policy){policy.value='none';policy.dispatchEvent(new Event('change',{bubbles:true}))}\n  const count=Number(packRaw);if(count>0){const packs=document.getElementById('aePacks');let row=packs?.querySelector('.packEditRow:not(.basePack)');if(!row&&packs){addPackEditorRow();row=packs.querySelector('.packEditRow:not(.basePack)')}\n   if(row){row.querySelector('.aePackName').value='Packung';row.querySelector('.aePackQty').value=String(count);row.querySelector('.aePackUnit').value='Stück';if(d.barcode)row.querySelector('.aePackBarcode').value=d.barcode;row.querySelector('.aePackQty').dispatchEvent(new Event('input',{bubbles:true}))}}\n }else if(qtyRaw){document.getElementById('aeContentQty').value=String(qty);document.getElementById('aeContentUnit').value=unit;updateEditorStockContent()}\n const label=d.photos.back?.parsed||d.photos.front?.parsed||{};\n const photoDates422=[...new Set(guideViews194.map(view=>d.photos[view]?.parsed?.mhdDate).filter(Boolean))];articleGuideMhd422=photoDates422.length===1?photoDates422[0]:'';\n if(label.mhdPolicy||articleGuideMhd422){const policy=document.getElementById('aeMhdPolicy144');if(policy){policy.value='required';policy.dispatchEvent(new Event('change',{bubbles:true}))}}\n const notes=document.querySelector?.('#aeLmivFields [data-lmiv=\"notes\"]');\n if(notes){const additions=[];if(label.ingredients)additions.push('Zutaten laut Etikett: '+label.ingredients);if(label.temperature)additions.push('Kühlung laut Etikett: '+label.temperature);\n  if(additions.length)notes.value=[notes.value.trim(),...additions].filter(Boolean).join('\\n')}\n if(d.photos.front){articlePhotoData=d.photos.front.data;document.getElementById('aePhotoPreview').innerHTML=`<img src=\"${d.photos.front.data}\" alt=\"Vorderseite des Artikels\">`}\n articleGuideSource194=d.lookup?.found?{source:'Open Food Facts',url:d.lookup.sourceUrl,barcode:d.barcode,license:'ODbL',confirmedAt:new Date().toISOString()}:null;\n appendGuideReferences194(d);articleNameInputChanged();updatePurchaseQuote();endGuide194();\n // These are editable proposals, not a completed article or stock booking.\n showFeedback('Angaben zur Prüfung übernommen','info',4000,'Name und Packungsinhalt prüfen. Noch nichts gebucht.');\n}\nfunction useExistingGuide194(){\n const d=productGuide194,hit=d?.existing;if(!d||!hit||!state.products[hit.productCode])return;\n openArticleEditor(hit.productCode);\n if(editingArticleCode!==hit.productCode)return;\n try{appendGuideReferences194(d,hit.packIndex);showFeedback('Vorhandener Artikel geöffnet','info',4000,'Fotos im Formular ergänzt. Bitte prüfen und Artikel speichern.')}catch(error){showFeedback('Referenzfotos prüfen','warn',5000,error.message)}\n}\nconst openArticleBefore194=openArticleEditor;\nopenArticleEditor=function(...args){endGuide194();articleGuideSource194=null;articleGuideBookingBarcode194='';articleGuideMhd422='';const result=openArticleBefore194(...args);const button=document.getElementById('labelButton183');if(button&&!editingArticleCode){button.textContent='Produkt mit Fotos anlegen';button.onclick=startProductGuide194}\n document.getElementById('guideSavedSource194')?.remove();const source=state.products?.[editingArticleCode]?.barcodeSource194;\n if(button&&source?.source==='Open Food Facts'&&validGtin179(String(source.barcode||'')))button.insertAdjacentHTML('afterend',`<p id=\"guideSavedSource194\" class=\"tiny\">Produktangaben ergänzt mit <a href=\"https://world.openfoodfacts.org/product/${source.barcode}\" target=\"_blank\" rel=\"noopener noreferrer\">Open Food Facts</a> · ODbL, am Produkt bestätigt.</p>`);\n return result};\nconst closeArticleBefore194=closeArticleEditor;\ncloseArticleEditor=function(...args){endGuide194();return closeArticleBefore194(...args)};\nconst saveArticleBefore194=saveArticleEditor;\nsaveArticleEditor=function(...args){if(productGuide194)return showFeedback('Fotoaufnahme noch offen','warn',4500,'Angaben und Fotos zuerst ins Formular übernehmen oder Fotos abbrechen.');return saveArticleBefore194(...args)};\n\nconst androidBackBefore194=window.zapasioHandleBack;\nwindow.zapasioHandleBack=function(){\n if(['appConfirm','appPrompt'].some(id=>document.getElementById(id)?.classList?.contains('show')))return androidBackBefore194?.()||false;\n const d=productGuide194;\n if(d&&guideAlive194(d)){\n  d.serial++;d.lookupSerial++;d.busy=false;d.lookupBusy=false;\n  if(guideStream194){stopGuideCamera194();renderGuide194();return true}\n  if(d.step>0)guideStep194(-1);else endGuide194();\n  return true;\n }\n return androidBackBefore194?.()||false;\n};\n"},{"src":"barcode-conflict195.js?v=1","code":"'use strict';\n// An article key is stable. A bottle barcode that is another article's key\n// cannot be claimed by a carton or a second article without reconciliation.\nfunction barcodeCollision195(row){\n const code=row?.querySelector('.aePackBarcode')?.value.trim().toUpperCase()||'';\n if(!code)return null;\n const main=document.getElementById('aeCode')?.value.trim().toUpperCase()||'';\n if(code===main)return {same:true,code};\n const hit=barcodeConflict(code,editingArticleCode||'');\n return hit?{hit,code}:null;\n}\nfunction showBarcodeCollision195(row){\n if(!row)return;\n let panel=row.querySelector('.barcodeCollision195');\n const collision=barcodeCollision195(row);\n if(!collision){panel?.remove();return}\n if(!panel){panel=document.createElement('div');panel.className='barcodeCollision195';row.appendChild(panel)}\n if(collision.same){panel.innerHTML='<b>Dieser Barcode gehört bereits zur Einzelpackung dieses Artikels.</b><p>Hat der Karton keinen eigenen Barcode? Dann erhält er beim Speichern einen internen Code. Ein Flaschenscan bleibt eine Flasche.</p><button type=\"button\" class=\"soft\" onclick=\"clearCartonBarcode195(this)\">Karton ohne Barcode speichern</button>';return}\n const {hit,code}=collision,owner=state.products[hit.productCode],target=state.products[editingArticleCode];\n panel.innerHTML=`<b>Barcode ${esc(code)} gehört schon zu ${esc(owner?.name||hit.productName)}.</b><p>${esc(owner?.name||hit.productName)}: ${esc(String(owner?.stock??0))} ${esc(owner?.unit||'')}. ${esc(target?.name||document.getElementById('aeName')?.value||'Dieser Artikel')}: ${esc(String(target?.stock??document.getElementById('aeStock')?.value??0))} ${esc(target?.unit||document.getElementById('aeUnit')?.value||'')}.</p><p>Ist das derselbe Artikel, müssen Bestand und gegebenenfalls MHD vor der Zusammenführung geprüft werden. Der Barcode darf nicht zwei Artikeln gehören. Hat der Karton keinen eigenen Barcode, speichere ihn mit internem Code.</p><div class=\"barcodeCollisionActions195\"><button type=\"button\" class=\"soft\" onclick=\"openBarcodeOwner195(this)\">Artikel mit Barcode öffnen</button><button type=\"button\" class=\"soft\" onclick=\"clearCartonBarcode195(this)\">Karton ohne Barcode speichern</button></div>`;\n panel.dataset.ownerCode=hit.productCode;\n}\nfunction clearCartonBarcode195(button){const row=button?.closest('.packEditRow');if(!row)return;const field=row.querySelector('.aePackBarcode');field.value='';field.classList.remove('barcodeConflict','barcodeOk');field.title='Interner Code wird beim Artikelspeichern erzeugt.';showBarcodeCollision195(row);showFeedback('Karton ohne eigenen Barcode','info',4500,'Beim Artikelspeichern wird ein interner Gebindecode erzeugt. Der Flaschenbarcode bleibt beim bestehenden Artikel.');}\nfunction openBarcodeOwner195(button){const code=button?.closest('.barcodeCollision195')?.dataset.ownerCode;if(!state.products[code])return;askConfirm('Artikel mit dem Barcode öffnen? Nicht gespeicherte Änderungen am aktuellen Artikel gehen verloren.',()=>openArticleEditor(code),'Artikel öffnen','Barcode bereits vergeben');}\nconst checkPackBarcodeBefore195=checkPackBarcodeLive;\ncheckPackBarcodeLive=function(input){const result=checkPackBarcodeBefore195(input);showBarcodeCollision195(input?.closest('.packEditRow'));return result};\nconst saveArticleBefore195=saveArticleEditor;\nsaveArticleEditor=function(...args){\n const rows=[...document.querySelectorAll('#aePacks .packEditRow')].slice(1);\n for(const row of rows){const conflict=barcodeCollision195(row);if(!conflict)continue;showBarcodeCollision195(row);row.scrollIntoView?.({block:'center',behavior:'smooth'});showFeedback('Barcode-Zuordnung prüfen','warn',6000,conflict.same?'Der Code ist bereits die Einzelpackung dieses Artikels. Für einen Karton ohne Barcode den internen Code verwenden.':'Der Barcode gehört schon zu einem anderen Artikel. Beide Bestände und MHD vor einer Zusammenführung prüfen.');return}\n return saveArticleBefore195(...args);\n};\n"},{"src":"recognized-stock198.js?v=422","code":"'use strict';\nconst RECOGNIZED_TEXT278={\n de:{back:'Zurück',receive:'Auf Lager buchen',withdraw:'Aus Lager entnehmen',quantity:'Wie viele {0}?',packFactor:'1 {0} = {1} {2} im Bestand.',samePrice:'Preis gleich geblieben?',currentPrice:'Aktueller EK: {0} je {1}',noPrice:'Noch kein EK hinterlegt',yes:'Ja',no:'Nein',newPrice:'Neuer Einkaufspreis je {0}',pricePlaceholder:'z. B. 2,50',mhdOptional:'MHD dieser Ware (optional)',mhdRequired:'MHD dieser Ware',mhdReview:'MHD-Regel noch offen. Bitte im Artikel MHD erforderlich, optional oder kein MHD festlegen.',whichLot:'Welche MHD-Charge?',chooseLot:'Bitte MHD wählen',lotRemoved:'MHD {0} wird entnommen.',noLot:'Für diesen Bestand ist noch keine MHD-Charge erfasst.',review:'Buchung prüfen',photoBarcode:'Fotografierter Barcode',inStock:'Im Lager',referencePhotos:'Referenzfotos ergänzen',close:'Schließen',unbooked:'Noch nichts gebucht',selectedPending:'Artikel ausgewählt. Noch nichts gebucht.',barcodeOwner:'Der Barcode gehört zu {0}.',similarCompare:'Ähnliche Artikel im Lager: nur vergleichen',compareArticle:'Artikel vergleichen',relatedNotBooked:'Diese ähnlichen Artikel werden nicht bebucht.',useBarcodeOwner:'Diesen Barcode-Artikel verwenden',bookingAfterConfirm:'Erst nach Menge, MHD und der letzten Bestätigung ändert sich der Bestand.',missingArticle:'Artikel nicht mehr vorhanden.',validQuantity:'Bitte eine gültige Anzahl eingeben.',priceChoice:'Bitte bestätigen, ob der Einkaufspreis gleich geblieben ist.',validPrice:'Bitte einen gültigen neuen EK netto eingeben.',mhdRule:'Bitte zuerst die MHD-Regel im Artikel festlegen.',mhdDate:'Bitte das MHD dieser Ware eingeben.',stockShort:'Der Bestand reicht für diese Entnahme nicht.',lotMissing:'MHD-Charge fehlt. Bitte erst die Chargen prüfen.',lotChoice:'Bitte die MHD-Charge auswählen.',lotShort:'In dieser MHD-Charge sind nicht genügend Stück.',barcodeReview:'Barcode prüfen',barcodeFirst:'Produkt und fotografierten Barcode zuerst bestätigen.',confirmTitle:'Buchung prüfen',bookIn:'Einbuchen',bookOut:'Entnehmen',expiryShort:'MHD',newPriceShort:'neuer EK',confirmStock:'Bestand bestätigen',success:'Bestand gebucht',mappingChanged:'Barcode-Zuordnung wurde geändert. Bitte erneut prüfen.',mhdChanged:'MHD-Regel wurde geändert. Bitte erneut prüfen.',shelfChanged:'Haltbarkeit oder Einlagerungstag hat sich geändert. Bitte erneut prüfen.',adminPrice:'Den Einkaufspreis dürfen nur Admins ändern.',priceChanged:'Der Einkaufspreis wurde inzwischen geändert. Bitte erneut prüfen.',lotChanged:'MHD-Charge oder Bestand hat sich geändert. Bitte erneut prüfen.'},\n pl:{back:'Wstecz',receive:'Przyjmij do magazynu',withdraw:'Wydaj z magazynu',quantity:'Ile ({0})?',packFactor:'1 {0} = {1} {2} w magazynie.',samePrice:'Cena bez zmian?',currentPrice:'Aktualna cena zakupu: {0} za {1}',noPrice:'Brak zapisanej ceny zakupu',yes:'Tak',no:'Nie',newPrice:'Nowa cena zakupu za {0}',pricePlaceholder:'np. 2,50',mhdOptional:'Termin ważności tej dostawy (opcjonalnie)',mhdRequired:'Termin ważności tej dostawy',mhdReview:'Reguła terminu ważności nie jest ustalona. W artykule wybierz: wymagany, opcjonalny albo bez terminu.',whichLot:'Z której partii?',chooseLot:'Wybierz termin ważności',lotRemoved:'Towar z terminem {0} zostanie wydany.',noLot:'Dla tego stanu nie zapisano jeszcze partii z terminem ważności.',review:'Sprawdź zapis',photoBarcode:'Sfotografowany kod kreskowy',inStock:'W magazynie',referencePhotos:'Dodaj zdjęcia referencyjne',close:'Zamknij',unbooked:'Jeszcze nic nie zapisano',selectedPending:'Artykuł wybrany. Jeszcze nic nie zapisano.',barcodeOwner:'Kod kreskowy należy do: {0}.',similarCompare:'Podobne artykuły w magazynie: tylko do porównania',compareArticle:'Porównaj artykuł',relatedNotBooked:'Te podobne artykuły nie zostaną zaksięgowane.',useBarcodeOwner:'Użyj artykułu z tego kodu',bookingAfterConfirm:'Stan zmieni się dopiero po podaniu ilości, terminu ważności i końcowym potwierdzeniu.',missingArticle:'Artykuł już nie istnieje.',validQuantity:'Wpisz prawidłową ilość.',priceChoice:'Potwierdź, czy cena zakupu pozostała bez zmian.',validPrice:'Wpisz prawidłową nową cenę zakupu netto.',mhdRule:'Najpierw ustaw regułę terminu ważności w artykule.',mhdDate:'Wpisz termin ważności tej dostawy.',stockShort:'Stan magazynowy jest za mały dla tego wydania.',lotMissing:'Brak partii z terminem ważności. Najpierw sprawdź partie.',lotChoice:'Wybierz partię z terminem ważności.',lotShort:'W tej partii nie ma wystarczającej ilości.',barcodeReview:'Sprawdź kod kreskowy',barcodeFirst:'Najpierw potwierdź produkt i sfotografowany kod kreskowy.',confirmTitle:'Sprawdź zapis',bookIn:'Przyjmij',bookOut:'Wydaj',expiryShort:'termin',newPriceShort:'nowa cena zakupu',confirmStock:'Potwierdź stan',success:'Stan zapisany',mappingChanged:'Przypisanie kodu kreskowego zostało zmienione. Sprawdź ponownie.',mhdChanged:'Reguła terminu ważności została zmieniona. Sprawdź ponownie.',shelfChanged:'Okres trwałości lub dzień przyjęcia uległ zmianie. Sprawdź ponownie.',adminPrice:'Tylko administrator może zmienić cenę zakupu.',priceChanged:'Cena zakupu została w międzyczasie zmieniona. Sprawdź ponownie.',lotChanged:'Partia lub stan magazynowy uległy zmianie. Sprawdź ponownie.'},\n en:{back:'Back',receive:'Receive stock',withdraw:'Withdraw stock',quantity:'How many {0}?',packFactor:'1 {0} = {1} {2} in stock.',samePrice:'Same price?',currentPrice:'Current purchase price: {0} per {1}',noPrice:'No purchase price saved',yes:'Yes',no:'No',newPrice:'New purchase price per {0}',pricePlaceholder:'e.g. 2.50',mhdOptional:'Best-before date for this delivery (optional)',mhdRequired:'Best-before date for this delivery',mhdReview:'The best-before rule is not set. In the item, choose required, optional, or no best-before date.',whichLot:'Which best-before batch?',chooseLot:'Select a best-before date',lotRemoved:'The batch dated {0} will be withdrawn.',noLot:'No best-before batch has been recorded for this stock.',review:'Review transaction',photoBarcode:'Photographed barcode',inStock:'In stock',referencePhotos:'Add reference photos',close:'Close',unbooked:'Nothing booked yet',selectedPending:'Item selected. Nothing booked yet.',barcodeOwner:'The barcode belongs to {0}.',similarCompare:'Similar stock items: compare only',compareArticle:'Compare item',relatedNotBooked:'These similar items will not be booked.',useBarcodeOwner:'Use this barcode item',bookingAfterConfirm:'Stock changes only after quantity, best-before date, and final confirmation.',missingArticle:'The item no longer exists.',validQuantity:'Enter a valid quantity.',priceChoice:'Confirm whether the purchase price stayed the same.',validPrice:'Enter a valid new net purchase price.',mhdRule:'Set the item’s best-before rule first.',mhdDate:'Enter the best-before date for this delivery.',stockShort:'There is not enough stock for this withdrawal.',lotMissing:'Best-before batch missing. Review the batches first.',lotChoice:'Select the best-before batch.',lotShort:'There is not enough stock in this batch.',barcodeReview:'Review barcode',barcodeFirst:'Confirm the product and photographed barcode first.',confirmTitle:'Review transaction',bookIn:'Receive',bookOut:'Withdraw',expiryShort:'best before',newPriceShort:'new purchase price',confirmStock:'Confirm stock',success:'Stock updated',mappingChanged:'The barcode assignment changed. Review it again.',mhdChanged:'The best-before rule changed. Review it again.',shelfChanged:'The shelf life or receipt date changed. Review it again.',adminPrice:'Only administrators can change the purchase price.',priceChanged:'The purchase price changed in the meantime. Review it again.',lotChanged:'The batch or stock changed. Review it again.'}\n};\nfunction recognizedLanguage278(){try{const language=String(business130()?.language||'').toLowerCase();if(['de','pl','en'].includes(language))return language}catch{}const fallback=String(document.documentElement?.lang||'de').slice(0,2).toLowerCase();return ['de','pl','en'].includes(fallback)?fallback:'de'}\nfunction recognizedText278(key,...values){const language=recognizedLanguage278(),template=RECOGNIZED_TEXT278[language]?.[key]??RECOGNIZED_TEXT278.de[key]??key;return template.replace(/\\{(\\d+)\\}/g,(_,index)=>String(values[Number(index)]??''))}\nfunction recognizedMhdPolicy256(article){if(article?.mhdPolicy144==='review'&&article?.mhd)return 'required';return ['required','optional','none','review'].includes(article?.mhdPolicy144)?article.mhdPolicy144:article?.mhd?'required':'none'}\n// A confirmed barcode match can use the existing stock transaction without an article form.\nfunction recognizedMismatch198(d){\n const chosen=String(d.barcode||''),read=String(d.photos.barcode?.parsed?.barcode||'');\n return chosen&&read&&chosen.padStart(14,'0')!==read.padStart(14,'0');\n}\n// Group display by expiry within one product; keep all underlying batch identities.\nfunction expiryGroups322(batches){\n const groups=new Map();\n for(const b of batches||[]){if(!(Number(b.qty)>0)||!b.mhd)continue;\n  const date=typeof parseMhd147==='function'?(parseMhd147(b.mhd)?.iso||b.mhd):b.mhd;\n  const key=JSON.stringify([date,!!b.consignment168,b.lotNumber||b.batchNumber||'']);\n  let g=groups.get(key);if(!g){g={id:b.id,mhd:date,qty:0,members:[]};groups.set(key,g)}\n  g.qty+=Number(b.qty);g.members.push(b);\n }\n return [...groups.values()].sort((a,b)=>String(a.mhd).localeCompare(String(b.mhd)));\n}\nfunction expirySelection322(batches,id){const groups=expiryGroups322(batches);return id?groups.find(g=>g.members.some(b=>b.id===id)):groups.length===1?groups[0]:null}\nfunction expiryAllocation322(group,qty){\n if(!group||!(qty>0)||qty>group.qty+1e-8)throw Error(recognizedText278('lotShort'));\n let remaining=qty;const rows=[];\n for(const b of group.members){const take=Math.min(remaining,Number(b.qty));if(take>0)rows.push({id:b.id,qty:take,available:Number(b.qty),mhd:b.mhd,consignment:!!b.consignment168});remaining-=take;if(remaining<=1e-8)break}\n return rows;\n}\nfunction withdrawExpiry322(code,entry){\n const a=state.products[code],lots=state.batches?.[code]||[],rows=entry.lotAllocation;\n if(!rows?.length||Number(a.stock)+1e-8<entry.qty||Math.abs(rows.reduce((n,r)=>n+r.qty,0)-entry.qty)>1e-8)throw Error(recognizedText278('lotChanged'));\n // Validate every selected batch before changing any batch.\n for(const r of rows){const b=lots.find(b=>b.id===r.id);if(!b||Number(b.qty)!==r.available||b.mhd!==r.mhd||!!b.consignment168!==r.consignment)throw Error(recognizedText278('lotChanged'))}\n for(const r of rows){const b=lots.find(b=>b.id===r.id);b.qty-=r.qty;if(b.consignment168)a.consignmentQty168=Math.max(0,(Number(a.consignmentQty168)||0)-r.qty);if(Number(b.packSize)>0)b.packCount=Math.ceil(Math.max(0,b.qty-1e-9)/Number(b.packSize))}\n a.stock=Number(a.stock)-entry.qty;state.batches[code]=lots.filter(b=>Number(b.qty)>1e-8);\n}\n\nfunction recognizedBooking214(d,a,pack,bookingUnit,batches,mode){\n batches=expiryGroups322(batches);\n const admin=mode==='in'&&currentAccessUser().role==='admin';\n const choice=d.priceChoice214;\n const unit=bookingUnit||'Stück',back=`<button type=\"button\" class=\"soft\" onclick=\"chooseRecognized198('')\">${esc(recognizedText278('back'))}</button>`;\n const currentPrice=Number(a.price)>0?moneyFmt(Number(a.price)*(pack?Number(packUsableQty(pack)):1)):recognizedText278('noPrice');\n const price=admin?`<div class=\"recognizedPrice214\"><b>${esc(recognizedText278('samePrice'))}</b><p>${esc(recognizedText278('currentPrice',currentPrice,unit))}</p><div class=\"guideActions194\"><button type=\"button\" class=\"${choice==='same'?'primary':'soft'}\" onclick=\"chooseRecognizedPrice214('same')\">${esc(recognizedText278('yes'))}</button><button type=\"button\" class=\"${choice==='changed'?'primary':'soft'}\" onclick=\"chooseRecognizedPrice214('changed')\">${esc(recognizedText278('no'))}</button></div></div>${choice==='changed'?`<label>${esc(recognizedText278('newPrice',unit))}<input id=\"recognizedPrice214\" type=\"text\" inputmode=\"decimal\" placeholder=\"${esc(recognizedText278('pricePlaceholder'))}\" autocomplete=\"off\"></label>${priceControls228('recognizedPrice214')}`:''}`:'';\n const readyForMhd=!admin||['same','changed'].includes(choice),policy=recognizedMhdPolicy256(a);\n return `<div class=\"recognizedBooking198\"><b>${esc(recognizedText278(mode==='in'?'receive':'withdraw'))}</b><label>${esc(recognizedText278('quantity',unit))}<input id=\"recognizedQty198\" type=\"number\" min=\"1\" step=\"${pack||isWholeQuantityUnit(a.unit)?'1':'any'}\" value=\"${esc(String(d.quickQty198??1))}\"></label>${pack?`<small>${esc(recognizedText278('packFactor',bookingUnit,packUsableQty(pack),a.unit||'Stück'))}</small>`:''}${price}${readyForMhd&&mode==='in'&&['required','optional'].includes(policy)?`<label>${esc(recognizedText278(policy==='optional'?'mhdOptional':'mhdRequired'))}<input id=\"recognizedMhd198\" type=\"date\" value=\"${esc(receiptMhd242(a,d.receiptMhd258||''))}\" ${policy==='required'?'required':''}></label>`:''}${readyForMhd&&mode==='in'&&policy==='review'?`<p class=\"recognizedWarn198\">${esc(recognizedText278('mhdReview'))}</p>`:''}${mode==='out'&&a.mhd?batches.length>1?`<label>${esc(recognizedText278('whichLot'))}<select id=\"recognizedLot198\"><option value=\"\">${esc(recognizedText278('chooseLot'))}</option>${batches.map(b=>`<option value=\"${esc(b.id)}\">${esc(formatDate(b.mhd))} · ${esc(String(b.qty))} ${esc(a.unit||'')}</option>`).join('')}</select></label>`:batches.length===1?`<small>${esc(recognizedText278('lotRemoved',formatDate(batches[0].mhd)))}</small>`:`<p class=\"recognizedWarn198\">${esc(recognizedText278('noLot'))}</p>`:''}<div class=\"guideActions194\">${back}${readyForMhd?`<button type=\"button\" class=\"primary\" onclick=\"confirmRecognized198()\">${esc(recognizedText278('review'))}</button>`:''}</div></div>`;\n}\nfunction chooseRecognizedPrice214(choice){const d=productGuide194;if(!d?.existing||d.quickMode198!=='in'||currentAccessUser().role!=='admin'||!['','same','changed'].includes(choice))return;const qty=document.getElementById('recognizedQty198')?.value;if(qty!==undefined)d.quickQty198=qty;d.priceChoice214=choice;renderGuide194()}\nfunction recognizedCompact198(){\n const d=productGuide194,modal=document.getElementById('articleModal'),box=document.getElementById('guideBox194');\n if(!modal||!box)return;\n modal.classList.toggle('guideExisting198',!!d?.existing);\n if(!d?.existing)return;\n const code=d.existing.productCode,a=state.products[code];if(!a)return;\n const exact=typeof localBarcode194==='function'?localBarcode194(d.barcode):null;\n if(exact?.productCode===code&&!recognizedMismatch198(d))d.confirmed198=true;\n const pack=d.existing.packIndex>0?getPacks(a,code)[d.existing.packIndex]:null;\n const bookingUnit=pack?.[0]||a.unit;\n const related=d.confirmed198?[]:guideRelated194(d),photo=d.photos.barcode?.data||'',mismatch=recognizedMismatch198(d),mode=d.quickMode198||'';\n const batches=(state.batches?.[code]||[]).filter(batch=>Number(batch.qty)>0&&batch.mhd);\n const needsChoice=related.length>0&&!d.confirmed198;\n const prices=(a.priceHistory||[]).slice().sort((x,y)=>Number(y.ts||0)-Number(x.ts||0)).slice(0,6);\n const options=related.map(item=>`<div class=\"recognizedCompareRow198\"><span><b>${esc(item.article.name)}</b><small>${esc(recognizedText278('inStock'))}: ${esc(String(item.article.stock??0))} ${esc(item.article.unit||'')}</small></span><button type=\"button\" class=\"soft\" data-code=\"${esc(item.code)}\" onclick=\"inspectGuideArticle194(this.dataset.code)\">${esc(recognizedText278('compareArticle'))}</button></div>`).join('');\n box.innerHTML=`<div class=\"recognizedHead198\">${photo?`<img src=\"${photo}\" alt=\"${esc(recognizedText278('photoBarcode'))}\">`:''}<div><b>${esc(a.name)}</b><button type=\"button\" class=\"soft stockInfoButton311\" onclick=\"openMobileProductInfo311()\" aria-label=\"${esc(stockInfoText311('info'))}\">ⓘ ${esc(stockInfoText311('info'))}</button><small>Barcode ${esc(d.barcode)} · ${esc(recognizedText278('inStock'))}: ${esc(String(a.stock??0))} ${esc(a.unit||'')}</small></div></div>\n ${mismatch?'<p class=\"recognizedWarn198\">Das fotografierte Etikett und die eingegebene Barcodenummer unterscheiden sich. Bitte erst die Nummer am Produkt prüfen.</p>':''}\n ${d.confirmed198?`<small class=\"recognizedSelected311\">${esc(recognizedText278('selectedPending'))}</small>`:''}\n ${related.length?`<section class=\"recognizedRelated198\" aria-label=\"${esc(recognizedText278('similarCompare'))}\"><div class=\"recognizedStatus198\"><strong>${esc(recognizedText278(d.confirmed198?'selectedPending':'unbooked'))}</strong><span>${esc(recognizedText278('barcodeOwner',a.name))}</span></div><h3>${esc(recognizedText278('similarCompare'))}</h3>${options}<p class=\"recognizedDecisionNote198\">${esc(recognizedText278('relatedNotBooked'))}</p>${!mismatch&&!d.confirmed198?`<button type=\"button\" class=\"primary recognizedOwnerAction198\" onclick=\"confirmBarcodeOwner198()\">${esc(recognizedText278('useBarcodeOwner'))}</button>`:''}<p class=\"recognizedDecisionNote198\">${esc(recognizedText278('bookingAfterConfirm'))}</p></section>`:''}\n ${mismatch?'':needsChoice?'':mode==='prices'?`<div class=\"recognizedBooking198\"><b>Preise für ${esc(a.name)}</b><p>Aktueller EK: ${Number(a.price)>0?esc(moneyFmt(Number(a.price))):'Noch kein EK hinterlegt'} je ${esc(a.unit||'Stück')}</p>${currentAccessUser().role==='admin'?`<label>Einkaufspreis (€ je ${esc(a.unit||'Stück')})<input id=\"recognizedEditPrice227\" type=\"text\" inputmode=\"decimal\" value=\"${Number(a.price)>0?esc(Number(a.price).toFixed(2).replace('.',',')):''}\" placeholder=\"z. B. 4,20\"></label>${priceControls228('recognizedEditPrice227')}<button type=\"button\" class=\"primary\" onclick=\"saveRecognizedPrice227()\">Preis speichern</button>`:''}${prices.length?prices.map(entry=>`<p>${esc(entry.date||'Ohne Datum')} · ${esc(moneyFmt(Number(entry.price)||0))}${entry.supplier?' · '+esc(entry.supplier):''}</p>`).join(''):''}<button type=\"button\" class=\"soft\" onclick=\"chooseRecognized198('')\">Zurück</button></div>`:mode==='inventory'?`<div class=\"recognizedBooking198\"><b>Bestand zählen</b>${a.mhd?(batches.length?batches.map(batch=>`<label>MHD ${esc(formatDate(batch.mhd))} · bisher ${esc(String(batch.qty))}<input class=\"recognizedCount198\" data-id=\"${esc(batch.id)}\" inputmode=\"numeric\" type=\"number\" min=\"0\" step=\"${isWholeQuantityUnit(a.unit)?'1':'any'}\" value=\"${esc(String(batch.qty))}\"></label>`).join(''):'<p class=\"recognizedWarn198\">Keine MHD-Charge vorhanden. Erst das MHD bei der Einbuchung erfassen.</p>'):`<label>Gezählt: ${esc(a.unit||'Stück')}<input id=\"recognizedActual198\" type=\"number\" min=\"0\" step=\"${isWholeQuantityUnit(a.unit)?'1':'any'}\" value=\"${esc(String(a.stock??0))}\"></label>`}<div class=\"guideActions194\"><button type=\"button\" class=\"soft\" onclick=\"chooseRecognized198('')\">Zurück</button><button type=\"button\" class=\"primary\" onclick=\"confirmInventory198()\">Zählung prüfen</button></div></div>`:mode?recognizedBooking214(d,a,pack,bookingUnit,batches,mode):`<div class=\"guideActions194\"><button type=\"button\" class=\"primary\" onclick=\"chooseRecognized198('in')\">Auf Lager buchen</button><button type=\"button\" class=\"soft\" onclick=\"chooseRecognized198('out')\">Aus Lager entnehmen</button><button type=\"button\" class=\"soft\" onclick=\"chooseRecognized198('inventory')\">Inventur</button><button type=\"button\" class=\"soft\" onclick=\"chooseRecognized198('prices')\">Preise</button></div>`}\n ${!mode?`<button type=\"button\" class=\"soft\" onclick=\"useExistingGuide194()\">${esc(recognizedText278('referencePhotos'))}</button>`:''}<button type=\"button\" class=\"soft\" onclick=\"closeArticleEditor()\">${esc(recognizedText278('close'))}</button>`;\n}\nfunction confirmBarcodeOwner198(){const d=productGuide194;if(!d?.existing)return;d.confirmed198=true;renderGuide194()}\nfunction chooseRecognized198(mode){const d=productGuide194;if(!d?.existing||!['','in','out','inventory','prices'].includes(mode))return;d.quickMode198=mode;d.priceChoice214='';renderGuide194()}\nfunction withoutMhdText335(commission=false){return stockInfoText311('unassigned')+(commission?' · '+({de:'Kommission',en:'Consignment',pl:'Komis'}[recognizedLanguage278()]||'Kommission'):'')}\nfunction withdrawalSnapshot335(code){const a=state.products[code];return JSON.stringify([a.stock,a.consignmentQty168||0,state.batches?.[code]||[]])}\nfunction withdrawalGroups335(code,a=state.products[code]){\n const lots=(state.batches?.[code]||[]).filter(b=>Number(b.qty)>0),groups=expiryGroups322(lots);\n if(recognizedMhdPolicy256(a)!=='optional')return groups;\n const known=lots.reduce((n,b)=>n+Number(b.qty),0),knownCommission=lots.filter(b=>b.consignment168).reduce((n,b)=>n+Number(b.qty),0);\n const implicit=Math.max(0,Number(a.stock)-known),implicitCommission=Math.min(implicit,Math.max(0,(Number(a.consignmentQty168)||0)-knownCommission));\n for(const commission of [false,true]){const members=lots.filter(b=>!b.mhd&&!!b.consignment168===commission),extra=commission?implicitCommission:implicit-implicitCommission,qty=members.reduce((n,b)=>n+Number(b.qty),0)+extra;if(qty>1e-8)groups.push({id:commission?'unassigned-commission335':'unassigned335',mhd:'',qty,members,noMhd:true,commission,implicit:extra})}\n return groups;\n}\n// Explicit quantities across expiry groups; all selections are validated before mutation.\nconst WITHDRAWAL_TEXT459={\n de:{title:'Aus welchen Chargen entnehmen?',hint:'Wähle die Chargen und die Menge je Charge.',available:'Verfügbar',selected:'Ausgewählt',of:'von',amount:'Entnahmemenge',sum:'Die ausgewählten Chargenmengen müssen zusammen {0} ergeben.',invalid:'Bitte gültige Chargenmengen eingeben.',less:'Weniger entnehmen',more:'Mehr entnehmen'},\n en:{title:'Which batches are you taking from?',hint:'Select batches and the quantity from each.',available:'Available',selected:'Selected',of:'of',amount:'Withdrawal quantity',sum:'The selected batch quantities must total {0}.',invalid:'Enter valid batch quantities.',less:'Take less',more:'Take more'},\n pl:{title:'Z których partii pobierasz?',hint:'Wybierz partie i ilość z każdej.',available:'Dostępne',selected:'Wybrano',of:'z',amount:'Ilość do wydania',sum:'Suma ilości z wybranych partii musi wynosić {0}.',invalid:'Podaj prawidłowe ilości partii.',less:'Pobierz mniej',more:'Pobierz więcej'}\n};\nfunction withdrawalText459(key,...values){const text=WITHDRAWAL_TEXT459[recognizedLanguage278()]?.[key]||WITHDRAWAL_TEXT459.de[key]||key;return text.replace(/\\{(\\d+)\\}/g,(_,i)=>String(values[Number(i)]??''))}\nfunction withdrawalPlan459(code,selection,qty){\n const a=state.products[code],groups=withdrawalGroups335(code),rows=[],seen=new Set();\n if(!a||!Number.isFinite(qty)||qty<=0||Number(a.stock)+1e-8<qty)throw Error(recognizedText278('stockShort'));\n for(const item of selection||[]){\n  if(seen.has(item.id))throw Error(withdrawalText459('invalid'));seen.add(item.id);\n  const group=groups.find(g=>g.id===item.id),take=Number(item.qty);\n  if(!group||!String(item.qty).trim()||!Number.isFinite(take)||take<=0||(isWholeQuantityUnit(a.unit)&&!Number.isInteger(take)))throw Error(withdrawalText459('invalid'));\n  if(take>group.qty+1e-8)throw Error(recognizedText278('lotShort'));\n  let left=take;const allocations=[];for(const b of group.members){const amount=Math.min(left,Number(b.qty));if(amount>0)allocations.push({id:b.id,qty:amount,mhd:b.mhd||'',consignment:!!b.consignment168});left-=amount;if(left<=1e-8)break;}\n  rows.push({id:group.id,qty:take,mhd:group.mhd,noMhd:!!group.noMhd,commission:group.noMhd?!!group.commission:!!group.members[0]?.consignment168,allocations,unassignedQty:Math.max(0,left)});\n }\n if(!rows.length)throw Error(recognizedText278('lotChoice'));\n if(Math.abs(rows.reduce((n,r)=>n+r.qty,0)-qty)>1e-8)throw Error(withdrawalText459('sum',formatQtySmart(qty,a.unit)));\n return {rows,snapshot:withdrawalSnapshot335(code)};\n}\nfunction withdrawSelected459(code,entry){\n const plan=entry.multiLots459;\n if(!plan||withdrawalSnapshot335(code)!==plan.snapshot)throw Error(recognizedText278('lotChanged'));\n const checked=withdrawalPlan459(code,plan.rows,entry.qty),groups=withdrawalGroups335(code),a=state.products[code];\n for(const row of checked.rows){\n  const group=groups.find(g=>g.id===row.id);let left=row.qty;\n  for(const b of group.members){const take=Math.min(left,Number(b.qty));b.qty-=take;left-=take;if(Number(b.packSize)>0)b.packCount=Math.ceil(Math.max(0,b.qty-1e-9)/Number(b.packSize));if(left<=1e-8)break;}\n  if(row.commission)a.consignmentQty168=Math.max(0,(Number(a.consignmentQty168)||0)-row.qty);\n }\n a.stock=Number(a.stock)-entry.qty;state.batches??={};state.batches[code]=(state.batches[code]||[]).filter(b=>Number(b.qty)>1e-8);\n}\nfunction withdrawalSelection335(code,id){const groups=withdrawalGroups335(code);return id?groups.find(g=>g.id===id||g.members.some(b=>b.id===id)):groups.length===1?groups[0]:null}\nfunction withdrawWithoutMhd335(code,entry){\n if(withdrawalSnapshot335(code)!==entry.noMhd.snapshot)throw Error(recognizedText278('lotChanged'));\n const group=withdrawalSelection335(code,entry.lotId);if(!group?.noMhd||entry.qty>group.qty+1e-8)throw Error(recognizedText278('lotShort'));\n let remaining=entry.qty;for(const b of group.members){const take=Math.min(remaining,Number(b.qty));b.qty-=take;remaining-=take;if(Number(b.packSize)>0)b.packCount=Math.ceil(Math.max(0,b.qty-1e-9)/Number(b.packSize));if(remaining<=1e-8)break}\n const a=state.products[code];a.stock=Number(a.stock)-entry.qty;if(group.commission)a.consignmentQty168=Math.max(0,(Number(a.consignmentQty168)||0)-entry.qty);state.batches[code]=(state.batches[code]||[]).filter(b=>Number(b.qty)>1e-8);\n}\n// Labels are presentation only; the existing guarded transaction remains authoritative.\nfunction bookingExpiryWarning342(date){\n if(typeof expiryWarning322==='function')return expiryWarning322(date);\n const iso=typeof parseMhd147==='function'?parseMhd147(date)?.iso:/^\\d{4}-\\d{2}-\\d{2}$/.test(date||'')?date:null;\n return iso&&iso<localTodayISO()?{className:'expiryExpired322',label:stockInfoText311('expired')}:null;\n}\nfunction bookingDateText342(date){const warning=bookingExpiryWarning342(date);return formatDate(date)+(warning?' · '+warning.label:'')}\nfunction bookingDateHtml342(date){const warning=bookingExpiryWarning342(date);return `<span${warning?` class=\"bookingExpiry342 ${warning.className}\"`:''}>${esc(bookingDateText342(date))}</span>`}\nfunction recognizedQuantityMessage342(a,pack){\n const lang=recognizedLanguage278(),whole=!!pack||isWholeQuantityUnit(a.unit),unit=pack?.[0]||a.unit||'Stück';\n const words={de:whole?'Bitte eine ganze Anzahl größer als 0 eingeben. Einheit: '+unit+'.':'Bitte eine Menge größer als 0 eingeben. Dezimalmengen sind erlaubt, zum Beispiel 0,5 '+unit+'.',en:whole?'Enter a whole quantity greater than 0. Unit: '+unit+'.':'Enter a quantity greater than 0. Decimals are allowed, for example 0.5 '+unit+'.',pl:whole?'Podaj całkowitą ilość większą niż 0. Jednostka: '+unit+'.':'Podaj ilość większą niż 0. Ułamki są dozwolone, np. 0,5 '+unit+'.'};\n return words[lang]||words.de;\n}\nfunction recognizedInput198(d){\n const a=state.products[d.existing.productCode];if(!a)throw Error(recognizedText278('missingArticle'));\n const raw=document.getElementById('recognizedQty198')?.value||'',pack=d.existing.packIndex>0?getPacks(a,d.existing.productCode)[d.existing.packIndex]:null,count=Number(raw),factor=pack?Number(packUsableQty(pack)):1,qty=count*factor;\n if(!a||!raw||!Number.isFinite(count)||count<=0||!(factor>0)||!Number.isFinite(qty)||((pack||isWholeQuantityUnit(a.unit))&&!Number.isInteger(count)))throw Error(recognizedQuantityMessage342(a,pack));\n let newUnitPrice=null;\n const admin=d.quickMode198==='in'&&currentAccessUser().role==='admin';\n if(admin){\n  if(!['same','changed'].includes(d.priceChoice214))throw Error(recognizedText278('priceChoice'));\n  if(d.priceChoice214==='changed'){\n   const rawPrice=document.getElementById('recognizedPrice214')?.value?.trim()||'';\n   const packPrice=readPrice228('recognizedPrice214');\n   if(!rawPrice||!Number.isFinite(packPrice)||packPrice<=0)throw Error(recognizedText278('validPrice'));\n   newUnitPrice=packPrice/factor;\n  }\n }\n const policyBefore420=recognizedMhdPolicy256(a),mhdPolicy=d.quickMode198==='in'&&['required','optional','none'].includes(d.stockWizard300?.policy420)?d.stockWizard300.policy420:policyBefore420;if(d.quickMode198==='in'&&mhdPolicy==='review')throw Error(recognizedText278('mhdRule'));const mhd=d.quickMode198==='in'&&mhdPolicy!=='none'?(document.getElementById('recognizedMhd198')?.value||''):'',lotId=document.getElementById('recognizedLot198')?.value||'';\n const receiptLots422=d.quickMode198==='in'&&mhdPolicy!=='none'&&d.stockWizard300?.sameMhd422===false&&count>1?validateReceiptLots422(d.stockWizard300,factor,mhdPolicy,!!pack||isWholeQuantityUnit(a.unit)):null;\n if(!receiptLots422&&d.quickMode198==='in'&&(mhdPolicy==='required'||mhd)){const parsed=new Date(mhd+'T00:00:00Z');if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(mhd)||!Number.isFinite(parsed.getTime())||parsed.toISOString().slice(0,10)!==mhd)throw Error(recognizedText278('mhdDate'));}\n if(d.quickMode198==='out'&&qty>Number(a.stock)+1e-8)throw Error(recognizedText278('stockShort'));\n let lotAllocation=null,noMhd=null,multiLots459=null;\n if(d.quickMode198==='out'&&['required','optional'].includes(mhdPolicy)&&Array.isArray(d.stockWizard300?.lots459)){if(d.stockWizard300.reviewSnapshot459&&d.stockWizard300.reviewSnapshot459!==withdrawalSnapshot335(d.existing.productCode))throw Error(recognizedText278('lotChanged'));multiLots459=withdrawalPlan459(d.existing.productCode,d.stockWizard300.lots459,qty);}\n else if(d.quickMode198==='out'&&['required','optional'].includes(mhdPolicy)){const group=withdrawalSelection335(d.existing.productCode,lotId);if(!group)throw Error(recognizedText278('lotChoice'));if(qty>group.qty+1e-8)throw Error(recognizedText278('lotShort'));if(group.noMhd)noMhd={snapshot:withdrawalSnapshot335(d.existing.productCode)};else lotAllocation=expiryAllocation322(group,qty);}\n return {multiLots459,receiptLots422,code:d.existing.productCode,qty,count,packName:pack?.[0]||a.unit,mhd,lotId,lotAllocation,noMhd,mode:d.quickMode198,mhdPolicy,policyBefore420,barcode:d.barcode,newUnitPrice,oldUnitPrice:Number(a.price)||0,shelfDays:shelfDays242(a),receiptDay:localTodayISO()};\n}\nfunction confirmRecognized198(reviewed420=false){\n const d=productGuide194;if(!d?.existing||d.quickPending198||recognizedMismatch198(d)||guideRelated194(d).length&&!d.confirmed198){showFeedback(recognizedText278('barcodeReview'),'warn',4000,recognizedText278('barcodeFirst'));return}\n let entry;try{entry=recognizedInput198(d)}catch(error){showFeedback(recognizedText278('confirmTitle'),'warn',5000,error.message);return}\n const a=state.products[entry.code];d.quickPending198=true;\n const selectedLot335=entry.mode==='out'?withdrawalSelection335(entry.code,entry.lotId):null;\n const selectionText335=entry.multiLots459?entry.multiLots459.rows.map(r=>(r.noMhd?withoutMhdText335(r.commission):bookingDateText342(r.mhd))+' · '+formatQtySmart(r.qty,a.unit)).join('; '):selectedLot335?(selectedLot335.noMhd?withoutMhdText335(selectedLot335.commission):bookingDateText342(selectedLot335.mhd)):'';\n const ask420=reviewed420?(_message,yes)=>yes():askConfirm;\n ask420(`${recognizedText278(entry.mode==='in'?'bookIn':'bookOut')}: ${new Intl.NumberFormat(recognizedLanguage278()==='de'?'de-AT':recognizedLanguage278(),{maximumFractionDigits:2}).format(entry.qty)} ${a.unit} ${a.name}${entry.mhd?' · '+recognizedText278('expiryShort')+' '+formatDate(entry.mhd):''}${selectionText335?' · '+selectionText335:''}${entry.newUnitPrice!==null?' · '+recognizedText278('newPriceShort')+' '+moneyFmt(entry.newUnitPrice)+' / '+a.unit:''}?`,()=>{\n  if(productGuide194!==d||!state.products[entry.code]||recognizedMismatch198(d))return;\n  const ok=operationTransaction(entry.mode==='in'?'Wareneingang per Barcode':'Entnahme per Barcode',entry.mode==='in'?'stockAdd':'scan',()=>{\n   const currentArticle=state.products[entry.code];if(!currentArticle)throw Error(recognizedText278('missingArticle'));\n   if(localBarcode194(entry.barcode)?.productCode!==entry.code)throw Error(recognizedText278('mappingChanged'));\n   if(entry.mode==='in'){\n    if(recognizedMhdPolicy256(currentArticle)!==entry.policyBefore420)throw Error(recognizedText278('mhdChanged'));\n    if(shelfDays242(currentArticle)!==entry.shelfDays||(entry.shelfDays&&localTodayISO()!==entry.receiptDay))throw Error(recognizedText278('shelfChanged'));\n    if(entry.mhdPolicy!==entry.policyBefore420){if(currentAccessUser().role!=='admin')throw Error('Die MHD-Regel darf nur ein Admin ergänzen.');currentArticle.mhdPolicy144=entry.mhdPolicy;currentArticle.mhd=entry.mhdPolicy!=='none';}\n    if(entry.newUnitPrice!==null){\n     if(currentAccessUser().role!=='admin')throw Error(recognizedText278('adminPrice'));\n     if((Number(currentArticle.price)||0)!==entry.oldUnitPrice)throw Error(recognizedText278('priceChanged'));\n     currentArticle.price=entry.newUnitPrice;\n     currentArticle.priceQuoteQty=entry.qty/entry.count;\n     currentArticle.priceQuoteUnit=currentArticle.unit;\n     recordPurchasePrice(entry.code,entry.newUnitPrice,currentArticle.supplier,'Wareneingang per Barcode',{qty:entry.qty,packName:entry.packName,packQty:entry.qty/entry.count});\n    }\n    currentArticle.stock=Number(currentArticle.stock||0)+entry.qty;\n    if(entry.receiptLots422){for(const row of entry.receiptLots422)if(row.mhd)addBatch(entry.code,row.qty,row.mhd,{packName:entry.packName,packSize:entry.qty/entry.count,packCount:row.count,packCode:entry.barcode});}\n    else if(entry.mhd)addBatch(entry.code,entry.qty,entry.mhd,{packName:entry.packName,packSize:entry.qty/entry.count,packCount:entry.count,packCode:entry.barcode});\n    state.log.push(`${stamp()} | ${currentArticle.name} | +${entry.qty} | Barcode-Wareneingang${entry.mhd?' · MHD '+entry.mhd:''}`);\n    addMovement('in',entry.code,entry.qty,{reason:'Bestätigter Barcode '+entry.barcode,unitCost:Number(currentArticle.price)||0});\n   }else{\n    if(recognizedMhdPolicy256(currentArticle)!==entry.mhdPolicy)throw Error(recognizedText278('mhdChanged'));\n    if(entry.multiLots459)withdrawSelected459(entry.code,entry);\n    else if(entry.noMhd)withdrawWithoutMhd335(entry.code,entry);\n    else if(entry.lotAllocation)withdrawExpiry322(entry.code,entry);\n    else takeStockLots(entry.code,entry.qty);\n    state.log.push(`${stamp()} | ${currentArticle.name} | −${entry.qty} | Barcode-Entnahme${selectionText335?' · '+selectionText335:''}`);\n    addMovement('out',entry.code,-entry.qty,{reason:'Bestätigter Barcode '+entry.barcode+(entry.multiLots459?' · '+selectionText335:''),...(entry.multiLots459?{lotAllocation459:entry.multiLots459.rows.map(r=>({...r}))}:{})});\n   }\n  });return afterOperation275(ok,ok=>{\n  if(ok){closeArticleEditor(false);showFeedback(recognizedText278('success'),'success',4000,`${a.name}: ${entry.mode==='in'?'+':'−'}${new Intl.NumberFormat('de-AT',{maximumFractionDigits:2}).format(entry.qty)} ${a.unit}.`)}else d.quickPending198=false;\n ;return ok;});},recognizedText278(entry.mode==='in'?'bookIn':'bookOut'),recognizedText278('confirmStock'));\n if(!reviewed420)setTimeout(()=>{if(productGuide194===d&&!busyOperation275())d.quickPending198=false},500);\n}\nfunction confirmInventory198(){\n const d=productGuide194;if(!d?.existing||d.quickMode198!=='inventory'||d.quickPending198)return;\n const code=d.existing.productCode,a=state.products[code];if(!a||recognizedMismatch198(d))return;\n const batches=(state.batches?.[code]||[]).filter(b=>Number(b.qty)>0&&b.mhd),entries=[];\n if(a.mhd){if(!batches.length){showFeedback('MHD fehlt','warn',4500,'Zuerst die vorhandene Ware einer MHD-Charge zuordnen.');return}\n  if(Math.abs(batches.reduce((sum,b)=>sum+(Number(b.qty)||0),0)-(Number(a.stock)||0))>1e-8){showFeedback('MHD-Chargen prüfen','warn',5000,'Bestand und MHD-Chargen stimmen nicht überein. Bitte zuerst die Chargen im Artikel prüfen.');return}\n  for(const row of document.querySelectorAll('.recognizedCount198')){const value=row.value,qty=Number(value);if(value===''||!Number.isFinite(qty)||qty<0||(isWholeQuantityUnit(a.unit)&&!Number.isInteger(qty))){showFeedback('Anzahl prüfen','warn');return}entries.push({id:row.dataset.id,qty})}\n  if(entries.length!==batches.length){showFeedback('Chargen prüfen','warn');return}\n }else{const raw=document.getElementById('recognizedActual198')?.value||'',qty=Number(raw);if(raw===''||!Number.isFinite(qty)||qty<0||(isWholeQuantityUnit(a.unit)&&!Number.isInteger(qty))){showFeedback('Anzahl prüfen','warn');return}entries.push({id:'',qty})}\n const actual=entries.reduce((sum,row)=>sum+row.qty,0),before=Number(a.stock)||0;\n if(actual===before&&(!a.mhd||entries.every((row,i)=>row.qty===Number(batches[i].qty)))){showFeedback('Bestand stimmt','info');return}\n d.quickPending198=true;\n askConfirm(`${a.name}: ${before} ${a.unit} im Lager, ${actual} ${a.unit} gezählt. Bestand korrigieren?`,()=>{\n  if(productGuide194!==d||!state.products[code])return;\n  const ok=operationTransaction('Einzelinventur per Barcode','inventory',()=>{\n   const product=state.products[code],old=Number(product.stock)||0;\n   if(product.mhd){const available=(state.batches?.[code]||[]).filter(b=>Number(b.qty)>0&&b.mhd);if(available.length!==entries.length||entries.some(row=>!available.some(b=>b.id===row.id)))throw Error('MHD-Chargen wurden inzwischen geändert.');for(const row of entries){const batch=available.find(b=>b.id===row.id);batch.qty=row.qty;if(Number(batch.packSize)>0)batch.packCount=Math.ceil(row.qty/Number(batch.packSize))}state.batches[code]=available.filter(b=>Number(b.qty)>0)}\n   product.stock=actual;state.log.push(`${stamp()} | ${product.name} | ${actual-old>=0?'+':''}${actual-old} | Einzelinventur Barcode | Soll ${old}, Ist ${actual}`);addMovement('inventory',code,actual-old,{reason:'Gezählter Bestand per Barcode'});\n  });return afterOperation275(ok,ok=>{\n  if(ok){closeArticleEditor(false);showFeedback('Inventur gespeichert','success',3500,`${a.name}: ${actual} ${a.unit}.`)}else d.quickPending198=false;\n ;return ok;});},'Bestand korrigieren','Inventur bestätigen');\n setTimeout(()=>{if(productGuide194===d)d.quickPending198=false},500);\n}\nconst renderGuideBefore198=renderGuide194;\nrenderGuide194=function(...args){const result=renderGuideBefore198(...args);recognizedCompact198();return result};\nconst endGuideBefore198=endGuide194;\nendGuide194=function(...args){document.getElementById('articleModal')?.classList.remove('guideExisting198');return endGuideBefore198(...args)};\n\nfunction saveRecognizedPrice227(){\n const d=productGuide194,code=d?.existing?.productCode;\n if(!code||d.quickMode198!=='prices'||currentAccessUser().role!=='admin')return;\n const raw=document.getElementById('recognizedEditPrice227')?.value.trim()||'',price=(()=>{try{return readPrice228('recognizedEditPrice227')}catch(error){showFeedback('Preis prüfen','warn',6000,error.message);return NaN}})();\n if(!raw||!Number.isFinite(price)||price<=0){showFeedback('Preis prüfen','warn',6000,'Einen Netto-Einkaufspreis größer als 0 eingeben.');return}\n const saved=operationTransaction('Einkaufspreis geändert','articles',()=>{\n  if(currentAccessUser().role!=='admin')throw Error('Zum Ändern des Einkaufspreises fehlen die Rechte.');\n  const article=state.products[code];if(!article)throw Error('Artikel nicht mehr vorhanden.');\n  article.price=price;article.priceQuoteQty=1;article.priceQuoteUnit=article.unit;\n  recordPurchasePrice(code,price,article.supplier||'','Preis manuell ergänzt',{qty:1,packName:article.unit,packQty:1});\n });return afterOperation275(saved,saved=>{\n if(saved){renderGuide194();showFeedback('Einkaufspreis gespeichert','success',3000)}\n;return saved;});}\n\nconst STOCK_INFO_TEXT311={\n de:{expired:'Abgelaufen',info:'Info',product:'Produkt',quantity:'Menge',stock:'Aktueller Bestand',value:'Lagerwert',expiry:'MHD',moves:'Bewegungen',price:'Einkaufspreis',noPrice:'Preis fehlt',noExpiry:'Kein MHD erfasst',unassigned:'Ohne zugeordnetes MHD',noMoves:'Noch keine Bewegungen',last30:'Buchungen in 30 Tagen',readonly:'Nur Ansicht',close:'Schließen'},\n en:{expired:'Expired',info:'Info',product:'Product',quantity:'Quantity',stock:'Current stock',value:'Stock value',expiry:'Best before',moves:'Movements',price:'Purchase price',noPrice:'Price missing',noExpiry:'No best-before date recorded',unassigned:'Without assigned expiry',noMoves:'No movements yet',last30:'Transactions in 30 days',readonly:'Read only',close:'Close'},\n pl:{expired:'Termin przekroczony',info:'Info',product:'Produkt',quantity:'Ilość',stock:'Aktualny stan',value:'Wartość magazynu',expiry:'Termin ważności',moves:'Ruchy magazynowe',price:'Cena zakupu',noPrice:'Brak ceny',noExpiry:'Brak terminu ważności',unassigned:'Bez przypisanego terminu',noMoves:'Brak ruchów',last30:'Zapisy w ciągu 30 dni',readonly:'Tylko podgląd',close:'Zamknij'}\n};\nfunction stockInfoText311(key){return STOCK_INFO_TEXT311[recognizedLanguage278()]?.[key]||STOCK_INFO_TEXT311.de[key]||key}\nfunction totalPiecesLabel434(){return ({de:'Gesamtstückzahl',en:'Total pieces',pl:'Łączna liczba sztuk'})[recognizedLanguage278()]||'Gesamtstückzahl'}\nfunction mobileInfoDateRow320(date,qty,unit){\n const parsed=parseMhd147(date),expired=!!parsed?.iso&&parsed.iso<localTodayISO();\n return `<li${expired?' class=\"mobileInfoExpired320\"':''}><b>${esc(formatDate(date))}${expired?' · '+esc(stockInfoText311('expired')):''}</b><span>${esc(formatQtySmart(qty,unit))}</span></li>`;\n}\nfunction mobileProductInfoHtml311(code){\n const info=articleInfoData127(code);if(!info)return '';\n const a=info.a,unit=a.unit||'Stück',dates=new Map();\n for(const batch of info.batches){const date=parseMhd147(batch.mhd)?.iso||batch.mhd;dates.set(date,(dates.get(date)||0)+Number(batch.qty));}\n const assigned=[...dates.values()].reduce((sum,qty)=>sum+qty,0),unassigned=Math.max(0,(Number(a.stock)||0)-assigned);\n const metric=(label,value)=>`<div><dt>${esc(stockInfoText311(label))}</dt><dd>${esc(value)}</dd></div>`;\n const history=info.moves.slice(0,5).map(move=>`<li><span>${esc(articleMovementLabel127(move.type))} · ${esc(formatQtySmart(Number(move.qty)||0,unit))}</span><small>${esc(new Date(Number(move.ts)||move.date).toLocaleString(recognizedLanguage278()==='pl'?'pl-PL':recognizedLanguage278()==='en'?'en-GB':'de-AT'))}</small></li>`).join('');\n return `<div class=\"card mobileInfoCard311\"><div class=\"modalHeader\"><div><h2>${esc(a.name)}</h2><small>${esc(stockInfoText311('readonly'))}</small></div><button class=\"soft\" type=\"button\" onclick=\"closeArticleInfo127()\">${esc(stockInfoText311('close'))}</button></div><dl class=\"guidedReview300\">${metric('stock',formatQtySmart(Number(a.stock)||0,unit))}${totalStockPieces434(a)!==null?`<div><dt>${esc(totalPiecesLabel434())}</dt><dd>${esc(formatQtySmart(totalStockPieces434(a),'Stück'))}</dd></div>`:''}${metric('value',Number(a.price)>0?moneyFmt((Number(a.stock)||0)*Number(a.price)):stockInfoText311('noPrice'))}${metric('price',Number(a.price)>0?moneyFmt(Number(a.price))+' / '+unit:stockInfoText311('noPrice'))}</dl><section><h3>${esc(stockInfoText311('expiry'))}</h3>${dates.size?`<ul class=\"mobileInfoDates311\">${[...dates].map(([date,qty])=>mobileInfoDateRow320(date,qty,unit)).join('')}</ul>`:`<p>${esc(stockInfoText311('noExpiry'))}</p>`}${unassigned>0&&recognizedMhdPolicy256(a)!=='none'?`<p>${esc(stockInfoText311('unassigned'))}: ${esc(formatQtySmart(unassigned,unit))}</p>`:''}</section><section><h3>${esc(stockInfoText311('moves'))}</h3><p>${esc(stockInfoText311('last30'))}: ${info.last30.length}</p>${history?`<ul class=\"mobileInfoMoves311\">${history}</ul>`:`<p>${esc(stockInfoText311('noMoves'))}</p>`}</section></div>`;\n}\nfunction openMobileProductInfo311(code=productGuide194?.existing?.productCode||current){\n if(typeof hasPermission==='function'&&!hasPermission('scan')&&!hasPermission('stock'))return;\n if(!state.products?.[code])return;\n if(productGuide194?.stockWizard300&&typeof captureStockWizard300==='function')captureStockWizard300(productGuide194);\n let modal=document.getElementById('articleInfoModal127');\n if(!modal){modal=document.createElement('div');modal.id='articleInfoModal127';modal.className='modal';document.body.appendChild(modal);}\n modal.innerHTML=mobileProductInfoHtml311(code);modal.style.display='flex';modal.querySelector('button')?.focus();\n}\nif(typeof openArticleInfo127==='function'){\n const openArticleInfoBefore311=openArticleInfo127;\n openArticleInfo127=function(code){if(window.innerWidth<=820)return openMobileProductInfo311(code);return openArticleInfoBefore311(code)};\n}\n"},{"src":"product-flow200.js?v=422","code":"'use strict';\n// Product entry starts with the code. New products continue through front and label photos.\nfunction startProductLookup200(){\n if(!canCreateArticles()){showFeedback('Produkt suchen','warn',4000,'Für diese Aufnahme fehlt die Artikelberechtigung. Bitte den Scanner in Lager verwenden.');return}\n mobileQuickCreate118=window.innerWidth<=820;\n openArticleEditor(null,'recognition420');\n if(document.getElementById('articleModal')?.style.display!=='flex')return;\n if(mobileQuickCreate118)document.getElementById('articleModal').classList.add('mobileCreateMode118');\n startProductGuide194();\n}\nconst renderGuideBefore200=renderGuide194;\nrenderGuide194=function(...args){\n const result=renderGuideBefore200(...args),modal=document.getElementById('articleModal'),d=productGuide194;\n modal?.classList.toggle('guideFocus200',!!d);\n const title=document.getElementById('articleEditorTitle');if(d&&title)title.textContent=d.existing?'Produkt erkannt':d.step===0?'Produkt suchen':'Neues Produkt fotografieren';\n return result;\n};\nconst lookupGuideBefore200=lookupGuideBarcode194;\nlookupGuideBarcode194=async function(...args){\n const d=productGuide194;await lookupGuideBefore200(...args);\n if(productGuide194===d&&d?.step===0&&!d.existing&&d.barcode&&validGtin179(d.barcode)&&!d.lookupBusy){d.step=1;d.message='';renderGuide194()}\n};\nconst endGuideBefore200=endGuide194;\nendGuide194=function(...args){const modal=document.getElementById('articleModal');modal?.classList.remove('guideFocus200');const result=endGuideBefore200(...args);if(modal?.style.display==='flex'&&!productGuide194){const title=document.getElementById('articleEditorTitle');if(title)title.textContent=editingArticleCode?'Artikel bearbeiten':'Artikel anlegen'}return result};\nfunction openRecognizedProduct200(code,packIndex=0){\n if(!state.products[code])return;\n mobileQuickCreate118=window.innerWidth<=820;\n openArticleEditor(null,'recognition420');\n if(document.getElementById('articleModal')?.style.display!=='flex')return;\n if(mobileQuickCreate118)document.getElementById('articleModal').classList.add('mobileCreateMode118');\n startProductGuide194();\n const d=productGuide194;if(!d)return;\n const pack=getPacks(state.products[code],code)[packIndex],barcode=String(pack?.[2]||code);\n d.barcode=barcode;d.existing={productCode:code,packIndex,pack};d.confirmed198=true;renderGuide194();\n}\nconst saveArticleBefore200=saveArticleEditor;\nsaveArticleEditor=function(...args){\n const catalogOnly=typeof productCreationIntent331!=='undefined'&&productCreationIntent331==='catalog';\n const inventorySession=typeof productGuide194!=='undefined'?productGuide194?.inventorySession182:null;\n const bookingBarcode=typeof articleGuideBookingBarcode194!=='undefined'?articleGuideBookingBarcode194:'';\n const receiptDate422=typeof articleGuideMhd422==='string'?articleGuideMhd422:'';\n const wasNew=!editingArticleCode,wasOpen=document.getElementById('articleModal')?.style.display==='flex';\n const before=wasNew&&wasOpen?new Set(Object.keys(state.products||{})):null;\n const result=saveArticleBefore200(...args);\n const code=current,a=state.products?.[code];\n if(!catalogOnly&&before&&a&&!before.has(code)&&!(Number(a.stock)>0)){\n  const resume200=()=>{\n  // Return to the originating inventory; never turn a count into a receipt.\n  if(inventorySession&&typeof photoInventory182!=='undefined'&&photoInventory182?.id===inventorySession){photoDraft144={step:'confirm',code,packIndex:0,inventorySession182:inventorySession};document.getElementById('photoModal144').style.display='flex';renderPhoto144();return result}\n  const bookingHit=bookingBarcode&&typeof localBarcode194==='function'?localBarcode194(bookingBarcode):null;\n  openRecognizedProduct200(code,bookingHit?.productCode===code?bookingHit.packIndex:0);\n  if(productGuide194?.existing?.productCode===code){productGuide194.receiptMhd258=receiptDate422;chooseRecognized198('in');document.getElementById('recognizedQty198')?.focus()}\n  };if(typeof queueArticleContinuation420==='function')queueArticleContinuation420(code,resume200);else resume200();\n }\n return result;\n};\n"},{"src":"menu215.js?v=318","code":"'use strict';\n// One entrance for product recognition; navigation reuses existing article and stock flows.\nfunction setupSearchMenu215(){\n const screen=document.getElementById('mobileScreenSearch');if(!screen)return;\n const title=screen.querySelector('.mobileStockIntro118 h1');if(title)title.textContent='Suche';\n const intro=screen.querySelector('.mobileStockIntro118 p');if(intro)intro.textContent='Produkt finden, dann auswählen, was du tun möchtest.';\n const card=screen.querySelector('.mobileSearchBox')?.closest('.mobileWorkCard');\n if(card&&!document.getElementById('searchMethods215')){\n  const methods=document.createElement('div');methods.id='searchMethods215';\n  methods.innerHTML='<h2>Wie möchtest du suchen?</h2><div class=\"searchMethodsGrid215\"><button type=\"button\" onclick=\"startPhoto144()\" aria-label=\"Fotosuche\" title=\"Fotosuche\"><svg class=\"uiIcon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M3 7h4l2-3h6l2 3h4v13H3z\"/><circle cx=\"12\" cy=\"13\" r=\"4\"/></svg><strong>Foto aufnehmen</strong></button><button type=\"button\" onclick=\"scanBarcode215()\" aria-label=\"Barcode scannen\" title=\"Barcode scannen\"><svg class=\"uiIcon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M3 8V3h5M16 3h5v5M21 16v5h-5M8 21H3v-5M7 7v10M10 7v10M14 7v10M17 7v10\"/></svg><strong>Barcode scannen</strong></button></div><label for=\"mobileArticleSearch\">Oder Name eingeben</label>';\n  card.insertBefore(methods,card.firstChild);\n  const input=document.getElementById('mobileArticleSearch');if(input)input.placeholder='Produktname oder Barcodenummer';\n }\n const tab=document.querySelector('.mobileBottomNav [data-mobiletab=\"search\"] b');if(tab)tab.textContent='Suche';\n}\nfunction scanBarcode215(){\n if(canCreateArticles()){startProductLookup200();return}\n if(!guardPermission('scan','Barcode scannen'))return;\n let input=document.getElementById('searchBarcodeFile215');\n if(!input){input=document.createElement('input');input.id='searchBarcodeFile215';input.type='file';input.accept='image/*';input.setAttribute('capture','environment');input.hidden=true;document.body.appendChild(input);\n  input.onchange=async()=>{const file=input.files?.[0];input.value='';if(!file)return;try{const code=String(await zReadBarcode166(file)).trim();if(!code){showFeedback('Barcode nicht lesbar','warn',4000,'Bitte erneut fotografieren oder die Nummer eingeben.');return}mobileShow('search');const field=document.getElementById('mobileArticleSearch');if(field){field.value=code;mobileSearchSubmit()}}catch{showFeedback('Barcode nicht lesbar','warn',4000,'Bitte die Nummer eingeben.')}};\n }\n input.click();\n}\nconst selectMobileBefore215=mobileSelectProduct;\nmobileSelectProduct=function(code,pack){\n if(state.products?.[code]&&canCreateArticles()&&!zBookingChoice162){\n  const index=pack?getPacks(state.products[code],code).findIndex(p=>p===pack||p[2]&&p[2]===pack[2]):0;\n  openRecognizedProduct200(code,Math.max(0,index));return;\n }\n return selectMobileBefore215(code,pack);\n};\nconst selectPhotoBefore215=selectPhoto144;\nselectPhoto144=function(code,packIndex=0){\n // An inventory session must stay in its own count-and-confirm workflow.\n if(state.products?.[code]&&canCreateArticles()&&!photoDraft144?.inventorySession182){\n  closePhoto144();openRecognizedProduct200(code,packIndex);return;\n }\n return selectPhotoBefore215(code,packIndex);\n};\nfunction beginProductFromPhoto216(){\n const front=photoDraft144?.captured179||lastCapturedPhoto147||'',inventorySession=photoDraft144?.inventorySession182||null;\n closePhoto144();startProductLookup200();\n if(productGuide194&&inventorySession)productGuide194.inventorySession182=inventorySession;\n if(productGuide194&&front){\n  productGuide194.photos.front={data:front,hash:'',parsed:{}};\n  productGuide194.message='Das Produktfoto wurde übernommen. Bitte zuerst den Barcode prüfen.';\n  renderGuide194();\n }\n}\n// An uncertain photo is resolved against the existing article list before any stock change.\npickExisting144=function(){\n const body=document.getElementById('photoBody144');if(!body||!photoDraft144)return;\n let panel=document.getElementById('photoManual216');\n if(!panel){panel=document.createElement('div');panel.id='photoManual216';panel.className='photoManual216';\n  panel.innerHTML='<label for=\"photoSearch216\">Produktname oder Barcode</label><input id=\"photoSearch216\" type=\"search\" autocomplete=\"off\" placeholder=\"z. B. Tomaten Salsa\" oninput=\"searchPhotoProduct216(this.value)\"><div id=\"photoSearchResults216\" aria-live=\"polite\"></div>';\n  const actions=body.querySelector('.photoActions144');if(actions)actions.insertAdjacentElement('beforebegin',panel);else body.appendChild(panel);\n }\n document.getElementById('photoSearch216')?.focus();\n};\nfunction searchPhotoProduct216(query){\n const box=document.getElementById('photoSearchResults216');if(!box)return;\n const text=String(query||'').trim();if(!text){box.innerHTML='';return}\n const exact=findScanCode(text),rows=exact?[[exact.productCode,state.products[exact.productCode],exact.packIndex]]:mobileProductSearch(text,8).map(([code,article])=>[code,article,0]);\n box.innerHTML=rows.length?rows.map(([code,article,index])=>`<button type=\"button\" class=\"soft\" onclick=\"selectPhoto144('${escJs(code)}',${index})\"><b>${esc(article.name)}</b><small>${esc(String(article.stock??0))} ${esc(article.unit||'Stück')} im Lager</small></button>`).join(''):'<p>Kein passender Artikel gefunden. Namen prüfen oder neues Produkt anlegen.</p>';\n}\nconst applyLanguageBefore215=applyLanguage130;\napplyLanguage130=function(...args){const result=applyLanguageBefore215(...args);if(business130().language==='de')setupSearchMenu215();return result};\nsetupSearchMenu215();\n"},{"src":"mobile-capture223.js?v=240","code":"'use strict';\n// One camera entry on the phone. Every branch reuses the existing article or\n// incoming-document review; this module never changes stock by itself.\nlet universalDraft223=null,universalSerial223=0;\nfunction startUniversalCapture223(){\n if(!guardPermission('scan','Fotoaufnahme'))return;\n let input=document.getElementById('universalPhoto223');\n if(!input){input=document.createElement('input');input.id='universalPhoto223';input.type='file';input.accept='image/*';input.setAttribute('capture','environment');input.hidden=true;document.body.appendChild(input);\n  input.onchange=()=>{const file=input.files?.[0];input.value='';if(file)void readUniversalCapture223(file)};\n }\n input.click();\n}\nasync function readUniversalCapture223(file){\n const serial=++universalSerial223;universalDraft223=null;\n openOpsPanel('Foto prüfen','<img id=\"universalPreview223\" hidden alt=\"Aufgenommenes Foto\"><p id=\"universalStatus223\" role=\"status\">Foto wird gelesen …</p>');\n try{\n  const preparedTask=prepareInbound183(file,{onPreview:dataUrl=>{const preview=document.getElementById('universalPreview223');if(serial===universalSerial223&&preview){preview.src=dataUrl;preview.hidden=false}},onProgress:message=>{const status=document.getElementById('universalStatus223');if(serial===universalSerial223&&status)status.textContent=message}}).then(value=>({value}),error=>({error}));\n  const code=String(await zReadBarcode166(file).catch(()=>'')).trim();\n  if(serial!==universalSerial223)return;\n  // A confirmed local GTIN can open the article without waiting for OCR.\n  if(validGtin179(code)){\n   const hit=localBarcode194(code);\n   if(hit){document.getElementById('opsDialog92')?.close();mobileSelectProduct(hit.productCode,hit.pack);return}\n  }\n  const result=await preparedTask;if(result.error)throw result.error;\n  const prepared=result.value,engine=await getDocumentModule226('inbound-document182');\n  if(serial!==universalSerial223)return;\n  const kind=engine.classifyInboundDocument182(prepared.ocr);\n  if(kind==='invoice'||kind==='delivery_note'){\n   if(!guardDocumentSave240()){const status=document.getElementById('universalStatus223');if(status)status.textContent='Belege erfassen nur Administratoren.';return;}\n   await openInbound182(kind);await readInbound182(file,prepared);return;\n  }\n  if(validGtin179(code)){\n   document.getElementById('opsDialog92')?.close();\n   if(canCreateArticles()){\n    startProductLookup200();\n    if(productGuide194){productGuide194.barcode=code;renderGuide194();void lookupGuideBarcode194()}\n   }else showFeedback('Barcode unbekannt','warn',5000,'Zum Anlegen eines Artikels fehlt die Berechtigung.');\n   return;\n  }\n  universalDraft223={file,prepared};\n  openOpsPanel('Was ist auf dem Foto?',`<div class=\"universalChoice223\"><img src=\"${prepared.dataUrl}\" alt=\"Aufgenommenes Foto\"><p>Die Aufnahme ist nicht eindeutig. Wähle, was du fotografiert hast.</p>\n   <button type=\"button\" class=\"primary\" onclick=\"chooseUniversalCapture223('product')\">Produkt oder Etikett</button>\n   ${canSaveDocuments240()?`<button type=\"button\" class=\"soft\" onclick=\"chooseUniversalCapture223('invoice')\">Bon oder Rechnung</button>`:''}\n   ${canSaveDocuments240()?`<button type=\"button\" class=\"soft\" onclick=\"chooseUniversalCapture223('delivery_note')\">Lieferschein</button>`:''}\n   <button type=\"button\" class=\"soft\" onclick=\"document.getElementById('opsDialog92').close()\">Abbrechen</button></div>`);\n  if(prepared.ocrIncomplete)document.querySelector('#opsDialog92 .universalChoice223 p').textContent='Die Schrift wurde nicht sicher gelesen. Wähle die passende Art und prüfe alle Angaben am Original.';\n }catch(error){if(serial===universalSerial223){const status=document.getElementById('universalStatus223');if(status)status.textContent='Foto konnte nicht gelesen werden: '+error.message}}\n}\nasync function chooseUniversalCapture223(kind){\n const draft=universalDraft223;if(!draft)return;if(kind!=='product'&&!guardDocumentSave240())return;universalDraft223=null;\n if(kind==='product'){\n  document.getElementById('opsDialog92')?.close();startPhoto144(draft.file);return;\n }\n if(!guardDocumentSave240()){const status=document.getElementById('universalStatus223');if(status)status.textContent='Belege erfassen nur Administratoren.';return;}\n await openInbound182(kind);await readInbound182(draft.file,draft.prepared);\n}\n"},{"src":"forms231.js?v=422","code":"'use strict';\n// Form adapters keep legacy storage units intact: prices retain their original\n// net/gross basis, and month-only expiry dates use the explicitly shown month end.\nfunction amount231(raw){\n let s=String(raw??'').trim().replace(/[\\s\\u00a0]/g,'');\n if(!s)return null;\n if(!/^-?\\d+(?:[.,]\\d+)*$/.test(s))throw Error('Betrag prüfen.');\n if(s.includes(','))s=s.replace(/\\./g,'').replace(',','.');\n const n=Number(s);if(!Number.isFinite(n)||n<0)throw Error('Einen Betrag ab 0 eingeben.');return n;\n}\nfunction convertPrice231(raw,inputBasis,storedBasis,vat){\n const amount=amount231(raw);if(amount===null)return null;\n if(inputBasis===storedBasis)return amount;\n const rate=amount231(vat);if(rate===null||rate>100)throw Error('USt. vom Beleg angeben, auch bei 0 %.');\n return Math.round((storedBasis==='net'?amount/(1+rate/100):amount*(1+rate/100))*1000000)/1000000;\n}\nfunction expiry231(day,month,year){\n const d=String(day||'').trim(),m=String(month||'').trim(),y=String(year||'').trim();\n if(!d&&!m&&!y)return '';\n if(!/^\\d{1,2}$/.test(m)||!/^\\d{4}$/.test(y)||+m<1||+m>12||+y<1900||+y>2199)throw Error('MHD: Monat und vierstelliges Jahr angeben.');\n const last=new Date(Date.UTC(+y,+m,0)).getUTCDate(),date=d?Number(d):last;\n if(d&&!/^\\d{1,2}$/.test(d)||!Number.isInteger(date)||date<1||date>last)throw Error('MHD: Tag passt nicht zu Monat und Jahr.');\n return y+'-'+m.padStart(2,'0')+'-'+String(date).padStart(2,'0');\n}\nfunction suppliers231(data,query){\n const values=[...(data.suppliers||[]),...(data.supplierProfiles||[]).map(x=>x.name),...Object.values(data.products||{}).map(x=>x.supplier),...(data.deliveryNotes182||[]).map(x=>x.supplier),...(data.inboundInvoices182||[]).map(x=>x.supplier),...(data.purchaseOrders||[]).map(x=>x.supplier)];\n const normalize=s=>String(s).normalize('NFKD').replace(/[\\u0300-\\u036f]/g,'').toLocaleLowerCase('de').replace(/ß/g,'ss');\n const q=normalize(query||'').trim(),unique=new Map();\n for(const value of values){const name=String(value||'').trim();if(name&&!unique.has(normalize(name)))unique.set(normalize(name),name)}\n return [...unique.values()].filter(x=>!q||normalize(x).includes(q)).sort((a,b)=>Number(normalize(b).startsWith(q))-Number(normalize(a).startsWith(q))||a.localeCompare(b,'de')).slice(0,8);\n}\nlet formSequence231=0,lastFeedback231=null;\nfunction visible231(el){return !!el&&el.isConnected&&!el.closest('[hidden]')&&el.getClientRects().length>0}\nfunction activeSurface231(){\n const question=[...document.querySelectorAll('.appConfirm.show')].filter(visible231).at(-1);\n if(question)return question;\n const native=[...document.querySelectorAll('dialog[open]')].filter(visible231).at(-1);\n return native||[...document.querySelectorAll('.modal')].filter(visible231).at(-1)||null;\n}\nfunction inlineMessage231(message,type='warn',surface=activeSurface231()){\n if(!surface)return;\n let box=surface.querySelector('.formFeedback231');\n if(!box){\n  box=document.createElement('div');box.className='formFeedback231';box.setAttribute('role','alert');\n  const anchor=surface.querySelector('button[onclick=\"confirmDelivery182()\"],button[onclick=\"confirmInvoiceStock190()\"]');\n  const content=surface.querySelector('.appConfirmCard')||surface.querySelector(':scope > .card')||surface.querySelector('.opsBody92')||surface;\n  // The .modal itself is a flex overlay. A direct child becomes a second column.\n  if(anchor)anchor.before(box);else content.appendChild(box);\n }\n box.dataset.type=type;box.textContent=message;\n if(type!=='info')box.scrollIntoView?.({block:'nearest'});\n}\nconst feedbackBefore231=showFeedback;\nshowFeedback=function(message,type,duration,detail){\n lastFeedback231={message,type,detail};\n const surface=activeSurface231();\n if(surface){\n  document.getElementById('feedbackToast')?.classList.remove('show');\n  inlineMessage231(detail||message,type,surface);\n  const duplicate=String(detail||message).match(/Positionen (\\d+) und (\\d+)/);\n  if(duplicate)highlightInbound232([Number(duplicate[1]),Number(duplicate[2])],surface);\n }else feedbackBefore231(message,type,duration,detail);\n};\nfunction highlightInbound232(positions,surface){\n const rows=[...surface.querySelectorAll('#inboundRows182 .inboundRow182')],box=surface.querySelector('.formFeedback231');\n if(!box)return;\n const actions=document.createElement('div');actions.className='feedbackActions232';\n for(const position of positions){const row=rows[position-1];if(!row)continue;row.classList.add('inboundProblem232');\n  const button=document.createElement('button');button.type='button';button.className='soft';button.textContent='Position '+position+' anzeigen';\n  button.addEventListener('click',()=>{for(const detail of row.querySelectorAll('details'))detail.open=true;row.scrollIntoView({block:'center',behavior:'smooth'});row.querySelector('.inboundProduct182')?.focus({preventScroll:true})});actions.appendChild(button);\n }box.appendChild(actions);\n}\nfunction label231(input){\n return input.labels?.[0]||input.closest('label')||input.parentElement?.querySelector(':scope > label')||input.closest('.field,.moneyInput143')?.querySelector(':scope > label')||null;\n}\nfunction textLabel231(input){\n const label=label231(input);if(!label)return input.getAttribute('aria-label')||'';\n return [...label.childNodes].filter(x=>x.nodeType===3).map(x=>x.textContent).join(' ').trim()||label.textContent;\n}\nfunction originalField231(input,refresh){\n const descriptor=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value');\n let internal=false;\n const write=value=>{internal=true;descriptor.set.call(input,value);internal=false};\n Object.defineProperty(input,'value',{configurable:true,get(){return descriptor.get.call(this)},set(value){descriptor.set.call(this,value);if(!internal)refresh(String(value??''))}});\n write.during=fn=>{internal=true;try{return fn()}finally{internal=false}};\n return write;\n}\nfunction send231(input,type='input'){input.dispatchEvent(new Event(type,{bubbles:true}))}\nfunction issue231(box,message){box.dataset.invalid231=message||'';const error=box.querySelector('.formError231');if(error){error.textContent=message;error.hidden=!message}}\n// Keep invalid input available to the save guard without announcing an error\n// while the user is still entering the date. Reveal it on the first attempt.\nfunction expiryIssue426(box,message){issue231(box,box.dataset.expirySubmitted426==='true'?message:'');box.dataset.invalid231=message||''}\nfunction moneyInput258(value){\n try{const n=amount231(value);return n===null?'':n.toLocaleString('de-AT',{minimumFractionDigits:2,maximumFractionDigits:2,useGrouping:false});}catch{return String(value??'');}\n}\nfunction installMoney231(input){\n if(input.dataset.adapted231||input.type==='hidden'||input.closest('.priceBasis228,.money231,.expiry231')||input.readOnly)return;\n const label=textLabel231(input),id=input.id||'';\n if(document.getElementById(id+'Basis')||/%|Prozent|gilt für|Preis gilt|Ziel-Wareneinsatz|Gewinn/i.test(label+id))return;\n if(!/€|Preis|\\bEK\\b|\\bVK\\b|Betrag|Kosten|Auftragswert|Netto.Umsatz|Brutto.Umsatz/i.test(label)&&!/^qc(?:Starter|Main|Dessert)130$/.test(id))return;\n if(!['text','number',''].includes(input.type)||input.disabled)return;\n input.dataset.adapted231='money';\n const sharedVat=['foodNetPer145','drinkGross189'].includes(id)?document.getElementById('foodVat145'):null;\n const storedBasis=/brutto/i.test(label)||id==='drinkGross189'?'gross':'net',originalLabel=label231(input),initial=input.value;\n const box=document.createElement('span');box.className='money231';box.innerHTML='<span class=\"moneyEntry259\"><input class=\"moneyAmount231\" type=\"text\" inputmode=\"decimal\"><span class=\"moneySymbol259\" aria-hidden=\"true\">€</span></span><span class=\"moneyOptions231\"><label>Betrag ist <select aria-label=\"Netto oder Brutto\"><option value=\"net\">Netto</option><option value=\"gross\">Brutto</option></select></label><label class=\"vatLabel231\" hidden>USt. % <input type=\"text\" inputmode=\"decimal\" class=\"moneyVat231\" placeholder=\"z. B. 10\"></label></span><small class=\"moneyResult231\"></small><small class=\"formError231\" role=\"alert\" hidden></small>';\n const display=box.querySelector('.moneyAmount231'),basis=box.querySelector('select'),vat=box.querySelector('.moneyVat231');\n display.id=(input.id||'amount-'+(++formSequence231))+'Visible231';display.value=moneyInput258(initial);display.placeholder=input.placeholder;display.setAttribute('aria-label',label.replace(/\\b(?:netto|brutto)(?:[\\s-]+)?/ig,'').trim());basis.value=storedBasis;\n input.type='hidden';input.after(box);\n if(originalLabel?.htmlFor===input.id)originalLabel.htmlFor=display.id;\n // A label with a nested input must point at its visible replacement.\n if(originalLabel?.contains(input))originalLabel.htmlFor=display.id;\n if(originalLabel)for(const node of originalLabel.childNodes)if(node.nodeType===3)node.textContent=node.textContent.replace(/\\b(?:netto|brutto)(?:[\\s-]+)?/ig,'').replace(/\\s{2,}/g,' ');\n const write=originalField231(input,value=>{display.value=moneyInput258(value);basis.value=storedBasis;vat.value='';box.querySelector('.vatLabel231').hidden=true;issue231(box,'');box.querySelector('.moneyResult231').textContent=''});\n function update(type){\n  box.querySelector('.vatLabel231').hidden=!!sharedVat||basis.value===storedBasis;\n  try{const n=convertPrice231(display.value,basis.value,storedBasis,sharedVat?sharedVat.value:vat.value);write(n===null?'':String(n).replace('.',','));issue231(box,'');\n   box.querySelector('.moneyResult231').textContent=n!==null&&basis.value!==storedBasis?'Entspricht '+n.toLocaleString('de-AT',{minimumFractionDigits:2,maximumFractionDigits:2})+' € '+(storedBasis==='net'?'netto':'brutto'):'';\n   write.during(()=>send231(input,type));\n  }catch(e){issue231(box,e.message)}\n }\n display.addEventListener('input',()=>update('input'));display.addEventListener('change',()=>update('change'));\n display.addEventListener('blur',()=>{if(!box.dataset.invalid231){if(display.value.trim())display.value=amount231(display.value).toLocaleString('de-AT',{minimumFractionDigits:2,maximumFractionDigits:2,useGrouping:false})}});\n basis.addEventListener('change',()=>{update('input');if(!box.dataset.invalid231)write.during(()=>send231(input,'change'))});\n vat.addEventListener('input',()=>update('input'));vat.addEventListener('change',()=>update('change'));\n if(sharedVat)for(const type of ['input','change'])sharedVat.addEventListener(type,()=>update('input'));\n input.formAdapter231={box,display,syncTax:sharedVat?()=>update('input'):null,sync(){display.disabled=input.disabled;display.readOnly=input.readOnly;basis.disabled=input.disabled||input.readOnly;vat.disabled=input.disabled||input.readOnly;box.hidden=input.hidden}};\n}\nfunction installExpiry231(input){\n if(input.dataset.adapted231||input.type==='hidden')return;\n if(!((input.type==='date'||input.classList.contains('mhdText147'))&&/mhd|photoDate144|aeBatchDate/i.test(input.id+' '+input.className+' '+textLabel231(input)))&&!input.classList.contains('invoiceMhdRaw191'))return;\n input.dataset.adapted231='expiry';if(input.classList.contains('invoiceMhdRaw191')){const label=label231(input);if(label)for(const n of label.childNodes)if(n.nodeType===3)n.textContent='MHD '}const initial=input.value,box=document.createElement('span');box.className='expiry231';\n box.innerHTML='<span class=\"expiryParts231\"><label>Tag <small>optional</small><input class=\"expiryDay231\" inputmode=\"numeric\" maxlength=\"2\" placeholder=\"TT\" aria-label=\"MHD Tag optional\"></label><label>Monat<input class=\"expiryMonth231\" inputmode=\"numeric\" maxlength=\"2\" placeholder=\"MM\" aria-label=\"MHD Monat\"></label><label>Jahr<input class=\"expiryYear231\" inputmode=\"numeric\" maxlength=\"4\" placeholder=\"JJJJ\" aria-label=\"MHD Jahr\"></label></span><small>Ohne Tag gilt Monatsende.</small><details class=\"expiryDays242\"><summary>Haltbarkeit in Tagen</summary><label class=\"expiryDaysLabel242\">Tage ab Einlagerung<input class=\"expiryDaysInput242\" type=\"number\" min=\"1\" max=\"3650\" step=\"1\" inputmode=\"numeric\" placeholder=\"z. B. 10\"></label></details><small class=\"formError231\" role=\"alert\" hidden></small>';\n const day=box.querySelector('.expiryDay231'),month=box.querySelector('.expiryMonth231'),year=box.querySelector('.expiryYear231'),days=box.querySelector('.expiryDaysInput242');\n const fill=value=>{const m=value.match(/^(\\d{4})-(\\d{2})(?:-(\\d{2}))?$/);day.value=m?(m[3]||''):'';month.value=m?m[2]:'';year.value=m?m[1]:'';issue231(box,'')};\n input.type='hidden';input.after(box);fill(initial);const write=originalField231(input,fill);\n for(const field of [day,month,year])for(const type of ['input','change'])field.addEventListener(type,()=>{\n  field.value=field.value.replace(/\\D/g,'').slice(0,field===year?4:2);\n  try{days.value='';write(expiry231(day.value,month.value,year.value));expiryIssue426(box,'');send231(input,type)}catch(e){write('');expiryIssue426(box,e.message);send231(input,type)}\n  if(type==='input'){field.value=field.value.replace(/\\D/g,'').slice(0,field===year?4:2);if(field.value.length===(field===year?4:2)&&document.activeElement===field){if(field===day)month.focus();else if(field===month)year.focus();else field.blur();}}\n });\n for(const type of ['input','change'])days.addEventListener(type,()=>{try{const value=days.value.trim()?dateAfterDays242(days.value):'';write(value);fill(value);send231(input,type)}catch(error){write('');expiryIssue426(box,error.message);send231(input,type)}});\n input.formAdapter231={box,display:month,sync(){for(const field of [day,month,year,days])field.disabled=input.disabled;box.hidden=input.hidden}};\n}\nfunction installSupplier231(input){\n // Search filters show matching cards already; they are not supplier-entry fields.\n if(input.dataset.supplier231||input.type!=='text')return;\n if(!/lieferant/i.test(textLabel231(input))&&!/^(?:inboundSupplier182|orderSupplier92)$/.test(input.id)&&input.name!=='supplier')return;\n input.dataset.supplier231='true';input.removeAttribute('list');input.autocomplete='off';\n const list=document.createElement('span');list.className='supplierMenu231';list.hidden=true;list.id='supplier-menu-'+(++formSequence231);list.setAttribute('role','listbox');input.after(list);\n input.setAttribute('role','combobox');input.setAttribute('aria-autocomplete','list');input.setAttribute('aria-controls',list.id);input.setAttribute('aria-expanded','false');let selected=-1;\n function close(){list.hidden=true;input.setAttribute('aria-expanded','false');input.removeAttribute('aria-activedescendant');selected=-1}\n function choose(name){input.value=name;close();send231(input);send231(input,'change')}\n function draw(){list.replaceChildren();selected=-1;const names=suppliers231(state,input.value);\n  for(const [i,name] of names.entries()){const button=document.createElement('button');button.type='button';button.textContent=name;button.id=list.id+'-'+i;button.setAttribute('role','option');button.addEventListener('pointerdown',e=>e.preventDefault());button.addEventListener('click',()=>choose(name));list.appendChild(button)}\n  list.hidden=!names.length;input.setAttribute('aria-expanded',String(!!names.length));\n }\n input.addEventListener('input',draw);input.addEventListener('focus',draw);\n input.addEventListener('blur',()=>setTimeout(close,150));\n input.addEventListener('keydown',event=>{const choices=[...list.children];if(event.key==='Escape'){close();return}if(event.key==='ArrowDown'||event.key==='ArrowUp'){if(list.hidden)draw();const options=[...list.children];if(!options.length)return;event.preventDefault();selected=(selected+(event.key==='ArrowDown'?1:-1)+options.length)%options.length;for(const [i,b] of options.entries())b.setAttribute('aria-selected',String(i===selected));input.setAttribute('aria-activedescendant',options[selected].id)}else if(event.key==='Enter'&&!list.hidden&&selected>=0&&choices[selected]){event.preventDefault();choose(choices[selected].textContent)}});\n}\nfunction validateForms231(surface=activeSurface231()||document){\n const invalid=[...surface.querySelectorAll('[data-invalid231]')].find(x=>x.dataset.invalid231&&visible231(x)&&!x.querySelector('input')?.disabled);\n if(!invalid)return true;\n if(invalid.classList.contains('expiry231')){invalid.dataset.expirySubmitted426='true';issue231(invalid,invalid.dataset.invalid231)}\n else inlineMessage231(invalid.dataset.invalid231,'warn',activeSurface231());\n invalid.querySelector('input:not([disabled])')?.focus();invalid.scrollIntoView?.({block:'center'});return false;\n}\nfunction installForms231(root=document){\n for(const input of [...(root.matches?.('input')?[root]:[]),...root.querySelectorAll('input')]){installSupplier231(input);installExpiry231(input);installMoney231(input);input.formAdapter231?.sync()}\n const settings=document.getElementById('tabSettings');if(settings&&!document.getElementById('webVersion231')){const p=document.createElement('p');p.id='webVersion231';p.className='muted';p.textContent='Zapasio · Webstand '+(globalThis.ZAPASIO_WEB_VERSION||'unbekannt');settings.prepend(p)}\n}\nfunction wrapBooking231(fn,selector){\n let pending=false;\n return async function(...args){\n  if(pending||!validateForms231())return false;pending=true;lastFeedback231=null;for(const row of document.querySelectorAll('.inboundProblem232'))row.classList.remove('inboundProblem232');\n  const button=document.querySelector(selector),surface=activeSurface231(),label=button?.textContent;\n  if(button){button.disabled=true;button.textContent='Wird geprüft und gebucht …';button.setAttribute('aria-busy','true')}\n  inlineMessage231('Positionen werden geprüft …','info',surface);\n  try{\n   await new Promise(resolve=>requestAnimationFrame(()=>setTimeout(resolve,0)));\n   const result=await fn.apply(this,args);\n   if(inboundDraft182&&surface?.isConnected&&(!lastFeedback231||lastFeedback231.type==='info'))inlineMessage231('Buchung nicht abgeschlossen. Bitte Angaben und Datenabgleich prüfen. Deine Eingaben bleiben erhalten.','warn',surface);\n   return result;\n  }catch(e){showFeedback('Buchung nicht abgeschlossen','error',7000,e.message);return false}\n  finally{pending=false;if(button?.isConnected){button.disabled=false;button.textContent=label;button.removeAttribute('aria-busy')}}\n };\n}\n// The visible money field formats user text. Legacy cent formatters must not\n// reinterpret a converted fractional net amount as thousands.\nif(typeof formatRecipePriceInput175==='function'){\n const priorFormat231=formatRecipePriceInput175;\n formatRecipePriceInput175=function(input,...args){if(input.dataset.adapted231!=='money')return priorFormat231(input,...args)};\n}\nif(typeof setFoodVat145==='function'){\n const priorVat231=setFoodVat145;\n setFoodVat145=function(...args){const result=priorVat231(...args);for(const id of ['foodNetPer145','drinkGross189'])document.getElementById(id)?.formAdapter231?.syncTax?.();return result};\n}\nconfirmDelivery182=wrapBooking231(confirmDelivery182,'button[onclick=\"confirmDelivery182()\"]');\nconfirmInvoiceStock190=wrapBooking231(confirmInvoiceStock190,'button[onclick=\"confirmInvoiceStock190()\"]');\n// Reuse all existing storage/permission/duplicate checks. Only presentation and\n// input validation are adapted; cloud/session boot is untouched.\ndocument.addEventListener('click',event=>{\n const button=event.target.closest('button,input[type=\"submit\"]');if(!button||button.closest('.supplierMenu231'))return;\n if(/speichern|buchen|bestätigen|übernehmen|weiter|abschließen|fertig/i.test(button.textContent||button.value||'')&&!validateForms231()){event.preventDefault();event.stopImmediatePropagation()}\n},true);\ndocument.addEventListener('submit',event=>{if(!validateForms231(event.target)){event.preventDefault();event.stopImmediatePropagation()}},true);\nlet formScheduled231=false;const formRoots247=new Set();\nnew MutationObserver(records=>{\n for(const r of records){\n  if(r.target.closest?.('.money231,.expiry231,.supplierMenu231,.formFeedback231'))continue;\n  if(r.type==='attributes')formRoots247.add(r.target);\n  else for(const node of r.addedNodes||[])if(node.nodeType===1)formRoots247.add(node);\n }\n if(formScheduled231||!formRoots247.size)return;formScheduled231=true;\n requestAnimationFrame(()=>{formScheduled231=false;const roots=[...formRoots247];formRoots247.clear();for(const root of roots)if(root.isConnected!==false)installForms231(root)});\n}).observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['disabled','readonly','hidden']});\ninstallForms231();\n\n"},{"src":"barcode-variable235.js?v=1","code":"'use strict';\n/*\n * GS1 prefixes 02 and 20-29 are restricted-circulation numbers. They can\n * contain a price or weight that changes for every pack, so they must never\n * become a permanent article or pack barcode.\n */\nfunction restrictedCirculationBarcode235(value){\n const code=String(value||'').replace(/[\\s-]/g,'');\n return /^(?:02|2[0-9])\\d{11}$/.test(code)||/^2\\d{11}$/.test(code)||/^[02]\\d{7}$/.test(code);\n}\nfunction restrictedBarcodeMessage235(code){\n return `Code ${code} ist ein Kassen-, Preis- oder Gewichtscode und keine dauerhafte Artikelnummer. Produktname und Etikett fotografieren; Zapasio legt dafür einen internen Artikelcode an.`;\n}\nfunction previouslyStoredRestricted235(code){\n if(!editingArticleCode||!state.products?.[editingArticleCode])return false;\n if(String(editingArticleCode).toUpperCase()===code)return true;\n return getPacks(state.products[editingArticleCode],editingArticleCode).some(pack=>String(pack?.[2]||'').toUpperCase()===code);\n}\n\nconst findScanCodeBefore235=findScanCode;\nfindScanCode=function(value){\n if(restrictedCirculationBarcode235(value))return null;\n return findScanCodeBefore235(value);\n};\n\nconst checkMainBarcodeBefore235=checkMainBarcodeLive;\ncheckMainBarcodeLive=function(){\n const field=document.getElementById('aeCode'),status=document.getElementById('aeCodeStatus');\n const code=String(field?.value||'').trim().toUpperCase();\n if(!restrictedCirculationBarcode235(code))return checkMainBarcodeBefore235();\n const legacy=previouslyStoredRestricted235(code);\n field?.classList?.remove('barcodeOk');field?.classList?.add(legacy?'barcodeLegacy235':'barcodeConflict');\n if(status){status.className='barcodeStatus '+(legacy?'':'bad');status.textContent=legacy?'Bestehender Preis- oder Gewichtscode. Er bleibt nur für den Altartikel sichtbar und wird nicht mehr zur automatischen Produkterkennung verwendet.':restrictedBarcodeMessage235(code)}\n if(typeof renderArticleBarcodeChoice427==='function')renderArticleBarcodeChoice427();\n return legacy;\n};\n\nconst checkPackBarcodeBefore235=checkPackBarcodeLive;\ncheckPackBarcodeLive=function(input){\n const code=String(input?.value||'').trim().toUpperCase();\n if(!restrictedCirculationBarcode235(code))return checkPackBarcodeBefore235(input);\n const legacy=previouslyStoredRestricted235(code);\n input?.classList?.remove('barcodeOk');input?.classList?.add(legacy?'barcodeLegacy235':'barcodeConflict');\n if(input)input.title=legacy?'Bestehender Preis- oder Gewichtscode; keine automatische Produkterkennung.':restrictedBarcodeMessage235(code);\n return legacy;\n};\n\nconst prefillNewArticleBefore235=prefillNewArticle;\nprefillNewArticle=function(name='',unit='',barcode=''){\n const code=String(barcode||'').trim().toUpperCase();\n const restricted=restrictedCirculationBarcode235(code);\n const result=prefillNewArticleBefore235(name,unit,restricted?'':barcode);\n if(restricted)showFeedback('Gewichtsbarcode nicht als Artikelnummer gespeichert','info',6500,restrictedBarcodeMessage235(code));\n return result;\n};\n\nconst guideStepBefore235=guideStep194;\nguideStep194=function(delta){\n const draft=productGuide194,field=document.getElementById('guideBarcode194');\n if(draft&&draft.step===0&&delta>0){\n  const code=String(field?.value||draft.barcode||'').replace(/[\\s-]/g,'');\n  if(restrictedCirculationBarcode235(code)){\n   draft.restrictedBarcode235=code;draft.barcode='';draft.lookup=null;draft.existing=null;draft.lookupSerial++;\n   draft.step=1;draft.message='Preis- oder Gewichtscode erkannt. Er wird nicht als Artikelnummer gespeichert. Jetzt Produktname und Etikett fotografieren.';\n   renderGuide194();return;\n  }\n }\n return guideStepBefore235(delta);\n};\n\nconst lookupGuideBarcodeBefore235=lookupGuideBarcode194;\nlookupGuideBarcode194=async function(...args){\n const draft=productGuide194,field=document.getElementById('guideBarcode194');\n if(draft){\n  const code=String(field?.value||draft.barcode||'').replace(/[\\s-]/g,'');\n  if(restrictedCirculationBarcode235(code)){\n   draft.restrictedBarcode235=code;draft.barcode='';draft.lookup=null;draft.existing=null;draft.lookupSerial++;\n   draft.step=1;draft.message='Preis- oder Gewichtscode erkannt. Er wird nicht als Artikelnummer gespeichert. Jetzt Produktname und Etikett fotografieren.';\n   renderGuide194();return;\n  }\n }\n return lookupGuideBarcodeBefore235(...args);\n};\n\nconst applyGuideBefore235=applyGuide194;\napplyGuide194=function(...args){\n const code=String(productGuide194?.barcode||'').replace(/[\\s-]/g,'');\n if(restrictedCirculationBarcode235(code)){\n  productGuide194.restrictedBarcode235=code;productGuide194.barcode='';productGuide194.message='Der Preis- oder Gewichtscode wird nicht gespeichert. Bitte die Angaben erneut prüfen.';renderGuide194();return;\n }\n return applyGuideBefore235(...args);\n};\n\nconst saveArticleBefore235=saveArticleEditor;\nsaveArticleEditor=function(...args){\n const fields=[document.getElementById('aeCode'),...document.querySelectorAll('#aePacks .aePackBarcode')];\n for(const field of fields){\n  const code=String(field?.value||'').trim().toUpperCase();\n  if(!restrictedCirculationBarcode235(code)||previouslyStoredRestricted235(code))continue;\n  field?.focus?.();field?.classList?.add('barcodeConflict');\n  showFeedback('Barcode nicht als Artikelnummer speicherbar','warn',7000,restrictedBarcodeMessage235(code));\n  return;\n }\n return saveArticleBefore235(...args);\n};\n"},{"src":"settings-language236.js?v=408&release=411","code":"/* Settings language slice: the settings hub and business/language card only. */\nconst SETTINGS_LANGUAGE236={\n  de:{\n    navigation:'Einstellungen Hauptbereiche',\n    groups:{\n      account:{title:'Mein Konto',text:'Persönliche Ansicht, Sprache und Zugriffsrechte.'},\n      business:{title:'Betrieb',text:'Betriebssitz, Standorte, Lieferanten und Zeitplanung.'},\n      stock:{title:'Lager & Artikel',text:'Strukturen, Kennzeichnung, Barcodes und Lagerregeln.'},\n      planning:{title:'Planung & Kalkulation',text:'Kostensätze, Vorlagen und schnelle Preisberechnung.'},\n      data:{title:'Daten & Sicherung',text:'Gemeinsamer Datenstand, Sicherungen und Protokolle.'}\n    },\n    card:{title:'Betriebssitz & Sprache',country:'Betrieb ist zu Hause in',language:'Sprache der App',hourly:'Personalkosten pro Stunde',kilometres:'Kilometerkosten',hint:'Steuersätze werden in Kalkulationen sichtbar vorgeschlagen und können je Leistung geändert werden.',countries:{AT:'Österreich',DE:'Deutschland',PL:'Polen'}},\n    saved:'Betriebseinstellung gespeichert'\n  },\n  pl:{\n    navigation:'Główne obszary ustawień',\n    groups:{\n      account:{title:'Moje konto',text:'Widok osobisty, język i uprawnienia dostępu.'},\n      business:{title:'Firma',text:'Siedziba firmy, lokalizacje, dostawcy i planowanie czasu.'},\n      stock:{title:'Magazyn i artykuły',text:'Struktury, oznaczenia, kody kreskowe i zasady magazynowe.'},\n      planning:{title:'Planowanie i kalkulacje',text:'Stawki kosztów, szablony i szybka kalkulacja cen.'},\n      data:{title:'Dane i kopie zapasowe',text:'Wspólne dane, kopie zapasowe i dzienniki.'}\n    },\n    card:{title:'Siedziba firmy i język',country:'Kraj siedziby firmy',language:'Język aplikacji',hourly:'Koszt pracy za godzinę',kilometres:'Koszt za kilometr',hint:'Stawki podatku są proponowane w kalkulacjach i można je zmienić dla każdej usługi.',countries:{AT:'Austria',DE:'Niemcy',PL:'Polska'}},\n    saved:'Ustawienie firmy zostało zapisane'\n  },\n  en:{\n    navigation:'Settings sections',\n    groups:{\n      account:{title:'My account',text:'Personal view, language and access permissions.'},\n      business:{title:'Business',text:'Business location, sites, suppliers and scheduling.'},\n      stock:{title:'Stock and items',text:'Structures, labelling, barcodes and stock rules.'},\n      planning:{title:'Planning and calculations',text:'Cost rates, templates and quick price calculations.'},\n      data:{title:'Data and backups',text:'Shared data, backups and logs.'}\n    },\n    card:{title:'Business location and language',country:'Business is based in',language:'App language',hourly:'Staff cost per hour',kilometres:'Cost per kilometre',hint:'Tax rates are suggested visibly in calculations and can be changed for each service.',countries:{AT:'Austria',DE:'Germany',PL:'Poland'}},\n    saved:'Business setting saved'\n  }\n};\n\nfunction settingsText236(lang=business130().language){return SETTINGS_LANGUAGE236[lang]||SETTINGS_LANGUAGE236.de}\n\nfunction refreshSettingsLanguage236(){\n  const lang=business130().language,t=settingsText236(lang);\n  const nav=document.querySelector('#settingsWorkshop130 .settingsMainTabs130');\n  if(nav)nav.setAttribute('aria-label',t.navigation);\n  document.querySelectorAll('[data-settings130]').forEach(button=>{const group=t.groups[button.dataset.settings130];if(group)button.textContent=group.title});\n  for(const [key,group] of Object.entries(t.groups)){\n    const intro=document.querySelector(`#settingsPanel130_${key} .settingsIntro130`);\n    if(!intro)continue;\n    const title=intro.querySelector('h2'),text=intro.querySelector('p');\n    if(title)title.textContent=group.title;\n    if(text)text.textContent=group.text;\n  }\n  const card=document.getElementById('businessCountry130');\n  if(!card)return;\n  const title=card.querySelector(':scope>summary span:first-child');\n  if(title)title.textContent=t.card.title;\n  const fields=[...card.querySelectorAll('.masterGroupFields>.field')];\n  const labels=[t.card.country,t.card.language,t.card.hourly,t.card.kilometres];\n  fields.forEach((field,index)=>{const label=field.querySelector('label'),control=field.querySelector('[onchange*=\"setBusiness130\"]'),key=control?.getAttribute?.('onchange')?.match(/setBusiness130\\(['\"]([^'\"]+)/)?.[1],currency={de:'Währung',en:'Currency',pl:'Waluta'}[lang]||'Währung',names={country:t.card.country,language:t.card.language,hourly:t.card.hourly,kmRate:t.card.kilometres,currency};const text=key?names[key]:labels[index];if(label&&text)label.textContent=text});\n  const country=fields[0]?.querySelector('select');\n  country?.querySelectorAll('option').forEach(option=>{if(t.card.countries[option.value])option.textContent=t.card.countries[option.value]});\n  const hint=card.querySelector(':scope>div>p.muted');\n  if(hint)hint.textContent=t.card.hint;\n}\n\nconst applyLanguageBefore236=applyLanguage130;\napplyLanguage130=function(...args){const result=applyLanguageBefore236(...args);refreshSettingsLanguage236();return result};\n\nconst organiseSettingsBefore236=organiseSettings130;\norganiseSettings130=function(...args){const result=organiseSettingsBefore236(...args);refreshSettingsLanguage236();return result};\n\nsetBusiness130=function(key,value){\n  const old=clone(business130());\n  state.business130[key]=['hourly','kmRate','vat'].includes(key)?Math.max(0,Number(String(value).replace(',','.'))||0):value;\n  if(key==='country')state.business130.vat=VAT130[value].rates[0];\n  const language=key==='language'?value:business130().language;\n  if(!persistState(settingsText236(language).saved,false)){state.business130=old;return}\n  render();\n  if(key==='language')applyLanguage130(value);\n};\n\nrefreshSettingsLanguage236();\n"},{"src":"dialog-design239.js?v=247","code":"'use strict';\n// Keep the meaning of dynamically rendered action buttons consistent.\nfunction styleDialogActions239(root=document){\n const buttons=[...(root.querySelectorAll?.('button')||[])];if(root.matches?.('button'))buttons.unshift(root);\n for(const button of buttons){\n  const label=button.textContent.trim().replace(/\\s+/g,' ');\n  button.classList.toggle('cancelAction239',/^(?:Fotos )?abbrechen$/i.test(label));\n  if(/^(?:Neuen? )?Artikel anlegen$/.test(label)){\n   button.classList.remove('danger');button.classList.add('primary');\n  }\n }\n}\nstyleDialogActions239();\nnew MutationObserver(records=>{for(const record of records){if(record.type==='characterData'){const button=record.target.parentElement?.closest('button');if(button)styleDialogActions239(button)}else{if(record.target.matches?.('button'))styleDialogActions239(record.target);for(const node of record.addedNodes||[])if(node.nodeType===1)styleDialogActions239(node)}}}).observe(document.body,{childList:true,subtree:true,characterData:true});\n"},{"src":"account-documents240.js?v=217","code":"'use strict';\nfunction canSaveDocuments240(){return currentAccessUser().role==='admin'}\nfunction guardDocumentSave240(){\n if(canSaveDocuments240())return true;\n showFeedback('Nur für Administratoren','warn',4000,'Rechnungen und Lieferscheine kannst du nicht speichern oder buchen.');return false;\n}\n// Guard both entry and save. Ordinary product stock entry keeps its own rights.\nfor(const name of ['openInboundAuto183','openInbound182','autoReadInbound183','autoChooseInbound183','readInbound182','confirmDelivery182','confirmInvoice182','confirmInvoiceStock190']){\n const original=window[name];if(typeof original!=='function')continue;\n window[name]=function(...args){if(!guardDocumentSave240())return false;return original.apply(this,args)};\n}\nfunction applyDocumentAccess240(root=document){\n const testSettings=document.getElementById('belegSettings217');if(testSettings)testSettings.hidden=!canSaveDocuments240();\n const buttons=[...(root.querySelectorAll?.('button[onclick]')||[])];if(root.matches?.('button[onclick]'))buttons.unshift(root);\n for(const button of buttons){\n  const action=button.getAttribute('onclick')||'';\n  if(/\\b(?:homeCaptureReceipt183|openInboundAuto183|openInbound182|openBelegTest216)\\(/.test(action))button.hidden=!canSaveDocuments240();\n }\n}\napplyDocumentAccess240();\nlet documentAccessRole247=currentAccessUser().role;\nnew MutationObserver(records=>{const role=currentAccessUser().role;if(role!==documentAccessRole247){documentAccessRole247=role;applyDocumentAccess240();return}for(const record of records)for(const node of record.addedNodes||[])if(node.nodeType===1)applyDocumentAccess240(node)}).observe(document.body,{childList:true,subtree:true});\nfunction zOpenSettings240(){\n document.getElementById('zAccountMenu162')?.setAttribute('hidden','');\n document.getElementById('zAccountButton162')?.setAttribute('aria-expanded','false');\n if(globalThis.gsCloud?.enabled&&!globalThis.gsCloud.user)return;\n closeSidebar();\n if(hasPermission('settings')){\n  if(matchMedia('(max-width:820px)').matches)document.body.classList.add('mobile-detail-mode');\n  showTab('settings');window.scrollTo({top:0,behavior:'smooth'});return;\n }\n // Reuse the existing personal settings card, without copying its input IDs\n // or exposing any business settings to an employee.\n renderPersonalSettings123();\n const card=document.getElementById('personalSettings123');if(!card)return;\n const parent=card.parentElement,next=card.nextSibling;\n openOpsPanel('Einstellungen','<div id=\"personalAccount240\"></div>');\n document.getElementById('personalAccount240').appendChild(card);card.open=true;\n document.getElementById('opsDialog92').addEventListener('close',()=>{\n  if(parent?.isConnected)parent.insertBefore(card,next?.parentElement===parent?next:null);\n },{once:true});\n}\n"},{"src":"warning-review243.js?v=362","code":"'use strict';\nlet warningAction243=null;\nfunction warningDate243(batch){\n const until=batch?.reviewUntil243;\n return until&&validDate144(until)&&until>batch.mhd?until:batch?.mhd||'';\n}\nfunction warningStatus243(batch){\n const date=warningDate243(batch),days=daysFromToday(date),reviewed=date!==batch.mhd;\n if(reviewed)return days<0?'Erneute Prüfung seit '+Math.abs(days)+' Tagen fällig':days===0?'Heute erneut prüfen':'In '+days+' Tagen erneut prüfen';\n return days<0?'MHD seit '+Math.abs(days)+' Tagen überschritten':days===0?'MHD läuft heute ab':'MHD läuft in '+days+' Tagen ab';\n}\nfunction warningLot243(code,id){\n const article=state.products?.[code],batch=(state.batches?.[code]||[]).find(b=>b.id===id);\n if(!article||!id||!batch||!(Number(batch.qty)>0))throw Error('Diese Charge ist nicht mehr vorhanden. Bitte die Warnung neu öffnen.');\n return {article,batch};\n}\nfunction warningLotHtml243(w){\n const {article,batch}=warningLot243(w.productCode,w.batchId),pack=mhdPackDisplay(w.productCode,batch);\n return `<section class=\"warningLot243\"><h3>${esc(article.name)}</h3><p class=\"warningReason243\" role=\"status\">${esc(warningStatus243(batch))}</p><p><b>${esc(pack?.label||formatQtySmart(batch.qty,article.unit))}</b>${pack?' · '+esc(formatQtySmart(batch.qty,article.unit)):''}<br>Ursprüngliches MHD: <b>${esc(formatDate(batch.mhd))}</b>${batch.reviewUntil243?'<br>Weiterverwendung bestätigt bis '+esc(formatDate(batch.reviewUntil243)):''}</p><div class=\"warningActions243\"><button type=\"button\" class=\"danger\" data-code=\"${esc(w.productCode)}\" data-lot=\"${esc(batch.id)}\" onclick=\"chooseWarningAction243(this.dataset.code,this.dataset.lot,'waste')\" ${hasPermission('scan')?'':'disabled'}>Ausschuss</button><button type=\"button\" class=\"primary\" data-code=\"${esc(w.productCode)}\" data-lot=\"${esc(batch.id)}\" onclick=\"chooseWarningAction243(this.dataset.code,this.dataset.lot,'review')\" ${hasPermission('articles')?'':'disabled'}>Weiterverwendung</button></div></section>`;\n}\nfunction openWarningGroup243(key){\n const group=groupedWarnings().find(g=>g.key===key);if(!group){showFeedback('Warnung erledigt','info',3000,'Der aktuelle Bestand hat diese Warnung nicht mehr.');return}\n warningAction243=null;\n let html='';\n if(group.stock){\n  const stockCode=warningAdminEditCode({...group,mhd:[]}),order=group.stock.order;\n  html+=`<section class=\"warningLot243\"><h3>Mindestbestand erreicht</h3><p>${esc(group.stock.text)}</p>${order?'<p>Bestellvorschlag: '+esc(order.count)+' × '+esc(order.name)+'</p>':''}<div class=\"warningActions243\"><button type=\"button\" class=\"primary\" onclick=\"if(guardPermission('shopping','Einkauf')){document.getElementById('opsDialog92').close();openSelfShopping()}\" ${hasPermission('shopping')?'':'disabled'}>Einkauf öffnen</button>${!order&&stockCode?`<button class=\"soft\" data-code=\"${esc(stockCode)}\" onclick=\"document.getElementById('opsDialog92').close();openWarningArticle(this.dataset.code,'pack')\">Bestellpackung prüfen</button>`:''}</div></section>`;\n }\n\n for(const w of group.mhd){try{html+=warningLotHtml243(w)}catch(error){html+=`<p>${esc(error.message)}</p>`}}\n openOpsPanel('Artikel prüfen',html);\n document.getElementById('opsDialog92')?.addEventListener('close',()=>{warningAction243=null},{once:true});\n}\nfunction chooseWarningAction243(code,id,kind){\n const permission=kind==='waste'?'scan':'articles';if(!['waste','review'].includes(kind)||!guardPermission(permission,'diese Chargenentscheidung'))return;\n let article,batch;try{({article,batch}=warningLot243(code,id))}catch(error){showFeedback('Charge prüfen','warn',4000,error.message);return}\n const packSize=Number(batch.packSize)||0,packCount=packSize>0?Number(batch.qty)/packSize:0;\n const usesPack=kind==='waste'&&packSize>0&&Math.abs(packCount-Math.round(packCount))<1e-8, factor=usesPack?packSize:1;\n warningAction243={code,id,kind,qty:Number(batch.qty),mhd:batch.mhd,until:batch.reviewUntil243||'',factor,usesPack,done:false,applying:false};\n const amount=Number(batch.qty)/factor,unit=usesPack?(batch.packName||'Packungen'):article.unit;\n openOpsPanel(kind==='waste'?'Ausschuss buchen':'Weiterverwendung prüfen',`<form id=\"warningActionForm243\" onsubmit=\"event.preventDefault();confirmWarningAction243()\"><h3>${esc(article.name)}</h3><p class=\"warningReason243\">${esc(warningStatus243(batch))}</p><p>${esc(formatQtySmart(batch.qty,article.unit))} · Ursprüngliches MHD ${esc(formatDate(batch.mhd))}</p>${kind==='waste'?`<label>Menge (${esc(unit)})<input id=\"warningQty243\" type=\"number\" min=\"${usesPack||isWholeQuantityUnit(article.unit)?1:.000001}\" max=\"${amount}\" step=\"${usesPack||isWholeQuantityUnit(article.unit)?1:'any'}\" inputmode=\"decimal\" value=\"${amount}\"></label>`:`<label>Weiterverwenden bis<input id=\"warningUntil243\" type=\"date\"></label><label class=\"warningCheck243\"><input id=\"warningResponsibility243\" type=\"checkbox\"> Ware geprüft. Ich bestätige die Weiterverwendung auf eigene Verantwortung.</label><p class=\"tiny\">Das ursprüngliche MHD bleibt erhalten.</p>`}${kind==='waste'?`<label>Grund<select id=\"warningReasonChoice272\"><option value=\"\">Bitte wählen</option><option>Abgelaufen</option><option>Verdorben</option><option>Beschädigt</option><option value=\"other\">Eigener Grund</option></select></label><label>Eigene Angabe / Ergänzung (optional)<input id=\"warningReason243\" type=\"text\" maxlength=\"500\" placeholder=\"Bei Bedarf ergänzen\"></label>`:`<label>Grund<input id=\"warningReason243\" type=\"text\" maxlength=\"500\" placeholder=\"Ergebnis deiner Prüfung\"></label>`}<div class=\"warningActions243\"><button type=\"button\" class=\"soft\" data-code=\"${esc(code)}\" onclick=\"backToWarning243(this.dataset.code)\">Zurück</button><button type=\"submit\" class=\"${kind==='waste'?'danger':'primary'}\">${kind==='waste'?'Ausschuss prüfen':'Entscheidung prüfen'}</button></div></form>`);\n}\nfunction backToWarning243(code){\n const group=groupedWarnings().find(g=>g.mhd.some(w=>w.productCode===code));\n if(group)openWarningGroup243(group.key);else document.getElementById('opsDialog92')?.close();\n}\nfunction confirmWarningAction243(){\n const ctx=warningAction243;if(!ctx||ctx.done||ctx.applying)return;\n let amount,date,reason;\n try{\n  const {article,batch}=warningLot243(ctx.code,ctx.id);reason=document.getElementById('warningReason243')?.value.trim()||'';if(ctx.kind==='waste'){const choice=document.getElementById('warningReasonChoice272')?.value;if(['Abgelaufen','Verdorben','Beschädigt'].includes(choice))reason=choice+(reason?' · '+reason:'');}\n  if(!reason)throw Error('Bitte einen kurzen Grund angeben.');\n  if(ctx.kind==='waste'){\n   const raw=Number(document.getElementById('warningQty243')?.value);amount=raw*ctx.factor;\n   if(!Number.isFinite(amount)||amount<=0||amount>Number(batch.qty)+1e-8||((ctx.usesPack||isWholeQuantityUnit(article.unit))&&!Number.isInteger(raw)))throw Error('Die Ausschussmenge muss zur vorhandenen Charge passen.');\n  }else{\n   date=document.getElementById('warningUntil243')?.value||'';\n   if(!validDate144(date)||date<=localTodayISO()||date<=warningDate243(batch))throw Error('Bitte ein Datum nach heute und nach der bisherigen Frist wählen.');\n   if(!document.getElementById('warningResponsibility243')?.checked)throw Error('Bitte die Prüfung auf eigene Verantwortung bestätigen.');\n  }\n }catch(error){showFeedback('Entscheidung prüfen','warn',5000,error.message);return}\n askConfirm(ctx.kind==='waste'?`${formatQtySmart(amount,state.products[ctx.code].unit)} ${state.products[ctx.code].name} aus dieser Charge als Ausschuss buchen?`:`Weiterverwendung von ${state.products[ctx.code].name} bis ${formatDate(date)} auf eigene Verantwortung bestätigen? Ursprüngliches MHD: ${formatDate(ctx.mhd)}.`,()=>{\n  if(warningAction243!==ctx||ctx.done||ctx.applying||!document.getElementById('warningActionForm243'))return;\n  ctx.applying=true;\n  const ok=operationTransaction(ctx.kind==='waste'?'Ausschuss aus MHD-Charge':'Weiterverwendung bestätigt',ctx.kind==='waste'?'scan':'articles',()=>{\n   const {article,batch}=warningLot243(ctx.code,ctx.id);\n   if(Number(batch.qty)!==ctx.qty||batch.mhd!==ctx.mhd||(batch.reviewUntil243||'')!==ctx.until)throw Error('Die Charge hat sich geändert. Bitte die Warnung neu prüfen.');\n   state.log??=[];const user=currentAccessUser(),actor=user.name||user.id||state.currentAccessUserId||'';\n   if(ctx.kind==='waste'){\n    const available=batch.consignment168?Number(article.consignmentQty168)||0:Number(article.stock)-(Number(article.consignmentQty168)||0);\n    if(amount>available+1e-8||amount>Number(article.stock)+1e-8)throw Error('Bestand und Charge stimmen nicht überein. Bitte neu prüfen.');\n    batch.qty-=amount;article.stock=Number(article.stock)-amount;\n    if(batch.consignment168)article.consignmentQty168=Number(article.consignmentQty168)-amount;\n    if(Number(batch.packSize)>0)batch.packCount=Math.ceil(Math.max(0,batch.qty-1e-9)/Number(batch.packSize));\n    state.batches[ctx.code]=state.batches[ctx.code].filter(b=>Number(b.qty)>1e-8);\n    const detail=`${reason} · Charge ${ctx.id} · MHD ${ctx.mhd}${ctx.until?' · Weiterverwendung bis '+ctx.until:''}`;\n    addMovement('waste',ctx.code,-amount,{reason:detail,unitCost:Number(article.price)||0,batchId:ctx.id,batchLabel362:'MHD '+ctx.mhd,source:'warning243'});\n    state.log.push(`${stamp()} | ${article.name} | −${amount} | Ausschuss | ${detail} | ${actor}`);\n   }else{\n    if(date<=localTodayISO())throw Error('Der Tag hat sich geändert. Bitte das Datum neu prüfen.');\n    batch.mhdReviews243??=[];\n    batch.mhdReviews243.push({at:new Date().toISOString(),userId:state.currentAccessUserId||user.id||'',userName:actor,originalMhd:batch.mhd,previousUntil:ctx.until,until:date,reason,qty:Number(batch.qty),ownResponsibility:true});\n    batch.reviewUntil243=date;\n    state.log.push(`${stamp()} | ${article.name} | Weiterverwendung auf eigene Verantwortung bis ${date} | Charge ${ctx.id} | ursprüngliches MHD ${batch.mhd} | ${reason} | ${actor}`);\n   }\n  });return afterOperation275(ok,ok=>{\n  ctx.applying=false;\n  if(ok){ctx.done=true;document.getElementById('opsDialog92')?.close();showFeedback(ctx.kind==='waste'?'Ausschuss gebucht':'Entscheidung gespeichert','success',4000)}\n ;return ok;});},ctx.kind==='waste'?'Ausschuss buchen':'Auf eigene Verantwortung bestätigen','Entscheidung bestätigen');\n}\nconst warningArticleBefore243=openWarningArticle;\nopenWarningArticle=function(code,problem='article'){\n if(problem==='mhd'){\n  const group=groupedWarnings().find(g=>g.mhd.some(w=>w.productCode===code));\n  if(group)return openWarningGroup243(group.key);\n  showFeedback('Keine aktuelle MHD-Warnung','info');return;\n }\n return warningArticleBefore243(code,problem);\n};\nconst warningCardBefore243=warningCardHtml;\nwarningCardHtml=function(group,...args){\n let html=warningCardBefore243(group,...args);if(!group)return html;\n const action=`<div class='warningFixBar'><button class='soft' data-warning-key=\"${esc(group.key)}\" onclick=\"openWarningGroup243(this.dataset.warningKey)\">Artikel prüfen</button></div>`;\n if(/<div class='warningFixBar'>/.test(html))return html.replace(/<div class='warningFixBar'>[\\s\\S]*?<\\/div>/,action);\n return html.replace(/<\\/details>$/,action+'</details>');\n};\n// Refresh warning surfaces after installing the context-preserving entry point.\n/* Initial full refresh is performed after all patches load. */\n"},{"src":"inventory245.js?v=275","code":"'use strict';\n// Shared inventory review for photo, barcode and the manual queue. No stock change on open.\nlet inventory245=null,inventoryQueue245=null;\nfunction inventorySnapshot245(code){\n const a=state.products[code];if(!a)throw Error('Artikel fehlt.');\n return JSON.stringify({stock:Number(a.stock)||0,unit:a.unit,mhd:a.mhd,mhdPolicy:a.mhdPolicy144||'',commission:Number(a.consignmentQty168)||0,lots:state.batches?.[code]||[],checked:a.lastInventory245||null});\n}\nfunction inventoryLots245(code){return (state.batches?.[code]||[]).filter(b=>Number(b.qty)>0)}\nfunction inventoryTracked245(code){return state.products[code]?.mhdPolicy144==='required'||!!state.products[code]?.mhd||inventoryLots245(code).length>0}\nfunction inventoryQuantity245(raw,unit){\n if(!/^\\d+(?:[.,]\\d+)?$/.test(String(raw).trim()))throw Error('Gültige Menge eingeben.');\n const n=Number(String(raw).replace(',','.'));if(!Number.isFinite(n)||n<0||(isWholeQuantityUnit(unit)&&!Number.isInteger(n)))throw Error('Menge und Einheit prüfen.');return n;\n}\nfunction inventoryBegin245(code,origin='single',inline=false){\n if(!guardPermission('inventory','Inventur'))return;\n inventory245={code,origin,snapshot:inventorySnapshot245(code),mode:'choice',done:false};\n if(!inline)inventoryRender245();return inventoryMarkup245();\n}\nfunction inventoryMarkup245(){\n const d=inventory245,a=state.products[d?.code];if(!a)return '';\n const lots=inventoryLots245(d.code),tracked=inventoryTracked245(d.code),step=isWholeQuantityUnit(a.unit)?'1':'any';\n let body=`<h3>${esc(a.name)}</h3><p class=\"inventoryStock245\">Sollbestand: <b>${esc(formatQtySmart(Number(a.stock)||0,a.unit))}</b></p>`;\n if(inventorySnapshot245(d.code)!==d.snapshot)return body+'<p>Bestand inzwischen geändert.</p><button class=\"primary\" onclick=\"inventoryRefresh245()\">Erneut prüfen</button>';\n if(d.mode==='choice')return body+`<p class=\"tiny\">Gesamten Lagerbestand geprüft?</p><div class=\"inventoryActions245\"><button class=\"primary\" onclick=\"inventorySave245('correct')\">Stand korrekt</button><button class=\"soft\" onclick=\"inventoryMode245('count')\">Passt nicht</button><button class=\"danger\" onclick=\"inventoryMode245('waste')\" ${hasPermission('scan')?'':'disabled'}>Ausschuss</button></div>`;\n if(d.mode==='count'){\n  body+=tracked?(lots.length?lots.map(b=>`<label>MHD ${esc(formatDate(b.mhd))}${b.consignment168?' · Kommission':''} (${esc(a.unit)})<input class=\"inventoryLotCount245\" data-lot=\"${esc(b.id)}\" inputmode=\"decimal\" type=\"number\" min=\"0\" step=\"${step}\" value=\"${esc(b.qty)}\"></label>`).join(''):'<p>Keine Charge erfasst. Bestand zuerst mit MHD zuordnen.</p>'):`<label>${Number(a.consignmentQty168)>0?'Eigener Bestand':'Tatsächlich im Lager'} (${esc(a.unit)})<input id=\"inventoryCount245\" inputmode=\"decimal\" type=\"number\" min=\"0\" step=\"${step}\" value=\"${Math.max(0,(Number(a.stock)||0)-(Number(a.consignmentQty168)||0))}\"></label>${Number(a.consignmentQty168)>0?`<label>Kommission (${esc(a.unit)})<input id=\"inventoryCommission245\" inputmode=\"decimal\" type=\"number\" min=\"0\" step=\"${step}\" value=\"${Number(a.consignmentQty168)}\"></label>`:''}`;\n }else{\n  body+=`<label>Ausschuss (${esc(a.unit)})<input id=\"inventoryWasteQty245\" inputmode=\"decimal\" type=\"number\" min=\"0\" step=\"${step}\" placeholder=\"Menge\"></label>`;\n  if(tracked)body+=`<label>Charge<select id=\"inventoryWasteLot245\"><option value=\"\">Bitte wählen</option>${lots.map(b=>`<option value=\"${esc(b.id)}\">${esc(formatDate(b.mhd))} · ${esc(formatQtySmart(b.qty,a.unit))}${b.consignment168?' · Kommission':''}</option>`).join('')}</select></label>`;\n  else if(Number(a.consignmentQty168)>0)body+='<label>Bestand<select id=\"inventoryWasteOwner245\"><option value=\"owned\">Eigene Ware</option><option value=\"commission\">Kommission</option></select></label>';\n  body+='<label>Grund<select id=\"inventoryWasteReason245\" onchange=\"document.getElementById(\\'inventoryOwnReason245\\').hidden=this.value!==\\'other\\'\"><option value=\"\">Bitte wählen</option><option>MHD überschritten</option><option>Defekt</option><option value=\"other\">Eigener Grund</option></select></label><label id=\"inventoryOwnReason245\" hidden>Eigener Grund<input id=\"inventoryReason245\" maxlength=\"300\"></label>';\n }\n return body+`<div class=\"inventoryActions245\"><button class=\"soft\" onclick=\"inventoryMode245('choice')\">Zurück</button><button class=\"${d.mode==='waste'?'danger':'primary'}\" onclick=\"inventorySubmit245()\">${d.mode==='waste'?'Ausschuss prüfen':'Bestand prüfen'}</button></div>`;\n}\nfunction inventoryRender245(){openOpsPanel('Inventur',`<div class=\"inventoryReview245\">${inventoryMarkup245()}</div>`)}\nfunction inventoryMode245(mode){if(!inventory245||!['choice','count','waste'].includes(mode))return;inventory245.mode=mode;inventoryRender245()}\nfunction inventorySubmit245(){\n const d=inventory245,a=state.products[d?.code];if(!d||!a||d.done)return;\n try{\n  let payload={};\n  if(d.mode==='count'){\n   if(inventoryTracked245(d.code))payload.lots=[...document.querySelectorAll('.inventoryLotCount245')].map(el=>({id:el.dataset.lot,qty:inventoryQuantity245(el.value,a.unit)}));\n   else {payload.owned=inventoryQuantity245(document.getElementById('inventoryCount245')?.value??'',a.unit);payload.commission=inventoryQuantity245(document.getElementById('inventoryCommission245')?.value??'0',a.unit)}\n  }else{\n   payload={qty:inventoryQuantity245(document.getElementById('inventoryWasteQty245')?.value??'',a.unit),lot:document.getElementById('inventoryWasteLot245')?.value||'',commission:document.getElementById('inventoryWasteOwner245')?.value==='commission',reason:document.getElementById('inventoryWasteReason245')?.value||''};\n   if(payload.reason==='other')payload.reason=document.getElementById('inventoryReason245')?.value.trim()||'';\n   if(!payload.reason||payload.qty<=0)throw Error('Menge und Grund angeben.');\n  }\n  const kind=d.mode;const actual=kind==='count'?(payload.lots?payload.lots.reduce((s,r)=>s+r.qty,0):payload.owned+payload.commission):Number(a.stock)-payload.qty;\n  askConfirm(`${a.name}: ${formatQtySmart(Number(a.stock)||0,a.unit)} → ${formatQtySmart(actual,a.unit)}${kind==='waste'?'\\nGrund: '+payload.reason:''}`,()=>{if(inventory245===d)inventorySave245(kind,payload)},kind==='waste'?'Ausschuss buchen':'Bestand speichern','Inventur bestätigen');\n }catch(e){showFeedback('Inventur prüfen','warn',4000,e.message)}\n}\nfunction inventorySave245(kind,payload={}){\n const d=inventory245;if(!d||d.done||!['correct','count','waste'].includes(kind))return false;\n const ok=operationTransaction('Inventur gespeichert','inventory',()=>{\n  if(inventorySnapshot245(d.code)!==d.snapshot)throw Error('Bestand wurde inzwischen geändert. Artikel erneut prüfen.');\n  const a=state.products[d.code],before=Number(a.stock)||0,lots=inventoryLots245(d.code),tracked=inventoryTracked245(d.code);\n  if(kind!=='count'&&tracked&&Math.abs(lots.reduce((s,b)=>s+Number(b.qty),0)-before)>1e-8)throw Error('Bestand und MHD-Chargen stimmen nicht überein. Chargen zuerst prüfen.');\n  if(kind==='count'){\n   if(tracked){\n    const rows=payload.lots||[];if(!lots.length&&!payload.newLot&&!(payload.newLots||[]).length&&payload.explicitZero253!==true)throw Error('Gezählte Menge eingeben.');if(rows.length!==lots.length||new Set(rows.map(r=>r.id)).size!==lots.length||rows.some(r=>!lots.some(b=>b.id===r.id)))throw Error('Alle vorhandenen Chargen zählen.');\n    for(const row of rows){const b=lots.find(b=>b.id===row.id);b.qty=inventoryQuantity245(row.qty,a.unit);if(row.mhd!==undefined){if(validDate144(b.mhd)&&row.mhd!==b.mhd)throw Error('Vorhandenes MHD bleibt erhalten.');if(!validDate144(row.mhd))throw Error('MHD prüfen.');b.mhd=row.mhd}if(Number(b.packSize)>0)b.packCount=b.qty/Number(b.packSize)}\n    for(const n of [...(payload.newLots||[]),...(payload.newLot?[payload.newLot]:[])]){const qty=inventoryQuantity245(n.qty,a.unit);if(qty<=0||!validDate144(n.mhd))throw Error('Menge und MHD der weiteren Charge prüfen.');lots.push({id:'inventory-'+Date.now()+'-'+Math.random().toString(36).slice(2),qty,mhd:n.mhd,consignment168:!!n.commission})}\n    a.stock=lots.reduce((s,b)=>s+Number(b.qty),0);a.consignmentQty168=lots.filter(b=>b.consignment168).reduce((s,b)=>s+Number(b.qty),0);state.batches[d.code]=lots.filter(b=>b.qty>0);\n   }else {a.consignmentQty168=inventoryQuantity245(payload.commission,a.unit);a.stock=inventoryQuantity245(payload.owned,a.unit)+a.consignmentQty168}\n  }else if(kind==='waste'){\n   if(!guardPermission('scan','Ausschuss'))throw Error('Keine Berechtigung für Ausschuss.');\n   const qty=inventoryQuantity245(payload.qty,a.unit);if(qty<=0||qty>before||!String(payload.reason||'').trim())throw Error('Ausschussmenge und Grund prüfen.');\n   let commission=!!payload.commission;\n   if(tracked){const b=lots.find(b=>b.id===payload.lot);if(!b||qty>Number(b.qty))throw Error('Betroffene Charge und Menge prüfen.');commission=!!b.consignment168;b.qty-=qty;if(Number(b.packSize)>0)b.packCount=b.qty/Number(b.packSize);state.batches[d.code]=lots.filter(b=>b.qty>0)}\n   else if(qty>(commission?Number(a.consignmentQty168)||0:before-(Number(a.consignmentQty168)||0)))throw Error('Menge übersteigt den gewählten Bestand.');\n   a.stock=before-qty;if(commission)a.consignmentQty168=Math.max(0,(Number(a.consignmentQty168)||0)-qty);\n  }\n  const at=new Date().toISOString(),actor=currentAccessUser();\n  // Disposal is a partial check, not a confirmation that the entire stock was counted.\n  if(kind!=='waste')a.lastInventory245={at,userId:actor.id||'',userName:actor.name||'',before,actual:Number(a.stock),kind};\n  addMovement(kind==='waste'?'waste':'inventory',d.code,Number(a.stock)-before,{reason:kind==='waste'?payload.reason:'Inventur: '+(kind==='correct'?'Stand korrekt':'Gezählter Bestand'),source:'inventory245',batchId:payload.lot||'',unitCost:Number(a.price)||0});\n  state.log.push(`${stamp()} | ${a.name} | ${Number(a.stock)-before} | ${kind==='waste'?'Ausschuss: '+payload.reason:'Inventur: '+(kind==='correct'?'Stand korrekt':'gezählt '+a.stock)}`);\n });return afterOperation275(ok,ok=>{\n if(ok){d.done=true;inventoryNext245(d,kind);showFeedback(kind==='waste'?'Ausschuss gespeichert':'Inventur gespeichert','success',2200)}else if(state.products[d.code]&&inventorySnapshot245(d.code)!==d.snapshot)inventoryRender245();return ok;\n;return ok;});}\nfunction inventoryNext245(d,kind){\n document.getElementById('opsDialog92')?.close();\n if(kind==='waste'){inventoryBegin245(d.code,d.origin);return}\n if(d.origin==='photo'){closePhoto144();captureForInventory182();return}\n if(d.origin==='queue'&&inventoryQueue245){inventoryQueue245.index++;inventoryQueueNext245();return}\n if(d.origin==='barcode'){endGuide194();closeArticleEditor(false);openOpsPanel('Inventur','<p>Gespeichert.</p><button class=\"primary\" onclick=\"document.getElementById(\\'opsDialog92\\').close();inventoryBarcode245()\">Nächsten Artikel scannen</button>');return}\n}\nfunction inventoryQueueNext245(){\n const q=inventoryQueue245;if(!q)return;while(q.index<q.codes.length&&!state.products[q.codes[q.index]])q.index++;\n if(q.index<q.codes.length){inventoryBegin245(q.codes[q.index],'queue');return}\n const ok=operationTransaction('Inventur abgeschlossen','inventory',()=>{state.lastInventory=new Date().toLocaleDateString('de-AT');state.lastInventoryISO=localTodayISO()});return afterOperation275(ok,ok=>{\n if(ok){inventoryQueue245=null;showFeedback('Inventur abgeschlossen','success')}\n;return ok;});}\nfunction inventoryQueueStart245(){if(!guardPermission('inventory','Inventur'))return;inventoryQueue245={codes:Object.keys(state.products).sort((a,b)=>state.products[a].name.localeCompare(state.products[b].name,'de')),index:0};inventoryQueueNext245()}\nconst inventoryRenderBefore245=renderInventory185;\nrenderInventory185=function(...args){const r=inventoryRenderBefore245(...args);const box=document.getElementById('inventoryRows');if(box)box.innerHTML=`<p>${Object.keys(state.products).length} Artikel · nur Lagerbestand zählen.</p><button class=\"primary\" onclick=\"${inventoryQueue245?'inventoryQueueNext245()':'inventoryQueueStart245()'}\">${inventoryQueue245?'Inventur fortsetzen':'Artikel nacheinander prüfen'}</button>`;document.querySelectorAll('.inventoryBody>button[onclick=\"finishInventory()\"]').forEach(b=>b.hidden=true);return r};\nconst inventoryPhotoBefore245=renderPhoto144;\nrenderPhoto144=function(...args){const r=inventoryPhotoBefore245(...args),d=photoDraft144;if(d?.step==='confirm'&&d.inventorySession182&&d.inventorySession182===photoInventory182?.id){const box=document.getElementById('photoBody144');if(box){if(!guardPermission('inventory','Inventur')){box.innerHTML='<p>Keine Berechtigung für Inventur.</p>';return r}if(!inventory245||inventory245.code!==d.code||inventory245.photoDraft!==d){inventoryBegin245(d.code,'photo',true);if(inventory245)inventory245.photoDraft=d}box.innerHTML=`<div class=\"inventoryReview245\">${inventoryMarkup245()}</div>`;const title=document.querySelector('#photoModal144 .modalHeader h2');if(title)title.textContent='Inventur';const subtitle=document.querySelector('#photoModal144 .modalHeader p');if(subtitle)subtitle.textContent='';}}return r};\nconst inventoryRecognizedBefore245=chooseRecognized198;\nchooseRecognized198=function(mode,...args){if(mode==='inventory'&&productGuide194?.existing)return inventoryBegin245(productGuide194.existing.productCode,'barcode');return inventoryRecognizedBefore245(mode,...args)};\nconst inventoryInfoBefore245=openArticleInfo127;\nopenArticleInfo127=function(code,...args){const r=inventoryInfoBefore245(code,...args),modal=document.getElementById('articleInfoModal127'),last=state.products[code]?.lastInventory245;const section=modal?.querySelector('.articleInfoSection');if(section){section.insertAdjacentHTML('beforeend',`<div class=\"articleInfoMetric\"><span>Letzte Inventur dieses Artikels</span><b>${last?esc(new Date(last.at).toLocaleString('de-AT')):'Noch nicht erfasst'}${last?.userName?'<br><small>'+esc(last.userName)+'</small>':''}</b></div>`)}return r};\nrenderInventory185();\n// Legacy saved inventories also carry the product-specific timestamp through the existing persistence.\nconst inventoryMovementBefore245=addMovement;\naddMovement=function(type,code,qty,extra,...rest){const r=inventoryMovementBefore245(type,code,qty,extra,...rest);if(type==='inventory'&&extra?.source!=='inventory245'&&state.products[code]){const a=state.products[code],actor=currentAccessUser();a.lastInventory245={at:new Date().toISOString(),userId:actor.id||'',userName:actor.name||'',actual:Number(a.stock)||0,kind:'count'}}return r};\n\nlet inventoryBarcodeIntent245=false;\nfunction inventoryBarcode245(){inventoryBarcodeIntent245=true;startProductGuide194()}\nconst inventoryGuideBefore245=renderGuide194;\nrenderGuide194=function(...args){const r=inventoryGuideBefore245(...args);if(inventoryBarcodeIntent245&&productGuide194?.existing){inventoryBarcodeIntent245=false;inventoryBegin245(productGuide194.existing.productCode,'barcode')}return r};\n\nfunction inventoryRefresh245(){const d=inventory245;if(!d)return;inventoryBegin245(d.code,d.origin);if(inventory245)inventory245.photoDraft=d.photoDraft}\n"},{"src":"inventory-list246.js?v=275","code":"'use strict';\n// The list remains mounted under a small native edit dialog. All changes use inventory245.\nlet manualInventoryVisible246=false,manualInventoryScroll246=null;\nfunction manualInventoryGroups246(){\n const groups=new Map();for(const [code,a] of Object.entries(state.products)){\n  const place=String(a.storage||'').trim()||'Ort fehlt';if(!groups.has(place))groups.set(place,[]);groups.get(place).push([code,a]);\n }\n const rank=p=>/tiefk|gefrier/i.test(p)?0:/kühl/i.test(p)?1:/^lager$/i.test(p)?2:p==='Ort fehlt'?4:3;\n return [...groups].sort(([a],[b])=>rank(a)-rank(b)||a.localeCompare(b,'de')).map(([place,rows])=>[place,rows.sort(([,a],[,b])=>String(a.name).localeCompare(String(b.name),'de'))]);\n}\nfunction manualInventoryChecked246(code){return state.manualInventory246?.checked?.[code]===inventorySnapshot245(code)}\nfunction manualInventoryContent246(a,code){\n const packs=getPacks(a,code),p=packs.find((p,i)=>i>0&&p[3])||packs[1]||packs[0];if(!p)return a.unit||'Stück';\n return formatPackMeasure(p[5]||p[1],p[6]||a.unit||'Stück');\n}\nfunction manualInventoryHtml246(){\n const session=state.manualInventory246,codes=Object.keys(state.products),checked=codes.filter(manualInventoryChecked246).length;\n return `<header class=\"manualInventoryHead246\"><h2>Manuelle Inventur</h2><span role=\"status\">${checked} von ${codes.length} geprüft</span><button class=\"primary manualInventoryScan446\" type=\"button\" onclick=\"manualInventoryScan446()\">${esc(manualInventoryScanText446('Barcode scannen','Scan barcode','Skanuj kod kreskowy'))}</button></header>${codes.length?manualInventoryGroups246().map(([place,rows])=>`<section class=\"manualInventoryGroup246\"><h3>${esc(place)}</h3>${rows.map(([code,a])=>{\n const done=manualInventoryChecked246(code);return `<div class=\"manualInventoryRow246 ${done?'checked':''}\" data-inventory-row=\"${esc(code)}\"><div class=\"manualInventoryPhoto246\">${a.photo?`<img src=\"${esc(a.photo)}\" alt=\"\" loading=\"lazy\">`:'<span aria-hidden=\"true\">□</span>'}</div><div class=\"manualInventoryText246\"><b>${esc(a.name)}</b><small>${esc(manualInventoryContent246(a,code))}</small><span>Bestand: <strong>${esc(formatQtySmart(Number(a.stock)||0,a.unit||'Stück'))}</strong></span>${done?'<small class=\"manualInventoryChecked246\">✓ Geprüft</small>':''}</div><div class=\"manualInventoryActions246\"><button class=\"${done?'primary':'soft'}\" type=\"button\" data-code=\"${esc(code)}\" data-snapshot=\"${esc(inventorySnapshot245(code))}\" onclick=\"manualInventoryAction246(this.dataset.code,'correct',this.dataset.snapshot)\" aria-label=\"${esc(a.name)}: Stand korrekt\">OK</button><button class=\"soft\" type=\"button\" data-code=\"${esc(code)}\" onclick=\"manualInventoryAction246(this.dataset.code,'minus')\" aria-label=\"${esc(a.name)}: weniger vorhanden\">−</button><button class=\"soft\" type=\"button\" data-code=\"${esc(code)}\" onclick=\"manualInventoryAction246(this.dataset.code,'plus')\" aria-label=\"${esc(a.name)}: mehr vorhanden\">+</button><button class=\"manualInventoryWaste246\" type=\"button\" data-code=\"${esc(code)}\" onclick=\"manualInventoryAction246(this.dataset.code,'waste')\" ${hasPermission('scan')?'':'disabled'}>Ausschuss</button></div></div>`}).join('')}</section>`).join(''):'<p>Noch keine Produkte vorhanden.</p>'}<footer class=\"manualInventoryFooter246\">${session?.closedAt?'<span>Inventur abgeschlossen</span>':''}<button class=\"primary\" type=\"button\" onclick=\"manualInventoryFinish246()\" ${checked!==codes.length||!codes.length?'disabled':''}>Inventur abschließen</button>${session?.closedAt?'<button class=\"soft\" onclick=\"manualInventoryStart246(true)\">Neue Inventur</button>':''}</footer>`;\n}\nfunction manualInventoryCaptureScroll246(){\n const nodes=[...document.querySelectorAll('.inventoryManual185,.contentWrap,.wrap,#mobileScreenInventory')];\n return {x:window.scrollX||0,y:window.scrollY||0,nodes:nodes.map(n=>[n,n.scrollTop,n.scrollLeft])};\n}\nfunction manualInventoryRestoreScroll246(saved){if(!saved)return;requestAnimationFrame(()=>{for(const [node,top,left] of saved.nodes){node.scrollTop=top;node.scrollLeft=left}window.scrollTo(saved.x,saved.y)})}\nfunction manualInventoryRender246(){\n const saved=manualInventoryCaptureScroll246(),html=manualInventoryVisible246?manualInventoryHtml246():'';\n for(const host of document.querySelectorAll('.inventoryManual185')){\n  let panel=host.querySelector('.manualInventoryPanel246');if(!panel){panel=document.createElement('section');panel.className='manualInventoryPanel246';host.appendChild(panel)}\n  panel.hidden=!manualInventoryVisible246;if(manualInventoryVisible246&&panel._manualHtml247!==html){panel.innerHTML=html;panel._manualHtml247=html;}\n }\n // The old sequential counter is superseded by this freely revisitable list.\n const fold=document.querySelector('.inventoryFold');if(fold)fold.hidden=true;\n for(const hub of document.querySelectorAll('.inventoryHub185')){\n  const section=hub.querySelector('section:last-child');if(section&&!section.querySelector('.manualInventoryOpen246'))section.insertAdjacentHTML('beforeend','<button class=\"primary manualInventoryOpen246\" onclick=\"manualInventoryStart246()\">Manuelle Inventur</button>');\n }\n manualInventoryRestoreScroll246(saved);\n}\nfunction manualInventoryStart246(fresh=false){\n if(!guardPermission('inventory','Inventur'))return;\n const ok=state.manualInventory246&&!fresh?true:operationTransaction('Inventur gestartet','inventory',()=>{state.manualInventory246={id:'manual-'+Date.now(),startedAt:new Date().toISOString(),checked:{}}});\n return afterOperation275(ok,saved=>{if(!saved)return false;manualInventoryVisible246=true;manualInventoryRender246();return true});\n}\nfunction manualInventoryAction246(code,kind,expected){\n if(!state.products[code]||!['correct','minus','plus','waste'].includes(kind)||!guardPermission('inventory','Inventur'))return;\n if(!state.manualInventory246)return afterOperation275(manualInventoryStart246(),ok=>{if(ok)return manualInventoryAction246(code,kind,expected);return false});\n if(kind==='correct'&&expected!==undefined&&expected!==inventorySnapshot245(code)){manualInventoryRender246();showFeedback('Bestand geändert','warn',3000,'Aktuellen Stand erneut prüfen.');return}\n manualInventoryScroll246=manualInventoryCaptureScroll246();inventoryBegin245(code,'list',true);\n if(kind==='correct'){inventorySave245('correct');return}\n if(kind==='waste'){inventoryMode245('waste');return}\n const a=state.products[code],lots=inventoryLots245(code),tracked=inventoryTracked245(code),step=isWholeQuantityUnit(a.unit)?'1':'any';inventory245.mode='count';\n openOpsPanel('Stand eintragen',`<form class=\"manualInventoryPopup246\" onsubmit=\"event.preventDefault();manualInventorySubmit246()\"><h3>${esc(a.name)}</h3><p>Bisher: ${esc(formatQtySmart(Number(a.stock)||0,a.unit))}</p><label>Tatsächlich vorhanden (${esc(a.unit||'Stück')})<input id=\"manualInventoryActual246\" type=\"number\" inputmode=\"decimal\" min=\"0\" step=\"${step}\" value=\"${Number(a.stock)||0}\"></label>${tracked?lots.length?`<label>Betroffene Charge<select id=\"manualInventoryLot246\" onchange=\"document.getElementById('manualInventoryNewLot246').hidden=this.value!=='new'\"><option value=\"\">Bitte wählen</option>${lots.map(b=>`<option value=\"${esc(b.id)}\" ${lots.length===1?'selected':''}>${esc(formatDate(b.mhd))}${b.consignment168?' · Kommission':''}</option>`).join('')}<option value=\"new\">Weitere Charge</option></select></label>`:'<input id=\"manualInventoryLot246\" type=\"hidden\" value=\"new\">':''}${tracked?`<div id=\"manualInventoryNewLot246\" ${lots.length?'hidden':''}><label>MHD der weiteren Charge<input id=\"manualInventoryMhd246\" type=\"date\"></label><label>Ware<select id=\"manualInventoryNewOwner246\"><option value=\"owned\">Eigener Bestand</option><option value=\"commission\">Kommission</option></select></label></div>`:''}${!tracked&&Number(a.consignmentQty168)>0?`<label>Davon Kommission (${esc(a.unit)})<input id=\"manualInventoryCommission246\" type=\"number\" inputmode=\"decimal\" min=\"0\" step=\"${step}\" value=\"${Number(a.consignmentQty168)}\"></label>`:''}<p id=\"manualInventoryError246\" class=\"manualInventoryError246\" role=\"alert\" hidden></p><div class=\"manualInventoryPopupActions246\"><button class=\"primary\" type=\"submit\">OK</button><button class=\"cancelAction239\" type=\"button\" onclick=\"manualInventoryCancel246()\">Abbrechen</button></div></form>`);\n const input=document.getElementById('manualInventoryActual246');input?.focus();input?.select();\n}\nfunction manualInventoryPayload246(code,actual,lotId='',date='',commission=0,newCommission=false){\n const a=state.products[code];actual=inventoryQuantity245(actual,a.unit);\n if(!inventoryTracked245(code)){commission=inventoryQuantity245(commission,a.unit);if(commission>actual)throw Error('Kommission übersteigt den Gesamtbestand.');return {owned:actual-commission,commission}}\n const lots=inventoryLots245(code),rows=lots.map(b=>({id:b.id,qty:Number(b.qty)})),sum=rows.reduce((n,b)=>n+b.qty,0);\n if(lotId==='new'){\n  const qty=actual-sum;if(qty<=0)throw Error('Für weniger Bestand eine vorhandene Charge wählen.');if(!validDate144(date))throw Error('MHD der weiteren Charge angeben.');return {lots:rows,newLot:{qty,mhd:date,commission:!!newCommission}};\n }\n const row=rows.find(b=>b.id===lotId);if(!row)throw Error('Betroffene Charge wählen.');row.qty=actual-(sum-row.qty);if(row.qty<0)throw Error('Menge ist kleiner als der Bestand der übrigen Chargen. Chargen einzeln prüfen.');return {lots:rows};\n}\nfunction manualInventorySubmit246(){\n const d=inventory245;if(!d||d.origin!=='list'||d.done)return;\n try{\n  const payload=manualInventoryPayload246(d.code,document.getElementById('manualInventoryActual246')?.value??'',document.getElementById('manualInventoryLot246')?.value||'',document.getElementById('manualInventoryMhd246')?.value||'',document.getElementById('manualInventoryCommission246')?.value??'0',document.getElementById('manualInventoryNewOwner246')?.value==='commission');\n  return afterOperation275(inventorySave245('count',payload),ok=>{if(!ok){const msg=document.getElementById('manualInventoryError246');if(msg){msg.textContent='Nicht gespeichert. Bitte Eingaben prüfen und erneut versuchen.';msg.hidden=false}}return ok});\n }catch(e){const msg=document.getElementById('manualInventoryError246');if(msg){msg.textContent=e.message;msg.hidden=false}}\n}\nfunction manualInventoryCancel246(){if(inventory245?.origin==='list')inventory245.done=true;document.getElementById('opsDialog92')?.close();manualInventoryRestoreScroll246(manualInventoryScroll246)}\nfunction manualInventoryFinish246(){\n if(!Object.keys(state.products).length||Object.keys(state.products).some(c=>!manualInventoryChecked246(c))){showFeedback('Noch offene Artikel','warn');return}\n const ok=operationTransaction('Inventur abgeschlossen','inventory',()=>{\n  if(Object.keys(state.products).some(c=>!manualInventoryChecked246(c)))throw Error('Bestand inzwischen geändert. Betroffene Artikel erneut prüfen.');\n  state.manualInventory246.closedAt=new Date().toISOString();state.lastInventory=new Date().toLocaleDateString('de-AT');state.lastInventoryISO=localTodayISO();\n });return afterOperation275(ok,ok=>{if(ok){manualInventoryRender246();showFeedback('Inventur abgeschlossen','success')}\n;return ok;});}\nconst manualInventoryNextBefore246=inventoryNext245;\ninventoryNext245=function(d,kind){if(d.origin!=='list')return manualInventoryNextBefore246(d,kind);document.getElementById('opsDialog92')?.close();manualInventoryRender246();manualInventoryRestoreScroll246(manualInventoryScroll246)};\nconst manualInventoryMovementBefore246=addMovement;\naddMovement=function(type,code,qty,extra,...rest){const r=manualInventoryMovementBefore246(type,code,qty,extra,...rest);if(type==='inventory'&&extra?.source==='inventory245'&&inventory245?.origin==='list'&&state.manualInventory246){state.manualInventory246.checked[code]=inventorySnapshot245(code);delete state.manualInventory246.closedAt}return r};\nconst manualInventoryRenderBefore246=renderInventory185;\nrenderInventory185=function(...args){const r=manualInventoryRenderBefore246(...args);manualInventoryRender246();return r};\nmanualInventoryRender246();\n\nconst manualInventoryDialogBefore246=inventoryRender245;\ninventoryRender245=function(...args){const d=inventory245;if(d?.origin==='list'&&state.products[d.code]&&inventorySnapshot245(d.code)!==d.snapshot){openOpsPanel('Stand eintragen',`<p class=\"manualInventoryStale246\" role=\"alert\">Bestand inzwischen geändert. Bitte neu prüfen.</p><div class=\"manualInventoryPopupActions246\"><button class=\"primary\" data-code=\"${esc(d.code)}\" onclick=\"manualInventoryAction246(this.dataset.code,'minus')\">Erneut prüfen</button><button class=\"cancelAction239\" onclick=\"manualInventoryCancel246()\">Abbrechen</button></div>`);return}return manualInventoryDialogBefore246(...args)};\nconst manualInventoryPanelBefore246=openOpsPanel;\nopenOpsPanel=function(title,html,...args){const r=manualInventoryPanelBefore246(title,html,...args);const dialog=document.getElementById('opsDialog92');dialog?.classList?.toggle('manualInventoryDialog246',String(html).includes('manualInventoryPopup246')||String(html).includes('manualInventoryStale246'));return r};\n\n// Inventory owns this callback: decoding selects an existing item, never a receipt or new article.\nfunction manualInventoryScanText446(de,en,pl){return typeof stockText420==='function'?stockText420(de,en,pl):de}\nfunction manualInventoryScan446(){\n if(!guardPermission('inventory','Inventur'))return;\n return startLiveScan391(manualInventoryScanned446,manualInventoryScanPhoto446);\n}\nfunction manualInventoryScanned446(value){\n if(!guardPermission('inventory','Inventur'))return false;\n const hit=findScanCode(String(value||'').trim());\n if(!hit||!state.products[hit.productCode]){showFeedback(manualInventoryScanText446('Produkt nicht im Lager gefunden','Product not found in inventory','Nie znaleziono produktu w magazynie'),'warn',4000,manualInventoryScanText446('Barcode prüfen oder den Artikel in der Inventurliste auswählen.','Check the barcode or select the item in the inventory list.','Sprawdź kod lub wybierz produkt z listy inwentaryzacji.'));return false}\n const open=()=>{manualInventoryScroll246=manualInventoryCaptureScroll246();return inventoryBegin245(hit.productCode,'list')};\n if(!state.manualInventory246)return afterOperation275(manualInventoryStart246(),ok=>ok?open():false);\n return open();\n}\nfunction manualInventoryScanPhoto446(){\n if(!guardPermission('inventory','Inventur'))return;\n let input=document.getElementById('manualInventoryScanFile446');\n if(!input){\n  input=document.createElement('input');input.id='manualInventoryScanFile446';input.type='file';input.accept='image/*';input.setAttribute('capture','environment');input.hidden=true;document.body.appendChild(input);\n  input.onchange=async()=>{const file=input.files?.[0];input.value='';if(!file)return;try{const code=String(await zReadBarcode166(file)||'').trim();if(code){manualInventoryScanned446(code);return}}catch{}showFeedback(manualInventoryScanText446('Barcode nicht lesbar','Barcode unreadable','Nie można odczytać kodu'),'warn',4000,manualInventoryScanText446('Erneut scannen oder die Nummer im Scanner eingeben.','Scan again or enter the number in the scanner.','Zeskanuj ponownie lub wpisz numer w skanerze.'))};\n }\n input.click();\n}\n"},{"src":"performance247.js?v=248","code":"'use strict';\n// Caches only live during a synchronous render. A later stock transaction always reads fresh data.\nlet performancePass247=null;\nconst membersBefore247=mainMembers;\nmainMembers=function(id){if(!performancePass247)return membersBefore247(id);if(!performancePass247.members.has(id))performancePass247.members.set(id,membersBefore247(id));return performancePass247.members.get(id).slice()};\nconst summaryBefore247=mainSummaryById;\nmainSummaryById=function(id){if(!performancePass247)return summaryBefore247(id);if(!performancePass247.summaries.has(id))performancePass247.summaries.set(id,summaryBefore247(id));const result=performancePass247.summaries.get(id);return result?{...result,members:result.members.slice()}:result};\nconst renderBefore247=render;\nfunction performanceRun247(fn){if(performancePass247)return fn();performancePass247={members:new Map(),summaries:new Map()};try{return fn()}finally{performancePass247=null}}\nrender=function(...args){return performanceRun247(()=>renderBefore247(...args))};\n// Rebuild skipped administration sections when the user actually opens them.\nconst tabBefore247=showTab;\nshowTab=function(...args){const result=tabBefore247(...args);for(const [id,fn] of globalThis.zapasioLazySections247||[]){const tab=document.getElementById(id)?.closest('.tabPanel');if(tab?.classList.contains('active')){globalThis.zapasioLazySections247.delete(id);performanceRun247(fn)}}return result};\n// Permission transitions need a full access-button update, not a document-wide scan for every inserted text.\nconst permissionsBefore247=applyPermissionsUI;\napplyPermissionsUI=function(...args){const r=permissionsBefore247(...args);const role=currentAccessUser().role;if(role!==documentAccessRole247){documentAccessRole247=role;applyDocumentAccess240()}return r};\nglobalThis.zapasioPerformanceReady247=true;\n/* The bootstrap performs the initial render after all later patches. */\n"},{"src":"performance248.js?v=1","code":"'use strict';\n// Membership indexing replaces repeated product scans, and expires at the end of each render.\nconst membersBefore248=mainMembers;\nmainMembers=function(id){\n if(!performancePass247)return membersBefore248(id);\n if(!performancePass247.memberIndex248){const index=new Map();for(const row of Object.entries(state.products||{})){const key=row[1].mainArticleId;if(!index.has(key))index.set(key,[]);index.get(key).push(row)}performancePass247.memberIndex248=index}\n return (performancePass247.memberIndex248.get(id)||[]).slice();\n};\n// These helpers are read-only. Their data is never cached between user operations.\nconst warningsBefore248=getWarnings;\ngetWarnings=function(...args){if(args.length||!performancePass247)return warningsBefore248(...args);if(!performancePass247.warnings248)performancePass247.warnings248=warningsBefore248();return performancePass247.warnings248.map(row=>({...row}))};\nconst stockMetricsBefore248=stockValueMetrics;\nstockValueMetrics=function(...args){if(args.length||!performancePass247)return stockMetricsBefore248(...args);if(!performancePass247.stockMetrics248)performancePass247.stockMetrics248=stockMetricsBefore248();return {...performancePass247.stockMetrics248}};\n// Resizing desktop tabs reuses lazy-section refreshing and does not touch stock or persistence.\nwindow.addEventListener('resize',()=>{for(const [id,fn] of globalThis.zapasioLazySections247||[]){const tab=document.getElementById(id)?.closest('.tabPanel');if(tab?.classList.contains('active')){globalThis.zapasioLazySections247.delete(id);performanceRun247(fn)}}});\n"},{"src":"article-draft249.js?v=2","code":"'use strict';\n// Keep an unfinished new-article form on this device across a WebView reload.\n// Photos and pack rows are intentionally excluded; no stock or cloud write happens here.\nconst ARTICLE_DRAFT_TTL249=48*60*60*1000;\nlet articleDraftTimer249=null,articleDraftDirty249=false;\nconst ARTICLE_DRAFT_TEXT249={\n de:{locale:'de-AT',title:'Ungespeicherte Eingabe wiederhergestellt',draft:time=>`Lokaler Entwurf von ${time}. Fotos und zusätzliche Gebinde bitte erneut prüfen.`,discard:'Verwerfen',feedback:'Eingabe wiederhergestellt',detail:'Der Artikel ist noch nicht gespeichert. Fotos und Gebinde bitte prüfen.'},\n pl:{locale:'pl-PL',title:'Przywrócono niezapisane dane',draft:time=>`Lokalny szkic z ${time}. Sprawdź ponownie zdjęcia i dodatkowe opakowania.`,discard:'Odrzuć',feedback:'Przywrócono dane',detail:'Artykuł nie został jeszcze zapisany. Sprawdź zdjęcia i opakowania.'},\n en:{locale:'en-GB',title:'Unsaved entry restored',draft:time=>`Local draft from ${time}. Please check photos and additional pack sizes again.`,discard:'Discard',feedback:'Entry restored',detail:'The item has not been saved yet. Please check photos and pack sizes.'}\n};\n\nfunction articleDraftLanguage249(){\n let language='de';\n try{language=String(business130()?.language||language).toLowerCase()}catch{\n  language=String(document.documentElement?.lang||language).toLowerCase();\n }\n return ['de','pl','en'].includes(language)?language:'de';\n}\nfunction articleDraftText249(language=articleDraftLanguage249()){return ARTICLE_DRAFT_TEXT249[language]||ARTICLE_DRAFT_TEXT249.de}\n\nfunction articleDraftScope249(){\n let workspace='local',user='device';\n try{workspace=String(cloudConfig()?.workspace||workspace)}catch{}\n try{user=String(cloudRuntime()?.user?.id||currentAccessUser()?.id||user)}catch{}\n return `${workspace.replace(/[^a-z0-9_-]/gi,'_')}:${user.replace(/[^a-z0-9_-]/gi,'_')}`;\n}\nfunction articleDraftKey249(){return `zapasio_article_draft249:${articleDraftScope249()}`}\nfunction articleDraftFields249(){\n const modal=document.getElementById('articleModal');\n if(!modal)return[];\n return [...modal.querySelectorAll('input[id^=\"ae\"],select[id^=\"ae\"],textarea[id^=\"ae\"]')]\n  .filter(el=>el.id&&el.type!=='file'&&el.type!=='password'&&!el.closest('#aePacks,#aeBatchRows'));\n}\nfunction articleDraftRead249(){\n try{\n  const value=JSON.parse(localStorage.getItem(articleDraftKey249())||'null');\n  if(!value||value.scope!==articleDraftScope249()||Date.now()-Number(value.savedAt)>ARTICLE_DRAFT_TTL249){\n   localStorage.removeItem(articleDraftKey249());return null;\n  }\n  return value;\n }catch{return null}\n}\nfunction saveArticleDraft249(){\n clearTimeout(articleDraftTimer249);articleDraftTimer249=null;\n const modal=document.getElementById('articleModal');\n if(!articleDraftDirty249||modal?.style.display!=='flex'||editingArticleCode||(typeof productGuide194!=='undefined'&&productGuide194))return false;\n const fields={};\n for(const el of articleDraftFields249())fields[el.id]=el.type==='checkbox'?!!el.checked:String(el.value??'').slice(0,1000);\n try{localStorage.setItem(articleDraftKey249(),JSON.stringify({scope:articleDraftScope249(),savedAt:Date.now(),fields,mobile:typeof mobileQuickCreate118!=='undefined'&&!!mobileQuickCreate118}));return true}catch{return false}\n}\nfunction scheduleArticleDraft249(){\n articleDraftDirty249=true;clearTimeout(articleDraftTimer249);\n articleDraftTimer249=setTimeout(saveArticleDraft249,180);\n}\nfunction clearArticleDraft249(){\n clearTimeout(articleDraftTimer249);articleDraftTimer249=null;articleDraftDirty249=false;\n try{localStorage.removeItem(articleDraftKey249())}catch{}\n document.getElementById('articleDraftNotice249')?.remove();\n}\nfunction showArticleDraftNotice249(savedAt){\n document.getElementById('articleDraftNotice249')?.remove();\n const header=document.querySelector('#articleModal .modalHeader');if(!header)return;\n const notice=document.createElement('div');notice.id='articleDraftNotice249';notice.className='articleDraftNotice249';\n const text=articleDraftText249(),time=new Date(savedAt).toLocaleTimeString(text.locale,{hour:'2-digit',minute:'2-digit'});\n notice.innerHTML=`<span><b>${text.title}</b><small>${text.draft(time)}</small></span><button type=\"button\" class=\"soft\" onclick=\"discardArticleDraft249()\">${text.discard}</button>`;\n header.insertAdjacentElement('afterend',notice);\n}\nfunction restoreArticleDraft249(){\n const draft=articleDraftRead249();if(!draft)return false;\n const set=(id,value)=>{const el=document.getElementById(id);if(!el||value===undefined)return;if(el.type==='checkbox')el.checked=!!value;else el.value=String(value)};\n set('aeName',draft.fields.aeName);set('aeCategory',draft.fields.aeCategory);\n if(typeof refreshArticleSubgroups==='function')refreshArticleSubgroups(String(draft.fields.aeSubgroup||''));\n if(typeof articleNameInputChanged==='function')articleNameInputChanged();\n for(const [id,value] of Object.entries(draft.fields||{}))if(!['aeCategory','aeSubgroup'].includes(id))set(id,value);\n set('aeSubgroup',draft.fields.aeSubgroup);\n if(typeof updateArticleFoodOptions==='function')updateArticleFoodOptions();\n if(typeof updateArticleUnitUi==='function')updateArticleUnitUi();\n if(typeof updatePurchaseQuote==='function')updatePurchaseQuote('article');\n if(typeof checkMainBarcodeLive==='function')checkMainBarcodeLive();\n if(draft.mobile)document.getElementById('articleModal')?.classList.add('mobileCreateMode118');\n articleDraftDirty249=true;showArticleDraftNotice249(draft.savedAt);\n const text=articleDraftText249();\n if(typeof showFeedback==='function')showFeedback(text.feedback,'success',3200,text.detail);\n return true;\n}\nfunction discardArticleDraft249(){clearArticleDraft249();openArticleEditor()}\n\ndocument.addEventListener('input',event=>{if(event.target?.closest?.('#articleModal'))scheduleArticleDraft249()},true);\ndocument.addEventListener('change',event=>{if(event.target?.closest?.('#articleModal'))scheduleArticleDraft249()},true);\nwindow.addEventListener('pagehide',saveArticleDraft249);\n\nconst openArticleBefore249=openArticleEditor;\nopenArticleEditor=function(code=null){\n const result=openArticleBefore249(code);\n articleDraftDirty249=false;document.getElementById('articleDraftNotice249')?.remove();\n if(!code&&document.getElementById('articleModal')?.style.display==='flex'&&!(typeof productGuide194!=='undefined'&&productGuide194))restoreArticleDraft249();\n return result;\n};\nconst closeArticleBefore249=closeArticleEditor;\ncloseArticleEditor=function(...args){\n const wasOpen=document.getElementById('articleModal')?.style.display==='flex';\n const result=closeArticleBefore249(...args);if(wasOpen)clearArticleDraft249();return result;\n};\nif(typeof prefillNewArticle==='function'){\n const prefillNewArticleBefore249=prefillNewArticle;\n prefillNewArticle=function(...args){clearArticleDraft249();const result=prefillNewArticleBefore249(...args);articleDraftDirty249=true;saveArticleDraft249();return result};\n}\nif(typeof startProductGuide194==='function'){\n const startProductGuideBefore249=startProductGuide194;\n startProductGuide194=function(...args){clearArticleDraft249();return startProductGuideBefore249(...args)};\n}\n"},{"src":"mobile-actions250.js?v=1","code":"'use strict';\n// Select a method before showing inventory work. Never change stock here.\nfunction inventoryPhoto250(){\n if(photoInventory182?.rows?.length||photoInventory182?.sceneUrl)return resumeInventoryPhoto185();\n return startInventoryPhoto185();\n}\nfunction inventoryView250(){\n for(const host of document.querySelectorAll('#mobileScreenInventory,#tabInventory185')){\n  host.classList.toggle('inventoryCounting250',manualInventoryVisible246);\n  const photo=host.querySelector('[onclick=\"inventoryPhoto250()\"]'),manual=host.querySelector('.manualInventoryOpen246');\n  if(photo)photo.disabled=!hasPermission('scan');\n  if(manual)manual.disabled=!hasPermission('inventory');\n  const heading=host.querySelector('.mobileSimpleHeader h1,.pageHeader h1');\n  if(heading)heading.textContent=manualInventoryVisible246?'Manuelle Inventur':'Inventur';\n  const back=host.querySelector('.inventoryBack185');\n  if(back){back.textContent=manualInventoryVisible246?'Zurück':'Zurück zu Mehr';back.setAttribute('onclick',manualInventoryVisible246?'inventoryHome250()':\"mobileShow('more')\");}\n }\n}\nfunction inventoryHome250(){\n manualInventoryVisible246=false;manualInventoryRender246();\n requestAnimationFrame(()=>{document.getElementById('mobileScreenInventory')?.scrollTo?.(0,0);window.scrollTo(0,0)});\n}\nconst inventoryRenderBefore250=manualInventoryRender246;\nmanualInventoryRender246=function(...args){const r=inventoryRenderBefore250(...args);inventoryView250();return r};\nconst inventoryMobileBefore250=mobileShow;\nmobileShow=function(which='today',...args){\n // Navigation resets the view, but not the persisted counts or progress.\n manualInventoryVisible246=false;\n const r=inventoryMobileBefore250(which,...args);manualInventoryRender246();return r;\n};\nconst inventoryDesktopBefore250=openInventoryDesktop185;\nopenInventoryDesktop185=function(...args){manualInventoryVisible246=false;const r=inventoryDesktopBefore250(...args);manualInventoryRender246();return r};\nmanualInventoryRender246();\n"},{"src":"inventory-reconcile251.js?v=275","code":"'use strict';\nfunction inventoryMismatch251(code){\n if(!inventoryTracked245(code))return false;\n const a=state.products[code],lots=inventoryLots245(code);\n return Math.abs(lots.reduce((s,b)=>s+Number(b.qty),0)-(Number(a.stock)||0))>1e-8||Math.abs(lots.filter(b=>b.consignment168).reduce((s,b)=>s+Number(b.qty),0)-(Number(a.consignmentQty168)||0))>1e-8||lots.some(b=>!validDate144(b.mhd));\n}\nfunction inventoryReconcile251(){\n const d=inventory245,a=state.products[d?.code];if(!d||!a||!guardPermission('inventory','Inventur'))return;\n d.mode='reconcile';\n const step=isWholeQuantityUnit(a.unit)?'1':'any',lots=inventoryLots245(d.code),sum=lots.reduce((s,b)=>s+Number(b.qty),0);\n openOpsPanel('Mengen prüfen',`<form class=\"manualInventoryPopup246 inventoryReconcile251\" onsubmit=\"event.preventDefault();inventoryReconcileSubmit251()\"><h3>${esc(a.name)}</h3><label>Tatsächlich vorhanden (${esc(a.unit||'Stück')})<input id=\"inventoryActual251\" type=\"number\" inputmode=\"decimal\" min=\"0\" step=\"${step}\" value=\"${Number(a.stock)||0}\" oninput=\"inventoryReconcileUpdate251()\"></label><p>Mengen nach MHD prüfen.</p>${lots.map(b=>`<label class=\"inventoryDateRow251\">${validDate144(b.mhd)?`MHD ${esc(formatDate(b.mhd))}`:'MHD fehlt'}${b.consignment168?' · Kommission':''}<input class=\"inventoryExistingQty251\" data-lot=\"${esc(b.id)}\" type=\"number\" inputmode=\"decimal\" min=\"0\" step=\"${step}\" value=\"${esc(b.qty)}\" oninput=\"inventoryReconcileUpdate251()\">${!validDate144(b.mhd)?`<input class=\"inventoryMissingDate251\" data-lot=\"${esc(b.id)}\" type=\"date\" aria-label=\"MHD ergänzen\" oninput=\"inventoryReconcileUpdate251()\">`:''}</label>`).join('')}<div id=\"inventoryNewRows251\"></div><button type=\"button\" class=\"soft\" onclick=\"inventoryNewRow251()\">Weitere Ware mit MHD</button><p id=\"inventorySummary251\" role=\"status\"></p><p id=\"inventoryError251\" role=\"alert\" class=\"manualInventoryError246\" hidden></p><div class=\"manualInventoryPopupActions246\"><button id=\"inventoryConfirm251\" class=\"primary\" type=\"submit\">Bestätigen</button><button class=\"cancelAction239\" type=\"button\" onclick=\"inventoryReconcileCancel251()\">Abbrechen</button></div></form>`);\n if(!lots.length||sum<Number(a.stock))inventoryNewRow251(Math.max(0,(Number(a.stock)||0)-sum));\n inventoryReconcileUpdate251();\n document.getElementById('opsDialog92')?.classList?.add('manualInventoryDialog246');\n}\nfunction inventoryNewRow251(qty=0){\n const a=state.products[inventory245?.code];if(!a)return;const step=isWholeQuantityUnit(a.unit)?'1':'any';\n document.getElementById('inventoryNewRows251')?.insertAdjacentHTML('beforeend',`<div class=\"inventoryNewRow251\"><label>Weitere Menge (${esc(a.unit)})<input class=\"inventoryNewQty251\" type=\"number\" inputmode=\"decimal\" min=\"0\" step=\"${step}\" value=\"${qty}\" oninput=\"inventoryReconcileUpdate251()\"></label><label>MHD<input class=\"inventoryNewDate251\" type=\"date\" oninput=\"inventoryReconcileUpdate251()\"></label><label>Ware<select class=\"inventoryNewOwner251\"><option value=\"owned\">Eigener Bestand</option><option value=\"commission\">Kommission</option></select></label><button class=\"cancelAction239\" type=\"button\" onclick=\"this.closest('.inventoryNewRow251').remove();inventoryReconcileUpdate251()\">Entfernen</button></div>`);\n inventoryReconcileUpdate251();\n}\nfunction inventoryReconcileValidate251(code,actual,rows,newLots){\n const a=state.products[code],lots=inventoryLots245(code);actual=inventoryQuantity245(actual,a.unit);\n if(rows.length!==lots.length||new Set(rows.map(r=>r.id)).size!==lots.length)throw Error('Alle Mengen prüfen.');\n const checked=rows.map(r=>{const b=lots.find(b=>b.id===r.id);if(!b)throw Error('Bestand geändert. Erneut prüfen.');const qty=inventoryQuantity245(r.qty,a.unit),mhd=validDate144(b.mhd)?b.mhd:r.mhd;if(qty>0&&!validDate144(mhd))throw Error('MHD ergänzen.');return {id:r.id,qty,...(!validDate144(b.mhd)&&qty>0?{mhd}:{} )}});\n const added=newLots.map(n=>({...n,qty:inventoryQuantity245(n.qty,a.unit)})).filter(n=>n.qty>0);if(added.some(n=>!validDate144(n.mhd)))throw Error('MHD der weiteren Ware ergänzen.');\n if(Math.abs(checked.reduce((s,r)=>s+r.qty,0)+added.reduce((s,n)=>s+n.qty,0)-actual)>1e-8)throw Error('MHD-Mengen müssen zur gezählten Menge passen.');\n return {lots:checked,newLots:added,...(actual===0?{explicitZero253:true}:{})};\n}\nfunction inventoryReconcileRead251(){\n const rows=[...document.querySelectorAll('.inventoryExistingQty251')].map(el=>({id:el.dataset.lot,qty:el.value,mhd:[...document.querySelectorAll('.inventoryMissingDate251')].find(n=>n.dataset.lot===el.dataset.lot)?.value}));\n const added=[...document.querySelectorAll('.inventoryNewRow251')].map(row=>({qty:row.querySelector('.inventoryNewQty251').value,mhd:row.querySelector('.inventoryNewDate251').value,commission:row.querySelector('.inventoryNewOwner251').value==='commission'}));\n return {rows,added,actual:document.getElementById('inventoryActual251')?.value??''};\n}\nfunction inventoryReconcileUpdate251(){\n const d=inventory245,a=state.products[d?.code];if(!a)return;\n const v=inventoryReconcileRead251(),summary=document.getElementById('inventorySummary251'),button=document.getElementById('inventoryConfirm251');\n const sum=[...v.rows,...v.added].reduce((s,r)=>s+(Number(String(r.qty).replace(',','.'))||0),0);\n const actual=Number(String(v.actual).replace(',','.')),difference=actual-sum;\n if(summary)summary.textContent=Number.isFinite(actual)&&Math.abs(difference)>1e-8?(difference>0?'Noch '+formatQtySmart(difference,a.unit)+' zuordnen':formatQtySmart(-difference,a.unit)+' zu viel zugeordnet'):'Zugeordnet: '+formatQtySmart(sum,a.unit);\n try{if(inventorySnapshot245(d.code)!==d.snapshot)throw Error('Bestand geändert');inventoryReconcileValidate251(d.code,v.actual,v.rows,v.added);if(button)button.disabled=false}catch{if(button)button.disabled=true}\n}\nfunction inventoryReconcileSubmit251(){\n const d=inventory245;if(!d||d.done||d.mode!=='reconcile')return;\n const error=document.getElementById('inventoryError251');\n try{\n  if(inventorySnapshot245(d.code)!==d.snapshot)throw Error('Bestand inzwischen geändert. Abbrechen und erneut prüfen.');\n  const v=inventoryReconcileRead251(),payload=inventoryReconcileValidate251(d.code,v.actual,v.rows,v.added);\n  return afterOperation275(inventorySave245('count',payload),ok=>{if(!ok&&error){error.textContent='Nicht gespeichert. Bitte erneut versuchen.';error.hidden=false}return ok});\n }catch(e){if(error){error.textContent=e.message;error.hidden=false}}\n}\nfunction inventoryReconcileCancel251(){\n if(inventory245)inventory245.mode='choice';\n document.getElementById('opsDialog92')?.close();\n if(inventory245?.origin==='list')manualInventoryRestoreScroll246(manualInventoryScroll246);\n if(inventory245?.origin==='photo')renderPhoto144();\n}\nconst inventorySaveBefore251=inventorySave245;\ninventorySave245=function(kind,payload={}){\n const d=inventory245;\n if(kind==='correct'&&d&&!d.done&&state.products[d.code]&&inventorySnapshot245(d.code)===d.snapshot&&inventoryMismatch251(d.code)){\n  if(!guardPermission('inventory','Inventur'))return false;\n  inventoryReconcile251();return false;\n }\n return inventorySaveBefore251(kind,payload);\n};\nconst manualInventoryActionBefore251=manualInventoryAction246;\nmanualInventoryAction246=function(code,kind,expected){\n if(['minus','plus'].includes(kind)&&state.products[code]&&inventoryTracked245(code)&&(inventoryMismatch251(code)||inventoryLots245(code).length>1)){\n  if(!guardPermission('inventory','Inventur'))return;\n  if(!state.manualInventory246)return afterOperation275(manualInventoryStart246(),ok=>{if(ok)return manualInventoryAction246(code,kind,expected);return false});\n  manualInventoryScroll246=manualInventoryCaptureScroll246();inventoryBegin245(code,'list',true);return inventoryReconcile251();\n }\n return manualInventoryActionBefore251(code,kind,expected);\n};\n\n// Background refresh must not replace an open reconciliation with another inventory form.\nconst inventoryRenderBefore251=inventoryRender245;\ninventoryRender245=function(...args){\n const d=inventory245;if(d?.mode!=='reconcile')return inventoryRenderBefore251(...args);\n if(!document.querySelector('.inventoryReconcile251'))return inventoryReconcile251();\n if(state.products[d.code]&&inventorySnapshot245(d.code)!==d.snapshot){\n  const error=document.getElementById('inventoryError251');if(error){error.textContent='Bestand inzwischen geändert. Abbrechen und erneut prüfen.';error.hidden=false}\n  const button=document.getElementById('inventoryConfirm251');if(button)button.disabled=true;\n }\n};\n\nconst inventoryMarkupBefore251=inventoryMarkup245;\ninventoryMarkup245=function(...args){\n if(inventory245?.mode==='reconcile')return '<p>Mengenprüfung geöffnet.</p><button class=\"soft\" onclick=\"inventoryReconcile251()\">Mengen prüfen</button>';\n return inventoryMarkupBefore251(...args);\n};\n"},{"src":"inventory-first-count253.js?v=275","code":"'use strict';\n// A new MHD article has no lot yet. Ask for the counted amount and its date.\nfunction inventoryFirstCount253(){\n const d=inventory245,a=state.products[d?.code];if(!d||!a||d.done||!guardPermission('inventory','Inventur'))return;\n if(inventoryLots245(d.code).length)return inventoryReconcile251();\n d.mode='reconcile';const step=isWholeQuantityUnit(a.unit)?'1':'any';\n openOpsPanel('Bestand eintragen',`<form class=\"manualInventoryPopup246 inventoryReconcile251 inventoryFirstCount253\" onsubmit=\"event.preventDefault();inventoryReconcileSubmit251()\"><h3>${esc(a.name)}</h3><label>Tatsächlich vorhanden (${esc(a.unit||'Stück')})<input id=\"inventoryActual251\" type=\"number\" inputmode=\"decimal\" min=\"0\" step=\"${step}\" placeholder=\"Menge\" oninput=\"inventoryReconcileUpdate251()\"></label><label>MHD<input id=\"inventoryFirstMhd253\" type=\"date\" oninput=\"inventoryReconcileUpdate251()\"></label>${Number(a.consignmentQty168)>0?'<label>Ware<select id=\"inventoryFirstOwner253\"><option value=\"owned\">Eigener Bestand</option><option value=\"commission\">Kommission</option></select></label>':''}<div id=\"inventoryNewRows251\"></div><button type=\"button\" class=\"soft\" onclick=\"inventoryNewRow251()\">Weiteres MHD</button><p id=\"inventoryError251\" class=\"manualInventoryError246\" role=\"alert\" hidden></p><div class=\"manualInventoryPopupActions246\"><button id=\"inventoryConfirm251\" type=\"submit\" class=\"primary\" disabled>Bestand speichern</button><button type=\"button\" class=\"cancelAction239\" onclick=\"inventoryReconcileCancel251()\">Abbrechen</button></div></form>`);\n document.getElementById('opsDialog92')?.classList?.add('manualInventoryDialog246');inventoryReconcileUpdate251();\n}\nconst inventoryReadBefore253=inventoryReconcileRead251;\ninventoryReconcileRead251=function(...args){\n if(!document.querySelector('.inventoryFirstCount253'))return inventoryReadBefore253(...args);\n const actual=document.getElementById('inventoryActual251')?.value??'';\n const extra=[...document.querySelectorAll('.inventoryNewRow251')].map(row=>({qty:row.querySelector('.inventoryNewQty251').value,mhd:row.querySelector('.inventoryNewDate251').value,commission:row.querySelector('.inventoryNewOwner251').value==='commission'}));const remainder=Number(String(actual).replace(',','.'))-extra.reduce((sum,n)=>sum+Number(String(n.qty).replace(',','.')),0);return {actual,rows:[],added:[{qty:Number.isFinite(remainder)&&extra.length?String(remainder):actual,mhd:document.getElementById('inventoryFirstMhd253')?.value||'',commission:document.getElementById('inventoryFirstOwner253')?.value==='commission'},...extra]};\n};\nconst inventoryModeBefore253=inventoryMode245;\ninventoryMode245=function(mode,...args){\n const d=inventory245;if(mode==='count'&&d&&!d.done&&inventoryTracked245(d.code)){\n  if(!inventoryLots245(d.code).length)return inventoryFirstCount253();\n  if(inventoryMismatch251(d.code))return inventoryReconcile251();\n }\n return inventoryModeBefore253(mode,...args);\n};\nconst manualInventoryActionBefore253=manualInventoryAction246;\nmanualInventoryAction246=function(code,kind,expected){\n if(['minus','plus'].includes(kind)&&state.products[code]&&inventoryTracked245(code)&&!inventoryLots245(code).length){\n  if(!guardPermission('inventory','Inventur'))return;\n  if(!state.manualInventory246)return afterOperation275(manualInventoryStart246(),ok=>{if(ok)return manualInventoryAction246(code,kind,expected);return false});\n  manualInventoryScroll246=manualInventoryCaptureScroll246();inventoryBegin245(code,'list',true);return inventoryFirstCount253();\n }\n return manualInventoryActionBefore253(code,kind,expected);\n};\n// Do not let an old or programmatically triggered empty form confirm zero stock.\nconst inventorySubmitBefore253=inventorySubmit245;\ninventorySubmit245=function(...args){\n const d=inventory245;if(d?.mode==='count'&&inventoryTracked245(d.code)&&!inventoryLots245(d.code).length)return inventoryFirstCount253();\n return inventorySubmitBefore253(...args);\n};\n// Reopening a first-count dialog uses the same simple form, including after X/back.\nconst inventoryReconcileBefore253=inventoryReconcile251;\ninventoryReconcile251=function(...args){\n const d=inventory245;if(d&&state.products[d.code]&&inventoryTracked245(d.code)&&!inventoryLots245(d.code).length)return inventoryFirstCount253();\n return inventoryReconcileBefore253(...args);\n};\n"},{"src":"cloud-safety254.js?v=1","code":"'use strict';\n// Tenant/user-scoped durable outbox. Never send a recovered edit against a newer revision.\nfunction cloudOutboxKey254(){const c=cloudRuntime(),workspace=cloudConfig()?.workspace;return c.user?.id&&workspace?'zapasio_outbox254:'+workspace+':'+c.user.id:null}\nfunction cloudOutboxRead254(){const key=cloudOutboxKey254();if(!key)return null;try{return JSON.parse(localStorage.getItem(key)||'null')}catch{return null}}\nfunction cloudRights254(){const u=cloudRuntime().user||{},p=u.permissions||{};return JSON.stringify([u.role||'',Object.keys(p).sort().map(k=>[k,p[k]])])}\nfunction cloudOutboxSave254(task){const key=cloudOutboxKey254();if(!key)throw Error('Zugang zuerst prüfen');localStorage.setItem(key,JSON.stringify({revision:cloudRuntime().revision,rights:cloudRights254(),task,at:new Date().toISOString()}))}\nfunction cloudOutboxClear254(){const key=cloudOutboxKey254();if(key)localStorage.removeItem(key)}\nqueueCloudSave=function(message){const c=cloudRuntime(),task={data:clone(state),message:message||'Änderung'};cloudOutboxSave254(task);c.pending=task;c.status='Auf diesem Gerät gespeichert · Wird gesendet';updateCloudStatus();flushCloudSave()};\nlet cloudRetryTimer254=null,cloudRetryDelay254=1500;\nfunction cloudRetry254(){clearTimeout(cloudRetryTimer254);const c=cloudRuntime();if(!c.retryable254||!c.user)return;cloudRetryTimer254=setTimeout(async()=>{if(!c.retryable254||!c.user||c.busy)return cloudRetry254();if(typeof navigator!=='undefined'&&navigator.onLine===false)return cloudRetry254();c.blocked=false;c.pending=c.failedTask160||cloudOutboxRead254()?.task;c.failedTask160=null;await flushCloudSave()},cloudRetryDelay254);cloudRetryDelay254=Math.min(60000,cloudRetryDelay254*2)}\nflushCloudSave=async function(){\n const c=cloudRuntime();if(c.busy||!c.pending||c.blocked)return;c.busy=true;let task;\n try{while(c.pending&&!c.blocked){task=c.pending;c.pending=null;const first=c.base===null;const result=await cloudRequest('/rest/v1/rpc/gs_write',{p_workspace:cloudConfig().workspace,p_revision:c.revision,p_data:task.data,p_action:task.message});c.revision=result.revision;c.base=clone(task.data);c.failedTask160=null;c.permissionDenied160='';c.deviceError449=false;c.retryable254=false;cloudRetryDelay254=1500;if(c.pending)cloudOutboxSave254(c.pending);else cloudOutboxClear254();if(first)finishCloudLogin();c.status='Auf dem Server gespeichert · '+new Date().toLocaleTimeString('de-AT');updateCloudStatus()}\n if(!c.pending&&!c.blocked)showFeedback('Auf dem Server gespeichert','success',1800);\n }catch(error){const message=String(error.message||error),denied=/Rechte fehlen|nicht freigegeben|Administrator|Schreibberechtigung|Kein Zugriff|Zugriff verweigert/.test(message),conflict=/Datenkonflikt|neueren gemeinsamen Stand|Datenstand|Chargen|Bestand prüfen|Menge prüfen|Gerätesicherung/.test(message);c.failedTask160=c.pending||task;c.pending=null;c.blocked=true;c.permissionDenied160=denied?message:'';c.deviceError449=/Gerätesicherung/.test(message);c.retryable254=!denied&&!conflict&&!c.deviceError449;c.status=c.deviceError449?'Speichern auf diesem Gerät fehlgeschlagen':denied?'Speichern auf dem Server nicht erlaubt':conflict?'Auf anderem Gerät geändert · Eingabe prüfen':'Auf diesem Gerät gespeichert · Wird bei Verbindung gesendet';showFeedback(c.deviceError449?'Nicht auf diesem Gerät gespeichert':denied?'Berechtigung fehlt':conflict?'Auf anderem Gerät geändert':'Auf diesem Gerät gespeichert','warn',5000,c.deviceError449?'Bitte die Eingabe geöffnet lassen und den Gerätespeicher prüfen. Es wurde nichts an den Server gesendet.':denied?'Deine Eingabe bleibt auf diesem Gerät. Bitte Peter um die nötige Berechtigung.':conflict?'Deine Eingabe bleibt erhalten. Öffne „Eingabe prüfen“, bevor du weiterarbeitest.':'Die App sendet deine Eingabe automatisch, sobald die Verbindung wieder funktioniert.');updateCloudStatus();\n }finally{c.busy=false;if(typeof moveSensitiveToSession139==='function')moveSensitiveToSession139();if(c.retryable254)cloudRetry254()}\n};\nfunction cloudRecoverOutbox254(){const c=cloudRuntime(),saved=cloudOutboxRead254();if(!saved?.task?.data||!c.user)return false;if(saved.rights!==cloudRights254()){c.retryable254=false;showFeedback('Zugriffsrechte geändert','warn',6000,'Gesicherte Eingabe zuerst mit der Administration prüfen.');return false}state=migrate(validBackupState(clone(saved.task.data)));c.failedTask160=saved.task;if(saved.revision===c.revision){c.blocked=false;c.pending=saved.task;c.failedTask160=null;flushCloudSave()}else{c.blocked=true;c.retryable254=false;c.status='Auf anderem Gerät geändert · Eingabe prüfen';showFeedback('Eingabe wiederhergestellt','warn',6000,'Deine Eingabe bleibt auf diesem Gerät. Öffne „Eingabe prüfen“, um den aktuellen Serverstand zu laden.')}render();updateCloudStatus();return true}\nconst resumeCloudBefore254=resumeCloudSession119;\nresumeCloudSession119=async function(...args){const ok=await resumeCloudBefore254(...args);if(ok)cloudRecoverOutbox254();return ok};\nconst signInCloudBefore254=signInCloud;\nsignInCloud=async function(...args){const result=await signInCloudBefore254(...args);if(cloudRuntime().user)cloudRecoverOutbox254();return result};\nconst cloudPullBefore254=cloudPull;\ncloudPull=async function(...args){\n const c=cloudRuntime();if(c.pending||c.busy){showFeedback('Übertragung läuft','info');return false}\n const saved=cloudOutboxRead254();\n if(saved&&!c.discardOutbox254){\n  const key=cloudOutboxKey254(),raw=localStorage.getItem(key);\n  askConfirm('Deine noch nicht gesendete Eingabe durch die Daten vom Server ersetzen? Deine Eingabe wird dabei nicht auf den Server übertragen.',async()=>{\n   if(c.pending||c.busy||cloudOutboxKey254()!==key||localStorage.getItem(key)!==raw){showFeedback('Eingabe bleibt erhalten','warn',5000,'Der lokale Stand hat sich geändert. Bitte den Abgleich erneut öffnen.');return false}\n   c.discardOutbox254=true;\n   try{\n    // This confirmation already authorizes the load. Wait for actual durable\n    // readback instead of opening a second, unawaited confirmation dialog.\n    const loaded=await cloudPullBefore254(false);if(loaded!==true)return false;\n    if(cloudOutboxKey254()!==key||localStorage.getItem(key)!==raw){showFeedback('Neuere Eingabe bleibt erhalten','warn');return false}\n    cloudOutboxClear254();await window.deviceStore257?.flush();\n    c.failedTask160=null;c.retryable254=false;clearTimeout(cloudRetryTimer254);updateCloudStatus();return true;\n   }catch(error){\n    // Keep the recovery record if the device transaction could not finish.\n    if(cloudOutboxKey254()===key&&!cloudOutboxRead254())try{localStorage.setItem(key,raw)}catch{}\n    c.blocked=true;c.retryable254=false;c.status='Gerätesicherung prüfen';showFeedback('Abgleich nicht abgeschlossen','error',6000,'Die gesicherte Eingabe bleibt erhalten. '+error.message);updateCloudStatus();return false;\n   }finally{c.discardOutbox254=false}\n  },'Serverdaten laden','Eingabe ersetzen?');return false;\n }\n return cloudPullBefore254(...args);\n};\nconst cloudSettingsBefore254=openCloudSettings;\nopenCloudSettings=function(...args){const result=cloudSettingsBefore254(...args);if(cloudOutboxRead254())document.querySelector('#opsDialog92 .opsBody92')?.insertAdjacentHTML('afterbegin','<button class=\"soft\" onclick=\"cloudRecoverOutbox254()\">Meine Eingabe anzeigen</button>');return result};\naddEventListener('online',()=>{const c=cloudRuntime();if(c.retryable254){cloudRetryDelay254=100;cloudRetry254()}});\n// Reserve durable recovery storage before changing the primary local snapshot.\nconst persistBefore254=persistState;\npersistState=function(...args){const c=cloudRuntime();if(!c.enabled||!c.user||c.blocked||c.base===null)return persistBefore254(...args);const key=cloudOutboxKey254();let previous;try{previous=localStorage.getItem(key);cloudOutboxSave254({data:clone(state),message:args[0]||'Änderung'})}catch(error){showFeedback('Speichern nicht möglich','error',5000,typeof window.storageErrorText257==='function'?window.storageErrorText257(error):'Die Gerätesicherung ist fehlgeschlagen. Die Eingabe bleibt geöffnet.');return false}const ok=persistBefore254(...args);if(!ok){try{if(previous===null)localStorage.removeItem(key);else localStorage.setItem(key,previous)}catch{}}return ok};\n"},{"src":"core-language254.js?v=1","code":"'use strict';\n// Shared short actions in the existing core workflows. Product names and inputs remain untouched.\nconst CORE_LANGUAGE254={\n 'Inventur':['Inventory','Inwentaryzacja'],'Foto-Inventur':['Photo inventory','Inwentaryzacja ze zdjęciem'],'Manuelle Inventur':['Manual inventory','Ręczna inwentaryzacja'],'Stand korrekt':['Stock correct','Stan prawidłowy'],'Passt nicht':['Different count','Inny stan'],'Ausschuss':['Waste','Straty'],'Zurück':['Back','Wstecz'],'Weiter':['Next','Dalej'],'Abbrechen':['Cancel','Anuluj'],'Schließen':['Close','Zamknij'],'× Schließen':['× Close','× Zamknij'],'OK':['OK','OK'],'Ja':['Yes','Tak'],'Nein':['No','Nie'],'Bestätigen':['Confirm','Potwierdź'],'Bestand speichern':['Save stock','Zapisz stan'],'Bestand prüfen':['Review stock','Sprawdź stan'],'Stand eintragen':['Enter count','Wpisz stan'],'Bestand eintragen':['Enter stock','Wpisz stan'],'Mengen prüfen':['Review quantities','Sprawdź ilości'],'Weiteres MHD':['Another expiry date','Kolejny termin ważności'],'Weitere Ware mit MHD':['More stock with expiry','Dodatkowy stan z terminem'],'Entfernen':['Remove','Usuń'],'Ausschuss prüfen':['Review waste','Sprawdź straty'],'Ausschuss buchen':['Record waste','Zapisz straty'],'Grund':['Reason','Powód'],'Eigener Grund':['Other reason','Inny powód'],'Defekt':['Damaged','Uszkodzone'],'MHD überschritten':['Past best-before date','Po terminie'],'Bitte wählen':['Select','Wybierz'],'Kommission':['Consignment','Komis'],'Eigener Bestand':['Owned stock','Własny stan'],'Eigene Ware':['Owned goods','Własny towar'],'Ware':['Goods','Towar'],'MHD':['Best-before date','Termin ważności'],'Tag':['Day','Dzień'],'Monat':['Month','Miesiąc'],'Jahr':['Year','Rok'],'optional':['optional','opcjonalnie'],'Menge':['Quantity','Ilość'],'Artikel anlegen':['Create item','Dodaj artykuł'],'Artikel bearbeiten':['Edit item','Edytuj artykuł'],'Artikel speichern':['Save item','Zapisz artykuł'],'Artikel prüfen':['Review item','Sprawdź artykuł'],'Produkt erkannt':['Product found','Produkt rozpoznany'],'Artikel gefunden':['Item found','Artykuł znaleziony'],'Produkt suchen':['Find product','Znajdź produkt'],'Neues Produkt fotografieren':['Photograph new product','Zrób zdjęcie nowego produktu'],'Foto aufnehmen':['Take photo','Zrób zdjęcie'],'Foto auswählen':['Select photo','Wybierz zdjęcie'],'Fotos abbrechen':['Cancel photos','Anuluj zdjęcia'],'Buchung prüfen':['Review transaction','Sprawdź zapis'],'Einbuchen':['Receive stock','Przyjmij towar'],'Entnehmen':['Withdraw stock','Wydaj towar'],'Auf Lager buchen':['Receive stock','Przyjmij do magazynu'],'Aus Lager entnehmen':['Withdraw stock','Wydaj z magazynu'],'Preis gleich geblieben?':['Same price?','Ta sama cena?'],'Produktdaten gefunden':['Product details found','Znaleziono dane produktu'],'Daten übernehmen':['Use details','Użyj danych'],'Originalbarcode ergänzen':['Add original barcode','Dodaj oryginalny kod'],'Code erzeugen':['Generate code','Utwórz kod'],'Rechnung prüfen':['Review invoice','Sprawdź fakturę'],'Lieferschein buchen':['Review delivery','Sprawdź dostawę'],'Produkt ergänzen':['Add product','Dodaj produkt'],'Produkt entfernen':['Remove product','Usuń produkt'],'Artikelname':['Item name','Nazwa artykułu'],'Barcode':['Barcode','Kod kreskowy'],'Lagereinheit':['Stock unit','Jednostka magazynowa'],'Artikelart':['Item type','Rodzaj artykułu'],'MHD-Regel':['Expiry rule','Reguła ważności'],'MHD erforderlich':['Expiry required','Termin wymagany'],'MHD optional':['Optional expiry','Termin opcjonalny'],'Kein MHD':['No expiry','Bez terminu'],'Noch zu prüfen':['Needs review','Do sprawdzenia'],'Gelieferte Menge':['Delivered quantity','Dostarczona ilość'],'MHD vom Produkt':['Product expiry','Termin produktu'],'Gesamten Lagerbestand geprüft?':['Whole stock counted?','Cały stan policzony?'],'Mengen nach MHD prüfen.':['Count each expiry date.','Policz według terminów.'],'Ausloggen':['Log out','Wyloguj'],'Einstellungen':['Settings','Ustawienia']\n};\nfunction coreTranslation254(text,language){const key=Object.hasOwn(CORE_LANGUAGE254,text)?text:Object.keys(CORE_LANGUAGE254).find(k=>CORE_LANGUAGE254[k].includes(text));return key?(language==='pl'?CORE_LANGUAGE254[key][1]:language==='en'?CORE_LANGUAGE254[key][0]:key):text}\nfunction applyCoreLanguage254(){const language=business130().language;if(!['de','en','pl'].includes(language))return;for(const el of document.querySelectorAll('#opsDialog92 button,#opsDialog92 label,#opsDialog92 option,#opsDialog92 h2,#photoModal144 button,#photoModal144 label,#photoModal144 h2,#articleModal button,#articleModal label,#articleModal option,#guideBox194 button,#guideBox194 label,#guideBox194 option,#guideBox194 h2,#profileMenu123 button')){if(el.tagName==='OPTION'&&!el.hasAttribute('value'))el.setAttribute('value',el.value);for(const node of el.childNodes){if(node.nodeType!==3)continue;const value=node.textContent,plain=value.trim(),translated=coreTranslation254(plain,language);if(translated!==plain)node.textContent=value.replace(plain,translated)}}}\nlet languageFrame254=null;\nconst languageObserver254=new MutationObserver(()=>{if(languageFrame254!==null)return;languageFrame254=requestAnimationFrame(()=>{languageFrame254=null;applyCoreLanguage254()})});\nfor(const id of ['opsDialog92','photoModal144','articleModal']){const host=document.getElementById(id);if(host)languageObserver254.observe(host,{childList:true,subtree:true})}\nconst applyLanguageBefore254=applyLanguage130;\napplyLanguage130=function(...args){const result=applyLanguageBefore254(...args);applyCoreLanguage254();return result};\n"},{"src":"daily-calculation255.js?v=406","code":"'use strict';\n// Empty quantities mean unknown, never zero. This calculation does not book stock.\nfunction dailyKnown255(value){return value!==null&&value!==undefined&&value!==''}\nfunction dailyValidation255(row){\n  for(const key of ['sold','prepared','leftover']){\n    if(key!=='sold'&&!dailyKnown255(row[key]))continue;\n    if(!dailyKnown255(row[key])||!Number.isInteger(Number(row[key]))||Number(row[key])<0)return 'Ganze, nicht negative Portionszahlen eingeben';\n  }\n  if(dailyKnown255(row.prepared)&&(Number(row.sold)>Number(row.prepared)||(dailyKnown255(row.leftover)&&Number(row.sold)+Number(row.leftover)>Number(row.prepared))))return 'Verkauft und übrig übersteigen die Vorbereitung';\n  if(!Number.isFinite(Number(row.sellPriceNet??0))||Number(row.sellPriceNet??0)<0)return 'Verkaufspreis prüfen';\n  return '';\n}\nfunction dailyOptionalTotal255(items,key){\n  const known=items.filter(x=>dailyKnown255(x[key]));\n  if(!known.length)return 'Nicht erfasst';\n  const total=known.reduce((s,x)=>s+Number(x[key]),0);\n  return total+(known.length<items.length?' (teilweise)':'');\n}\nfunction dailyEconomics255(items){\n  let revenue=0,cost=0,complete=true;\n  for(const row of items){\n    if(!row.recipeId)continue;\n    const sold=Number(row.sold)||0;\n    if(row.economics255){revenue+=row.economics255.revenue;cost+=row.economics255.cost;complete=complete&&row.economics255.complete;continue}\n    revenue+=sold*(Number(row.sellPriceNet)||0);\n    if(!sold)continue;\n    const recipe=findRecipeById(row.recipeId)?.recipe;\n    const economics=recipe?recipeEconomicMeta(recipe):null;\n    // Quantities in this form are portions, never kg or litres.\n    if(!economics?.complete||!['Portionen','Stück'].includes(recipePlanningUnit108(recipe))||!Number.isFinite(economics.unitCost)){complete=false;continue}\n    cost+=sold*economics.unitCost;\n  }\n  return {revenue,cost,complete,contribution:complete?revenue-cost:null};\n}\nfunction dailyEconomicsHtml255(items){\n  const result=dailyEconomics255(items);\n  return `<div class=\"dailyCloseSummary dailyEconomics255\"><div><small>Netto-Umsatz</small><b>${moneyFmt(result.revenue)}</b></div><div><small>Rechnerischer Wareneinsatz</small><b>${result.complete?moneyFmt(result.cost):'Kosten unvollständig'}</b></div><div><small>Umsatz abzüglich Wareneinsatz</small><b>${result.complete?moneyFmt(result.contribution):'Nicht berechenbar'}</b></div></div><p class=\"tiny\">Nach verkauften Portionen und Rezeptur, inklusive hinterlegter Verpackung. ${items.some(r=>r.dayCashSummary405)?'Enthält auch erfasste weitere Verkäufe, Ausschuss und Eigenverbrauch.':'Reste und Ausschuss sind darin nicht enthalten.'} Personal, Fahrt und weitere Betriebskosten sind hier nicht abgezogen. Keine Lagerbuchung.</p>`;\n}\nrenderDailyCloseSummary=function(){\n  const box=document.getElementById('dailyCloseSummary');if(!box)return;\n  // Drafts always use the current sale quantities and prices, not saved snapshots.\n  const items=dailyCloseDraft.map(({economics255,...row})=>row);\n  const sold=items.reduce((s,x)=>s+(Number(x.sold)||0),0);\n  box.innerHTML=`<div><small>Verkauft</small><b>${sold}</b></div><div><small>Vorbereitet</small><b>${dailyOptionalTotal255(items,'prepared')}</b></div><div><small>Übrig</small><b>${dailyOptionalTotal255(items,'leftover')}</b></div>${dailyEconomicsHtml255(items)}`;\n};\n// Recalculate the snapshot when an existing closing is edited.\nconst loadDailyBefore255=loadDailyCloseForDate;\nloadDailyCloseForDate=function(){loadDailyBefore255();for(const row of dailyCloseDraft)delete row.economics255;renderDailyCloseRows()};\nconst dishRankingBefore255=dishRanking118;\ndishRanking118=function(metric='sold',days=90){\n  if(metric==='sold')return dishRankingBefore255(metric,days);\n  const map={},cutoff=rankCutoff118(days);\n  for(const closing of state.dailyClosings||[]){\n    if(new Date(closing.date+'T12:00:00').getTime()<cutoff)continue;\n    for(const item of closing.items||[]){\n      const key=metric==='sellThrough'?'prepared':'leftover';\n      if(!dailyKnown255(item[key]))continue;\n      const row=map[item.recipeId]||(map[item.recipeId]={name:findRecipeById(item.recipeId)?.recipe?.name||'Unbekanntes Gericht',sold:0,prepared:0,leftover:0});\n      row.sold+=Number(item.sold)||0;row.prepared+=Number(item.prepared)||0;row.leftover+=Number(item.leftover)||0;\n    }\n  }\n  return Object.values(map).filter(x=>metric!=='sellThrough'||x.prepared>0).map(x=>({name:x.name,value:metric==='sellThrough'?x.sold/x.prepared*100:x.leftover,unit:metric==='sellThrough'?'%':'Port.',detail:'Nur Tage mit erfasster '+(metric==='sellThrough'?'Vorbereitung':'Restmenge')})).sort((a,b)=>b.value-a.value);\n};\n"},{"src":"new-article-mhd256.js?v=277","code":"'use strict';\n// Resolve the article rule before the existing flow continues to quantity and date.\nfunction newArticleMhdReady256(){\n  if(editingArticleCode||productGuide194)return true;\n  const policy=document.getElementById('aeMhdPolicy144');\n  const days=document.getElementById('aeMhdDays242')?.value.trim()||'';\n  if(days||['required','optional','none'].includes(policy?.value))return true;\n  showFeedback('MHD-Regel auswählen','warn',5000,'Soll beim Wareneingang ein MHD erfasst werden? Erforderlich, optional oder kein MHD wählen.');\n  policy?.scrollIntoView?.({block:'center',behavior:'smooth'});\n  policy?.focus?.();\n  return false;\n}\nconst saveArticleBefore256=saveArticleEditor;\nsaveArticleEditor=function(...args){if(!newArticleMhdReady256())return;return saveArticleBefore256(...args)};\nconst openArticleBefore256=openArticleEditor;\nopenArticleEditor=function(...args){\n  const result=openArticleBefore256(...args);\n  if(!editingArticleCode){\n    const policy=document.getElementById('aeMhdPolicy144');\n    const field=policy?.closest('.field');\n    const label=field?.querySelector('label');\n    if(label){label.textContent='Soll beim Wareneingang ein MHD erfasst werden?';label.htmlFor='aeMhdPolicy144'}\n    const placeholder=policy?.querySelector('option[value=\"review\"]');\n    if(placeholder)placeholder.textContent='Bitte wählen';\n    const category=document.getElementById('aeCategory')?.closest('.field');\n    if(field&&category){\n      category.insertAdjacentElement('afterend',field);\n      // The old storage section was collapsed before this wrapper moved the\n      // MHD fields into the first section. Moving preserves the hidden flag.\n      field.dataset.articleFold=category.dataset.articleFold||'1';\n      field.hidden=false;\n    }\n    // Grouping is part of the existing editor, but was inaccessible in quick\n    // creation because its section heading and field were both hidden.\n    const group=document.getElementById('aeMainArticle')?.closest('.field');\n    const factor=document.getElementById('aeMainFactorField');\n    if(group&&field){\n      field.insertAdjacentElement('afterend',group);\n      group.dataset.articleFold=field.dataset.articleFold;group.hidden=false;\n      if(factor){group.insertAdjacentElement('afterend',factor);factor.dataset.articleFold=field.dataset.articleFold;factor.hidden=false;}\n    }\n  }\n  // Keep the three related assignments together, including after a previous\n  // quick-create invocation moved the MHD fields into this section.\n  const category=document.getElementById('aeCategory')?.closest('.field');\n  let previous=category;\n  for(const id of ['aeSubgroup','aeShoppingArea173']){\n    const field=document.getElementById(id)?.closest('.field');\n    if(previous&&field){previous.insertAdjacentElement('afterend',field);field.dataset.articleFold=category.dataset.articleFold||'1';field.hidden=category.hidden;previous=field;}\n  }\n  return result;\n};\n\nfunction receiptMhdError256(article,date){\n const policy=recognizedMhdPolicy256(article);\n if(policy==='review')return 'Bitte zuerst die MHD-Regel im Artikel festlegen.';\n if(policy==='none')return '';\n if(!date)return policy==='required'?'Bitte das MHD dieser Ware eingeben.':'';\n const parsed=new Date(date+'T00:00:00Z');\n return !/^\\d{4}-\\d{2}-\\d{2}$/.test(date)||!Number.isFinite(parsed.getTime())||parsed.toISOString().slice(0,10)!==date?'Bitte ein gültiges MHD eingeben.':'';\n}\n"},{"src":"mhd-input258.js?v=276","code":"'use strict';\nfunction formatMhdDigits262(raw){return String(raw??'').replace(/^(\\d{2})(\\d{2})(\\d{4})$/, '$1.$2.$3');}\nfunction parseShelfOrDate258(raw){\n const value=formatMhdDigits262(String(raw||'').trim());\n if(!value)return {days:'',date:''};\n if(/^\\d+$/.test(value)){dateAfterDays242(value);return {days:value,date:''};}\n const parsed=parseMhd147(value);\n if(!parsed)throw Error('Tage oder ein gültiges MHD eingeben, z. B. 10 oder 10.10.2026.');\n let date=parsed.iso;\n if(parsed.monthOnly){const [year,month]=date.split('-').map(Number);date+='-'+new Date(Date.UTC(year,month,0)).getUTCDate();}\n return {days:'',date};\n}\nconst saveArticleBefore258=saveArticleEditor;\nsaveArticleEditor=function(...args){\n const field=document.getElementById('aeMhdDays242'),raw=field?.value||'';\n let parsed;try{parsed=parseShelfOrDate258(raw)}catch(error){showFeedback('MHD prüfen','warn',5000,error.message);const group=field?.closest?.('.field');if(group){if(group.dataset.articleFold&&typeof setArticleFold==='function')setArticleFold(group.dataset.articleFold,true);group.hidden=false;}field?.scrollIntoView?.({block:'center'});field?.focus();return;}\n if(!parsed.date)return saveArticleBefore258(...args);\n const stock=Number(document.getElementById('aeStock')?.value)||0;\n collectExistingMhdEditor();\n if(stock>0&&articleBatchDraft.length>1){showFeedback('MHD-Chargen prüfen','warn',5000,'Bei mehreren Chargen das Datum direkt bei der jeweiligen Menge eintragen.');return;}\n const oldDraft=clone(articleBatchDraft),oldPolicy=document.getElementById('aeMhdPolicy144').value;\n const before=state.products[current];\n document.getElementById('aeMhdPolicy144').value='required';document.getElementById('aeMhd').checked=true;\n if(stock>0){articleBatchDraft=[{...(articleBatchDraft[0]||{}),id:articleBatchDraft[0]?.id||'manual-'+Date.now(),mhd:parsed.date,qty:stock}];renderExistingMhdEditor();}\n field.value='';\n let result;\n try{result=saveArticleBefore258(...args);}finally{field.value=raw;}\n const saved=state.products[current];\n if(saved&&saved!==before){\n  if(typeof productGuide194!=='undefined'&&productGuide194?.existing?.productCode===current)productGuide194.receiptMhd258=parsed.date;\n  if(typeof photoDraft144!=='undefined'&&photoDraft144?.code===current)photoDraft144.receiptMhd258=parsed.date;\n }else{articleBatchDraft=oldDraft;document.getElementById('aeMhdPolicy144').value=oldPolicy;document.getElementById('aeMhd').checked=oldPolicy==='required';renderExistingMhdEditor();}\n return result;\n};\n"},{"src":"stock-actions264.js?v=1","code":"'use strict';\nfunction arrangeStockActions264(){\n const scan=document.querySelector('#tabLager .scan'),row=document.querySelector('#tabLager .stockCreateAction261');\n if(!scan||!row)return;\n const search=scan.querySelector('button[onclick=\"lookup()\"]');\n if(search)row.prepend(search);\n const inbound=document.getElementById('inboundActions182');\n if(inbound){row.appendChild(inbound);const button=inbound.querySelector('button');if(button){button.textContent='Beleg erfassen';button.title='Foto oder PDF erfassen';}}\n}\nconst renderBefore264=render;\nrender=function(...args){const result=renderBefore264(...args);arrangeStockActions264();return result;};\narrangeStockActions264();\n"},{"src":"booking-layout265.js?v=1","code":"'use strict';\nconst openModalBefore265=openModal;\nopenModal=function(kind){\n const result=openModalBefore265(kind),modal=document.getElementById('modal');\n if(!modal||modal.style.display!=='flex')return result;\n modal.dataset.booking265=kind;\n if(kind==='in'){\n  const field=document.getElementById('mhdField'),input=document.getElementById('mhd'),container=document.getElementById('inFields'),policy=recognizedMhdPolicy256(p());\n  if(field&&container){container.prepend(field);field.style.display=['required','optional'].includes(policy)?'block':'none';}\n  if(input&&['required','optional'].includes(policy)){installExpiry231(input);input.formAdapter231?.sync();}\n }\n return result;\n};\n"},{"src":"yield-calculator266.js?v=1","code":"'use strict';\nfunction yieldResult266(weight,kind,value){\n const kg=Number(String(weight).replace(',','.')),parameter=Number(String(value).replace(',','.'));\n if(!String(weight).trim()||!Number.isFinite(kg)||kg<=0)throw Error('Ausgangsgewicht über 0 eingeben.');\n if(!String(value).trim()||!Number.isFinite(parameter))throw Error(kind==='loss'?'Verschnitt in Prozent eingeben.':'Quellfaktor eingeben.');\n if(kind==='loss'){if(parameter<0||parameter>=100)throw Error('Verschnitt zwischen 0 und unter 100 % eingeben.');return {input:kg,output:kg*(1-parameter/100),difference:kg*parameter/100};}\n if(kind!=='gain'||parameter<1)throw Error('Quellfaktor ab 1 eingeben.');\n return {input:kg,output:kg*parameter,difference:kg*(parameter-1)};\n}\nfunction openYield266(){\n let modal=document.getElementById('yieldModal266');\n if(!modal){modal=document.createElement('div');modal.id='yieldModal266';modal.className='modal';modal.innerHTML=`<div class=\"card\"><div class=\"modalHeader\"><h2>Verschnitt & Ausbeute</h2><button class=\"close\" type=\"button\" onclick=\"document.getElementById('yieldModal266').style.display='none'\">× Schließen</button></div><div class=\"field\"><label for=\"yieldProduct266\">Produkt (optional)</label><select id=\"yieldProduct266\" onchange=\"yieldProductChanged266()\"></select></div><div class=\"yieldForm266\"><div class=\"field\"><label for=\"yieldWeight266\">Ausgangsgewicht (kg)</label><input id=\"yieldWeight266\" inputmode=\"decimal\" placeholder=\"z. B. 5\" oninput=\"updateYield266()\"></div><div class=\"field\"><label for=\"yieldKind266\">Berechnung</label><select id=\"yieldKind266\" onchange=\"yieldKindChanged266()\"><option value=\"loss\">Verschnitt, z. B. Gemüse putzen</option><option value=\"gain\">Quellen, z. B. Reis kochen</option></select></div><div class=\"field\"><label id=\"yieldParameterLabel266\" for=\"yieldParameter266\">Verschnitt (%)</label><input id=\"yieldParameter266\" inputmode=\"decimal\" placeholder=\"Eigene Verschnittquote\" oninput=\"updateYield266()\"></div></div><p id=\"yieldNote266\" class=\"muted\">Verschnitt aus eigener Erfahrung oder Messung eingeben.</p><div id=\"yieldResult266\" aria-live=\"polite\"></div><p class=\"tiny\">Rechnerischer Richtwert. Die tatsächliche Ausbeute hängt von Produkt und Zubereitung ab.</p></div>`;document.body.appendChild(modal);}\n const select=document.getElementById('yieldProduct266');select.innerHTML='<option value=\"\">Ohne Lagerartikel rechnen</option>'+Object.entries(state.products||{}).sort((a,b)=>a[1].name.localeCompare(b[1].name)).map(([code,a])=>`<option value=\"${esc(code)}\">${esc(a.name)}</option>`).join('');\n modal.style.display='flex';updateYield266();document.getElementById('yieldWeight266').focus();\n}\nfunction riceYield267(article){const type=article?.riceType131||detectedRiceType131(article?.name||'');if(!type)return null;const saved=Number(article.riceYieldFactor131);return {factor:saved>1?saved:riceDefaultFactor131(type),saved:saved>1};}\nfunction applyRiceYield267(){const article=state.products[document.getElementById('yieldProduct266').value],profile=riceYield267(article);if(document.getElementById('yieldKind266').value!=='gain'||!profile)return;document.getElementById('yieldParameter266').value=String(profile.factor).replace('.',',');document.getElementById('yieldNote266').textContent=profile.saved?'Gespeicherter Küchenfaktor aus dem vorhandenen Reisumrechner übernommen.':'Richtwert der Reissorte aus dem vorhandenen Reisumrechner übernommen. Durch einen gemessenen Wert ersetzbar.';}\nfunction yieldKindChanged266(){const gain=document.getElementById('yieldKind266').value==='gain';document.getElementById('yieldParameterLabel266').textContent=gain?'Quellfaktor':'Verschnitt (%)';document.getElementById('yieldParameter266').value='';document.getElementById('yieldParameter266').placeholder=gain?'Eigener Faktor, z. B. 2,5':'Eigene Verschnittquote';document.getElementById('yieldNote266').textContent=gain?'Faktor = fertiges Gewicht geteilt durch trockenes Gewicht. Eigenen gemessenen Faktor verwenden.':'Verschnitt aus eigener Erfahrung oder Messung eingeben.';applyRiceYield267();updateYield266();}\nfunction yieldProductChanged266(){const a=state.products[document.getElementById('yieldProduct266').value];document.getElementById('yieldKind266').value=riceYield267(a)?'gain':'loss';yieldKindChanged266();if(!riceYield267(a)&&a&&a.prepLossPct!==null&&a.prepLossPct!==undefined&&Number.isFinite(Number(a.prepLossPct))){document.getElementById('yieldParameter266').value=String(a.prepLossPct||0).replace('.',',');document.getElementById('yieldNote266').textContent='Hinterlegte Verschnittquote des Artikels übernommen. Bei Bedarf für diese Rechnung ändern.';}updateYield266();}\nfunction updateYield266(){const out=document.getElementById('yieldResult266');if(!out)return;try{const kind=document.getElementById('yieldKind266').value,r=yieldResult266(document.getElementById('yieldWeight266').value,kind,document.getElementById('yieldParameter266').value),fmt=n=>n.toLocaleString('de-AT',{maximumFractionDigits:2})+' kg';out.innerHTML=`<div class=\"yieldAnswer266\"><small>${kind==='loss'?'Verwendbare Menge':'Fertige Menge'}</small><strong>${fmt(r.output)}</strong><span>${kind==='loss'?'Verschnitt':'Gewichtszunahme'}: ${fmt(r.difference)}</span></div>`;}catch(error){out.textContent=error.message;}}\n"},{"src":"mhd-check268.js?v=1","code":"'use strict';\nfunction refreshMhdCheck268(){\n const field=document.getElementById('sameMhdCheck268');if(!field)return;\n const count=Number(document.getElementById('qty')?.value),pack=document.getElementById('pack'),policy=recognizedMhdPolicy256(p());\n const fingerprint=JSON.stringify([current,count,pack?.selectedIndex,pack?.value,policy]);\n if(field.dataset.fingerprint268!==fingerprint){field.dataset.fingerprint268=fingerprint;field.querySelector('select').value='';}\n field.hidden=mode!=='in'||count<=1||!['required','optional'].includes(policy);\n field.querySelector('label').textContent=`Haben alle ${count} Packungen / Einheiten dasselbe MHD?`;\n}\nconst openModalBefore268=openModal;\nopenModal=function(...args){const result=openModalBefore268(...args);const host=document.getElementById('mhdField');if(host&&!document.getElementById('sameMhdCheck268')){const box=document.createElement('div');box.id='sameMhdCheck268';box.className='field';box.innerHTML='<label for=\"sameMhdAnswer268\"></label><select id=\"sameMhdAnswer268\"><option value=\"\">Bitte prüfen</option><option value=\"yes\">Ja, alle gleich</option><option value=\"no\">Nein, unterschiedliche MHD</option></select><small>Bei unterschiedlichen MHD die Teilmengen unten mit ihrem Datum erfassen.</small>';host.prepend(box);document.getElementById('qty').addEventListener('input',refreshMhdCheck268);document.getElementById('pack').addEventListener('change',refreshMhdCheck268);}const answer=document.getElementById('sameMhdAnswer268');if(answer)answer.value='';refreshMhdCheck268();return result;};\nconst bookBefore268=book;\nbook=function(...args){refreshMhdCheck268();const check=document.getElementById('sameMhdCheck268'),policy=recognizedMhdPolicy256(p()),date=document.getElementById('mhd')?.value;if(check&&!check.hidden&&(policy==='required'||date)&&check.querySelector('select').value!=='yes'){showFeedback('MHD der Packungen prüfen','warn',5000,check.querySelector('select').value==='no'?'Bitte die Menge mit gleichem MHD eingeben und mit diesem Datum buchen. Danach die nächste Teilmenge erfassen.':'Bitte bestätigen, ob alle Packungen dasselbe MHD haben.');check.querySelector('select').focus();return;}return bookBefore268(...args);};\n"},{"src":"mhd-split269.js?v=1","code":"'use strict';\nlet receiptLots269=null,splitRows269=[],splitSaving269=false;\nfunction validateSplit269(rows,total,whole){\n if(rows.length<2)throw Error('Mindestens zwei MHD-Zeilen erfassen.');\n for(const row of rows){if(!Number.isFinite(row.count)||row.count<=0||whole&&!Number.isInteger(row.count))throw Error('Jede Teilmenge muss positiv sein; ganze Packungen als ganze Zahl eingeben.');const d=new Date(row.date+'T00:00:00Z');if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(row.date)||!Number.isFinite(d.getTime())||d.toISOString().slice(0,10)!==row.date)throw Error('Für jede Teilmenge ein gültiges MHD eingeben.');}\n if(Math.abs(rows.reduce((sum,r)=>sum+r.count,0)-total)>1e-7)throw Error('Die Teilmengen müssen zusammen die Gesamtmenge ergeben.');\n return rows;\n}\nfunction collectSplit269(){return [...document.querySelectorAll('#splitMhdRows269 .splitMhdRow269')].map(row=>({count:Number(row.querySelector('.splitQty269').value),date:row.querySelector('.splitDate269').value}));}\nfunction updateSplitCount269(){const count=collectSplit269().reduce((sum,r)=>sum+(Number.isFinite(r.count)?r.count:0),0),out=document.getElementById('splitMhdCount269');if(out)out.textContent=`${count.toLocaleString('de-AT')} von ${Number(document.getElementById('qty').value).toLocaleString('de-AT')} Packungen / Einheiten erfasst`;}\nfunction renderSplit269(){\n const box=document.getElementById('splitMhdRows269');if(!box)return;\n box.innerHTML=splitRows269.map((r,i)=>`<div class=\"splitMhdRow269\"><b>${esc(p().name)} · Teilmenge ${i+1}</b><div class=\"splitMhdFields269\"><label>Menge<input class=\"splitQty269\" type=\"number\" min=\"0\" step=\"any\" value=\"${r.count}\" oninput=\"updateSplitCount269()\"></label><label>MHD<input id=\"mhdSplitDate269_${i}\" class=\"splitDate269\" type=\"date\" value=\"${esc(r.date)}\"></label></div>${splitRows269.length>2?`<button class=\"soft\" type=\"button\" onclick=\"removeSplit269(${i})\">Zeile entfernen</button>`:''}</div>`).join('');\n box.querySelectorAll('.splitDate269').forEach(installExpiry231);updateSplitCount269();\n}\nfunction nextSplitRows270(rows,total){\n const next=rows.map(r=>({...r})),sum=next.reduce((s,r)=>s+r.count,0),missing=total-sum;\n if(!Number.isFinite(total)||total<=0||next.some(r=>!Number.isFinite(r.count)||r.count<0)||missing<-1e-7)throw Error('Bitte zuerst die Teilmengen prüfen.');\n const empty=next.find(r=>r.count===0);\n if(missing>=1){if(empty)empty.count=1;else next.push({count:1,date:''});return next;}\n const donor=next.reduce((best,r)=>r.count>(best?.count||0)?r:best,null);\n if(donor?.count>1){donor.count-=1;if(empty)empty.count=1;else next.push({count:1,date:''});return next;}\n throw Error('Alle Packungen sind bereits einzeln zugeordnet.');\n}\nfunction addSplit269(){try{splitRows269=nextSplitRows270(collectSplit269(),Number(document.getElementById('qty').value));renderSplit269();}catch(error){showFeedback('MHD-Teilmengen prüfen','warn',4000,error.message);}}\nfunction removeSplit269(i){splitRows269=collectSplit269();splitRows269.splice(i,1);renderSplit269();}\nfunction toggleSplit269(){\n const split=document.getElementById('sameMhdAnswer268')?.value==='no'&&!document.getElementById('sameMhdCheck268')?.hidden,host=document.getElementById('mhdField'),input=document.getElementById('mhd');if(!host||!input)return;\n let box=document.getElementById('splitMhd269');if(!box){box=document.createElement('div');box.id='splitMhd269';box.innerHTML='<div id=\"splitMhdRows269\"></div><p id=\"splitMhdCount269\" aria-live=\"polite\"></p><p class=\"tiny\">Tag optional. Ohne Tag gilt Monatsende.</p><button class=\"soft\" type=\"button\" onclick=\"addSplit269()\">Weiteres MHD</button>';host.appendChild(box);}\n box.hidden=!split;host.classList.toggle('splitActive269',split);document.getElementById('singleMhdLabel269').hidden=split;if(input.formAdapter231)input.formAdapter231.box.hidden=split;else input.hidden=split;\n if(split&&!splitRows269.length){const total=Number(document.getElementById('qty').value);splitRows269=[{count:1,date:input.value},{count:total-1,date:''}];renderSplit269();}\n}\nconst openModalBefore269=openModal;\nopenModal=function(...args){splitRows269=[];const result=openModalBefore269(...args),answer=document.getElementById('sameMhdAnswer268');if(answer&&!answer.dataset.split269){answer.dataset.split269='1';answer.addEventListener('change',toggleSplit269);document.getElementById('qty').addEventListener('input',()=>{splitRows269=[];toggleSplit269();});document.getElementById('pack').addEventListener('change',()=>{splitRows269=[];toggleSplit269();});}toggleSplit269();return result;};\nfunction splitFingerprint269(){return JSON.stringify([current,mode,document.getElementById('qty').value,document.getElementById('pack').selectedIndex,document.getElementById('pack').value,document.getElementById('supplier').value,document.getElementById('price').value,document.getElementById('priceForQty').value,document.getElementById('priceUnit131')?.value,document.getElementById('zCommission168')?.checked,document.getElementById('sameMhdAnswer268')?.value,collectSplit269()]);}\nconst bookBefore269=book;\nbook=function(...args){\n if(splitSaving269)return;\n if(mode!=='in'||document.getElementById('sameMhdAnswer268')?.value!=='no')return bookBefore269(...args);\n refreshMhdCheck268();if(document.getElementById('sameMhdAnswer268').value!=='no')return bookBefore269(...args);\n let rows;try{const whole=Number(document.getElementById('pack').selectedIndex)>0||isWholeQuantityUnit(p().unit);rows=validateSplit269(collectSplit269(),Number(document.getElementById('qty').value),whole);const quote=bookingPurchaseQuote();if(quote.error)throw Error(quote.error);}catch(error){showFeedback('MHD-Teilmengen prüfen','warn',5000,error.message);return;}\n const fingerprint=splitFingerprint269(),name=p().name;\n askConfirm(name+'\\n'+rows.map(r=>`${r.count} Packung(en) / Einheiten · MHD ${formatDate(r.date)}`).join('\\n'),async()=>{\n  if(fingerprint!==splitFingerprint269()){showFeedback('Eingabe geändert','warn',4000,'Bitte die aktuellen Teilmengen erneut prüfen.');return;}\n  const input=document.getElementById('mhd'),old=input.value,modal=document.getElementById('modal'),wasInert=modal.inert;receiptLots269=rows;splitSaving269=true;modal.inert=true;\n  try{input.value=rows[0].date;return await bookBefore268(...args);}finally{receiptLots269=null;splitSaving269=false;modal.inert=wasInert;input.value=old;}\n },'Wareneingang buchen','Teilmengen bestätigen');\n};\n"},{"src":"storage-feedback257.js?v=1","code":"'use strict';\n// The legacy UI accepts an operation synchronously; success is deferred until its device transaction completes.\nconst feedbackBefore257=showFeedback;\nshowFeedback=function(title,kind,...args){\n const store=window.deviceStore257;\n if(kind==='success'&&store?.pending){feedbackBefore257('Speicherung läuft','info',2000);store.flush().then(()=>feedbackBefore257(title,kind,...args)).catch(()=>{});return}\n return feedbackBefore257(title,kind,...args);\n};\nconst persistBefore257=persistState;\npersistState=function(...args){\n const store=window.deviceStore257;\n if(store.error){store.flush().then(()=>feedbackBefore257('Gerätespeicher wieder bereit','info',4000,'Bitte die Eingabe erneut speichern.')).catch(()=>{});feedbackBefore257('Noch nicht gespeichert','error',6000,window.storageErrorText257(store.error));return false}\n const ok=persistBefore257(...args);\n if(ok&&store.pending)feedbackBefore257('Speicherung läuft','info',2000);\n return ok;\n};\nconst requestBefore257=cloudRequest;\ncloudRequest=async function(...args){\n // Both the main record and the outbox must be durable before a write leaves this device.\n if(String(args[0]).includes('/gs_write')){try{await window.deviceStore257.flush()}catch(error){throw Error('Gerätesicherung fehlgeschlagen: '+window.storageErrorText257(error))}}\n return requestBefore257(...args);\n};\nwindow.deviceStore257.onError(error=>{\n const c=cloudRuntime();if(c.enabled){c.blocked=true;c.retryable254=false;c.status='Gerätesicherung fehlgeschlagen';updateCloudStatus()}\n feedbackBefore257('Noch nicht auf dem Gerät gespeichert','error',10000,window.storageErrorText257(error));\n});\n\n// Keep the article form visible and prevent another operation until its local commit finishes.\nconst articleBefore257=saveArticleEditor;\nlet articleSaving257=false;\nsaveArticleEditor=function(...args){\n if(articleSaving257)return;\n const store=window.deviceStore257,modal=document.getElementById('articleModal');\n const checkpoint=store.checkpoint(),before=clone(state),editor={code:editingArticleCode,current,photo:articlePhotoData,batches:clone(articleBatchDraft),refs:clone(pendingArticleLinkRefs),editorRefs:clone(editorRefs144)};\n const result=articleBefore257(...args);\n if(!store.pending||modal?.style.display==='flex')return result;\n articleSaving257=true;modal.style.display='flex';const wasInert=modal.inert;modal.inert=true;\n const blocker=document.createElement('div');blocker.style.cssText='position:absolute;inset:0;background:#ffffffd9;z-index:99999;display:grid;place-content:center;font:16px system-ui';blocker.textContent='Artikel wird gesichert …';modal.appendChild(blocker);\n const c=cloudRuntime();\n store.flush().then(()=>{modal.style.display='none';feedbackBefore257(c.enabled?'Auf diesem Gerät gespeichert':'Artikel gespeichert','success',2500)}).catch(error=>{\n  store.rollback(checkpoint);state=before;editingArticleCode=editor.code;current=editor.current;articlePhotoData=editor.photo;articleBatchDraft=editor.batches;pendingArticleLinkRefs=editor.refs;editorRefs144=editor.editorRefs;\n  c.pending=null;c.failedTask160=null;c.retryable254=false;c.blocked=false;\n  if(typeof productGuide194!=='undefined')productGuide194=null;\n  modal.style.display='flex';feedbackBefore257('Artikel nicht gespeichert','error',8000,window.storageErrorText257(error));\n }).finally(()=>{blocker.remove();modal.inert=wasInert;articleSaving257=false});\n return result;\n};\n"},{"src":"storage-commit275.js?v=420","code":"'use strict';\n// A local operation owns one durable device commit. Its callers await the\n// boolean result before closing dialogs, clearing drafts or advancing queues.\nlet operationOwner275=null;\nlet pendingRecipeAutosave347=null;\nfunction recipeEditing347(){\n const field=typeof document!=='undefined'?document.activeElement:null;\n return !!(field?.closest?.('.recipeEditor')&&(field.matches?.('input,textarea,select,[contenteditable=\"true\"]')));\n}\nfunction busyOperation275(){return !!operationOwner275}\nfunction operationOverlay275(){\n if(typeof document==='undefined'||!document.body||!document.createElement)return ()=>{};\n const box=document.createElement('dialog');box.id='durableBusy275';box.setAttribute('aria-label','Eingabe wird auf diesem Gerät gespeichert');box.setAttribute('role','status');box.style.cssText='width:min(360px,calc(100vw - 32px));height:auto;max-height:180px;padding:24px;border:1px solid #cad7c4;border-radius:14px;background:white;color:#183c2c;text-align:center;z-index:2147483647';box.textContent='Eingabe wird auf diesem Gerät gespeichert …';box.addEventListener('cancel',event=>event.preventDefault());document.body.appendChild(box);box.showModal();return ()=>{box.close();box.remove()};\n}\nasync function commitOperation275(label,permission,mutate){\n if(operationOwner275){showFeedback('Speicherung läuft','info',2500,'Bitte kurz warten.');return false}\n if(!guardPermission(permission,label))return false;\n const store=window.deviceStore257;if(!store){showFeedback('Gerätespeicher nicht bereit','error');return false}\n const owner={phase:'drain',allowPersist:false,staged:false};operationOwner275=owner;\n let before,checkpoint,release,removeOverlay=()=>{},completed=false;\n try{\n  removeOverlay=operationOverlay275();release=store.pause();\n  // Drain earlier writes before taking a checkpoint; the new operation must\n  // neither roll them back nor accidentally retry its own failed commit.\n  await store.flush();if(!guardPermissionBefore275(permission,label))throw Error('Die Berechtigung wurde geändert. Bitte erneut prüfen.');checkpoint=store.checkpoint();before=clone(state);\n  owner.phase='mutating';mutate();owner.phase='persisting';owner.allowPersist=true;\n  let accepted;try{accepted=persistState(label,false)}finally{owner.allowPersist=false}\n  if(!accepted)throw Error(owner.persistFailure||'Noch nicht gebucht. Bitte den Datenabgleich prüfen. Deine Eingaben bleiben erhalten.');\n  owner.phase='committing';await store.flush();completed=true;\n }catch(error){\n  if(checkpoint){store.rollback(checkpoint);state=before}\n  owner.error=error;\n }finally{\n  release?.();removeOverlay();operationOwner275=null;\n }\n if(!completed){showFeedback(label+' nicht gespeichert','error',8000,owner.phase==='committing'?window.storageErrorText257(owner.error):owner.error?.message||'Die Eingabe bleibt geöffnet.');return false}\n // No new request leaves this device before the local commit succeeds.\n if(owner.staged){try{queueCloudSaveBefore275(label)}catch(error){const c=cloudRuntime();c.blocked=true;c.status='Auf diesem Gerät gespeichert · Abgleich offen';showFeedback('Übertragung noch offen','warn',6000,error.message);updateCloudStatus()}}\n try{render()}catch(error){showFeedback('Gespeichert, Ansicht erneut öffnen','warn',5000,error.message)}showFeedback(cloudRuntime().enabled?'Auf diesem Gerät gespeichert':label,'success',3000,cloudRuntime().enabled?(cloudRuntime().blocked?'Datenabgleich noch offen.':'Übertragung zum gemeinsamen Stand läuft.'):'');return true;\n}\nconst queueCloudSaveBefore275=queueCloudSave;\nqueueCloudSave=function(message){\n if(!operationOwner275)return queueCloudSaveBefore275(message);\n cloudOutboxSave254({data:clone(state),message:message||'Änderung'});operationOwner275.staged=true;\n};\nconst outboxClearBefore275=cloudOutboxClear254;\ncloudOutboxClear254=function(){if(operationOwner275?.staged)return;return outboxClearBefore275()};\nconst feedbackBefore275=showFeedback;\nshowFeedback=function(title,kind,...args){if(operationOwner275&&['warn','error'].includes(kind))operationOwner275.persistFailure=args[1]||title;if(operationOwner275&&kind==='success')return;return feedbackBefore275(title,kind,...args)};\nconst persistStateBefore275=persistState;\npersistState=function(...args){if(operationOwner275&&!operationOwner275.allowPersist)return false;return persistStateBefore275(...args)};\nconst guardPermissionBefore275=guardPermission;\nguardPermission=function(...args){if(operationOwner275&&operationOwner275.phase!=='mutating'&&operationOwner275.phase!=='persisting')return false;return guardPermissionBefore275(...args)};\nconst cloudUpdatesBefore275=checkCloudUpdates;\ncheckCloudUpdates=function(...args){if(operationOwner275)return;return cloudUpdatesBefore275(...args)};\nconst cloudUiBusyBefore275=cloudUiBusy119;\ncloudUiBusy119=function(...args){return !!operationOwner275||cloudUiBusyBefore275(...args)};\n\n// Capture the recipe state before any collector mutates it. This replaces the\n// old stack of pre-collection wrappers while retaining their permission and\n// portion validation. Invalid saves leave the visible editor untouched.\nsaveRecipeDetails=function(gi,ri,automatic=false){\n clearTimeout(recipeSaveTimer);let check,recipe;\n const result=operationTransaction(automatic?'Rezept automatisch gespeichert':'Rezept gespeichert','recipes',()=>{\n  const group=state.recipeGroups[gi];recipe=group?.recipes[ri];if(!recipe)throw Error('Rezept nicht mehr vorhanden.');\n  collectRecipeEditorAt(gi,ri);\n  const validation=recipePortionValidation130(recipe);if(!validation.ok)throw Error(validation.message);\n  if(recipe.portioning?.enabled&&!recipePortioning108(recipe))throw Error('Fertige Menge und Portionsgröße müssen beide Gewicht oder beide Volumen sein.');\n  recipe.ingredients=recipe.ingredients.filter(x=>x.name||x.productCode||x.recipeId);\n  check=recipeNeedDetails(recipe.id,Number(recipe.yieldQty)||1,recipe.yieldUnit);activeRecipeGroup=gi;activeRecipeIndex=ri;\n });\n return afterOperation275(result,ok=>{if(ok&&pendingRecipeAutosave347?.gi===gi&&pendingRecipeAutosave347?.ri===ri)pendingRecipeAutosave347=null;if(ok&&check?.errors?.length)showFeedback('Verknüpfung prüfen','warn',5000,check.errors[0]);return ok});\n};\nscheduleRecipeAutosave=function(gi,ri){\n clearTimeout(recipeSaveTimer);if(!guardPermissionBefore275('recipes','Rezepte bearbeiten'))return;\n pendingRecipeAutosave347={gi,ri,id:state.recipeGroups?.[gi]?.recipes?.[ri]?.id};\n recipeSaveTimer=setTimeout(()=>{\n  if(operationOwner275||recipeEditing347())return scheduleRecipeAutosave(gi,ri);\n  const pending=pendingRecipeAutosave347;\n  if(!pending||state.recipeGroups?.[gi]?.recipes?.[ri]?.id!==pending.id){pendingRecipeAutosave347=null;return}\n  // Never collect from an editor that has disappeared: absent portion fields\n  // would otherwise overwrite its settings. Keep the unsaved warning instead.\n  if(typeof document!=='undefined'&&!document.getElementById(`ingredientRows_${gi}_${ri}`))return;\n  return saveRecipeDetails(gi,ri,true);\n },650);\n};\n\nfunction finishBookDurable275(total){\n const code=current,kind=mode,approved=typeof zConfirmedUnassigned187==='undefined'?null:zConfirmedUnassigned187;\n const result=operationTransaction(kind==='in'?'Wareneingang gespeichert':kind==='trude'?'In Trude eingeräumt':kind==='return'?'Zurück ins Lager gebucht':kind==='waste'?'Ausschuss gespeichert':'Entnahme gespeichert',kind==='in'?'stockAdd':'scan',()=>{\n  if(current!==code||mode!==kind)throw Error('Der Buchungsauftrag hat sich geändert. Bitte erneut prüfen.');\n  const x=p();if(!x)throw Error('Artikel nicht mehr vorhanden.');\n  const previousApproval=typeof zConfirmedUnassigned187==='undefined'?null:zConfirmedUnassigned187;\n  if(typeof zConfirmedUnassigned187!=='undefined')zConfirmedUnassigned187=approved;\n  try{if(mode==='in'){const supplier=document.getElementById('supplier').value,quote=bookingPurchaseQuote(),newPrice=quote.missing?Number(x.price)||0:quote.unitPrice,packEl=document.getElementById('pack'),packs=getPacks(x,current),pi=Math.max(0,packEl?.selectedIndex||0),pack=packs[pi]||packs[0];if(quote.error)throw Error(quote.error);x.stock+=total;if(document.getElementById('zCommission168')?.checked)x.consignmentQty168=(Number(x.consignmentQty168)||0)+total;x.supplier=supplier;if(newPrice>0){x.price=newPrice;x.priceQuoteQty=quote.quantity;x.priceQuoteUnit=x.unit;recordPurchasePrice(current,newPrice,supplier,'Wareneingang',{qty:total,packName:pack?.[0]||'',packQty:Number(pack?.[1])||1})}const mhdVal=recognizedMhdPolicy256(x)!=='none'?document.getElementById('mhd').value:'';if(mhdVal){const lots=typeof receiptLots269!=='undefined'&&receiptLots269?receiptLots269:[{count:Number(document.getElementById('qty')?.value)||0,date:mhdVal}];for(const lot of lots)addBatch(current,lot.count*(Number(packEl.value)||1),lot.date,{packName:pack?.[0]||'',packSize:packUsableQty(pack),packCount:lot.count,packCode:pack?.[2]||'',consignment168:!!document.getElementById('zCommission168')?.checked});}const mhd=typeof receiptLots269!=='undefined'&&receiptLots269?' · MHD '+receiptLots269.map(l=>l.count+' × '+l.date).join(', '):mhdVal?' · MHD '+mhdVal:'';state.log.push(`${stamp()} | ${x.name} | +${total} | Wareneingang | ${x.supplier}${newPrice>0?' · EK '+moneyFmt(newPrice):''}${mhd}`);addMovement('in',current,total,{unitCost:newPrice,supplier,reason:document.getElementById('zCommission168')?.checked?'Kommission':'Eigener Bestand'})}else if(mode==='return'){returnStockLots(current,takeTrudeReturnLots(current,total));state.log.push(`${stamp()} | ${x.name} | +${total} | Aus Trude zurück ins Lager`);addMovement('trude-return',current,total,{reason:'Aus Trude zurück'})}else{if(mode==='trude')storeTrudeLots(current,takeStockLots(current,total,!!document.getElementById('zCommission168')?.checked));else{x.stock-=total;const fromCommission=!!document.getElementById('zCommission168')?.checked;if(fromCommission)x.consignmentQty168=Math.max(0,(Number(x.consignmentQty168)||0)-total);if(x.mhd)consumeBatches(current,total,fromCommission);}const reason=mode==='waste'?document.getElementById('reason').value:'';let why=mode==='trude'?'Trude eingeräumt':mode==='waste'?'Ausschuss · '+reason:'Entnahme';state.log.push(`${stamp()} | ${x.name} | −${total} | ${why}`);addMovement(mode,current,-total,{reason:reason+(document.getElementById('zCommission168')?.checked?' · Kommission':' · Eigener Bestand')})}\n   if(kind==='in'){const inputQty=Number(String(document.getElementById('priceForQty')?.value||'0').replace(',','.'));x.priceInputQty131=inputQty>0?inputQty:1;x.priceInputUnit131=document.getElementById('priceUnit131')?.value||x.unit;}\n  }finally{if(typeof zConfirmedUnassigned187!=='undefined')zConfirmedUnassigned187=previousApproval;}\n });\n return afterOperation275(result,ok=>{if(ok){lastScanPackName='';lastScanPackQty=1;lastScanPackCode='';closeModal()}return ok});\n}\n\nfunction savePhotoDurable275(){\n const d=photoDraft144;if(!d)return false;\n const result=operationTransaction('Wareneingang gespeichert','stockAdd',()=>{\n  if(photoDraft144!==d)throw Error('Fotoauftrag geändert.');\n  if(d.autoDays242&&(shelfDays242(state.products[d.code])!==d.autoDays242||localTodayISO()!==d.receiptDay242))throw Error('Haltbarkeit oder Einlagerungstag hat sich geändert. Bitte erneut prüfen.');\n  const a=state.products[d.code];if(!a)throw Error('Artikel nicht mehr vorhanden.');\n  const p=getPacks(a,d.code)[d.packIndex]||getPacks(a,d.code)[0],total=d.count*packUsableQty(p);\n  a.stock=(Number(a.stock)||0)+total;if(document.getElementById('photoCommission168')?.checked)a.consignmentQty168=(Number(a.consignmentQty168)||0)+total;d.lines.forEach(x=>addBatch(d.code,x.count*packUsableQty(p),x.date,{packName:p[0],packSize:packUsableQty(p),packCount:x.count,packCode:p[2]||'',consignment168:!!document.getElementById('photoCommission168')?.checked}));state.log.push(`${stamp()} | ${a.name} | +${total} | Wareneingang per Produktfoto`);addMovement('in',d.code,total,{unitCost:Number(a.price)||0,supplier:a.supplier||'',source:'photo-confirmed',reason:document.getElementById('photoCommission168')?.checked?'Kommission':'Eigener Bestand'});\n });\n return afterOperation275(result,ok=>{if(ok){closePhoto144();showFeedback('Wareneingang gespeichert','success',2500,d.count+' Packungen')}return ok});\n}\n\n// Replace the old price wrapper: its metadata is now inside the same commit.\nfinishBook=finishBookDurable275;\nsavePhoto144=savePhotoDurable275;\n// Hardware Back must respect the same commit lock as native dialog controls.\nif(typeof window.zapasioHandleBack==='function'){\n const backBefore275=window.zapasioHandleBack;\n window.zapasioHandleBack=function(...args){if(operationOwner275)return true;return backBefore275.apply(this,args)};\n}\nif(typeof window.addEventListener==='function')window.addEventListener('beforeunload',event=>{if(operationOwner275||pendingRecipeAutosave347){event.preventDefault();event.returnValue=''}});\n\n// Tab navigation waits for the same durable recipe commit before hiding inputs.\nif(typeof showTab==='function'){\n const showTabBefore347=showTab;\n showTab=function(...args){\n  const pending=pendingRecipeAutosave347;\n  if(!pending)return showTabBefore347.apply(this,args);\n  if(operationOwner275)return false;\n  if(typeof document!=='undefined'&&!document.getElementById(`ingredientRows_${pending.gi}_${pending.ri}`))return false;\n  return afterOperation275(saveRecipeDetails(pending.gi,pending.ri,true),ok=>ok?showTabBefore347.apply(this,args):false);\n };\n}\n"},{"src":"variant-groups206.js?v=1","code":"'use strict';\nfunction applyVariantGroup206(data,id,name,unit,rows){\n if(!name||!rows.length||new Set(rows.map(r=>r.code)).size!==rows.length||rows.some(r=>!data.products[r.code]||!Number.isFinite(r.factor)||r.factor<=0))throw Error('Name, Artikel und Packungsinhalte prüfen.');\n const chosen=new Set(rows.map(r=>r.code)),groups=data.mainArticles||{},old=groups[id],redirects=new Map();\n for(const row of rows){\n  const a=data.products[row.code],direct=convertUnit(1,a.unit,unit);\n  if(direct!==null&&Math.abs(direct-row.factor)>1e-8)throw Error('Die Umrechnung passt nicht zur Lagereinheit.');\n  const from=a.mainArticleId;if(!from||from===id)continue;\n  const members=Object.entries(data.products).filter(([,p])=>p.mainArticleId===from);\n  if(members.some(([code])=>!chosen.has(code)))throw Error('Eine bestehende Variantengruppe nur vollständig übernehmen.');\n  const g=groups[from];if(!g)continue;\n  const ratios=members.map(([code,p])=>rows.find(r=>r.code===code).factor/(Number(p.mainFactor)||1));\n  if(ratios.some(r=>Math.abs(r-ratios[0])>1e-8))throw Error('Packungsinhalte der bisherigen Gruppe sind widersprüchlich.');\n  redirects.set(from,{ratio:ratios[0],group:g});\n }\n const oldMembers=Object.entries(data.products).filter(([,a])=>a.mainArticleId===id);\n if(old&&oldMembers.some(([code])=>!chosen.has(code)))throw Error('Bestehende Varianten nicht still aus der Gruppe entfernen.');\n let ratio=old?convertUnit(1,old.unit,unit):1;\n if(old&&ratio===null){const ratios=oldMembers.map(([code,a])=>rows.find(r=>r.code===code).factor/(Number(a.mainFactor)||1));if(!ratios.length||ratios.some(r=>Math.abs(r-ratios[0])>1e-8))throw Error('Bisherige Gruppenmenge nicht eindeutig umrechenbar.');ratio=ratios[0];}\n let min=(Number(old?.min)||0)*ratio;\n for(const {ratio:r,group:g} of redirects.values())min=Math.max(min,(Number(g.min)||0)*r);\n // Validate every affected recipe before making any change, even in isolated use.\n const updates=[];\n for(const g of data.recipeGroups||[])for(const recipe of g.recipes||[])for(const ing of recipe.ingredients||[]){\n  const source=ing.mainArticleId,redirect=redirects.get(source),changed=source===id&&old&&old.unit!==unit;\n  if(!redirect&&!changed)continue;\n  const sourceGroup=redirect?.group||old,r=redirect?.ratio||ratio;\n  const qty=convertUnit(Number(ing.qty)||0,ing.unit,sourceGroup.unit);\n  if(qty===null){if(convertUnit(Number(ing.qty)||0,ing.unit,unit)===null)throw Error('Rezeptmenge passt nicht zur gemeinsamen Einheit.');updates.push({ing,qty:Number(ing.qty)||0,unit:ing.unit});}\n  else updates.push({ing,qty:qty*r,unit});\n }\n data.mainArticles??={};data.mainArticles[id]={...old,id,name,unit,min,preferredSupplier:old?.preferredSupplier||'',preferredPack:chosen.has((old?.preferredPack||'').split('|')[0])?old.preferredPack:''};\n for(const row of rows){data.products[row.code].mainArticleId=id;data.products[row.code].mainFactor=row.factor;}\n for(const x of updates){x.ing.mainArticleId=id;x.ing.qty=x.qty;x.ing.unit=x.unit;}\n for(const from of redirects.keys())delete data.mainArticles[from];\n return id;\n}\n"},{"src":"quick-goods-calculator294.js?v=206","code":"'use strict';\nfunction goodsStockFactor294(code,unit){\n const a=state.products?.[code];if(!a)return null;\n const direct=convertUnit(1,a.unit,unit);if(direct!==null)return direct;\n const group=typeof mainGroup==='function'?mainGroup(a.mainArticleId):null;\n const grouped=group?convertUnit(Number(a.mainFactor)||1,group.unit,unit):null;\n if(grouped!==null&&grouped>0)return grouped;\n const bridge=articlePhysicalBridge(code,unit);return bridge&&!bridge.ambiguous&&bridge.size>0?bridge.size:null;\n}\nfunction goodsRows294(details){\n const rows=[],groups=new Map();\n for(const n of details.needs||[]){\n  const group=!n.pinnedVariant&&n.mainArticleId&&typeof mainGroup==='function'?mainGroup(n.mainArticleId):null;\n  if(!group){rows.push({...n});continue;}\n  const key=group.id+'|'+normalizeUnit(n.unit);let row=groups.get(key);\n  if(!row){row={...n,name:group.name,need:0,usableNeed:0,groupCodes:mainMembers(group.id).map(([code])=>code)};groups.set(key,row);rows.push(row);}\n  row.need+=Number(n.need)||0;row.usableNeed+=Number(n.usableNeed)||0;\n }\n return rows;\n}\nfunction goodsPack294(need,barcode='',requiredNeed=null){\n const choices=[];\n for(const code of need.groupCodes||[need.productCode]){\n  const article=state.products[code];if(!article)continue;\n  const list=getPacks(article,code),factor=goodsStockFactor294(code,need.unit);\n  for(const [i,p] of list.entries()){\n   if(i===0&&(!need.groupCodes||factor===null||convertUnit(1,article.unit,need.unit)!==null))continue;\n   const measure=usable=>packMeasureInUnit(p,article,need.unit,usable)??(factor===null?null:(usable?packUsableQty(p):Number(p[1]))*factor);\n   const usable=measure(true),gross=measure(false);if(!(usable>0)||!(gross>0))continue;\n   choices.push({code,article,pack:p,usable,gross});\n  }\n }\n if(!choices.length)return null;\n const choice=choices.find(x=>x.pack[2]===barcode)||choices.find(x=>x.pack[3])||choices[0];\n const target=requiredNeed===null?Number(need.need):Math.max(0,Number(requiredNeed)||0),count=Math.max(0,Math.ceil(target/choice.usable-1e-9));\n return {...choice,packs:choices.map(x=>x.pack),choices,count,required:target,available:count*choice.usable,leftover:Math.max(0,count*choice.usable-target)};\n}\nfunction goodsStock294(need){\n let stock=0;\n for(const code of need.groupCodes||[need.productCode]){\n  const a=state.products?.[code],factor=a?goodsStockFactor294(code,need.unit):null;\n  if(!a||!Number.isFinite(Number(a.stock))||Number(a.stock)<0||factor===null)return {known:false,stock:0,toBuy:Number(need.need)||0};\n  stock+=Number(a.stock)*factor;\n }\n return {known:true,stock,toBuy:Math.max(0,(Number(need.need)||0)-stock)};\n}\nfunction goodsPackLabel294(pack){\n const count=Number(pack?.count)||0,name=String(pack?.pack?.[0]||'Gebinde').trim();\n if(!count)return 'nichts zuzukaufen';\n const forms={dose:['Dose','Dosen'],packung:['Packung','Packungen'],flasche:['Flasche','Flaschen'],becher:['Becher','Becher'],karton:['Karton','Kartons'],sack:['Sack','Säcke'],kiste:['Kiste','Kisten']};\n const key=name.toLocaleLowerCase('de');if(forms[key])return `${count} ${forms[key][count===1?0:1]}`;\n if(/^(?:kg|g|ml|l|liter)$/i.test(name)){const meta=packDisplayMeta(pack.pack,state.products?.[pack.pack?.[2]]?.unit||name);return `${count} × ${formatPackMeasure(meta.contentValue,meta.contentUnit)}`;}\n return `${count} × ${name}`;\n}\nfunction goodsRequest294(recipeId,people,perPerson='1',optional={}){\n const count=Number(people),rate=Number(String(perPerson).replace(',','.')),found=findRecipeById(recipeId);\n if(!found)throw Error('Bitte ein Rezept auswählen.');\n if(!/^\\d+$/.test(String(people).trim())||!Number.isSafeInteger(count)||count<1)throw Error('Bitte eine ganze Personenzahl größer als 0 eingeben.');\n if(!/^\\d+(?:[.,]\\d+)?$/.test(String(perPerson).trim())||!Number.isFinite(rate)||rate<=0)throw Error('Bitte eine positive Menge je Person eingeben.');\n const recipe=found.recipe,unit=recipePlanningUnit108(recipe),total=count*rate;\n if(!Number.isFinite(total))throw Error('Die eingegebene Menge ist zu groß.');\n if(physicalUnitKind(unit))throw Error('Für dieses Rezept zuerst eine Portionsgröße im Rezept hinterlegen.');\n if(!(Number(recipe.yieldQty)>0))throw Error('Die Rezept-Grundmenge muss größer als 0 sein.');\n if(!(recipe.ingredients||[]).some(i=>Number(i.qty)>0))throw Error('Für dieses Rezept fehlen Zutatenmengen.');\n const choices={};for(const item of optionalIngredients138(recipeId)){if(optional[item.key])choices[item.key]=total;}\n const details=cateringNeedDetails({lines:[{recipeId,qty:total,unit,optionalChoices138:choices}]});\n return {name:recipe.name,people:count,perPerson:rate,total,unit,details};\n}\nfunction openGoodsCalculator294(){\n if(!guardPermission('recipes','Warenrechner'))return;\n let modal=document.getElementById('goodsModal294');\n if(!modal){modal=document.createElement('div');modal.id='goodsModal294';modal.className='modal';modal.setAttribute('role','dialog');modal.setAttribute('aria-modal','true');modal.innerHTML=`<div class=\"card\"><div class=\"modalHeader\"><h2>Schneller Warenrechner</h2><button type=\"button\" class=\"close\" onclick=\"document.getElementById('goodsModal294').style.display='none'\">× Schließen</button></div><p>Rezept auswählen und Zutaten für deine Personenzahl berechnen.</p><div class=\"field\"><label for=\"goodsRecipe294\">Rezept</label><select id=\"goodsRecipe294\" onchange=\"goodsRecipeChanged294()\"></select></div><div class=\"field\"><label for=\"goodsPeople294\">Personen</label><input id=\"goodsPeople294\" inputmode=\"numeric\" value=\"1\" oninput=\"updateGoods294()\"></div><div class=\"field\"><label id=\"goodsRateLabel294\" for=\"goodsRate294\">Portionen je Person</label><input id=\"goodsRate294\" inputmode=\"decimal\" value=\"1\" oninput=\"updateGoods294()\"></div><div id=\"goodsOptions294\"></div><div id=\"goodsResult294\" aria-live=\"polite\"></div><p class=\"tiny\">Bedarf, vorhandener Lagerbestand und Zukauf werden getrennt gezeigt. Hinterlegter Verschnitt und Abtropfgewichte werden verwendet. Fehlende Werte zuerst im Artikel ergänzen. Die Berechnung verändert keinen Bestand und legt keinen Einsatz an.</p></div>`;document.body.appendChild(modal);}\n document.getElementById('goodsRecipe294').innerHTML='<option value=\"\">Bitte auswählen</option>'+recipeOptions143();\n document.getElementById('goodsPeople294').value='1';document.getElementById('goodsRate294').value='1';\n modal.style.display='flex';goodsRecipeChanged294();document.getElementById('goodsRecipe294').focus();\n}\nfunction goodsRecipeChanged294(){\n const id=document.getElementById('goodsRecipe294').value,found=findRecipeById(id),unit=found?recipePlanningUnit108(found.recipe):'Portionen';\n document.getElementById('goodsRateLabel294').textContent=unit+' je Person';\n document.getElementById('goodsOptions294').innerHTML=optionalIngredients138(id).map(x=>`<label style=\"display:flex;align-items:center;gap:10px;padding:8px 0\"><input type=\"checkbox\" data-goods-option=\"${esc(x.key)}\" style=\"width:auto\" onchange=\"updateGoods294()\">${esc(x.ing.name||'Optionale Zutat')} für alle Personen</label>`).join('');\n updateGoods294();\n}\nfunction updateGoods294(){\n const out=document.getElementById('goodsResult294');if(!out)return;\n if(!hasPermission('recipes')){out.textContent='Keine Berechtigung für Rezepte.';return;}\n try{\n const selected={},packChoices={};for(const checkbox of document.querySelectorAll('[data-goods-option]'))selected[checkbox.getAttribute('data-goods-option')]=checkbox.checked;\n for(const select of document.querySelectorAll('[data-goods-pack]'))packChoices[select.getAttribute('data-goods-pack')]=select.value;\n const result=goodsRequest294(document.getElementById('goodsRecipe294').value,document.getElementById('goodsPeople294').value,document.getElementById('goodsRate294').value,selected),d=result.details;\n const row=(name,qty)=>`<div style=\"display:flex;justify-content:space-between;gap:16px;padding:12px 0;border-bottom:1px solid var(--line)\"><span style=\"min-width:0;overflow-wrap:anywhere\">${esc(name)}</span><strong style=\"flex-shrink:0\">${esc(qty)}</strong></div>`;\n out.innerHTML=`<h3>${esc(result.name)} für ${result.people.toLocaleString('de-AT')} Personen</h3><p class=\"muted\">${esc(formatQtySmart(result.total,result.unit))} insgesamt · hinterlegte Portionsgröße, Verschnitt und ganze Rezeptansätze berücksichtigt.</p>`+\n ((d.errors||[]).length?`<p class=\"planWarn\">Berechnung unvollständig: ${d.errors.map(x=>esc(String(x))).join('<br>')}</p>`:'')+\n '<h3>Benötigte Zutaten und Artikel</h3>'+goodsRows294(d).map((n,index)=>{const stock=goodsStock294(n),pack=goodsPack294(n,packChoices[n.mainArticleId||n.productCode],stock.toBuy),recommendation=stock.toBuy<=1e-9?'nichts zuzukaufen':pack?goodsPackLabel294(pack):formatQtySmart(stock.toBuy,n.unit);return `<section class=\"goodsItem294\"><h4>${esc(n.name)}</h4><div class=\"goodsSummary294\"><div><span>Benötigt</span><b>${esc(formatQtySmart(n.need,n.unit))}</b></div>${stock.known?`<div><span>Vorhanden</span><b>${esc(formatQtySmart(stock.stock,n.unit))}</b></div>`:''}<div><span>Zuzukaufen</span><b>${esc(formatQtySmart(stock.toBuy,n.unit))}</b></div><div><span>Gebindeempfehlung</span><b>${esc(recommendation)}</b></div></div>`+\n (n.prepLossPct>0?`<p class=\"tiny\">${esc(formatQtySmart(n.usableNeed,n.unit))} verwendbare Menge · ${esc(String(n.prepLossPct))} % hinterlegter Verschnitt berücksichtigt.</p>`:'')+\n (pack&&stock.toBuy>1e-9?`<p class=\"tiny\">Je Gebinde: ${esc(formatQtySmart(pack.gross,n.unit))} Inhalt · ${esc(formatQtySmart(pack.usable,n.unit))} nutzbar${pack.article?.drainWeightEnabled?' nach Abtropfen':''}. Einkauf ergibt ${esc(formatQtySmart(pack.available,n.unit))}; ${esc(formatQtySmart(pack.leftover,n.unit))} Rest vor Vorbereitung.</p>`+(pack.packs.length>1?`<div class=\"field\"><label for=\"goodsPack294_${index}\">Gebinde für ${esc(n.name)}</label><select id=\"goodsPack294_${index}\" data-goods-pack=\"${esc(n.mainArticleId||n.productCode)}\" onchange=\"updateGoods294()\">${pack.packs.map(p=>`<option value=\"${esc(p[2])}\" ${p[2]===pack.pack[2]?'selected':''}>${esc(p[0])} · ${esc(packQtyText(p,pack.choices.find(x=>x.pack===p)?.article.unit||n.unit))}</option>`).join('')}</select></div>`:''):stock.toBuy>1e-9?'<p class=\"tiny\">Kein eindeutig umrechenbares Gebinde hinterlegt. Benötigte Einkaufsmenge ist oben angegeben; Packungsinhalt und gegebenenfalls Abtropfgewicht im Artikel ergänzen.</p>':'<p class=\"tiny\">Lagerbestand deckt den berechneten Bedarf.</p>')+'</section>';}).join('')+\n ((d.free||[]).length?'<h3>Zuordnung oder Menge prüfen</h3>'+d.free.map(n=>row(n.name,formatQtySmart(n.qty,n.unit))+`<p class=\"tiny\">${esc(n.reason||'Rezept prüfen')}</p>`).join(''):'')+\n ((d.batchPlans||[]).length?'<h3>Ganze Rezeptansätze</h3>'+d.batchPlans.map(n=>`<p>${esc(n.name)}: ${esc(String(n.batches))} Ansätze, ${esc(formatQtySmart(n.produced,n.unit))} hergestellt${n.surplus>0?' · '+esc(formatQtySmart(n.surplus,n.unit))+' zusätzlich':''}.</p>`).join(''):'');\n }catch(error){out.textContent=error.message;}\n}\n"},{"src":"quick-load208.js?v=210","code":"'use strict';\nlet quickLoadExtras208=[],quickLoadChecks208={},quickLoadId208='',quickLoadSavedSignature208='';\nfunction quickLoadSaved208(){return Array.isArray(state.quickLoadLists208)?state.quickLoadLists208:[]}\nfunction emptyQuickLoadExtra210(x){return !String(x.name||'').trim()&&Number(x.qty)===1&&String(x.unit||'').trim()==='Stück'}\nfunction quickLoadDraft208(){return {name:(document.getElementById('quickLoadName208')?.value||'').trim(),rows:clone(quickLoadRows.filter(r=>r.ref)),extras:clone(quickLoadExtras208.filter(x=>!emptyQuickLoadExtra210(x))),checks:clone(quickLoadChecks208)}}\nfunction quickLoadDirty208(){const d=quickLoadDraft208();return (d.rows.length||d.extras.length||d.name)&&JSON.stringify(d)!==quickLoadSavedSignature208}\nfunction quickLoadStatus208(){const el=document.getElementById('quickLoadStatus208');if(el)el.textContent=quickLoadDirty208()?'Änderungen noch nicht gespeichert.':quickLoadId208?'Liste gespeichert.':'Noch keine Liste gespeichert.'}\nfunction quickLoadPicker208(){const el=document.getElementById('quickLoadSaved208');if(!el)return;el.innerHTML='<option value=\"\">Gespeicherte Liste auswählen</option>'+quickLoadSaved208().map(x=>`<option value=\"${esc(x.id)}\" ${x.id===quickLoadId208?'selected':''}>${esc(x.name)}</option>`).join('')}\nfunction initQuickLoad208(){\n const modal=document.getElementById('quickLoadModal');if(!modal||document.getElementById('quickLoadName208'))return;\n modal.querySelector('.modalHeader .close').setAttribute('onclick','closeQuickLoad208()');\n const rows=document.getElementById('quickLoadRows');\n rows.insertAdjacentHTML('beforebegin',`<div class=\"quickLoadNoPrint208 quickLoadSavedBar208\"><label for=\"quickLoadSaved208\">Gespeicherte Ladelisten</label><select id=\"quickLoadSaved208\"></select><div class=\"actions\"><button class=\"soft\" onclick=\"openSavedQuickLoad208()\">Öffnen</button><button class=\"soft\" onclick=\"newQuickLoad208()\">Neue Liste</button></div></div><div class=\"field\"><label for=\"quickLoadName208\">Name der Ladeliste</label><input id=\"quickLoadName208\" maxlength=\"100\" placeholder=\"Zum Beispiel Zapiekankas oder Bowls\" oninput=\"quickLoadStatus208()\"></div>`);\n const out=document.getElementById('quickLoadResult');out.insertAdjacentHTML('beforebegin',`<div class=\"quickLoadNoPrint208\"><h3>Sonstiges</h3><p class=\"tiny\">Eigene Positionen, etwa Servietten, Werkzeug oder Kabel.</p><div id=\"quickLoadExtras208\"></div><button class=\"soft\" onclick=\"addQuickLoadExtra208()\">+ Sonstiges hinzufügen</button><div class=\"actions\" style=\"margin-top:16px\"><button class=\"primary\" onclick=\"saveQuickLoad208()\">Liste speichern</button><button class=\"soft\" onclick=\"saveQuickLoad208(true)\">Als neue Liste speichern</button><button class=\"soft\" onclick=\"resetQuickLoadChecks208()\">Häkchen zurücksetzen</button><button class=\"soft\" onclick=\"saveQuickLoadPdf208()\">PDF speichern</button><button class=\"soft\" id=\"quickLoadPrint208\" onclick=\"printQuickLoad208()\">Drucken</button></div><p id=\"quickLoadStatus208\" class=\"tiny\" role=\"status\"></p></div>`);\n if(globalThis.ZapasioAndroid)document.getElementById('quickLoadPrint208').hidden=true;\n document.getElementById('quickLoadPrint208').insertAdjacentHTML('afterend','<button class=\"soft\" onclick=\"deleteSavedQuickLoad208()\">Gespeicherte Liste löschen</button>');\n}\nopenQuickLoadList=function(){if(!guardPermission('load','Ladeliste'))return;initQuickLoad208();quickLoadPicker208();renderQuickLoadRows();renderQuickLoadExtras208();document.getElementById('quickLoadModal').style.display='flex';if(quickLoadRows.some(r=>r.ref)||quickLoadExtras208.length)renderQuickLoadResult();quickLoadStatus208()};\nfunction closeQuickLoad208(){document.getElementById('quickLoadModal').style.display='none';document.body.classList.remove('quickLoadPrint208')}\nfunction replaceQuickLoad208(fn){if(quickLoadDirty208())askConfirm('Ungespeicherte Änderungen verwerfen?',fn,'Verwerfen','Ladeliste wechseln');else fn()}\nfunction newQuickLoad208(){if(!guardPermission('load','Ladeliste'))return;replaceQuickLoad208(()=>{quickLoadId208='';quickLoadRows=[{ref:'',qty:1}];quickLoadExtras208=[];quickLoadChecks208={};document.getElementById('quickLoadName208').value='';quickLoadSavedSignature208=JSON.stringify(quickLoadDraft208());quickLoadPicker208();renderQuickLoadRows();renderQuickLoadExtras208();renderQuickLoadResult();quickLoadStatus208()})}\nfunction openSavedQuickLoad208(){if(!guardPermission('load','Ladeliste'))return;const id=document.getElementById('quickLoadSaved208').value,list=quickLoadSaved208().find(x=>x.id===id);if(!list){showFeedback('Bitte eine gespeicherte Liste auswählen','warn');return}replaceQuickLoad208(()=>{quickLoadId208=id;quickLoadRows=clone(list.rows?.length?list.rows:[{ref:'',qty:1}]);quickLoadExtras208=clone(list.extras||[]);quickLoadChecks208=clone(list.checks||{});document.getElementById('quickLoadName208').value=list.name;quickLoadSavedSignature208=JSON.stringify(quickLoadDraft208());renderQuickLoadRows();renderQuickLoadExtras208();renderQuickLoadResult();quickLoadStatus208()})}\nfunction addQuickLoadExtra208(){if(!guardPermission('load','Ladeliste'))return;quickLoadExtras208.push({id:'extra-'+Date.now()+'-'+Math.random().toString(36).slice(2,8),name:'',qty:1,unit:'Stück'});renderQuickLoadExtras208();document.getElementById('quickLoadExtras208').lastElementChild?.querySelector('input')?.focus();renderQuickLoadResult();quickLoadStatus208()}\nfunction setQuickLoadExtra208(i,key,value){if(!guardPermission('load','Ladeliste'))return;const x=quickLoadExtras208[i];if(!x||!['name','qty','unit'].includes(key))return;x[key]=key==='qty'?Number(value):String(value);renderQuickLoadResult();quickLoadStatus208()}\nfunction removeQuickLoadExtra208(i){if(!guardPermission('load','Ladeliste'))return;quickLoadExtras208.splice(i,1);renderQuickLoadExtras208();renderQuickLoadResult();quickLoadStatus208()}\nfunction renderQuickLoadExtras208(){const el=document.getElementById('quickLoadExtras208');if(!el)return;el.innerHTML=quickLoadExtras208.map((x,i)=>`<div class=\"quickLoadExtra208\"><div class=\"field\"><label for=\"qlExtraName_${i}\">Was mitnehmen?</label><input id=\"qlExtraName_${i}\" maxlength=\"160\" value=\"${esc(x.name)}\" oninput=\"setQuickLoadExtra208(${i},'name',this.value)\"></div><div class=\"field\"><label for=\"qlExtraQty_${i}\">Menge</label><input id=\"qlExtraQty_${i}\" type=\"number\" min=\"0\" step=\"any\" value=\"${esc(x.qty)}\" oninput=\"setQuickLoadExtra208(${i},'qty',this.value)\"></div><div class=\"field\"><label for=\"qlExtraUnit_${i}\">Einheit</label><input id=\"qlExtraUnit_${i}\" maxlength=\"40\" value=\"${esc(x.unit)}\" oninput=\"setQuickLoadExtra208(${i},'unit',this.value)\"></div><button class=\"soft\" onclick=\"removeQuickLoadExtra208(${i})\">Entfernen</button></div>`).join('')}\nconst setQuickLoadRowBefore208=setQuickLoadRow,addQuickLoadRowBefore208=addQuickLoadRow,removeQuickLoadRowBefore208=removeQuickLoadRow;\nsetQuickLoadRow=function(...args){if(!guardPermission('load','Ladeliste'))return;setQuickLoadRowBefore208(...args);quickLoadStatus208()};\naddQuickLoadRow=function(){if(!guardPermission('load','Ladeliste'))return;addQuickLoadRowBefore208();quickLoadStatus208()};\nremoveQuickLoadRow=function(...args){if(!guardPermission('load','Ladeliste'))return;removeQuickLoadRowBefore208(...args);quickLoadStatus208()};\nfunction quickLoadModel208(rows=quickLoadRows,extras=quickLoadExtras208){\n const details=quickLoadDetails(rows),errors=[...details.errors],sections=[{name:'Speisen und Produkte',items:[]},{name:'Zutaten und Artikel',items:[]},{name:'Ausstattung und Material',items:[]},{name:'Sonstiges',items:[]},{name:'Zuordnung prüfen',items:[]}];\n for(const row of rows){if(!row.ref)continue;const isRecipe=row.ref.startsWith('r:'),item=isRecipe?findRecipeById(row.ref.slice(2))?.recipe:state.products[row.ref.slice(2)];if(item)sections[0].items.push({name:item.name,qty:formatQtySmart(row.qty,isRecipe?recipePlanningUnit108(item):item.unit)});}\n for(const x of details.items)sections[1].items.push({name:x.name,qty:formatQtySmart(x.need,x.unit)});\n for(const x of details.equipment)sections[2].items.push({name:x.label,qty:x.meta});\n for(const x of extras){if(emptyQuickLoadExtra210(x))continue;if(!String(x.name||'').trim()||!String(x.unit||'').trim()||!Number.isFinite(Number(x.qty))||Number(x.qty)<=0||(isWholeQuantityUnit(x.unit)&&!Number.isInteger(Number(x.qty)))){errors.push('Sonstiges: Bezeichnung, positive Menge und passende Einheit eintragen.');continue}sections[3].items.push({name:x.name.trim(),qty:formatQtySmart(x.qty,x.unit)});}\n for(const x of details.free)sections[4].items.push({name:x.name,qty:formatQtySmart(x.qty,x.unit),warning:x.reason||'Lagerartikel oder Einheit prüfen'});\n return {sections:sections.filter(x=>x.items.length),errors,selected:details.selected+sections[3].items.length};\n}\nfunction quickLoadCheckKey208(section,item){return JSON.stringify([section,item.name,item.qty,item.warning||''])}\nfunction resetQuickLoadChecks208(){if(!guardPermission('load','Ladeliste'))return;quickLoadChecks208={};renderQuickLoadResult();quickLoadStatus208()}\nfunction setQuickLoadCheck208(key,checked){if(!guardPermission('load','Ladeliste'))return;quickLoadChecks208[key]=!!checked;quickLoadStatus208()}\nrenderQuickLoadResult=function(){if(!guardPermission('load','Ladeliste'))return;const out=document.getElementById('quickLoadResult'),d=quickLoadModel208();const warnings=d.errors.length?'<p class=\"planWarn\">'+d.errors.map(x=>esc(String(x))).join('<br>')+'</p>':'';if(!d.selected){out.innerHTML=warnings+'<p>Bitte ein Produkt, Rezept oder eine eigene Position hinzufügen.</p>';return false}out.innerHTML=warnings+'<h2>'+esc(quickLoadDraft208().name||'Ladeliste')+'</h2>'+d.sections.map(section=>'<h3>'+esc(section.name)+'</h3>'+section.items.map(x=>{const key=quickLoadCheckKey208(section.name,x);return `<label class=\"quickLoadCheck208\"><input type=\"checkbox\" ${quickLoadChecks208[key]?'checked':''} onchange=\"${esc(`setQuickLoadCheck208(${JSON.stringify(key)},this.checked)`)}\"><span>${esc(x.name)}${x.warning?'<small>'+esc(x.warning)+'</small>':''}</span><b>${esc(x.qty)}</b></label>`}).join('')).join('')+'<p class=\"tiny quickLoadNoPrint208\">Abhaken, danach Liste speichern. Eine Vorlage für den nächsten Einsatz über „Als neue Liste speichern“ anlegen.</p>';return d.errors.length===0};\nfunction saveQuickLoad208(asCopy=false){\n if(!guardPermission('load','Ladeliste'))return false;const draft=quickLoadDraft208(),model=quickLoadModel208(draft.rows,draft.extras);if(!draft.name){showFeedback('Bitte der Ladeliste einen Namen geben','warn');document.getElementById('quickLoadName208')?.focus();return false}if(model.errors.length||!model.selected){renderQuickLoadResult();return false}\n const id=!asCopy&&quickLoadId208?quickLoadId208:'load-'+Date.now()+'-'+Math.random().toString(36).slice(2,8),signature=JSON.stringify(draft);\n return afterOperation275(operationTransaction('Ladeliste gespeichert','load',()=>{state.quickLoadLists208??=[];const index=state.quickLoadLists208.findIndex(x=>x.id===id);if(!asCopy&&quickLoadId208&&index<0)throw Error('Die gespeicherte Liste wurde inzwischen entfernt. Bitte als neue Liste speichern.');if(state.quickLoadLists208.some(x=>x.id!==id&&x.name.toLocaleLowerCase('de')===draft.name.toLocaleLowerCase('de')))throw Error('Dieser Listenname ist schon vergeben. Bitte einen anderen Namen wählen.');const saved={id,...draft,updatedAt:new Date().toISOString(),updatedBy:currentAccessUser().name};if(index<0)state.quickLoadLists208.push(saved);else state.quickLoadLists208[index]=saved;}),ok=>{if(!ok)return false;quickLoadId208=id;quickLoadSavedSignature208=signature;quickLoadPicker208();quickLoadStatus208();showFeedback('Ladeliste gespeichert','success',3500,draft.name);return true});\n}\nfunction deleteSavedQuickLoad208(){if(!guardPermission('load','Ladeliste'))return;const id=quickLoadId208,list=quickLoadSaved208().find(x=>x.id===id);if(!list){showFeedback('Zuerst eine gespeicherte Liste öffnen','warn');return}askDeleteConfirm(`Gespeicherte Liste „${list.name}“ löschen?`,()=>afterOperation275(operationTransaction('Ladeliste gelöscht','load',()=>{state.quickLoadLists208=quickLoadSaved208().filter(x=>x.id!==id)}),ok=>{if(ok){quickLoadId208='';quickLoadSavedSignature208='';quickLoadPicker208();quickLoadStatus208()}}),'Ladeliste löschen')}\nfunction printQuickLoad208(){if(!guardPermission('load','Ladeliste')||!renderQuickLoadResult())return;document.body.classList.add('quickLoadPrint208');window.addEventListener('afterprint',()=>document.body.classList.remove('quickLoadPrint208'),{once:true});try{window.print()}catch(e){document.body.classList.remove('quickLoadPrint208');showFeedback('Druckdialog konnte nicht geöffnet werden','warn',5000,'Bitte PDF speichern verwenden.')}}\nasync function createQuickLoadPdf208(model,name,checks={}){\n if(model.errors.length||!model.selected)throw Error('Ladeliste zuerst vervollständigen.');if(typeof loadAsset254==='function')await loadAsset254('pdf');\n const {PDFDocument,StandardFonts,rgb}=PDFLib,doc=await PDFDocument.create(),font=await doc.embedFont(StandardFonts.Helvetica),bold=await doc.embedFont(StandardFonts.HelveticaBold);let page,y;const color=rgb(.02,.24,.18);\n const newPage=()=>{page=doc.addPage();page.setSize(595.28,841.89);y=790;page.drawText('Zapasio Ladeliste',{x:44,y,size:18,font:bold,color});y-=30;};newPage();\n const line=(text,strong=false)=>{for(const part of pdfLines178(text,strong?bold:font,strong?14:11,500)){if(y<55)newPage();page.drawText(part,{x:44,y,size:strong?14:11,font:strong?bold:font,color});y-=strong?23:17;}};\n line(name||'Ladeliste',true);line(new Date().toLocaleDateString('de-AT'));line('Bedarf ohne Lagerabzug. Keine Bestandsbuchung.');y-=10;\n for(const section of model.sections){if(y<100)newPage();line(section.name,true);for(const item of section.items){line((checks[quickLoadCheckKey208(section.name,item)]?'[x] ':'[ ] ')+item.name+' · '+item.qty);if(item.warning)line('Prüfen: '+item.warning);}y-=12;}\n const pages=doc.getPages();pages.forEach((p,i)=>p.drawText(`${i+1} / ${pages.length}`,{x:510,y:28,size:9,font,color}));return doc.save();\n}\nasync function saveQuickLoadPdf208(){if(!guardPermission('load','Ladeliste'))return false;try{const draft=quickLoadDraft208(),model=quickLoadModel208(draft.rows,draft.extras),bytes=await createQuickLoadPdf208(model,draft.name,draft.checks),name='Zapasio-Ladeliste-'+(draft.name||'Liste').replace(/[^a-zA-Z0-9äöüÄÖÜß_-]/g,'-')+'.pdf';if(globalThis.ZapasioAndroid?.saveBase64File){let binary='';for(let i=0;i<bytes.length;i+=8192)binary+=String.fromCharCode(...bytes.subarray(i,i+8192));ZapasioAndroid.saveBase64File('data:application/pdf;base64,'+btoa(binary),name);showFeedback('PDF wird gespeichert','info',5000,'Unter Downloads/Zapasio öffnen und bei Bedarf drucken.');}else{downloadShoppingPdf178(new File([bytes],name,{type:'application/pdf'}));showFeedback('PDF heruntergeladen','success',3500);}return true}catch(error){renderQuickLoadResult();showFeedback('PDF konnte nicht erstellt werden','warn',6000,error.message);return false}}\n"},{"src":"stock-preview211.js?v=1","code":"'use strict';\nfunction stockIdentity211(code,a){\n if(a.mainArticleId&&state.mainArticles?.[a.mainArticleId]&&Object.values(state.products||{}).filter(p=>p.mainArticleId===a.mainArticleId).length>1)return 'main:'+a.mainArticleId;\n const norm=x=>String(x||'').normalize('NFKC').trim().toLocaleLowerCase('de').replace(/\\s+/g,' ');\n const identity=[norm(a.name),norm(a.supplier),norm(a.manufacturer),norm(unitText(a))];\n // These two records were explicitly confirmed as the same Sin Carne by Peter.\n const confirmed=['GS-GEB-1044','GS-GEB-1048'].includes(code)&&norm(a.name).includes('sin')&&norm(a.name).includes('carne');\n const packs=(a.packs||[]).map(p=>p.filter((_,i)=>i!==2));\n return JSON.stringify([...identity,confirmed?'confirmed-sin-carne':JSON.stringify(packs)]);\n}\nfunction stockSearchGroups211(rows){\n const groups=new Map();\n for(const [code,a] of rows){const key=stockIdentity211(code,a);if(!groups.has(key))groups.set(key,{key,members:[]});groups.get(key).members.push([code,a]);}\n return [...groups.values()].map(g=>{const [code,a]=g.members[0],ms=g.key.startsWith('main:')?mainSummaryForProduct(code):null;return {...g,name:ms?.name||a.name,unit:ms?.unit||unitText(a),total:g.members.reduce((sum,[,p])=>sum+(Number(p.stock)||0)*(ms?(Number(p.mainFactor)||1):1),0)};});\n}\nfunction stockPreviewOpen211(code){if(!state.products?.[code])return;current=code;showTab('lager');render();}\nfunction stockPreviewCard211(g){\n const first=g.members[0],a=first[1],ms=mainSummaryForProduct(first[0]),low=ms?ms.min>0&&ms.total<=ms.min:a.min>0&&a.stock<=a.min;\n const locations=[...new Set(g.members.map(([,p])=>p.storage||'Allgemein'))],dates=g.members.map(([code])=>nextMhd(code)?.mhd).filter(Boolean).sort();\n const action=code=>esc(`stockPreviewOpen211(${JSON.stringify(code)})`);\n const positions=g.members.map(([code,p])=>{const mhd=nextMhd(code)?.mhd;return `<div class=\"stockPosition211\"><div><b>${esc(p.storage||'Allgemein')}</b><small>${esc(code)}${mhd?' · MHD '+esc(formatDate(mhd)):''}</small></div><strong>${esc(formatQtySmart(p.stock,unitText(p)))}</strong><button class=\"soft\" onclick=\"${action(code)}\">Artikel öffnen</button></div>`}).join('');\n return `<article class=\"stockPreview211 ${low?'stockLow':''}\"><div class=\"stockPreviewHead211\"><div><small>${esc(a.overviewGroup||a.cat||'Sonstiges')}</small><h3>${esc(g.name)}</h3>${a.supplier?'<p>'+esc(a.supplier)+'</p>':''}</div><div class=\"stockPreviewQty211\"><small>Auf Lager</small><strong>${esc(formatQtySmart(g.total,g.unit))}</strong></div></div><dl class=\"stockPreviewFacts211\"><div><dt>Lagerort</dt><dd>${esc(locations.join(' · '))}</dd></div><div><dt>Nächstes MHD</dt><dd>${dates.length?esc(formatDate(dates[0])):'Nicht hinterlegt'}</dd></div></dl>${g.members.length>1?`<details class=\"stockPreviewPositions211\"><summary>${g.members.length} Lagerpositionen ansehen</summary>${positions}</details>`:`<div class=\"stockPreviewFooter211\"><small>${esc(first[0])}</small><button class=\"soft\" onclick=\"${action(first[0])}\">Artikel öffnen</button></div>`}</article>`;\n}\nrenderStockSearch=function(){\n const input=document.getElementById('stockProductSearch'),box=document.getElementById('stockSearchResults');if(!input||!box)return;\n const query=input.value.trim(),searching=Boolean(query);\n for(const id of ['stockQuickTitle','stockQuickAccess','stockWarnings','stockOverview']){const node=document.getElementById(id);if(node)node.style.display=searching?'none':'';}\n if(!searching){box.innerHTML='';return;}\n const groups=stockSearchGroups211(mobileProductSearch(query,Object.keys(state.products||{}).length));\n box.innerHTML=`<p class=\"stockSearchCount211\">${groups.length} ${groups.length===1?'Produkt':'Produkte'} gefunden</p>${groups.length?'<div class=\"stockPreviewGrid211\">'+groups.map(stockPreviewCard211).join('')+'</div>':'<p class=\"muted\">Kein passendes Produkt gefunden. Suchbegriff oder Barcode prüfen.</p>'}`;\n};\n"},{"src":"invoice-mhd213.js?v=1","code":"'use strict';\nlet invoiceMhdSequence213=0;\nfunction validateInvoiceLots213(lots,total,whole){\n if(lots.length<2)throw Error('Mindestens zwei MHD-Teilmengen eingeben.');\n for(const lot of lots){if(!Number.isFinite(lot.qty)||lot.qty<=0||whole&&!Number.isInteger(lot.qty))throw Error('Jede MHD-Teilmenge muss positiv sein; Stückzahlen als ganze Zahl eingeben.');const date=new Date(lot.mhd+'T00:00:00Z');if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(lot.mhd)||!Number.isFinite(date.getTime())||date.toISOString().slice(0,10)!==lot.mhd)throw Error('Für jede Teilmenge ein gültiges MHD eingeben.');}\n if(Math.abs(lots.reduce((sum,lot)=>sum+lot.qty,0)-total)>1e-7)throw Error('Die MHD-Teilmengen müssen zusammen die Gesamtmenge ergeben.');\n return lots;\n}\nfunction invoiceSplitValues213(row){return [...row.querySelectorAll('.invoiceLot213')].map(lot=>({qty:Number(lot.querySelector('.invoiceLotQty213').value),mhd:lot.querySelector('.invoiceLotDate213').value}));}\nfunction renderInvoiceLots213(row,lots){\n const box=row.querySelector('.invoiceLots213');box.innerHTML=lots.map((lot,i)=>`<section class=\"invoiceLot213\"><b>Teilmenge ${i+1}</b><label>Menge<input class=\"invoiceLotQty213\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" value=\"${esc(lot.qty)}\"></label><label>MHD<input id=\"invoiceMhd213_${++invoiceMhdSequence213}\" class=\"invoiceLotDate213\" type=\"date\" value=\"${esc(lot.mhd)}\"></label>${lots.length>2?'<button class=\"soft invoiceLotRemove213\" type=\"button\">Teilmenge entfernen</button>':''}</section>`).join('');\n for(const date of box.querySelectorAll('.invoiceLotDate213'))installExpiry231(date);\n box.querySelectorAll('.invoiceLotRemove213').forEach((button,i)=>button.addEventListener('click',()=>{const values=invoiceSplitValues213(row);values.splice(i,1);renderInvoiceLots213(row,values);}));\n}\nfunction refreshInvoiceMhd213(row){\n const choice=row.querySelector('.invoiceMhdChoice191'),qty=Number(row.querySelector('.inboundQty182').value),active=choice.value==='yes'&&qty>1&&row.querySelector('.invoiceDisposition191').value==='stock';\n let host=row.querySelector('.invoiceSameMhd213');\n if(!host){host=document.createElement('div');host.className='invoiceSameMhd213';host.innerHTML='<label>Alle MHD gleich?<select class=\"invoiceSameAnswer213\"><option value=\"\">Bitte wählen</option><option value=\"yes\">Ja, alle gleich</option><option value=\"no\">Nein, unterschiedliche MHD</option></select></label><div class=\"invoiceSplit213\" hidden><p class=\"tiny\">Mengen mit gleichem MHD zusammen erfassen. Zusammen müssen sie die Gesamtmenge ergeben.</p><div class=\"invoiceLots213\"></div><button type=\"button\" class=\"soft invoiceLotAdd213\">Weiteres MHD hinzufügen</button></div>';row.querySelector('.invoiceMhdInput191').before(host);\n  host.querySelector('.invoiceSameAnswer213').addEventListener('change',()=>refreshInvoiceMhd213(row));\n  host.querySelector('.invoiceLotAdd213').addEventListener('click',()=>renderInvoiceLots213(row,[...invoiceSplitValues213(row),{qty:'',mhd:''}]));\n }\n host.hidden=!active;\n const split=active&&host.querySelector('.invoiceSameAnswer213').value==='no';host.querySelector('.invoiceSplit213').hidden=!split;\n row.querySelector('.invoiceMhdInput191').hidden=choice.value!=='yes'||split;\n if(split&&!row.querySelectorAll('.invoiceLot213').length)renderInvoiceLots213(row,[{qty:1,mhd:row.querySelector('.inboundMhd182').value},{qty:qty-1,mhd:''}]);\n}\nfunction invoiceMhdLots213(row){\n if(row.querySelector('.invoiceMhdChoice191')?.value!=='yes')return null;\n const total=Number(row.querySelector('.inboundQty182').value);if(!(total>1))return null;\n const answer=row.querySelector('.invoiceSameAnswer213')?.value;if(!['yes','no'].includes(answer))throw Error('Alle MHD gleich? Bitte mit Ja oder Nein bestätigen.');\n if(answer==='yes')return null;\n const code=row.querySelector('.inboundProduct182').value,unit=state.products?.[code]?.unit||row.querySelector('.inboundUnit185').value;\n return validateInvoiceLots213(invoiceSplitValues213(row),total,isWholeQuantityUnit(unit));\n}\n"},{"src":"beleg-review216.js?v=218","code":"'use strict';\nconst BELEG_UI_TEXT218={\n de:{settings:'Belegerkennung · PC-Test',settingsText:'Nur für die gemeinsame Entwicklung: gespeichertes Testergebnis mit einem Foto vergleichen.',settingsOpen:'PC-Test öffnen',title:'Belegerkennung · Test prüfen',close:'Testansicht schließen',intro:'Zuerst die am PC erzeugte JSON-Testdatei laden, danach das passende Foto. Ein Foto allein wird hier noch nicht ausgelesen. Keine Lagerbuchung.',json:'1. Ergebnis aus dem PC-Test (.json)',image:'2. Zugehöriges Belegfoto',none:'Noch kein Ergebnis ausgewählt.',zoom:'Foto vergrößern',photoAlt:'Beleg zum Vergleich mit den Positionen',rows:'Positionen prüfen',save:'Geprüftes Testergebnis herunterladen',show:'Ergebnis anzeigen oder kopieren',exportLabel:'Geprüftes JSON-Ergebnis',noTransfer:'Eine Warenübernahme ist in dieser Testansicht nicht möglich.',selectImage:'Belegfoto auswählen',confirm:'Angaben bestätigen',saveTitle:'Geprüftes Ergebnis speichern',photoLoaded:'Foto geladen. Noch die JSON-Datei aus dem PC-Test auswählen.',selectJson:'Zuerst die JSON-Datei aus dem PC-Test auswählen.',progress:'{photo} · {checked}/{total} Positionen geprüft. Durchstreichungen und Angaben am Foto bestätigen.',product:'Produkt',ean:'EAN laut Foto',article:'Artikelnummer',quantity:'Menge (Kolli)',pack:'Inhalt pro Kolli',price:'Preis je Kolli (€)',amount:'Positionsbetrag (€)',rowSummary:'{quantity} Kolli · {price} € je Kolli · {amount} € gesamt',model:'Modellvorschlag: {state}. Am Foto prüfen.',modelCrossed:'durchgestrichen',modelNormal:'nicht durchgestrichen',details:'Angaben ansehen oder korrigieren',crossedQuestion:'Am Foto durchgestrichen?',choose:'Bitte prüfen',yes:'Ja',no:'Nein',action:'Was soll mit der Position geschehen?',test:'Nur Erkennung prüfen',stock:'Lagerartikel zuordnen',exclude:'Nicht ins Lager (z. B. Pfand, gestrichen, Gerät)',stockItem:'Lagerartikel',selectProduct:'Artikel auswählen',newProduct:'Neuer Artikel · spätere Anlage nötig',confirmRow:'Angaben am Foto geprüft; bei Lagerware auch Artikel, Gebinde und Lagereinheit',barcode:'Barcode: {result}. Eine passende Prüfziffer bestätigt nicht den Artikel.',fileError:'Datei konnte nicht gelesen werden.',tooLarge:'JSON-Datei ist zu groß.',imageError:'Bitte ein JPG oder PNG bis 25 MB auswählen.',imageOpenError:'Bild konnte nicht geöffnet werden.',selectJsonFirst:'Zuerst JSON-Testdatei auswählen.',waitImage:'Zugehöriges Belegfoto auswählen und Laden abwarten.',position:'Position {position}: {error}',confirmPhoto:'Angaben am Foto bestätigen.',androidRequested:'Speicherung im Android-Downloadordner angefordert. Alternativ Ergebnis anzeigen oder kopieren.',androidFailed:'Android-Speicherung nicht möglich. Ergebnis unten anzeigen oder kopieren.',saveFailed:'Datei konnte nicht gespeichert werden. Ergebnis unten anzeigen oder kopieren.',downloadRequested:'Download angefordert. Falls keine Datei erscheint: Ergebnis unten anzeigen oder kopieren.',downloadFailed:'Download nicht möglich. Ergebnis unten anzeigen oder kopieren.'},\n pl:{settings:'Rozpoznawanie dokumentów · Test na PC',settingsText:'Tylko do wspólnych testów: porównaj zapisany wynik ze zdjęciem.',settingsOpen:'Otwórz test na PC',title:'Rozpoznawanie dokumentu · Sprawdź test',close:'Zamknij widok testowy',intro:'Najpierw wczytaj plik JSON utworzony na komputerze, a następnie właściwe zdjęcie. Samo zdjęcie nie jest tutaj jeszcze odczytywane. Bez zapisu do magazynu.',json:'1. Wynik testu z komputera (.json)',image:'2. Odpowiednie zdjęcie dokumentu',none:'Nie wybrano jeszcze wyniku.',zoom:'Powiększ zdjęcie',photoAlt:'Dokument do porównania z pozycjami',rows:'Sprawdź pozycje',save:'Pobierz sprawdzony wynik',show:'Pokaż lub skopiuj wynik',exportLabel:'Sprawdzony wynik JSON',noTransfer:'W tym widoku testowym nie można przyjąć towaru do magazynu.',selectImage:'Wybierz zdjęcie dokumentu',confirm:'Potwierdź dane',saveTitle:'Zapisz sprawdzony wynik',photoLoaded:'Zdjęcie wczytane. Wybierz jeszcze plik JSON z testu na komputerze.',selectJson:'Najpierw wybierz plik JSON z testu na komputerze.',progress:'{photo} · sprawdzono {checked}/{total} pozycji. Potwierdź przekreślenia i dane na zdjęciu.',product:'Produkt',ean:'EAN ze zdjęcia',article:'Numer artykułu',quantity:'Ilość (opakowania)',pack:'Zawartość opakowania',price:'Cena za opakowanie (€)',amount:'Wartość pozycji (€)',rowSummary:'{quantity} op. · {price} € za op. · razem {amount} €',model:'Propozycja modelu: {state}. Sprawdź na zdjęciu.',modelCrossed:'przekreślona',modelNormal:'nieprzekreślona',details:'Pokaż lub popraw dane',crossedQuestion:'Czy pozycja jest przekreślona na zdjęciu?',choose:'Sprawdź',yes:'Tak',no:'Nie',action:'Co zrobić z tą pozycją?',test:'Tylko sprawdź rozpoznanie',stock:'Przypisz artykuł magazynowy',exclude:'Nie przyjmuj do magazynu (np. kaucja, pozycja skreślona, urządzenie)',stockItem:'Artykuł magazynowy',selectProduct:'Wybierz artykuł',newProduct:'Nowy artykuł · trzeba dodać później',confirmRow:'Dane sprawdzone na zdjęciu; dla towaru potwierdzono też artykuł, opakowanie i jednostkę',barcode:'Kod kreskowy: {result}. Poprawna cyfra kontrolna nie potwierdza artykułu.',fileError:'Nie udało się odczytać pliku.',tooLarge:'Plik JSON jest za duży.',imageError:'Wybierz JPG lub PNG do 25 MB.',imageOpenError:'Nie udało się otworzyć zdjęcia.',selectJsonFirst:'Najpierw wybierz plik JSON z testu.',waitImage:'Wybierz odpowiednie zdjęcie dokumentu i poczekaj na wczytanie.',position:'Pozycja {position}: {error}',confirmPhoto:'Potwierdź dane na zdjęciu.',androidRequested:'Zlecono zapis w folderze pobierania Androida. Wynik można też wyświetlić lub skopiować.',androidFailed:'Nie udało się zapisać pliku w Androidzie. Wyświetl lub skopiuj wynik poniżej.',saveFailed:'Nie udało się zapisać pliku. Wyświetl lub skopiuj wynik poniżej.',downloadRequested:'Zlecono pobieranie. Jeśli plik się nie pojawi, wyświetl lub skopiuj wynik poniżej.',downloadFailed:'Nie udało się pobrać pliku. Wyświetl lub skopiuj wynik poniżej.'},\n en:{settings:'Document recognition · PC test',settingsText:'For joint testing only: compare a saved result with its photo.',settingsOpen:'Open PC test',title:'Document recognition · Review test',close:'Close test view',intro:'First load the JSON test file created on the PC, then select the matching photo. A photo on its own is not read here yet. No stock transaction.',json:'1. PC test result (.json)',image:'2. Matching document photo',none:'No result selected yet.',zoom:'Enlarge photo',photoAlt:'Document to compare with the rows',rows:'Review rows',save:'Download reviewed test result',show:'Show or copy result',exportLabel:'Reviewed JSON result',noTransfer:'This test view cannot add goods to stock.',selectImage:'Select document photo',confirm:'Confirm details',saveTitle:'Save reviewed result',photoLoaded:'Photo loaded. Now select the JSON file from the PC test.',selectJson:'Select the JSON file from the PC test first.',progress:'{photo} · {checked}/{total} rows reviewed. Confirm crossings and details against the photo.',product:'Product',ean:'EAN from photo',article:'Article number',quantity:'Quantity (packs)',pack:'Contents per pack',price:'Price per pack (€)',amount:'Row amount (€)',rowSummary:'{quantity} packs · €{price} per pack · €{amount} total',model:'Model suggestion: {state}. Check against the photo.',modelCrossed:'crossed out',modelNormal:'not crossed out',details:'View or correct details',crossedQuestion:'Crossed out on the photo?',choose:'Review',yes:'Yes',no:'No',action:'What should happen to this row?',test:'Review recognition only',stock:'Assign stock item',exclude:'Do not add to stock (e.g. deposit, crossed out, equipment)',stockItem:'Stock item',selectProduct:'Select item',newProduct:'New item · create later',confirmRow:'Checked against the photo; for stock goods also confirmed item, pack and stock unit',barcode:'Barcode: {result}. A valid check digit does not confirm the item.',fileError:'The file could not be read.',tooLarge:'The JSON file is too large.',imageError:'Select a JPG or PNG up to 25 MB.',imageOpenError:'The image could not be opened.',selectJsonFirst:'Select the JSON test file first.',waitImage:'Select the matching document photo and wait for it to load.',position:'Row {position}: {error}',confirmPhoto:'Confirm the details against the photo.',androidRequested:'Saving to the Android downloads folder was requested. You can also show or copy the result.',androidFailed:'Android could not save the file. Show or copy the result below.',saveFailed:'The file could not be saved. Show or copy the result below.',downloadRequested:'Download requested. If no file appears, show or copy the result below.',downloadFailed:'Download failed. Show or copy the result below.'}\n};\nfunction belegUiLanguage218(){try{const language=String(business130()?.language||'').toLowerCase();if(['de','pl','en'].includes(language))return language}catch{}return 'de'}\nfunction belegText218(language=belegUiLanguage218()){return BELEG_UI_TEXT218[language]||BELEG_UI_TEXT218.de}\nfunction fillBelegText218(template,values){return template.replace(/\\{(\\w+)\\}/g,(_,key)=>String(values[key]??''))}\nfunction applyBelegSettingsLanguage218(){if(typeof document==='undefined'||typeof document.getElementById!=='function')return;const card=document.getElementById('belegSettings217');if(!card)return;const t=belegText218();card.querySelector('summary').textContent=t.settings;card.querySelector('p').textContent=t.settingsText;card.querySelector('button').textContent=t.settingsOpen}\nasync function openBelegTest216(){\n if(!guardDocumentSave240())return;\n const engine=await import('./beleg-test216.mjs?v=218');\n if(!guardDocumentSave240())return;\n const existing=document.getElementById('belegReviewDialog216');if(existing?.open)return;\n const language=belegUiLanguage218(),t=belegText218(language);\n const dialog=document.createElement('dialog');dialog.id='belegReviewDialog216';dialog.className='opsDialog92';\n dialog.innerHTML=`<header><h2>${t.title}</h2><button type=\"button\" class=\"soft\" aria-label=\"${t.close}\" data-close>×</button></header><div class=\"opsBody92\"><div class=\"belegReview216\"><p>${t.intro}</p><div class=\"belegFiles216\"><label>${t.json}<input id=\"belegJson216\" type=\"file\" accept=\"*/*\"></label><label>${t.image}<input id=\"belegImage216\" type=\"file\" accept=\"image/png,image/jpeg\"></label></div><p id=\"belegStatus216\" role=\"status\">${t.none}</p><div class=\"belegSplit216\"><section class=\"belegPhoto216\"><label>${t.zoom}<input id=\"belegZoom216\" type=\"range\" min=\"100\" max=\"300\" value=\"100\"></label><div class=\"belegPhotoScroll216\"><img id=\"belegPhoto216\" alt=\"${t.photoAlt}\" hidden></div></section><section id=\"belegRows216\" aria-label=\"${t.rows}\"></section></div><p id=\"belegExportError217\" role=\"alert\"></p><button id=\"belegSave216\" class=\"primary\">${t.save}</button><details id=\"belegExport217\" hidden><summary>${t.show}</summary><textarea id=\"belegExportText217\" readonly rows=\"8\" aria-label=\"${t.exportLabel}\"></textarea></details><p>${t.noTransfer}</p></div></div>`;\n document.body.appendChild(dialog);dialog.showModal();dialog.querySelector('[data-close]').onclick=()=>dialog.close();\n const status=document.getElementById('belegStatus216'),host=document.getElementById('belegRows216'),save=document.getElementById('belegSave216'),photo=document.getElementById('belegPhoto216');\n let review=null,imageUrl='',imageReady=false,serial=0;\n const alive=()=>dialog.open&&dialog.querySelector('#belegSave216')===save;\n const errorText=e=>{status.textContent=e.message||t.fileError;document.getElementById('belegExportError217').textContent=status.textContent;save.setAttribute('aria-disabled','true')};\n function update(){\n  if(!alive())return;\n  document.getElementById('belegExportError217').textContent='';\n  const checked=review?.rows.filter(r=>r.confirmed&&!engine.errors216(r,language).length).length||0;\n  const ready=canSaveDocuments240()&&imageReady&&review&&checked===review.rows.length;\n  save.setAttribute('aria-disabled',String(!ready));\n  if(review){const first=review.rows.find(r=>!r.confirmed||engine.errors216(r,language).length);save.title=!imageReady?t.selectImage:first?fillBelegText218(t.position,{position:first.index+1,error:engine.errors216(first,language).join(' ')||t.confirm}):t.saveTitle;}\n  if(!review)status.textContent=imageReady?t.photoLoaded:t.selectJson;\n  if(review)status.textContent=fillBelegText218(t.progress,{photo:review.photo,checked,total:review.rows.length});\n }\n function renderRows(){\n  host.textContent='';\n  for(const r of review.rows){\n   if(!r.disposition)r.disposition='test';\n   const card=document.createElement('article');card.className='belegRow216';\n   const fields=[['name',t.product],['ean',t.ean],['article',t.article],['quantity',t.quantity],['pack',t.pack],['price',t.price],['amount',t.amount]];\n   card.innerHTML=`<h3>${r.index+1}. ${esc(r.name)}</h3><p>${fillBelegText218(t.rowSummary,{quantity:esc(r.quantity),price:esc(r.price),amount:esc(r.amount)})}</p><p class=\"belegRaw216\">${esc(r.raw.originalzeile||r.name)}</p><p>${fillBelegText218(t.model,{state:r.raw.durchgestrichen?t.modelCrossed:t.modelNormal})}</p><details><summary>${t.details}</summary>${fields.map(([key,label])=>`<label>${label}<input data-field=\"${key}\" value=\"${esc(r[key])}\" ${['quantity','price','amount','pack'].includes(key)?'inputmode=\"decimal\"':''}></label>`).join('')}<p class=\"belegGtin216\"></p></details><label>${t.crossedQuestion}<select data-field=\"crossed\"><option value=\"\">${t.choose}</option><option value=\"yes\">${t.yes}</option><option value=\"no\">${t.no}</option></select></label><label>${t.action}<select data-field=\"disposition\"><option value=\"test\">${t.test}</option><option value=\"stock\">${t.stock}</option><option value=\"exclude\">${t.exclude}</option></select></label><label data-product-label>${t.stockItem}<select data-field=\"product\"><option value=\"\">${t.selectProduct}</option><option value=\"__new__\">${t.newProduct}</option></select></label><label class=\"belegConfirm216\"><input type=\"checkbox\" data-confirm> ${t.confirmRow}</label><p class=\"belegErrors216\" role=\"status\"></p>`;\n   function refresh(){\n    card.querySelector('.belegGtin216').textContent=fillBelegText218(t.barcode,{result:engine.gtin216(r.ean,language)});\n    card.querySelector('.belegErrors216').textContent=engine.errors216(r,language).join(' ');\n    const productSelect=card.querySelector('[data-field=\"product\"]');\n    if(r.disposition==='stock'&&!productSelect.dataset.loaded){\n     const selected=productSelect.value;productSelect.innerHTML=`<option value=\"\">${t.selectProduct}</option>`+Object.entries(state.products||{}).map(([id,p])=>`<option value=\"${esc(id)}\">${esc(p.name)} · ${esc(p.unit||'')}</option>`).join('')+`<option value=\"__new__\">${t.newProduct}</option>`;productSelect.value=selected;productSelect.dataset.loaded='yes';\n    }\n    card.querySelector('[data-product-label]').hidden=r.disposition!=='stock';\n    card.querySelector('[data-field=\"product\"]').disabled=r.disposition!=='stock';update();\n   }\n   card.querySelectorAll('[data-field]').forEach(input=>input.addEventListener(input.tagName==='SELECT'?'change':'input',()=>{\n    r[input.dataset.field]=input.value;r.confirmed=false;card.querySelector('[data-confirm]').checked=false;refresh();\n   }));\n   card.querySelector('[data-confirm]').addEventListener('change',event=>{r.confirmed=event.target.checked;refresh()});\n   host.appendChild(card);refresh();\n  }\n }\n document.getElementById('belegJson216').addEventListener('change',async event=>{\n  const attempt=++serial,file=event.target.files[0];review=null;host.textContent='';update();if(!file)return;\n  try{\n   if(file.size>2*1024*1024)throw Error(t.tooLarge);\n   const text=await file.text();if(!alive()||attempt!==serial)return;\n   review=engine.readTest216(text);document.getElementById('belegExport217').hidden=true;renderRows();update();\n  }catch(e){if(alive()&&attempt===serial)errorText(e)}\n });\n document.getElementById('belegImage216').addEventListener('change',event=>{\n  imageReady=false;if(review)for(const r of review.rows)r.confirmed=false;\n  host.querySelectorAll('[data-confirm]').forEach(c=>c.checked=false);\n  if(imageUrl)URL.revokeObjectURL(imageUrl);imageUrl='';photo.hidden=true;photo.removeAttribute('src');update();\n  const file=event.target.files[0];if(!file)return;\n  if(!(['image/png','image/jpeg'].includes(file.type)||(!file.type&&/\\.(png|jpe?g)$/i.test(file.name)))||file.size>25*1024*1024){errorText(Error(t.imageError));return}\n  imageUrl=URL.createObjectURL(file);\n  photo.onload=()=>{if(alive()){imageReady=true;photo.hidden=false;update()}};\n  photo.onerror=()=>{if(alive())errorText(Error(t.imageOpenError))};photo.src=imageUrl;\n });\n document.getElementById('belegZoom216').addEventListener('input',event=>photo.style.width=event.target.value+'%');\n save.addEventListener('click',()=>{\n  if(!guardDocumentSave240())return;\n  try{\n   if(!review)throw Error(t.selectJsonFirst);\n   if(!imageReady)throw Error(t.waitImage);\n   const first=review.rows.find(r=>!r.confirmed||engine.errors216(r,language).length);\n   if(first){const card=host.children[first.index];card.scrollIntoView({block:'center',behavior:'smooth'});throw Error(fillBelegText218(t.position,{position:first.index+1,error:engine.errors216(first,language).join(' ')||t.confirmPhoto}));}\n   const result=engine.exportReview216(review,imageReady,language);\n   const text=JSON.stringify(result,null,2),filename='Zapasio-Belegpruefung-'+Date.now()+'.json';\n   document.getElementById('belegExportText217').value=text;document.getElementById('belegExport217').hidden=false;\n   downloadBelegReview217(text,filename,status,language);\n\n  }catch(e){errorText(e)}\n });\n dialog.addEventListener('close',()=>{serial++;if(imageUrl)URL.revokeObjectURL(imageUrl);review=null;dialog.remove()},{once:true});\n}\n\nfunction downloadBelegReview217(text,filename,status,language=belegUiLanguage218()){\n const t=belegText218(language);\n const blob=new Blob([text],{type:'application/json'});\n if(typeof window.ZapasioAndroid?.saveBase64File==='function'){\n  const reader=new FileReader();reader.onload=()=>{\n   try{window.ZapasioAndroid.saveBase64File(reader.result,filename);status.textContent=t.androidRequested}\n   catch{status.textContent=t.androidFailed}\n  };reader.onerror=()=>{status.textContent=t.saveFailed};reader.readAsDataURL(blob);return;\n }\n const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=filename;a.hidden=true;\n document.body.appendChild(a);\n try{a.click();status.textContent=t.downloadRequested}\n catch{status.textContent=t.downloadFailed}\n finally{a.remove();setTimeout(()=>URL.revokeObjectURL(url),60000)}\n}\napplyBelegSettingsLanguage218();\nconst applyLanguageBefore218=typeof applyLanguage130==='function'?applyLanguage130:null;\nif(applyLanguageBefore218)applyLanguage130=function(...args){const result=applyLanguageBefore218(...args);applyBelegSettingsLanguage218();return result};\n"},{"src":"pack-content431.js?v=431","code":"'use strict';\n// Read-only: inventory remains in packs. Only confirmed piece contents count;\n// capacity, weights and product-name suggestions cannot imply a piece total.\nfunction totalStockPieces434(a){\n const c=a?.stockContent,stock=Number(a?.stock);\n if(!c||c.stockUnit!==a.unit||!isCountLikeUnit(a.unit)||normalizeUnit(a.unit)==='stk'||normalizeUnit(c.unit)!=='stk'||!Number.isSafeInteger(Number(c.qty))||Number(c.qty)<=0||a.stock===null||a.stock===undefined||String(a.stock).trim()===''||!Number.isFinite(stock)||validateStockContent(a,{...c,qty:Number(c.qty)}))return null;\n const total=stock*Number(c.qty);return Number.isFinite(total)&&Math.abs(total)<=Number.MAX_SAFE_INTEGER?total:null;\n}\n// Contents describe one inventory unit. Capacity is separate, optional metadata;\n// it never defines prices, grouping factors, batches or inventory quantities.\nfunction contentWords431(key){\n const l=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';\n const words={de:{content:'Packungsinhalt',hint:'Zum Beispiel: 50 Stück oder 500 g je Packung. Der Bestand bleibt in der gewählten Bestandseinheit.',capacity:'Fassungsvermögen je Stück (optional)',quantity:'Fassungsvermögen',unit:'Einheit des Fassungsvermögens',invalid:'Bitte ein Fassungsvermögen größer als 0 in ml oder Liter eingeben.',check:'Packungsinhalt prüfen',capacityCheck:'Fassungsvermögen prüfen'},en:{content:'Pack contents',hint:'For example: 50 pieces or 500 g per pack. Stock stays in the selected inventory unit.',capacity:'Capacity per piece (optional)',quantity:'Capacity',unit:'Capacity unit',invalid:'Enter a capacity greater than 0 in ml or litres.',check:'Check pack contents',capacityCheck:'Check capacity'},pl:{content:'Zawartość opakowania',hint:'Na przykład: 50 sztuk lub 500 g w opakowaniu. Stan pozostaje w wybranej jednostce magazynowej.',capacity:'Pojemność jednej sztuki (opcjonalnie)',quantity:'Pojemność',unit:'Jednostka pojemności',invalid:'Podaj pojemność większą od 0 w ml lub litrach.',check:'Sprawdź zawartość opakowania',capacityCheck:'Sprawdź pojemność'}};\n return (words[l]||words.de)[key];\n}\nfunction installArticleCapacity431(){\n const field=document.getElementById('aeContentField');if(!field||document.getElementById('aeCapacityQty431'))return;\n field.insertAdjacentHTML('beforeend',`<details id=\"aeCapacityDetails431\"><summary>${esc(contentWords431('capacity'))}</summary><label for=\"aeCapacityQty431\">${esc(contentWords431('quantity'))}</label><div class=\"recipeContentInput125\"><input id=\"aeCapacityQty431\" inputmode=\"decimal\"><select id=\"aeCapacityUnit431\" aria-label=\"${esc(contentWords431('unit'))}\"><option>ml</option><option>Liter</option></select></div></details>`);\n}\nfunction loadArticleCapacity431(article){\n installArticleCapacity431();const qty=document.getElementById('aeCapacityQty431'),unit=document.getElementById('aeCapacityUnit431'),details=document.getElementById('aeCapacityDetails431');\n if(qty)qty.value=article?.capacity431?String(article.capacity431.qty):'';\n if(unit)unit.value=article?.capacity431?.unit||'ml';\n if(details)details.open=!!qty?.value;\n}\nfunction readArticleCapacity431(){\n const raw=document.getElementById('aeCapacityQty431')?.value?.trim()||'',unit=document.getElementById('aeCapacityUnit431')?.value||'ml';\n if(!raw||!isCountLikeUnit(document.getElementById('aeUnit')?.value))return {value:null};\n const qty=Number(raw.replace(',','.'));if(!Number.isFinite(qty)||qty<=0||!['ml','Liter'].includes(unit))return {error:contentWords431('invalid')};\n return {value:{qty,unit}};\n}\nfunction guidedCapacityHtml431(){\n const qty=document.getElementById('aeCapacityQty431')?.value||'',unit=document.getElementById('aeCapacityUnit431')?.value||'ml';\n return `<details id=\"gaCapacityDetails431\" ${qty||document.getElementById('aeCapacityDetails431')?.open?'open':''}><summary>${esc(contentWords431('capacity'))}</summary><label for=\"gaCapacityQty431\">${esc(contentWords431('quantity'))}</label><input id=\"gaCapacityQty431\" class=\"guidedMainInput300\" inputmode=\"decimal\" value=\"${esc(qty)}\"><label for=\"gaCapacityUnit431\">${esc(contentWords431('unit'))}</label><select id=\"gaCapacityUnit431\" class=\"guidedMainInput300\">${['ml','Liter'].map(u=>`<option ${u===unit?'selected':''}>${u}</option>`).join('')}</select></details>`;\n}\nfunction captureGuidedCapacity431(){\n for(const suffix of ['Qty','Unit']){const source=document.getElementById('gaCapacity'+suffix+'431'),target=document.getElementById('aeCapacity'+suffix+'431');if(source&&target)target.value=source.value}\n const details=document.getElementById('gaCapacityDetails431'),target=document.getElementById('aeCapacityDetails431');if(details&&target)target.open=details.open;\n}\nfunction validateGuidedContent431(required=false){\n const raw=document.getElementById('aeContentQty')?.value?.trim()||'';\n if(required&&!raw){showFeedback(contentWords431('check'),'warn',5000,contentText436('required',document.getElementById('aeUnit')?.value||'Stück'));return false}\n const error=raw?validateStockContent({unit:document.getElementById('aeUnit')?.value,packs:[]},{qty:Number(raw.replace(',','.')),unit:document.getElementById('aeContentUnit')?.value}):'';\n const capacity=readArticleCapacity431();if(error||capacity.error){showFeedback(contentWords431(error?'check':'capacityCheck'),'warn',5000,error||capacity.error);return false}return true;\n}\nfunction capacityReviewHtml431(){const c=readArticleCapacity431().value;return c?`<div><dt>${esc(contentWords431('quantity'))}</dt><dd>${esc(formatQtySmart(c.qty,c.unit,{exact:true}))} / ${esc(stockText420('Stück','piece','sztuka'))}</dd></div>`:''}\n"},{"src":"mobile-guided-flow300.js?v=427","code":"'use strict';\n// Mobile guided-flow unification: one decision/input per step, back without data loss,\n// and stock changes only from the final review step.\nconst GUIDED300_LANG={\n de:{cancel:'Abbrechen',back:'Zurück',next:'Weiter',save:'Speichern',receive:'Wareneingang',newProduct:'Neues Produkt',step:'Schritt',qty:'Wie viele {0} kommen dazu?',samePrice:'Ist der Einkaufspreis weiterhin {0} je {1}?',newPrice:'Neuer Einkaufspreis je {0}',mhd:'Welches MHD hat diese Ware?',review:'Einlagerung prüfen',newStock:'Neuer Bestand',productName:'Wie heißt das Produkt?',stockUnit:'Wie soll der Bestand geführt werden?',content:'Wie viel enthält eine {0}?',stockGroup:'Gehört dieses Produkt zu einem gemeinsamen Gesamtvorrat?',stockGroupHint:'Nur Varianten derselben Ware zusammenfassen. Barcode, Preis und MHD bleiben je Variante getrennt.',hasMhd:'Hat dieses Produkt ein MHD?',storage:'Wo wird das Produkt gelagert?',supplier:'Von welchem Lieferanten kommt das Produkt?',purchasePrice:'Aktueller Einkaufspreis netto je {0}',productReview:'Produkt prüfen',noSupplier:'Später festlegen',noPrice:'Später eintragen',yes:'Ja',no:'Nein',found:'Automatisch erkannte Daten wurden übernommen. Bitte nur noch die betrieblichen Angaben ergänzen.'},\n pl:{cancel:'Anuluj',back:'Wstecz',next:'Dalej',save:'Zapisz',receive:'Przyjęcie towaru',newProduct:'Nowy produkt',step:'Krok',qty:'Ile ({0}) przyjmujesz?',samePrice:'Czy cena zakupu nadal wynosi {0} za {1}?',newPrice:'Nowa cena zakupu za {0}',mhd:'Jaki jest termin ważności tej dostawy?',review:'Sprawdź przyjęcie',newStock:'Nowy stan',productName:'Jak nazywa się produkt?',stockUnit:'W jakiej jednostce prowadzić stan?',content:'Ile zawiera jedna jednostka {0}?',stockGroup:'Czy ten produkt należy do wspólnego stanu magazynowego?',stockGroupHint:'Łącz tylko warianty tego samego towaru. Kod, cena i termin ważności pozostają oddzielne.',hasMhd:'Czy produkt ma termin ważności?',storage:'Gdzie produkt jest przechowywany?',supplier:'Od którego dostawcy pochodzi produkt?',purchasePrice:'Aktualna cena zakupu netto za {0}',productReview:'Sprawdź produkt',noSupplier:'Ustal później',noPrice:'Wpisz później',yes:'Tak',no:'Nie',found:'Automatycznie znalezione dane zostały przejęte. Uzupełnij tylko dane firmowe.'},\n en:{cancel:'Cancel',back:'Back',next:'Next',save:'Save',receive:'Receive stock',newProduct:'New product',step:'Step',qty:'How many {0} are being received?',samePrice:'Is the purchase price still {0} per {1}?',newPrice:'New purchase price per {0}',mhd:'What is the best-before date?',review:'Review receipt',newStock:'New stock',productName:'What is the product called?',stockUnit:'How should stock be counted?',content:'How much does one {0} contain?',stockGroup:'Should this product belong to a shared total stock?',stockGroupHint:'Only group variants of the same goods. Barcode, price and expiry lots stay separate.',hasMhd:'Does this product use a best-before date?',storage:'Where is the product stored?',supplier:'Which supplier provides the product?',purchasePrice:'Current net purchase price per {0}',productReview:'Review product',noSupplier:'Set later',noPrice:'Enter later',yes:'Yes',no:'No',found:'Automatically found data was applied. Only business-specific details remain.'}\n};\nfunction guidedText300(key,...values){const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de',tpl=GUIDED300_LANG[lang]?.[key]||GUIDED300_LANG.de[key]||key;return tpl.replace(/\\{(\\d+)\\}/g,(_,i)=>String(values[Number(i)]??''))}\nfunction guidedProgress300(title,index,total){return `<div class=\"guidedProgress300\"><small>${esc(title)} · ${esc(guidedText300('step'))} ${index} / ${total}</small><div><i style=\"width:${Math.round(index/total*100)}%\"></i></div></div>`}\nfunction guidedActions300(back,next,nextLabel){return `<div class=\"guidedActions300\">${back?`<button type=\"button\" class=\"soft\" onclick=\"${back}\">${esc(guidedText300('back'))}</button>`:`<button type=\"button\" class=\"soft\" onclick=\"endGuide194()\">${esc(guidedText300('cancel'))}</button>`}<button type=\"button\" class=\"primary\" onclick=\"${next}\">${esc(nextLabel||guidedText300('next'))}</button></div>`}\nfunction guidedRemember300(id,key){const d=productGuide194,el=document.getElementById(id);if(d&&el)d.stockWizard300[key]=el.value}\n\n// ---------- Existing product: one visible step for receipts and withdrawals ----------\nfunction stockWizardSteps300(d,a){\n const steps=['qty'],incoming=d.quickMode198==='in';\n if(incoming&&currentAccessUser().role==='admin')steps.push('priceChoice');\n if(incoming&&currentAccessUser().role==='admin'&&d.stockWizard300?.priceChoice==='changed')steps.push('priceValue');\n const policy=d.stockWizard300?.policy420||recognizedMhdPolicy256(a);\n if(incoming&&recognizedMhdPolicy256(a)==='review')steps.push('policy');\n if(incoming&&['required','optional'].includes(policy)){if(Number(d.stockWizard300?.qty)>1)steps.push('sameMhd');steps.push('mhd');}\n if(!incoming&&['required','optional'].includes(policy))steps.push('lot');\n steps.push('review');return steps;\n}\nfunction withdrawalTarget459(d,a,pack){return Number(d.stockWizard300.qty)*(pack?Number(packUsableQty(pack)):1)}\nfunction initWithdrawal459(d,a,pack){\n const w=d.stockWizard300;if(Array.isArray(w.lots459))return;\n const groups=withdrawalGroups335(d.existing.productCode),old=groups.find(g=>g.id===w.lot),one=old||(groups.length===1?groups[0]:null);\n w.lots459=one?[{id:one.id,qty:String(Math.min(withdrawalTarget459(d,a,pack),one.qty))}]:[];\n}\nfunction captureWithdrawal459(w){\n const box=document.getElementById('withdrawalLots459');if(!box)return;\n w.lots459=[...box.querySelectorAll('[data-lot-qty459]')].filter(el=>!el.disabled).map(el=>({id:el.dataset.lotQty459,qty:el.value}));\n}\nfunction withdrawalChange459(index,action,value){\n const d=productGuide194;if(!d?.existing||d.quickMode198!=='out'||d.quickPending198)return;\n const a=state.products[d.existing.productCode],w=d.stockWizard300,groups=withdrawalGroups335(d.existing.productCode),group=groups.find(g=>g.id===w?.lotIds459?.[index]);if(!a||!w||!group||w.lotView459!==withdrawalSnapshot335(d.existing.productCode)){renderStockWizard311();showFeedback(recognizedText278('confirmTitle'),'warn',5000,recognizedText278('lotChanged'));return;}\n captureWithdrawal459(w);const item=w.lots459.find(r=>r.id===group.id),pack=d.existing.packIndex>0?getPacks(a,d.existing.productCode)[d.existing.packIndex]:null;\n if(action==='toggle'){\n  if(value&&!item){const used=w.lots459.reduce((n,r)=>n+(Number(r.qty)||0),0),remaining=Math.max(0,withdrawalTarget459(d,a,pack)-used);w.lots459.push({id:group.id,qty:String(Math.min(group.qty,remaining||Math.min(1,group.qty)))});}\n  else if(!value)w.lots459=w.lots459.filter(r=>r.id!==group.id);\n }else if(item){item.qty=action==='input'?value:String(Math.round(Math.min(group.qty,Math.max(0,(Number(item.qty)||0)+(action==='plus'?1:-1)))*1e8)/1e8);}\n refreshWithdrawal459(d,a,pack);\n}\nfunction refreshWithdrawal459(d,a,pack){\n const w=d.stockWizard300,groups=withdrawalGroups335(d.existing.productCode);\n groups.forEach((group,i)=>{const item=w.lots459.find(r=>r.id===group.id),check=document.getElementById('withdrawalCheck459-'+i),input=document.getElementById('withdrawalQty459-'+i);if(check)check.checked=!!item;if(input){input.disabled=!item;if(document.activeElement!==input)input.value=item?.qty??'0';}for(const action of ['minus','plus']){const button=document.getElementById('withdrawal'+action+'459-'+i);if(button)button.disabled=!item;}});\n const total=w.lots459.reduce((n,r)=>n+(Number(r.qty)||0),0),status=document.getElementById('withdrawalTotal459');if(status)status.textContent=withdrawalText459('selected')+': '+formatQtySmart(total,a.unit)+' '+withdrawalText459('of')+' '+formatQtySmart(withdrawalTarget459(d,a,pack),a.unit);\n}\nfunction withdrawalHtml459(d,a,pack){\n initWithdrawal459(d,a,pack);const w=d.stockWizard300,groups=withdrawalGroups335(d.existing.productCode),snapshot=withdrawalSnapshot335(d.existing.productCode);if(w.lotView459&&w.lotView459!==snapshot)w.lots459=[];w.lotView459=snapshot;w.lotIds459=groups.map(g=>g.id);const total=w.lots459.reduce((n,r)=>n+(Number(r.qty)||0),0);\n return `<h3>${esc(withdrawalText459('title'))}</h3><p>${esc(withdrawalText459('hint'))}</p><div id=\"withdrawalLots459\">${groups.map((g,i)=>{const item=w.lots459.find(r=>r.id===g.id),date=g.noMhd?esc(withoutMhdText335(g.commission)):bookingDateHtml342(g.mhd),detail=[g.members[0]?.lotNumber||g.members[0]?.batchNumber||'',!g.noMhd&&g.members[0]?.consignment168?withoutMhdText335(true).split(' · ').pop():''].filter(Boolean).join(' · ');return `<section class=\"withdrawalLot459\"><label class=\"withdrawalCheck459\"><input id=\"withdrawalCheck459-${i}\" type=\"checkbox\" ${item?'checked':''} onchange=\"withdrawalChange459(${i},'toggle',this.checked)\"><span><b>${date}</b>${detail?`<small>${esc(detail)}</small>`:''}<small>${esc(withdrawalText459('available'))}: ${esc(formatQtySmart(g.qty,a.unit))}</small></span></label><div class=\"withdrawalAmount459\"><label for=\"withdrawalQty459-${i}\">${esc(withdrawalText459('amount'))} (${esc(a.unit)})</label><div class=\"withdrawalStepper459\"><button id=\"withdrawalminus459-${i}\" type=\"button\" class=\"soft\" aria-label=\"${esc(withdrawalText459('less'))}\" ${item?'':'disabled'} onclick=\"withdrawalChange459(${i},'minus')\">−</button><input id=\"withdrawalQty459-${i}\" data-lot-qty459=\"${esc(g.id)}\" type=\"number\" inputmode=\"decimal\" min=\"0\" max=\"${g.qty}\" step=\"${isWholeQuantityUnit(a.unit)?'1':'any'}\" value=\"${esc(item?.qty??'0')}\" ${item?'':'disabled'} oninput=\"withdrawalChange459(${i},'input',this.value)\"><button id=\"withdrawalplus459-${i}\" type=\"button\" class=\"soft\" aria-label=\"${esc(withdrawalText459('more'))}\" ${item?'':'disabled'} onclick=\"withdrawalChange459(${i},'plus')\">+</button></div></div></section>`}).join('')||`<p class=\"recognizedWarn198\">${esc(recognizedText278('noLot'))}</p>`}</div><p id=\"withdrawalTotal459\" class=\"withdrawalTotal459\" role=\"status\">${esc(withdrawalText459('selected'))}: ${esc(formatQtySmart(total,a.unit))} ${esc(withdrawalText459('of'))} ${esc(formatQtySmart(withdrawalTarget459(d,a,pack),a.unit))}</p>`;\n}\nfunction withdrawalReview459(d,a){\n const groups=withdrawalGroups335(d.existing.productCode);\n return (d.stockWizard300.lots459||[]).map(row=>{const g=groups.find(g=>g.id===row.id);return `<div><dt>${g?.noMhd?esc(withoutMhdText335(g.commission)):g?bookingDateHtml342(g.mhd):esc(recognizedText278('lotChanged'))}</dt><dd>${esc(formatQtySmart(Number(row.qty),a.unit))}</dd></div>`}).join('');\n}\nfunction stockWizardInit300(d){if(!d.stockWizard300)d.stockWizard300={step:'qty',qty:'1',priceChoice:'',price:'',mhd:'',lot:''}}\nfunction captureStockWizard300(d){\n stockWizardInit300(d);const w=d.stockWizard300;\n captureReceiptLots422(w);\n captureWithdrawal459(w);\n for(const [id,key] of [['guidedQty300','qty'],['recognizedPrice214','price'],['guidedMhd300','mhd'],['guidedLot300','lot']]){\n  const field=document.getElementById(id);if(field){w[key]=field.value;if(key==='mhd')w.mhdVisited=true;}\n }\n}\nfunction stockWizardGo300(direction){\n const d=productGuide194;if(!d?.existing||!['in','out'].includes(d.quickMode198)||d.quickPending198)return;\n captureStockWizard300(d);const a=state.products[d.existing.productCode];if(!a)return;\n const w=d.stockWizard300,step=w.step,pack=d.existing.packIndex>0?getPacks(a,d.existing.productCode)[d.existing.packIndex]:null;\n if(direction>0){\n  try{\n   if(typeof validateForms231==='function'&&!validateForms231())return;\n   if(step==='qty'){const n=Number(w.qty),factor=pack?Number(packUsableQty(pack)):1;if(!Number.isFinite(n)||n<=0||!(factor>0)||((pack||isWholeQuantityUnit(a.unit))&&!Number.isInteger(n)))throw Error(recognizedQuantityMessage342(a,pack));if(d.quickMode198==='out'&&n*factor>Number(a.stock)+1e-8)throw Error(recognizedText278('stockShort'));}\n   if(step==='priceChoice'&&!['same','changed'].includes(w.priceChoice))throw Error(recognizedText278('priceChoice'));\n   if(step==='priceValue'){const n=readPrice228('recognizedPrice214');if(!(n>0))throw Error(recognizedText278('validPrice'));w.price=String(n);}\n   if(step==='sameMhd'&&typeof w.sameMhd422!=='boolean')throw Error(stockText420('Bitte eine Auswahl treffen.','Please choose an option.','Wybierz opcję.'));\n   if(step==='mhd'&&w.sameMhd422===false&&Number(w.qty)>1){validateReceiptLots422(w,pack?Number(packUsableQty(pack)):1,(w.policy420||recognizedMhdPolicy256(a)),!!pack||isWholeQuantityUnit(a.unit));}\n   else if(step==='mhd'){if((w.policy420||recognizedMhdPolicy256(a))==='required'||w.mhd){const date=new Date(w.mhd+'T00:00:00Z');if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(w.mhd)||!Number.isFinite(date.getTime())||date.toISOString().slice(0,10)!==w.mhd)throw Error(recognizedText278('mhdDate'));}}\n   if(step==='policy'&&!w.policy420)throw Error('Bitte eine MHD-Regel auswählen.');\n   if(step==='lot'){if(w.lotView459!==withdrawalSnapshot335(d.existing.productCode)){renderStockWizard311();throw Error(recognizedText278('lotChanged'));}w.reviewSnapshot459=withdrawalPlan459(d.existing.productCode,w.lots459,Number(w.qty)*(pack?Number(packUsableQty(pack)):1)).snapshot;}\n  }catch(error){showFeedback(recognizedText278('confirmTitle'),'warn',5000,error.message);return;}\n }\n if(typeof clearArticleFeedback316==='function')clearArticleFeedback316();\n const steps=stockWizardSteps300(d,a),index=Math.max(0,steps.indexOf(step));w.step=steps[Math.max(0,Math.min(steps.length-1,index+direction))];renderStockWizard311();\n}\nfunction stockWizardPriceChoice300(choice){\n const d=productGuide194;if(!d?.stockWizard300||d.quickMode198!=='in'||currentAccessUser().role!=='admin'||!['same','changed'].includes(choice))return;\n d.stockWizard300.priceChoice=choice;d.priceChoice214=choice;\n const steps=stockWizardSteps300(d,state.products[d.existing.productCode]);d.stockWizard300.step=steps[steps.indexOf('priceChoice')+1];renderStockWizard311();\n}\nfunction stockWizardHtml300(d,a,pack,bookingUnit,batches=[]){\n batches=withdrawalGroups335(d.existing.productCode,a);stockWizardInit300(d);const w=d.stockWizard300,steps=stockWizardSteps300(d,a);if(!steps.includes(w.step))w.step=steps[0];\n const incoming=d.quickMode198==='in',idx=steps.indexOf(w.step)+1,unit=bookingUnit||a.unit||'Stück',factor=pack?Number(packUsableQty(pack)):1;\n const head=guidedProgress300(recognizedText278(incoming?'receive':'withdraw'),idx,steps.length),back=idx>1?'stockWizardGo300(-1)':\"chooseRecognized198('')\";\n if(w.step==='qty')return `<div class=\"guidedCard300\" data-movement432=\"${incoming?'in':'out'}\">${head}${typeof stockPackChoice444==='function'?stockPackChoice444(d,a):''}<h3 id=\"guidedQtyLabel432\">${esc(recognizedText278('quantity',unit))}</h3><input aria-labelledby=\"guidedQtyLabel432\" id=\"guidedQty300\" class=\"guidedMainInput300\" type=\"number\" min=\"${pack||isWholeQuantityUnit(a.unit)?'1':'0'}\" step=\"${pack||isWholeQuantityUnit(a.unit)?'1':'any'}\" value=\"${esc(String(w.qty))}\" inputmode=\"decimal\">${pack?`<p class=\"muted\">${esc(recognizedText278('packFactor',unit,factor,a.unit||''))}</p>`:''}<p id=\"stockReceiptTotal444\" class=\"cartonSummary444\" aria-live=\"polite\"></p>${guidedActions300(back,'stockWizardGo300(1)')}</div>`;\n if(w.step==='priceChoice'){const price=Number(a.price)>0?moneyFmt(Number(a.price)*factor):recognizedText278('noPrice');return `<div class=\"guidedCard300\" data-movement432=\"${incoming?'in':'out'}\">${head}<h3>${esc(recognizedText278('samePrice'))}</h3><p>${esc(recognizedText278('currentPrice',price,unit))}</p><div class=\"guidedChoice300\"><button type=\"button\" class=\"primary\" onclick=\"stockWizardPriceChoice300('same')\">${esc(guidedText300('yes'))}</button><button type=\"button\" class=\"soft\" onclick=\"stockWizardPriceChoice300('changed')\">${esc(guidedText300('no'))}</button></div><button type=\"button\" class=\"guidedBackLink300\" onclick=\"${back}\">${esc(guidedText300('back'))}</button></div>`;}\n if(w.step==='priceValue')return `<div class=\"guidedCard300\" data-movement432=\"${incoming?'in':'out'}\">${head}<label>${esc(recognizedText278('newPrice',unit))}<input id=\"recognizedPrice214\" class=\"guidedMainInput300\" inputmode=\"decimal\" value=\"${esc(w.price||'')}\" placeholder=\"${esc(recognizedText278('pricePlaceholder'))}\"></label>${priceControls228('recognizedPrice214')}${guidedActions300(back,'stockWizardGo300(1)')}</div>`;\n if(w.step==='sameMhd')return sameMhdHtml422(d,head,back);\n if(w.step==='mhd'&&w.sameMhd422===false&&Number(w.qty)>1)return splitMhdHtml422(d,head,back);\n if(w.step==='mhd')return `<div class=\"guidedCard300\" data-movement432=\"${incoming?'in':'out'}\">${head}<label>${esc(recognizedText278((w.policy420||recognizedMhdPolicy256(a))==='optional'?'mhdOptional':'mhdRequired'))}<input id=\"guidedMhd300\" class=\"guidedMainInput300\" type=\"date\" value=\"${esc(w.mhd||(!w.mhdVisited?receiptMhd242(a,d.receiptMhd258||''):''))}\"></label>${guidedActions300(back,'stockWizardGo300(1)')}</div>`;\n if(w.step==='policy')return `<div class=\"guidedCard300\" data-movement432=\"${incoming?'in':'out'}\">${head}<h3>MHD bei neuen Lieferungen</h3><p>Vorhandene Chargen bleiben unverändert. Welche Regel soll künftig gelten?</p>${currentAccessUser().role==='admin'?`<div class=\"guidedChoice300\"><button type=\"button\" class=\"soft\" onclick=\"stockPolicy420('required')\">MHD erforderlich</button><button type=\"button\" class=\"soft\" onclick=\"stockPolicy420('optional')\">MHD optional</button><button type=\"button\" class=\"soft\" onclick=\"stockPolicy420('none')\">Kein MHD</button></div><small>Wird zusammen mit der Buchung gespeichert.</small>`:'<p>Bitte einen Admin die MHD-Regel ergänzen lassen. Deine Menge bleibt erhalten.</p>'}<button type=\"button\" class=\"soft\" onclick=\"${back}\">Zurück</button></div>`;\n if(w.step==='lot')return `<div class=\"guidedCard300\" data-movement432=\"out\">${head}${withdrawalHtml459(d,a,pack)}${guidedActions300(back,'stockWizardGo300(1)')}</div>`;\n const count=Number(w.qty)||0,qty=count*factor,newPrice=w.priceChoice==='changed'?Number(w.price):null,mhd=incoming?w.mhd||'':'';\n return `<div class=\"guidedCard300\" data-movement432=\"${incoming?'in':'out'}\">${head}<h3>${esc(recognizedText278('review'))}</h3><dl class=\"guidedReview300\"><div><dt>${esc(stockInfoText311('product'))}</dt><dd>${esc(a.name)}</dd></div><div><dt>${esc(stockInfoText311('quantity'))}</dt><dd>${incoming?'+':'−'} ${esc(formatQtySmart(qty,a.unit))}</dd></div>${incoming?`<div><dt>${esc(stockInfoText311('price'))}</dt><dd>${esc(newPrice>0?moneyFmt(newPrice)+' / '+unit:Number(a.price)>0?moneyFmt(Number(a.price)*factor)+' / '+unit:'–')}</dd></div>`:''}${incoming&&w.sameMhd422===false&&Number(w.qty)>1?receiptLotsReview422(w,a,unit,factor):mhd?`<div><dt>MHD</dt><dd>${bookingDateHtml342(mhd)}</dd></div>`:''}${!incoming&&Array.isArray(w.lots459)?withdrawalReview459(d,a):''}<div><dt>${esc(guidedText300('newStock'))}</dt><dd><b>${esc(formatQtySmart((Number(a.stock)||0)+(incoming?qty:-qty),a.unit))}</b></dd></div></dl><input id=\"recognizedQty198\" type=\"hidden\" value=\"${esc(String(count))}\">${w.priceChoice==='changed'?`<input id=\"recognizedPrice214\" type=\"hidden\" value=\"${esc(w.price)}\">`:''}${mhd?`<input id=\"recognizedMhd198\" type=\"hidden\" value=\"${esc(mhd)}\">`:''}${!incoming?`<input id=\"recognizedLot198\" type=\"hidden\" value=\"${esc(w.lot||'')}\">`:''}${guidedActions300(back,'saveRecognizedWizard300()',({de:'Jetzt buchen',en:'Book now',pl:'Zaksieguj teraz'}[recognizedLanguage278()]||'Jetzt buchen'))}</div>`;\n}\nfunction saveRecognizedWizard300(){\n const d=productGuide194;if(!d?.existing||!['in','out'].includes(d.quickMode198)||d.quickPending198||d.stockWizard300?.step!=='review')return;\n d.priceChoice214=d.stockWizard300.priceChoice||'';\n // Existing guarded transaction and final confirmation handle both operations.\n return confirmRecognized198(true);\n}\nconst recognizedBookingBefore300=recognizedBooking214;\nrecognizedBooking214=function(d,a,pack,bookingUnit,batches,mode){if(['in','out'].includes(mode))return stockWizardHtml300(d,a,pack,bookingUnit,batches);return recognizedBookingBefore300(d,a,pack,bookingUnit,batches,mode)};\nconst chooseRecognizedBefore300=chooseRecognized198;\nchooseRecognized198=function(mode){\n const d=productGuide194;\n if(d?.existing){\n  if(d.stockWizard300){captureStockWizard300(d);(d.stockWizards311??={})[d.quickMode198]=d.stockWizard300;}\n  if(['in','out'].includes(mode)){d.quickMode198=mode;d.stockWizard300=d.stockWizards311?.[mode]||{step:'qty',qty:'1',priceChoice:'',price:'',mhd:'',lot:''};d.priceChoice214=d.stockWizard300.priceChoice;renderStockWizard311();return;}\n  d.stockWizard300=null;\n }\n return chooseRecognizedBefore300(mode);\n};\n\n// ---------- New product: mobile guided article setup ----------\nlet guidedArticle300=null;\nfunction guidedArticleSteps300(){const unit=document.getElementById('aeUnit')?.value||'Stück',countLike=isCountLikeUnit(unit);const steps=['name'];if(!guidedArticle300?.barcodeKnown331)steps.push('barcode');steps.push('category','unit');if(countLike)steps.push('content');steps.push('group','mhd','storage','supplier','price','review');return steps}\nfunction startGuidedArticle300(fromLookup=false){if(window.innerWidth>820||editingArticleCode||productGuide194||document.getElementById('articleModal')?.style.display!=='flex')return;guidedArticle300={step:'name',fromLookup,barcodeKnown331:!!articleBarcodeVisible331()};document.getElementById('articleModal')?.classList.add('guidedArticleMode300');renderGuidedArticle300()}\nfunction endGuidedArticle300(){guidedArticle300=null;document.getElementById('articleModal')?.classList.remove('guidedArticleMode300');document.getElementById('guidedArticle300')?.remove()}\nfunction guidedArticleGo300(direction){if(!guidedArticle300)return;if(guidedArticle300.step==='group'&&direction>0&&!guidedArticle300.groupConfirmed){showFeedback('Gesamtvorrat auswählen','warn',3200,'Bitte einen passenden Gesamtvorrat ausdrücklich zuordnen oder „Eigener Bestand“ wählen.');return}if(direction>0&&guidedArticle300.step==='name'&&!document.getElementById('gaName300')?.value.trim()){showFeedback('Produktname fehlt','warn');return}if(direction>0&&guidedArticle300.step==='unit'&&!document.getElementById('gaUnit300')?.value){showFeedback(contentWords431('check'),'warn',5000,contentText436('unitRequired'));return}if(direction>0&&guidedArticle300.step==='content'&&typeof validateDrain433==='function'&&!validateDrain433())return;captureGuidedArticle300();if(direction>0&&guidedArticle300.step==='content'&&(!validateGuidedContent431(true)||(typeof validateCarton444==='function'&&!validateCarton444())))return;if(guidedArticle300.step==='barcode'&&direction>0&&!checkMainBarcodeLive()){if(typeof renderArticleBarcodeChoice427==='function'&&renderArticleBarcodeChoice427(true))return;showFeedback(stockText420('Barcode bereits vergeben','Barcode already assigned','Kod jest już przypisany'),'warn');return}const steps=guidedArticleSteps300(),idx=Math.max(0,steps.indexOf(guidedArticle300.step));guidedArticle300.step=steps[Math.max(0,Math.min(steps.length-1,idx+direction))];if(typeof clearArticleFeedback316==='function')clearArticleFeedback316();renderGuidedArticle300()}\nfunction captureGuidedArticle300(){if(!guidedArticle300)return;const step=guidedArticle300.step;\n if(step==='name'){const field=document.getElementById('gaName300');if(field)document.getElementById('aeName').value=field.value.trim()}\n if(step==='barcode'){const field=document.getElementById('gaBarcode331');if(field)setArticleBarcode331(field.value)}\n if(step==='category'){const v=document.getElementById('gaCategory433')?.value,target=document.getElementById('aeCategory');if(v&&target&&target.value!==v){target.value=v;articleCategoryChanged()}}\n if(step==='unit'){const v=document.getElementById('gaUnit300')?.value,target=document.getElementById('aeUnit');if(v)guidedArticle300.unitAcknowledged436=true;if(v&&target&&target.value!==v){target.value=v;target.dispatchEvent(new Event('change',{bubbles:true}))}}\n if(step==='content'){const q=document.getElementById('gaContentQty300')?.value||'',u=document.getElementById('gaContentUnit300')?.value||'';document.getElementById('aeContentQty').value=q;document.getElementById('aeContentUnit').value=u;captureGuidedCapacity431();if(typeof captureCarton444==='function')captureCarton444();if(typeof captureDrain433==='function')captureDrain433();updateEditorStockContent();updatePurchaseQuote('article')}\n if(step==='group')return;\n if(step==='storage'){const v=document.getElementById('gaStorage300')?.value;if(v)document.getElementById('aeSubgroup').value=v}\n if(step==='supplier'){const v=document.getElementById('gaSupplier300')?.value;if(v!==undefined)document.getElementById('aeSupplier').value=v}\n if(step==='price'){const v=document.getElementById('gaPrice300')?.value||'';document.getElementById('aePrice').value=v;updatePurchaseQuote()}\n}\n\nfunction guidedGroupNorm300(value){return String(value||'').toLocaleLowerCase('de').normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').replace(/[^a-z0-9]+/g,' ').trim()}\nfunction guidedGroupTokens300(value){return guidedGroupNorm300(value).split(/\\s+/).filter(x=>x.length>2&&!['und','mit','der','die','das','von','fur','fuer'].includes(x))}\nfunction guidedGroupScore300(id){const g=mainGroup(id);if(!g)return 0;const current=guidedGroupNorm300(document.getElementById('aeName')?.value||''),target=guidedGroupNorm300(g.name||'');if(!current||!target)return 0;const compact=x=>x.replace(/\\s+/g,'');if(compact(current)===compact(target))return 1;if(compact(current).includes(compact(target))||compact(target).includes(compact(current)))return .92;const a=new Set(guidedGroupTokens300(current)),b=new Set(guidedGroupTokens300(target));if(!a.size||!b.size)return 0;const common=[...a].filter(x=>b.has(x)).length;let score=common/Math.min(a.size,b.size);const cat=document.getElementById('aeCategory')?.value||'',members=mainMembers(id);if(cat&&members.some(([,x])=>(x.overviewGroup||x.cat||'')===cat))score+=.06;return Math.min(1,score)}\nfunction guidedGroupFactor300(id){if(!id)return{ok:true,factor:1,source:'own'};const g=mainGroup(id),unit=document.getElementById('aeUnit')?.value||'Stück';if(!g)return{ok:false};const direct=convertUnit(1,unit,g.unit||'');if(direct!==null&&direct>0)return{ok:true,factor:direct,source:'unit'};const qty=costNumber(document.getElementById('aeContentQty')?.value||''),contentUnit=document.getElementById('aeContentUnit')?.value||'';if(qty>0&&contentUnit){const content=convertUnit(qty,contentUnit,g.unit||'');if(content!==null&&content>0)return{ok:true,factor:content,source:'content'}}return{ok:false,factor:null,source:'missing'}}\nfunction guidedMainCandidates300(){return mainArticleIds().map(id=>({id,g:mainGroup(id),score:guidedGroupScore300(id),factor:guidedGroupFactor300(id)})).filter(x=>x.g&&mainMembers(x.id).length&&x.score>=.55).sort((a,b)=>b.score-a.score||String(a.g.name).localeCompare(String(b.g.name),'de')).slice(0,5)}\nfunction guidedGroupChoiceHtml300(item){const selected=document.getElementById('aeMainArticle')?.value===item.id&&guidedArticle300?.groupConfirmed;const unsafe=!item.factor.ok;return `<div class=\"guidedGroupCandidate300 ${selected?'selected':''} ${unsafe?'unsafe':''}\"><div><b>${esc(item.g.name)}</b><small>${esc(item.g.unit||'Stück')}${unsafe?' · Umrechnung erforderlich':''}</small></div><button type=\"button\" class=\"${selected?'soft':'primary'}\" ${unsafe?'disabled':''} onclick=\"guidedChooseMainGroup300('${escJs(item.id)}')\">${selected?'Ausgewählt':'Zuordnen'}</button></div>`}\nfunction guidedChooseMainGroup300(id){if(!guidedArticle300)return;const factor=guidedGroupFactor300(id);if(id&&!factor.ok){showFeedback('Gesamtbestand noch nicht möglich','warn',5200,'Die Einheiten können nicht sicher umgerechnet werden. Bitte Packungsinhalt bzw. Umrechnung zuerst bestätigen.');return}const target=document.getElementById('aeMainArticle');if(!target)return;target.value=id;target.dispatchEvent(new Event('change',{bubbles:true}));articleMainGroupChanged();if(id){const factorEl=document.getElementById('aeMainFactor');if(factorEl){factorEl.value=roundedInputValue(factor.factor);factorEl.dataset.confirmed='true'}}if(typeof confirmMainFamily429==='function')confirmMainFamily429(id);guidedArticle300.groupConfirmed=true;guidedArticle300.groupSearch=false;if(typeof clearArticleFeedback316==='function')clearArticleFeedback316();renderGuidedArticle300()}\nfunction guidedOpenGroupSearch300(){if(!guidedArticle300)return;guidedArticle300.groupSearch=true;renderGuidedArticle300();setTimeout(()=>document.getElementById('gaGroupSearch300')?.focus(),0)}\nfunction guidedGroupSearchResults300(query){const q=guidedGroupNorm300(query);if(q.length<2)return[];return mainArticleIds().map(id=>({id,g:mainGroup(id),factor:guidedGroupFactor300(id)})).filter(x=>x.g&&mainMembers(x.id).length&&guidedGroupNorm300(x.g.name).includes(q)).slice(0,12)}\nfunction guidedFilterMainGroups300(value){if(!guidedArticle300)return;guidedArticle300.groupQuery=value;const box=document.getElementById('gaGroupSearchResults300');if(box)box.innerHTML=guidedGroupSearchResults300(value).map(guidedGroupChoiceHtml300).join('')||'<p class=\"muted\">Kein passender Artikel gefunden.</p>'}\nfunction guidedGroupStepHtml300(back){const candidates=guidedMainCandidates300(),selected=document.getElementById('aeMainArticle')?.value||'';let html=`<h3>${esc(guidedText300('stockGroup'))}</h3><p class=\"muted\">${esc(guidedText300('stockGroupHint'))}</p>`;if(candidates.length)html+=`<div class=\"guidedGroupCandidates300\"><small class=\"guidedGroupLabel300\">Passende Vorschläge</small>${candidates.map(guidedGroupChoiceHtml300).join('')}</div>`;else html+='<p class=\"muted\">Kein fachlich plausibler Gesamtvorrat erkannt.</p>';html+=`<button type=\"button\" class=\"soft guidedOwnStock300 ${guidedArticle300.groupConfirmed&&!selected?'selected':''}\" onclick=\"guidedChooseMainGroup300('')\">Eigener Bestand – nicht zusammenfassen</button>`;if(!guidedArticle300.groupSearch)html+=`<button type=\"button\" class=\"guidedBackLink300\" onclick=\"guidedOpenGroupSearch300()\">Anderen Artikel suchen</button>`;else{const q=guidedArticle300.groupQuery||'';html+=`<div class=\"guidedGroupSearch300\"><label for=\"gaGroupSearch300\">Anderen Gesamtvorrat suchen</label><input id=\"gaGroupSearch300\" class=\"guidedMainInput300\" value=\"${esc(q)}\" placeholder=\"z. B. Chili con Carne\" oninput=\"guidedFilterMainGroups300(this.value)\"><div id=\"gaGroupSearchResults300\">${guidedGroupSearchResults300(q).map(guidedGroupChoiceHtml300).join('')}${q.length>=2&&!guidedGroupSearchResults300(q).length?'<p class=\"muted\">Kein passender Artikel gefunden.</p>':''}</div></div>`}html+=guidedActions300(back,'guidedArticleGo300(1)');return html}\nfunction guidedArticleMhd300(value){const policy=document.getElementById('aeMhdPolicy144'),box=document.getElementById('aeMhd');if(policy){policy.value=value?'required':'none';policy.dispatchEvent(new Event('change',{bubbles:true}))}if(box)box.checked=!!value;guidedArticle300.step='storage';renderGuidedArticle300()}\nfunction saveGuidedArticle300(){captureGuidedArticle300();const name=document.getElementById('aeName')?.value.trim();if(!name){guidedArticle300.step='name';renderGuidedArticle300();showFeedback('Produktname fehlt','warn');return}return saveArticleEditor()}\nfunction selectOptions300(el,includeBlank=false){if(!el)return'';return `${includeBlank?`<option value=\"\">${esc(guidedText300('noSupplier'))}</option>`:''}${[...el.options].map(o=>`<option value=\"${esc(o.value)}\" ${o.value===el.value?'selected':''}>${esc(o.textContent)}</option>`).join('')}`}\nfunction renderGuidedArticle300(){if(!guidedArticle300)return;const modal=document.getElementById('articleModal');if(!modal)return;let box=document.getElementById('guidedArticle300');if(!box){box=document.createElement('section');box.id='guidedArticle300';box.className='guidedArticle300';const card=modal.querySelector(':scope > .card')||modal.querySelector('.card');const header=card?.querySelector('.modalHeader');if(card)header?.insertAdjacentElement('afterend',box);else modal.appendChild(box)}\n const steps=guidedArticleSteps300();if(!steps.includes(guidedArticle300.step))guidedArticle300.step=steps[0];const step=guidedArticle300.step,idx=steps.indexOf(step)+1,total=steps.length,back=idx>1?'guidedArticleGo300(-1)':\"endGuidedArticle300()\";const unit=document.getElementById('aeUnit')?.value||'Stück',name=document.getElementById('aeName')?.value||'',barcode=document.getElementById('aeCode')?.value||'',head=guidedProgress300(guidedText300('newProduct'),idx,total),found=guidedArticle300.fromLookup&&idx===1?`<p class=\"guidedSourceNote300\">${esc(guidedText300('found'))}</p>`:'';\n let body='';\n if(step==='name')body=`${found}<h3>${esc(guidedText300('productName'))}</h3><input id=\"gaName300\" class=\"guidedMainInput300\" value=\"${esc(name)}\"><p class=\"muted\">Barcode ${esc(barcode||'–')}</p>${guidedActions300(back,'guidedArticleGo300(1)')}`;\n if(step==='barcode')body=`<h3>${esc(stockText420('Barcode hinzufügen (optional)','Add barcode (optional)','Dodaj kod kreskowy (opcjonalnie)'))}</h3><label for=\"gaBarcode331\">${esc(stockText420('Barcode','Barcode','Kod kreskowy'))}</label><input id=\"gaBarcode331\" class=\"guidedMainInput300\" autocomplete=\"off\" value=\"${esc(barcode)}\" oninput=\"setArticleBarcode331(this.value)\"><button type=\"button\" class=\"soft\" onclick=\"scanArticleBarcode331()\">${esc(stockText420('Barcode scannen','Scan barcode','Zeskanuj kod'))}</button><p class=\"muted\">${esc(stockText420('Ohne Barcode weiter möglich.','You can continue without a barcode.','Możesz kontynuować bez kodu.'))}</p>${guidedActions300(back,'guidedArticleGo300(1)')}`;\n if(step==='unit')body=`<h3>${esc(guidedText300('stockUnit'))}</h3><select id=\"gaUnit300\" class=\"guidedMainInput300\">${typeof stockUnitOptions436==='function'?stockUnitOptions436(document.getElementById('aeUnit')):selectOptions300(document.getElementById('aeUnit'))}</select><p class=\"muted\">${esc(typeof contentText436==='function'?contentText436('unitHint'):'')}</p>${guidedActions300(back,'guidedArticleGo300(1)')}`;\n if(step==='category')body=`<h3>${esc(stockText420('Zu welcher Kategorie gehört das Produkt?','Which category does this product belong to?','Do jakiej kategorii należy produkt?'))}</h3><label for=\"gaCategory433\">${esc(familyWords340('category'))}</label><select id=\"gaCategory433\" class=\"guidedMainInput300\">${selectOptions300(document.getElementById('aeCategory'))}</select>${guidedActions300(back,'guidedArticleGo300(1)')}`;\n if(step==='content')body=`<h3>${esc(typeof contentHeading436==='function'?contentHeading436(unit):guidedText300('content',unit))}</h3><label for=\"gaContentQty300\">${esc(contentWords431('content'))}</label><div class=\"guidedInline300\"><input id=\"gaContentQty300\" class=\"guidedMainInput300\" inputmode=\"decimal\" value=\"${esc(document.getElementById('aeContentQty')?.value||'')}\"><select id=\"gaContentUnit300\" class=\"guidedMainInput300\">${typeof contentUnitOptions436==='function'?contentUnitOptions436(document.getElementById('aeContentUnit')):selectOptions300(document.getElementById('aeContentUnit'))}</select></div><p class=\"muted\">${esc(typeof contentText436==='function'?contentText436('hint'):contentWords431('hint'))}</p>${typeof guidedCarton444==='function'?guidedCarton444(unit):''}${guidedCapacityHtml431()}${guidedActions300(back,'guidedArticleGo300(1)')}`;\n if(step==='group')body=guidedGroupStepHtml300(back);\n if(step==='mhd')body=`<h3>${esc(guidedText300('hasMhd'))}</h3><div class=\"guidedChoice300\"><button type=\"button\" class=\"soft\" onclick=\"guidedArticleMhd300(false)\">${esc(guidedText300('no'))}</button><button type=\"button\" class=\"primary\" onclick=\"guidedArticleMhd300(true)\">${esc(guidedText300('yes'))}</button></div><button type=\"button\" class=\"guidedBackLink300\" onclick=\"${back}\">${esc(guidedText300('back'))}</button>`;\n if(step==='storage')body=`<h3>${esc(guidedText300('storage'))}</h3><select id=\"gaStorage300\" class=\"guidedMainInput300\">${selectOptions300(document.getElementById('aeSubgroup'))}</select>${guidedActions300(back,'guidedArticleGo300(1)')}`;\n if(step==='supplier')body=`<h3>${esc(guidedText300('supplier'))}</h3><select id=\"gaSupplier300\" class=\"guidedMainInput300\">${selectOptions300(document.getElementById('aeSupplier'),true)}</select>${guidedActions300(back,'guidedArticleGo300(1)')}`;\n if(step==='price')body=`<h3>${esc(guidedText300('purchasePrice',unit))}</h3><input id=\"gaPrice300\" class=\"guidedMainInput300\" inputmode=\"decimal\" value=\"${esc(document.getElementById('aePrice')?.value||'')}\" placeholder=\"${esc(guidedText300('noPrice'))}\">${guidedActions300(back,'guidedArticleGo300(1)')}`;\n if(step==='review'){const mhd=document.getElementById('aeMhd')?.checked,supplier=document.getElementById('aeSupplier')?.selectedOptions?.[0]?.textContent||'–',storage=document.getElementById('aeSubgroup')?.selectedOptions?.[0]?.textContent||'–',price=document.getElementById('aePrice')?.value||'–',content=document.getElementById('aeContentQty')?.value||'',group=document.getElementById('aeMainArticle')?.selectedOptions?.[0]?.textContent||'Eigener Bestand – nicht zusammenfassen';body=`<h3>${esc(guidedText300('productReview'))}</h3><dl class=\"guidedReview300\"><div><dt>Produkt</dt><dd>${esc(name)}</dd></div><div><dt>Barcode</dt><dd>${esc(barcode||'intern')}</dd></div><div><dt>Bestand</dt><dd>${esc(unit)}</dd></div>${content?`<div><dt>Inhalt</dt><dd>${esc(content)} ${esc(document.getElementById('aeContentUnit')?.value||'')}</dd></div>`:''}${capacityReviewHtml431()}${typeof cartonSummary444==='function'&&cartonSummary444()?`<div><dt>Karton</dt><dd>${esc(cartonSummary444())}</dd></div>`:''}<div><dt>Gesamtvorrat</dt><dd>${esc(group)}</dd></div><div><dt>MHD</dt><dd>${mhd?esc(guidedText300('yes')):esc(guidedText300('no'))}</dd></div><div><dt>Lager</dt><dd>${esc(storage)}</dd></div><div><dt>Lieferant</dt><dd>${esc(supplier)}</dd></div><div><dt>EK</dt><dd>${esc(price==='–'?'–':price+' €')}</dd></div></dl>${guidedActions300(back,'saveGuidedArticle300()',guidedText300('save'))}`}\n box.innerHTML=`${head}${body}`;\n if(typeof enhanceGuided433==='function')enhanceGuided433(box,step);\n if(typeof renderArticleBarcodeChoice427==='function')renderArticleBarcodeChoice427();\n}\n\n// Once the existing barcode/photo flow has filled the underlying article form, continue with the homogeneous mobile wizard.\nconst applyGuideBefore300=applyGuide194;\napplyGuide194=function(...args){const lookupData=productGuide194?.lookup?.found?structuredClone(productGuide194.lookup):null;const wasNew=!editingArticleCode;const result=applyGuideBefore300(...args);if(lookupData){articleGuideSource194={source:lookupData.source||'Zapasio Barcode Lookup',url:lookupData.sourceUrl||'',barcode:lookupData.barcode||'',license:lookupData.license||'',sources:Array.isArray(lookupData.sources)?lookupData.sources:[],confirmedAt:new Date().toISOString()}}if(wasNew&&window.innerWidth<=820&&document.getElementById('articleModal')?.style.display==='flex'&&!productGuide194)startGuidedArticle300(true);return result};\n\n// On mobile, a clear external barcode hit pre-fills the product immediately; photos stay optional for unknown or conflicting products.\nconst lookupGuideBefore300=lookupGuideBarcode194;\nlookupGuideBarcode194=async function(...args){const d=productGuide194;await lookupGuideBefore300(...args);if(window.innerWidth<=820&&productGuide194===d&&d?.lookup?.found&&!d.existing&&!d.busy&&!d.lookupBusy&&validGtin179(d.barcode)){acceptGuideLookup241()}};\n\n// Manual \"new article\" on mobile uses the same guided product setup.\nfunction clearArticleFeedback316(){\n const modal=document.getElementById('articleModal');\n for(const message of modal?.querySelectorAll?.('.formFeedback231')||[])message.remove();\n}\nconst openArticleBefore300=openArticleEditor;\nopenArticleEditor=function(...args){const result=openArticleBefore300(...args);clearArticleFeedback316();if(args[1]!=='recognition420'&&!editingArticleCode&&window.innerWidth<=820&&!productGuide194&&document.getElementById('articleModal')?.style.display==='flex'&&!guidedArticle300)startGuidedArticle300(false);return result};\n\nconst closeArticleBefore300=closeArticleEditor;\ncloseArticleEditor=function(...args){clearArticleFeedback316();endGuidedArticle300();return closeArticleBefore300(...args)};\nconst backBefore300=window.zapasioHandleBack;\nwindow.zapasioHandleBack=function(){if(document.getElementById('articleInfoModal127')?.style.display==='flex'){closeArticleInfo127();return true}if(document.getElementById('appConfirm')?.classList.contains('show'))return backBefore300?.()||false;if(guidedArticle300){const steps=guidedArticleSteps300(),idx=steps.indexOf(guidedArticle300.step);if(idx>0)guidedArticleGo300(-1);else endGuidedArticle300();return true}const d=productGuide194;if(d?.stockWizard300&&['in','out'].includes(d.quickMode198)&&document.getElementById('articleModal')?.style.display==='flex'){const steps=stockWizardSteps300(d,state.products[d.existing.productCode]),idx=steps.indexOf(d.stockWizard300.step);if(idx>0)stockWizardGo300(-1);else chooseRecognized198('');return true}if(d?.existing&&document.getElementById('articleModal')?.style.display==='flex'){closeArticleEditor();return true}return backBefore300?.()||false};\n\n// Multi-source barcode result presentation. Provider credentials stay server-side in the Edge Function.\nconst guideSourceHtmlBefore300=guideSourceHtml194;\nguideSourceHtml194=function(data,canAccept=false){\n if(!Array.isArray(data?.sources))return guideSourceHtmlBefore300(data,canAccept);\n const sourceNames=data.sources.filter(x=>x.found).map(x=>x.source);const links=data.sources.filter(x=>x.found&&x.sourceUrl).map(x=>`<a href=\"${esc(x.sourceUrl)}\" target=\"_blank\" rel=\"noopener noreferrer\">${esc(x.source)}</a>`).join(' · ');\n return `<div class=\"guideLookup194 ${data.found?'found':'missing'}\" role=\"status\"><strong>${data.found?'Produktdaten gefunden':'Kein Datenbanktreffer'}</strong>${data.found?`<p class=\"guideLookupName194\">${esc(data.name||'Produkt ohne Namen')}${data.brand?' · '+esc(data.brand):''}${data.quantityText?' · '+esc(data.quantityText):''}</p>`:'<p>Angaben aus Foto oder Beleg ergänzen.</p>'}${data.found&&canAccept?'<button type=\"button\" class=\"primary\" onclick=\"acceptGuideLookup241()\">Produktdaten übernehmen</button>':''}<small>${sourceNames.length?'Gefunden über: '+sourceNames.map(esc).join(', '):'Keine externe Quelle mit Treffer'}${links?' · '+links:''}</small></div>`;\n};\n\n// Existing stock operations do not require permission to create/edit articles.\nfunction openStockWizardProduct311(code,packIndex=0){\n const a=state.products?.[code];if(!a)return false;\n if(!hasPermission('scan')&&!hasPermission('stockAdd')&&!hasPermission('stock'))return false;\n const modal=document.getElementById('articleModal');if(!modal)return false;\n if(!document.getElementById('labelButton183')&&typeof installLabel183==='function')installLabel183();\n const anchor=document.getElementById('labelButton183')||modal.querySelector('.modalHeader');if(!anchor)return false;\n endGuide194();endGuidedArticle300();clearArticleFeedback316();\n current=code;const pack=getPacks(a,code)[packIndex];\n productGuide194={step:0,photos:{},barcode:String(pack?.[2]||code),lookup:null,existing:{productCode:code,packIndex,pack},busy:false,lookupBusy:false,message:'',serial:0,lookupSerial:0,confirmed198:true};\n const box=document.createElement('section');box.id='guideBox194';box.className='guide194';anchor.insertAdjacentElement('afterend',box);\n modal.style.display='flex';renderGuide194();return true;\n}\nconst openModalBefore311=openModal;\nopenModal=function(kind){\n if(['in','out'].includes(kind)){\n  if(!guardPermission(kind==='in'?'stockAdd':'scan',recognizedText278(kind==='in'?'receive':'withdraw')))return false;\n  const a=state.products?.[current],packs=a?getPacks(a,current):[],index=lastScanPackCode?Math.max(0,packs.findIndex(pack=>String(pack?.[2])===String(lastScanPackCode))):0;\n  if(openStockWizardProduct311(current,index)){chooseRecognized198(kind);return;}\n }\n return openModalBefore311(kind);\n};\nfunction renderStockWizard311(){\n renderGuide194();if(typeof stockReceiptTotal444==='function')stockReceiptTotal444();document.getElementById('articleModal')?.querySelector('.card')?.scrollTo?.({top:0});\n}\n"},{"src":"barcode-routing306.js?v=311","code":"'use strict';\n// P0 barcode routing hardening. One final gate decides whether a scanned code\n// opens an existing article or enters new-product recognition.\nfunction normalizeBarcode306(value){\n let raw=String(value||'').trim().toUpperCase();\n // Scanner symbology identifiers are metadata, not part of the printed GTIN.\n raw=raw.replace(/^\\](?:C[01]|E[04])/,'').replace(/^\\x1d/,'');\n const gs1=raw.match(/^(?:\\(01\\)|01)(\\d{14})(?:$|\\x1d)/);\n if(gs1&&validGtin179(gs1[1]))raw=gs1[1];\n return raw.replace(/[\\s-]/g,'').trim();\n}\nfunction resolveBarcode306(value){\n const code=normalizeBarcode306(value);if(!code)return null;\n const hit=typeof localBarcode194==='function'?localBarcode194(code):(typeof findScanCode==='function'?findScanCode(code):null);\n if(!hit||!state.products?.[hit.productCode])return null;\n const packIndex=Number.isInteger(hit.packIndex)?hit.packIndex:Math.max(0,getPacks(state.products[hit.productCode],hit.productCode).findIndex(pack=>pack===hit.pack||normalizeBarcode306(pack?.[2])===code));\n return {code,productCode:hit.productCode,packIndex:Math.max(0,packIndex),pack:hit.pack||getPacks(state.products[hit.productCode],hit.productCode)[Math.max(0,packIndex)]};\n}\nfunction openResolvedBarcode306(value){\n const hit=resolveBarcode306(value);if(!hit)return false;\n if(typeof endGuidedArticle300==='function')endGuidedArticle300();\n if(typeof canCreateArticles==='function'&&!canCreateArticles()){\n  if(typeof mobileShow==='function')mobileShow('search');\n  mobileSelectProduct(hit.productCode,hit.pack);\n  return true;\n }\n openRecognizedProduct200(hit.productCode,hit.packIndex);\n const d=productGuide194;\n if(d?.existing?.productCode===hit.productCode){d.barcode=hit.code;d.existing={productCode:hit.productCode,packIndex:hit.packIndex,pack:hit.pack};d.confirmed198=true;d.lookup=null;d.lookupBusy=false;renderGuide194()}\n return true;\n}\nasync function routeBarcode306(value){\n const code=normalizeBarcode306(value);if(!code)return false;\n if(openResolvedBarcode306(code))return true;\n if(!validGtin179(code))return false;\n if(!productGuide194){startProductLookup200()}\n const d=productGuide194;if(!d)return false;\n d.barcode=code;d.existing=null;d.lookup=null;d.message='Barcode wird geprüft …';\n renderGuide194();\n await lookupGuideBarcode194();\n return true;\n}\n\n// Final local gate in front of every layered lookup wrapper. A known barcode\n// never reaches the external/new-product path, even if an older handler fires.\nconst lookupGuideBefore306=lookupGuideBarcode194;\nlookupGuideBarcode194=async function(...args){\n const d=productGuide194;if(!d)return;\n const field=document.getElementById('guideBarcode194');\n const code=normalizeBarcode306(field?.value||d.barcode);\n if(code){d.barcode=code;const hit=resolveBarcode306(code);if(hit){d.lookupSerial++;d.lookupBusy=false;d.lookup=null;d.existing={productCode:hit.productCode,packIndex:hit.packIndex,pack:hit.pack};d.confirmed198=true;d.message='Barcode im eigenen Artikelstamm gefunden. Kein neuer Artikel nötig.';renderGuide194();return}}\n return lookupGuideBefore306(...args);\n};\n\n// The mobile scan button must route the decoded value itself instead of\n// passing it through generic text search / new-article fallback handlers.\nscanBarcode215=function(){\n if(!guardPermission('scan','Barcode scannen'))return;\n let input=document.getElementById('searchBarcodeFile306');\n if(!input){input=document.createElement('input');input.id='searchBarcodeFile306';input.type='file';input.accept='image/*';input.setAttribute('capture','environment');input.hidden=true;document.body.appendChild(input);\n  input.onchange=async()=>{const file=input.files?.[0];input.value='';if(!file)return;try{const code=normalizeBarcode306(await zReadBarcode166(file));if(!code){showFeedback('Barcode nicht lesbar','warn',4000,'Bitte erneut fotografieren oder die Nummer eingeben.');return}await routeBarcode306(code)}catch{showFeedback('Barcode nicht lesbar','warn',4000,'Bitte erneut fotografieren oder die Nummer eingeben.')}};\n }\n input.click();\n};\n\n// Keyboard / hardware scanner and manually entered GTINs use the same route.\nconst mobileSearchSubmitBefore306=mobileSearchSubmit;\nmobileSearchSubmit=function(){\n const field=document.getElementById('mobileArticleSearch'),raw=field?.value||'',code=normalizeBarcode306(raw);\n const barcodeLike=/^\\d{8,14}$/.test(code);\n if(barcodeLike){if(field)field.value='';void routeBarcode306(code);return}\n return mobileSearchSubmitBefore306();\n};\n\n// Re-check immediately before new-product data are applied. This closes races\n// where workspace/state finishes loading while a barcode lookup is open.\nconst applyGuideBefore306=applyGuide194;\napplyGuide194=function(...args){\n const code=normalizeBarcode306(productGuide194?.barcode||articleGuideBookingBarcode194||'');\n if(code&&openResolvedBarcode306(code))return;\n return applyGuideBefore306(...args);\n};\n\n// In the 9-step new-product flow the primary article code can deliberately be\n// empty when the barcode belongs to a purchase pack. Keep the scanned code\n// visible instead of showing \"Barcode –\".\nconst renderGuidedArticleBefore306=renderGuidedArticle300;\nrenderGuidedArticle300=function(...args){\n const result=renderGuidedArticleBefore306(...args),box=document.getElementById('guidedArticle300');\n if(!box||!guidedArticle300)return result;\n const visible=normalizeBarcode306(document.getElementById('aeCode')?.value||articleGuideBookingBarcode194||document.querySelector('#aePacks .aePackBarcode')?.value||'');\n if(visible&&guidedArticle300.step==='name'){\n  const note=[...box.querySelectorAll('p.muted')].find(p=>/^Barcode\\s/.test((p.textContent||'').trim()));\n  if(note)note.textContent='Barcode '+visible;\n }\n return result;\n};\n"},{"src":"p0-01-main-stock301.js?v=311","code":"'use strict';\n// P0-01: gleiche fachliche Artikel werden in der Bestandsuebersicht ueber den\n// vorhandenen Hauptartikel/Gesamtvorrat genau einmal gezeigt. Varianten, Preise,\n// Barcodes, Lagerorte und MHD-Chargen bleiben getrennt. Keine automatische\n// Zusammenfuehrung allein aufgrund eines aehnlichen Namens.\n\n\nfunction stockFactorSafe301(code,a,summary){\n const target=summary?.unit||'',source=a?.unit||'';\n if(typeof convertUnit==='function'){const direct=convertUnit(1,source,target);if(direct!==null&&direct>0)return direct}\n if(typeof articlePhysicalBridge==='function'){const bridge=articlePhysicalBridge(code,target);if(bridge&&!bridge.ambiguous&&Number(bridge.size)>0)return Number(bridge.size)}\n if(a?.mainFactorConfirmed===true&&Number(a.mainFactor)>0)return Number(a.mainFactor);\n return null;\n}\nfunction stockSafeSummary301(summary){\n if(!summary)return null;let total=0;const unsafe=[];\n for(const [code,a] of summary.members||[]){const factor=stockFactorSafe301(code,a,summary);if(!(factor>0)){unsafe.push({code,a});continue}total+=(Number(a.stock)||0)*factor}\n return {...summary,total:unsafe.length?null:total,totalSafe:unsafe.length===0,unsafeMembers:unsafe};\n}\nfunction stockPoolKey301(code,article){\n const summary=article?stockSafeSummary301(mainSummaryForProduct(code)):null;\n return summary&&summary.members?.length>1?'main:'+summary.id:'product:'+code;\n}\nfunction stockPoolLocation301(members){\n const values=[...new Set(members.map(([,a])=>String(a.storage||'Allgemein')))];\n return values.length===1?values[0]:'Mehrere Lagerorte';\n}\nfunction stockPoolCategory301(members){\n const values=[...new Set(members.map(([,a])=>String(a.overviewGroup||a.cat||'Sonstiges')))];\n return values[0]||'Sonstiges';\n}\nfunction stockPools301(){\n const pools=[],seen=new Set();\n for(const [code,a] of Object.entries(state.products||{})){\n  const summary=stockSafeSummary301(mainSummaryForProduct(code)),grouped=!!(summary&&summary.members?.length>1),key=grouped?'main:'+summary.id:'product:'+code;\n  if(seen.has(key))continue;seen.add(key);\n  const members=grouped?summary.members:[[code,a]],category=stockPoolCategory301(members),storage=stockPoolLocation301(members);\n  pools.push({key,grouped,summary:grouped?summary:null,members,category,storage,code:grouped?'':code,article:grouped?null:a});\n }\n return pools;\n}\nfunction stockPoolLow301(pool){\n if(pool.grouped)return pool.summary.totalSafe&&pool.summary.min>0&&pool.summary.total<=pool.summary.min;\n const a=pool.article;return Number(a.min)>0&&Number(a.stock)<=Number(a.min);\n}\nfunction stockPoolSearchText301(pool){\n const bits=[];\n if(pool.grouped)bits.push(pool.summary.name,pool.summary.unit,pool.summary.id);\n for(const [code,a] of pool.members){bits.push(code,a.name,a.supplier,a.storage,a.overviewGroup,a.cat,a.unit);for(const p of getPacks(a,code)||[])bits.push(p?.[0],p?.[2])}\n return bits.filter(Boolean).join(' ').toLocaleLowerCase('de');\n}\nfunction stockPoolsForSearch301(query){\n const q=String(query||'').trim().toLocaleLowerCase('de');if(!q)return[];\n const direct=new Set((mobileProductSearch(query,Object.keys(state.products||{}).length)||[]).map(([code])=>code));\n return stockPools301().filter(pool=>pool.members.some(([code])=>direct.has(code))||stockPoolSearchText301(pool).includes(q));\n}\nfunction stockBatchLines301(code,a){\n const rows=(state.batches?.[code]||[]).filter(b=>Number(b.qty)>0);\n if(!rows.length)return'';\n return `<div class=\"stockLots301\">${rows.map(b=>`<span><b>${esc(formatQtySmart(Number(b.qty)||0,unitText(a)))}</b>${b.mhd?` · MHD ${esc(formatDate(b.mhd))}`:''}</span>`).join('')}</div>`;\n}\nfunction openExistingStock301(code){\n if(!state.products?.[code])return;\n if(typeof window!=='undefined'&&window.innerWidth<=820&&typeof mobileShow==='function'){\n  mobileShow('search');\n  // Existing stock cards must bypass menu215's recognition/new-product wrapper.\n  // selectMobileBefore215 is the captured existing-product selector and keeps\n  // the normal mobile detail state for an already stored product.\n  if(typeof selectMobileBefore215==='function'){selectMobileBefore215(code);return}\n  if(typeof mobileSelectProduct==='function'){mobileSelectProduct(code);return}\n }\n current=code;showTab('lager');render();\n}\nfunction openStockMovement301(code,kind){\n if(!state.products?.[code]||!['in','out'].includes(kind))return;\n if(!guardPermission(kind==='in'?'stockAdd':'scan','Lagerbuchungen'))return;\n openExistingStock301(code);\n current=code;\n openModal(kind);\n}\nfunction stockMovementText301(key){\n const lang=typeof business130==='function'?business130()?.language:'de';\n const text={de:{in:'Einlagerung',out:'Entnahme'},en:{in:'Receive stock',out:'Withdraw stock'},pl:{in:'Przyjęcie',out:'Wydanie'}};\n return (text[lang]||text.de)[key];\n}\nfunction stockMovementActions301(code,a){\n const permitted=permission=>typeof hasPermission==='function'&&hasPermission(permission);\n return `<div class=\"stockMovementActions301\" role=\"group\" aria-label=\"${esc(a.name)}\"><button type=\"button\" class=\"primary\" ${permitted('stockAdd')?'':'disabled'} onclick=\"openStockMovement301('${escJs(code)}','in')\"><span aria-hidden=\"true\">+</span> ${esc(stockMovementText301('in'))}</button><button type=\"button\" class=\"soft\" ${permitted('scan')?'':'disabled'} onclick=\"openStockMovement301('${escJs(code)}','out')\"><span aria-hidden=\"true\">−</span> ${esc(stockMovementText301('out'))}</button></div>`;\n}\nfunction stockVariantHtml301(code,a){\n const barcode=getPacks(a,code)?.[0]?.[2]||code,nm=nextMhd(code);\n return `<div class=\"stockVariantRow301\"><button type=\"button\" class=\"stockVariant301\" onclick=\"openExistingStock301('${escJs(code)}')\"><span><b>${esc(a.name)}</b><small>${esc(a.supplier||'Kein Lieferant')}${a.storage?' · '+esc(a.storage):''}</small><small>Barcode ${esc(barcode)}${nm?' · nächstes MHD '+esc(formatDate(nm.mhd)):''}</small>${stockBatchLines301(code,a)}</span><strong>${esc(formatQtySmart(Number(a.stock)||0,unitText(a)))}</strong></button>${stockMovementActions301(code,a)}</div>`;\n}\nfunction stockPoolHtml301(pool){\n const low=stockPoolLow301(pool);\n if(!pool.grouped){const [code,a]=pool.members[0];return `<div class=\"stockSingle301 ${low?'stockLow':''}\">${stockVariantHtml301(code,a)}</div>`}\n const m=pool.summary,earliest=pool.members.map(([code])=>nextMhd(code)).filter(Boolean).sort((a,b)=>String(a.mhd).localeCompare(String(b.mhd)))[0];\n const totalHtml=m.totalSafe?`${esc(formatQtySmart(m.total,m.unit))}<small>gesamt</small>`:`<span class=\"stockTotalOpen301\">Gesamt offen</span><small>Umrechnung prüfen</small>`;\n return `<details class=\"stockMain301 ${low?'stockLow':''} ${m.totalSafe?'':'stockTotalUnsafe301'}\"><summary><span class=\"stockMainTitle301\"><i class=\"stockMainToggle301\" aria-hidden=\"true\"></i><span><strong>${esc(m.name)}</strong><small>${m.members.length} Varianten${earliest?' · nächstes MHD '+esc(formatDate(earliest.mhd)):''}</small></span></span><span class=\"stockQty\">${totalHtml}</span></summary>${m.totalSafe?'':`<p class=\"stockUnsafeHint301\">Kein Gesamtbestand berechnet: Für mindestens eine Variante fehlt eine bestätigte Umrechnung oder Packungsmenge.</p>`}<div class=\"stockMainBody301\">${pool.members.map(([code,a])=>stockVariantHtml301(code,a)).join('')}</div></details>`;\n}\n\nconst renderStockSearchBefore301=renderStockSearch;\nrenderStockSearch=function(){\n const input=document.getElementById('stockProductSearch'),box=document.getElementById('stockSearchResults');if(!input||!box)return renderStockSearchBefore301?.();\n const query=input.value.trim(),searching=Boolean(query);\n for(const id of ['stockQuickTitle','stockQuickAccess','stockWarnings','stockOverview']){const node=document.getElementById(id);if(node)node.style.display=searching?'none':''}\n if(!searching){box.innerHTML='';return}\n const pools=stockPoolsForSearch301(query);\n box.innerHTML=`<p class='muted'>${pools.length} ${pools.length===1?'Bestand':'Bestände'} gefunden</p>${pools.length?`<div class='stockList stockPools301'>${pools.map(stockPoolHtml301).join('')}</div>`:'<p class=\"muted\">Kein passender Bestand gefunden. Suchbegriff oder Barcode prüfen.</p>'}`;\n};\n\nconst renderStockPanelsBefore301=renderStockPanels248;\nrenderStockPanels248=function(){\n const pools=stockPools301(),groups={};\n for(const pool of pools){const g=pool.category||'Sonstiges',loc=pool.storage||'Allgemein';if(!groups[g])groups[g]={};if(!groups[g][loc])groups[g][loc]=[];groups[g][loc].push(pool)}\n let so='';const groupOrder=['Lebensmittel','Verbrauchsartikel'],orderedGroups=[...groupOrder,...Object.keys(groups).filter(k=>!groupOrder.includes(k))].filter(g=>groups[g]);\n const quick=document.getElementById('stockQuickAccess');if(quick)quick.innerHTML=orderedGroups.map(g=>{const items=Object.values(groups[g]).flat(),attention=items.filter(stockPoolLow301).length;return`<button onclick=\"openStockGroup('${escJs(g)}')\"><b>${esc(g)}</b><small>${items.length} ${items.length===1?'Bestand':'Bestände'}${attention?' · '+attention+' prüfen':''}</small></button>`}).join('');\n for(const g of orderedGroups){so+=`<details class='stockFold' id='${stockGroupId(g)}' open><summary><span>${esc(g)}</span><span class='foldChevron' aria-hidden='true'>▸</span></summary><div class='stockFoldBody'>`;for(const [loc,items] of Object.entries(groups[g])){so+=`<details class='stockSubFold' open><summary><span>${esc(loc)}</span><span class='foldChevron' aria-hidden='true'>▸</span></summary><div class='stockSubBody'><div class='stockList stockPools301'>${items.map(stockPoolHtml301).join('')}</div></div></details>`}so+='</div></details>'}\n const overview=document.getElementById('stockOverview');if(overview)overview.innerHTML=so||'<span class=\"muted\">Noch keine Artikel vorhanden.</span>';\n renderStockValueSummary();\n const warningGroups=groupedWarnings();let wn='';if(warningGroups.length){const urgent=warningGroups.some(w=>w.level==='danger');wn=`<div class='stockNotice ${urgent?'dangerNotice':'warn'}'><div class='noticeTitle'>${urgent?'Handlungsbedarf':'Hinweise zum Bestand'} · ${warningGroups.length} Artikel</div><div class='warningGroupList'>${dashboardWarningSummary(warningGroups,warningOpenKinds263('stockWarnings'))}</div></div>`}else wn=`<div class='stockNotice'><div class='noticeTitle'>Alles im grünen Bereich</div><div class='noticeLine muted'>Aktuell keine MHD- oder Mindestbestandswarnung.</div></div>`;\n const warnings=document.getElementById('stockWarnings');if(warnings)warnings.innerHTML=wn;renderStockSearch();\n};\n"},{"src":"mobile-workspace-rohentwurf01.js?v=397","code":"'use strict';\n// Rohentwurf 01: mobile entry points only. Existing handlers retain all permissions and booking rules.\nconst MOBILE_WORK01_TEXT={\n de:{events:'Einsätze',eventhelp:'Tagesgeschäft und Catering',tasks:'Aufgaben',title:'Arbeiten',intro:'Was möchtest du erledigen?',scan:'Barcode scannen',manual:'Produktname eingeben',create:'Neues Produkt anlegen',movement:'Einlagerung und Entnahme',inventory:'Schnelle Inventur',shopping:'Einkaufsliste',stock:'Bestand und MHD',recipes:'Rezepte und Mengen',calc:'Catering-Schnellkalkulation',photo:'Produktfotos',more:'Mehr',settings:'Einstellungen',help:'Hilfe'},\n en:{events:'Events',eventhelp:'Daily sales and catering',tasks:'Tasks',title:'Work',intro:'What would you like to do?',scan:'Scan barcode',manual:'Enter product name',create:'Create product',movement:'Receive and withdraw',inventory:'Quick inventory',shopping:'Shopping list',stock:'Stock and best-before dates',recipes:'Recipes and quantities',calc:'Quick catering calculation',photo:'Product photos',more:'More',settings:'Settings',help:'Help'},\n pl:{events:'Wydarzenia',eventhelp:'Sprzedaz dzienna i catering',tasks:'Zadania',title:'Praca',intro:'Co chcesz zrobić?',scan:'Skanuj kod kreskowy',manual:'Wpisz nazwę produktu',create:'Dodaj produkt',movement:'Przyjęcie i wydanie',inventory:'Szybka inwentaryzacja',shopping:'Lista zakupów',stock:'Stan i terminy ważności',recipes:'Przepisy i ilości',calc:'Szybka kalkulacja cateringu',photo:'Zdjęcia produktów',more:'Więcej',settings:'Ustawienia',help:'Pomoc'}\n};\nfunction mobileWorkText01(key){const language=business130()?.language||'de';return (MOBILE_WORK01_TEXT[language]||MOBILE_WORK01_TEXT.de)[key]||key}\nfunction mobileWorkActions01(){return [\n ['scan','scan'],['manual','scan'],['create','articles'],['movement','scan'],['inventory','inventory'],['tasks','preparation'],['shopping','shopping'],['stock','stock'],['recipes','recipes'],['calc','calc'],['photo','scan']\n]}\nfunction mobileWorkAllowed01(key,permission){return key==='tasks'?['preparation','load','setup','onsite'].some(p=>hasPermission(p)):key==='inventory'?hasPermission('inventory')||hasPermission('scan'):key==='movement'?hasPermission('scan')||hasPermission('stockAdd'):hasPermission(permission)}\nfunction openMobileWork01(key){\n const action=mobileWorkActions01().find(([name])=>name===key);if(!action||!mobileWorkAllowed01(...action))return;\n if(key==='scan'){mobileShow('search');scanBarcode215();return}\n if(key==='manual'){mobileShow('search');document.getElementById('mobileArticleSearch')?.focus();return}\n if(key==='create'){openMobileArticleCreate118();return}\n if(key==='movement'){mobileShow('search');return}\n if(key==='inventory'){openInventory185();return}\n if(key==='tasks'){mobileShow('tasks');return}\n if(key==='shopping'){openPurchasingOverview('list');return}\n if(key==='stock'){mobileOpenFullTab('stock');return}\n if(key==='recipes'){mobileOpenFullTab('recipes');return}\n if(key==='calc'){document.body.classList.add('mobile-detail-mode');openCalculations143('quick');return}\n if(key==='photo'){mobileShow('search');startPhoto144()}\n}\nfunction mobileWorkIcon01(key){\n const paths={tasks:'M9 6h11M9 12h11M9 18h11M3 6l1 1 2-2M3 12l1 1 2-2M3 18l1 1 2-2',scan:'M4 8V4h4M16 4h4v4M20 16v4h-4M8 20H4v-4M8 8v8M11 8v8M14 8v8M17 8v8',manual:'M10 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14M15 15l6 6',create:'M12 4v16M4 12h16',movement:'M4 8h15M15 4l4 4-4 4M20 16H5M9 12l-4 4 4 4',inventory:'M9 6h11M9 12h11M9 18h11M3 6l1 1 2-2M3 12l1 1 2-2M3 18l1 1 2-2',shopping:'M3 4h2l3 12h11l2-8H6M9 20h.01M18 20h.01',stock:'M3 7l9-4 9 4v10l-9 4-9-4V7M3 7l9 4 9-4M12 11v10',recipes:'M4 4h6l2 2 2-2h6v15h-6l-2 2-2-2H4V4M12 6v15',calc:'M6 3h12v18H6V3M9 7h6M9 11h1M14 11h1M9 15h1M14 15h1M9 18h1M14 18h1',photo:'M3 7h4l2-3h6l2 3h4v13H3V7M12 9a4 4 0 1 0 0 8 4 4 0 0 0 0-8'};\n return `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"${paths[key]||paths.stock}\"/></svg>`;\n}\nfunction mobileWorkHomeHtml01(){return `<section class=\"mobileWorkHeader01\"><h1>${esc(mobileWorkText01('title'))}</h1><p>${esc(mobileWorkText01('intro'))}</p></section><div class=\"mobileWorkGrid01\">${mobileWorkActions01().map(([key,permission])=>`<button type=\"button\" data-work-action=\"${key}\" class=\"${key==='scan'?'primary':'soft'}\" ${mobileWorkAllowed01(key,permission)?'':'disabled'} onclick=\"openMobileWork01('${key}')\"><span class=\"mobileWorkIcon01\">${mobileWorkIcon01(key)}</span><span>${esc(mobileWorkText01(key))}</span></button>`).join('')}</div>`}\nconst renderMobileTodayBeforeWork01=renderMobileToday;\nrenderMobileToday=function(...args){if(window.innerWidth>820)return renderMobileTodayBeforeWork01(...args);const host=document.getElementById('mobileTodayContent');if(host)host.innerHTML=mobileWorkHomeHtml01()};\nfunction configureMobileWorkNav01(){\n const methods=document.querySelector('#searchMethods215 .searchMethodsGrid215');\n if(methods){\n  let manual=methods.querySelector('[data-manual-work01]');\n  if(!manual){manual=document.createElement('button');manual.type='button';manual.dataset.manualWork01='true';manual.setAttribute('onclick',\"openMobileWork01('manual')\");methods.appendChild(manual);}\n  manual.textContent=mobileWorkText01('manual');manual.disabled=!hasPermission('scan');\n }\n\n const inventory=document.querySelector('.mobileBottomNav [data-mobiletab=\"tasks\"]');\n if(inventory){inventory.dataset.mobiletab='inventory';inventory.setAttribute('onclick','openInventory185()');}\n const button=document.querySelector('.mobileBottomNav [data-mobiletab=\"inventory\"]');if(button){const label=button.querySelector('b');if(label)label.textContent=mobileWorkText01('inventory');button.disabled=!mobileWorkAllowed01('inventory','inventory')}\n const more=document.querySelector('#mobileScreenMore .mobileMoreGrid');if(!more)return;\n let taskEntry=more.querySelector('[data-work-tasks354]');\n if(!taskEntry){taskEntry=document.createElement('button');taskEntry.type='button';taskEntry.dataset.workTasks354='true';taskEntry.setAttribute('onclick',\"openMobileWork01('tasks')\");more.prepend(taskEntry)}\n taskEntry.textContent=mobileWorkText01('tasks');taskEntry.disabled=!mobileWorkAllowed01('tasks','preparation');\n let events=more.querySelector('[data-events397]');\n if(!events){events=document.createElement('button');events.type='button';events.dataset.events397='true';events.setAttribute('onclick',\"openEventChoice367('overview')\");more.prepend(events)}\n events.innerHTML='<span aria-hidden=\"true\">'+mobileWorkIcon01('tasks')+'</span><b>'+mobileWorkText01('events')+'</b><small>'+mobileWorkText01('eventhelp')+'</small>';\n // Preserve existing nodes, current-user selector, profile/settings and help. Hide management shortcuts only.\n for(const button of more.querySelectorAll(':scope > button')){\n  const action=button.getAttribute('onclick')||'';\n  button.hidden=!['openInventory185()','openPurchasingOverview()',\"mobileOpenFullTab('stock')\",\"mobileOpenFullTab('recipes')\",\"mobileOpenFullTab('settings')\",\"mobileOpenFullTab('help')\",'openNotes123()',\"openMobileWork01('tasks')\",\"openEventChoice367('overview')\"].includes(action)||action===\"openEventChoice367('overview')\"&&!hasPermission('planning');\n }\n}\nconst applyLanguageBeforeWork01=applyLanguage130;\napplyLanguage130=function(...args){const result=applyLanguageBeforeWork01(...args);configureMobileWorkNav01();return result};\nconst renderBeforeWork01=render;\nrender=function(...args){const result=renderBeforeWork01(...args);configureMobileWorkNav01();return result};\nconfigureMobileWorkNav01();\n"},{"src":"mobile-search-design317.js?v=1","code":"'use strict';\n// Collapse optional search controls while typing; keep the actual search and result handlers.\nfunction setSearchFocus317(active){document.body.classList.toggle('searchInputActive317',!!active&&window.innerWidth<=820)}\ndocument.addEventListener('focusin',event=>{setSearchFocus317(event.target?.id==='mobileArticleSearch')});\ndocument.addEventListener('focusout',event=>{if(event.target?.id==='mobileArticleSearch')setTimeout(()=>setSearchFocus317(document.activeElement?.id==='mobileArticleSearch'),0)});\nfunction finishSearchTyping317(){document.getElementById('mobileArticleSearch')?.blur();setSearchFocus317(false)}\nconst selectBefore317=mobileSelectProduct;\nmobileSelectProduct=function(...args){finishSearchTyping317();return selectBefore317(...args)};\nconst submitBefore317=mobileSearchSubmit;\nmobileSearchSubmit=function(...args){finishSearchTyping317();return submitBefore317(...args)};\n"},{"src":"mobile-search-reference321.js?v=1","code":"'use strict';\n// Decorate existing controls; preserve their barcode/photo/navigation handlers.\nfunction applySearchReference321(){\n const language=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';\n const labels={de:{barcode:'Barcode scannen',photo:'Fotosuche',name:'Produktname oder Barcode'},en:{barcode:'Scan barcode',photo:'Photo search',name:'Product name or barcode'},pl:{barcode:'Skanuj kod kreskowy',photo:'Wyszukiwanie zdjęciem',name:'Nazwa produktu lub kod kreskowy'}}[language]||{barcode:'Barcode scannen',photo:'Fotosuche',name:'Produktname oder Barcode'};\n for(const [kind,handler,paths] of [['barcode','scanBarcode215','M4 8V4h4M16 4h4v4M20 16v4h-4M8 20H4v-4M8 7v10M11 7v10M14 7v10M17 7v10'],['photo','startPhoto144','M4 7h4l2-3h4l2 3h4v13H4z M16 13a4 4 0 1 1-8 0 4 4 0 0 1 8 0']]){\n  const button=document.querySelector(`#searchMethods215 button[onclick*=\"${handler}\"]`);if(!button)continue;\n  button.setAttribute('data-search-reference321',kind);button.setAttribute('aria-label',labels[kind]);button.setAttribute('title',labels[kind]);\n  button.innerHTML=`<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><path d=\"${paths}\"/></svg>`;\n }\n const field=document.getElementById('mobileArticleSearch');if(field)field.placeholder=labels.name;\n}\nconst searchMenuBefore321=setupSearchMenu215;\nsetupSearchMenu215=function(...args){const result=searchMenuBefore321(...args);applySearchReference321();return result};\napplySearchReference321();\n// Make room for results while typing, then dismiss the keyboard before selection.\nfunction setSearchFocus321(active){document.body.classList.toggle('searchInputActive317',!!active&&window.innerWidth<=820)}\ndocument.addEventListener('focusin',event=>setSearchFocus321(event.target?.id==='mobileArticleSearch'));\ndocument.addEventListener('focusout',event=>{if(event.target?.id==='mobileArticleSearch')setTimeout(()=>setSearchFocus321(document.activeElement?.id==='mobileArticleSearch'),0)});\nfunction finishSearchTyping321(){document.getElementById('mobileArticleSearch')?.blur();setSearchFocus321(false)}\nconst selectBefore321=mobileSelectProduct;mobileSelectProduct=function(...args){finishSearchTyping321();return selectBefore321(...args)};\nconst submitBefore321=mobileSearchSubmit;mobileSearchSubmit=function(...args){finishSearchTyping321();return submitBefore321(...args)};\n"},{"src":"mobile-stock-groups322.js?v=1","code":"'use strict';\n// App search uses the same explicitly assigned pools as the stock overview.\nfunction groupText322(key){const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';const words={de:{variants:'Varianten',total:'gesamt',open:'Umrechnung prüfen',results:'Suchergebnisse',soon:'MHD bald erreicht'},en:{variants:'variants',total:'total',open:'Check conversion',results:'Search results',soon:'Best before soon'},pl:{variants:'warianty',total:'razem',open:'Sprawdź przeliczenie',results:'Wyniki wyszukiwania',soon:'Termin ważności wkrótce'}};return (words[lang]||words.de)[key]}\nfunction expiryWarning322(date){const iso=typeof parseMhd147==='function'?parseMhd147(date)?.iso:null;if(!iso)return null;const today=localTodayISO();if(iso<today)return {className:'expiryExpired322',label:stockInfoText311('expired')};const days=(Date.parse(iso+'T00:00:00Z')-Date.parse(today+'T00:00:00Z'))/86400000;return days<=7?{className:'expirySoon322',label:groupText322('soon')}:null}\nfunction batchSummary322(code,a){return expiryGroups322(state.batches?.[code]||[]).map(b=>{const warning=expiryWarning322(b.mhd);return `<li${warning?` class=\"${warning.className}\"`:''}><span>${esc(formatQtySmart(b.qty,unitText(a)))}</span><span>${esc(formatDate(b.mhd))}${warning?` · ${esc(warning.label)}`:''}</span></li>`}).join('')}\nfunction openVariant322(code,kind){finishSearchTyping321();if(kind==='info'){openMobileProductInfo311(code);return}if(!['in','out'].includes(kind))return;if(!guardPermission(kind==='in'?'stockAdd':'scan',stockMovementText301(kind)))return;if(openStockWizardProduct311(code,0))chooseRecognized198(kind)}\nfunction mobileVariant322(code,a){\n const permitted=p=>typeof hasPermission==='function'&&hasPermission(p),status=stockState118(a),content=a.stockContent?.stockUnit===a.unit&&Number(a.stockContent.qty)>0?formatQtySmart(Number(a.stockContent.qty),a.stockContent.unit):'',maker=a.manufacturer||a.brand||a.supplier||'';\n return `<article class=\"mobileVariant322\"><button type=\"button\" class=\"mobileVariantSelect322\" onclick=\"mobileSelectProduct('${escJs(code)}')\"><span><b>${esc(a.name)}</b><small>${esc(maker)}${a.storage?' · '+esc(a.storage):''}${content?' · '+esc(content):''}</small><small>${esc(code)}</small><span class=\"mobileResultState118 ${status.cls}\">${esc(status.label)}</span></span><strong>${esc(formatQtySmart(Number(a.stock)||0,unitText(a)))}</strong></button>${(state.batches?.[code]||[]).some(b=>Number(b.qty)>0&&expiryWarning322(b.mhd)?.className==='expiryExpired322')?`<small class=\"expiryExpired322 mobileVariantWarning323\">${esc(stockInfoText311('expired'))}</small>`:(state.batches?.[code]||[]).some(b=>Number(b.qty)>0&&expiryWarning322(b.mhd))?`<small class=\"expirySoon322 mobileVariantWarning323\">${esc(groupText322('soon'))}</small>`:''}<div class=\"mobileVariantActions322\"><button type=\"button\" class=\"primary\" aria-label=\"${esc(stockMovementText301('in')+' '+a.name)}\" ${permitted('stockAdd')?'':'disabled'} onclick=\"openVariant322('${escJs(code)}','in')\">+ <span>${esc(stockMovementText301('in'))}</span></button><button type=\"button\" class=\"soft\" aria-label=\"${esc(stockMovementText301('out')+' '+a.name)}\" ${permitted('scan')?'':'disabled'} onclick=\"openVariant322('${escJs(code)}','out')\">− <span>${esc(stockMovementText301('out'))}</span></button><button type=\"button\" class=\"soft mobileVariantInfo322\" aria-label=\"${esc(stockInfoText311('info')+' '+a.name)}\" ${permitted('scan')||permitted('stock')?'':'disabled'} onclick=\"openVariant322('${escJs(code)}','info')\">ⓘ</button></div></article>`;\n}\nfunction mobilePool322(pool){if(!pool.grouped){const [code,a]=pool.members[0];return mobileVariant322(code,a)}const s=pool.summary,expired=pool.members.some(([code])=>(state.batches?.[code]||[]).some(b=>Number(b.qty)>0&&expiryWarning322(b.mhd)?.className==='expiryExpired322'));return `<details class=\"mobileGroup322\" data-pool322=\"${esc(pool.key)}\"><summary><span class=\"mobileGroupTitle322\"><i aria-hidden=\"true\"></i><span><b>${esc(s.name)}</b><small>${pool.members.length} ${esc(groupText322('variants'))}</small>${expired?`<small class=\"expiryExpired322\">${esc(stockInfoText311('expired'))}</small>`:''}</span></span><strong>${s.totalSafe?esc(formatQtySmart(s.total,s.unit)):esc(groupText322('open'))}<small>${s.totalSafe?esc(groupText322('total')):''}</small></strong></summary><div class=\"mobileGroupBody322\">${pool.members.map(([code,a])=>mobileVariant322(code,a)).join('')}</div></details>`}\nconst renderSearchBefore322=renderMobileSearchResults;\nrenderMobileSearchResults=function(query=''){\n if(window.innerWidth>820||mobileProductChosen&&!String(query||'').trim())return renderSearchBefore322(query);\n const box=document.getElementById('mobileSearchResults');if(!box)return;const rows=mobileRows103(query);if(!rows.length)return renderSearchBefore322(query);\n const exact=typeof findScanCode==='function'?findScanCode(String(query||'').trim()):null;\n const selected=new Set(rows.map(([code])=>code));const pools=exact?[{grouped:false,members:[[exact.productCode,state.products[exact.productCode]]]}]:stockPools301().filter(p=>p.members.some(([code])=>selected.has(code)));\n box.innerHTML=`<div class=\"mobileResultTitle\">${esc(groupText322('results'))} · ${pools.length}</div><div class=\"mobileResultList mobilePools322\">${pools.map(mobilePool322).join('')}</div>`;if(typeof syncProductFocus149==='function')syncProductFocus149();\n};\n// Stock overview batch labels follow the same display grouping and warning colors.\nstockBatchLines301=function(code,a){const rows=batchSummary322(code,a);return rows?`<ul class=\"mobileVariantLots322\">${rows}</ul>`:''};\n"},{"src":"cloud-speed326.js?v=329","code":"'use strict';\n// Read-only product details may receive updates; unfinished bookings still block.\nlet infoCode326=null,checkRunning326=null,checkTimer326=null;\nfor(const name of ['openArticleInfo127','openMobileProductInfo311']){\n const original=globalThis[name];if(typeof original!=='function')continue;\n globalThis[name]=function(code,...args){const result=original.call(this,code,...args);infoCode326=code;return result};\n}\nconst pullBefore326=cloudPullSilent119;\ncloudPullSilent119=async function(...args){\n const result=await pullBefore326.apply(this,args);\n const modal=document.getElementById('articleInfoModal127');\n if(result===true&&infoCode326&&modal&&getComputedStyle(modal).display!=='none'){\n  const card=modal.querySelector('.card'),top=card?.scrollTop||0,active=document.activeElement,focused=modal.contains(active);\n  openArticleInfo127(infoCode326);\n  const next=modal.querySelector('.card');if(next)next.scrollTop=top;\n  if(!focused&&active?.isConnected)active.focus?.({preventScroll:true});\n }\n return result;\n};\nconst checkBefore326=checkCloudUpdates;\ncheckCloudUpdates=function(...args){\n if(checkRunning326)return checkRunning326;\n checkRunning326=Promise.resolve().then(()=>checkBefore326.apply(this,args)).finally(()=>{checkRunning326=null});\n return checkRunning326;\n};\nfunction requestCloudCheck326(){\n clearTimeout(checkTimer326);\n checkTimer326=setTimeout(()=>{void checkCloudUpdates()},100);\n}\nfor(const name of ['closeArticleInfo127','closeArticleEditor','closeModal']){\n const original=globalThis[name];if(typeof original!=='function')continue;\n globalThis[name]=function(...args){const result=original.apply(this,args);requestCloudCheck326();return result};\n}\nwindow.addEventListener('focus',requestCloudCheck326);\n"},{"src":"product-lots328.js?v=330","code":"'use strict';\nfunction productLotsHtml328(code){\n const a=state.products?.[code];if(!a)return '';\n const groups=expiryGroups322(state.batches?.[code]||[]),unit=unitText(a);\n const dated=groups.reduce((n,b)=>n+Number(b.qty),0),unassigned=Math.max(0,(Number(a.stock)||0)-dated);\n const words={de:['MHD-Chargen','Ablaufdatum','Menge','Warnung'],en:['Expiry lots','Best-before date','Quantity','Warning'],pl:['Partie z terminem ważności','Termin ważności','Ilość','Ostrzeżenie']}[recognizedLanguage278()]||['MHD-Chargen','Ablaufdatum','Menge','Warnung'];\n const rows=groups.map(b=>{const warning=expiryWarning322(b.mhd),lot=b.lotNumber||b.batchNumber||'';return `<tr${warning?` class=\"${warning.className}\"`:''}><td>${esc(formatDate(b.mhd))}${lot?`<small>${esc(lot)}</small>`:''}</td><td>${esc(formatQtySmart(Number(b.qty),unit))}</td><td>${warning?esc(warning.label):'—'}</td></tr>`}).join('');\n return `<div class=\"productLots328\"><h4>${esc(words[0])}</h4>${rows?`<table><thead><tr><th>${esc(words[1])}</th><th>${esc(words[2])}</th><th>${esc(words[3])}</th></tr></thead><tbody>${rows}</tbody></table>`:`<p>${esc(stockInfoText311('noExpiry'))}</p>`}${unassigned>0?`<p>${esc(stockInfoText311('unassigned'))}: <b>${esc(formatQtySmart(unassigned,unit))}</b></p>`:''}</div>`;\n}\nconst openInfoBefore328=openArticleInfo127;\nopenArticleInfo127=function(code,...args){\n const result=openInfoBefore328.call(this,code,...args);\n if(window.innerWidth>820){const grid=document.querySelector('#articleInfoModal127 .articleInfoGrid');if(grid){grid.querySelector('.productLots328')?.remove();const section=[...grid.querySelectorAll('.articleInfoSection')].find(x=>['Mindesthaltbarkeit','Best-before date','Best before','Termin ważności'].includes(x.querySelector('h3')?.textContent.trim()));if(section)section.insertAdjacentHTML('beforeend',productLotsHtml328(code))}}\n return result;\n};\n"},{"src":"product-create331.js?v=427","code":"'use strict';\nlet productCreationIntent331='movement';\nconst openEditorBefore331=openArticleEditor;\nopenArticleEditor=function(...args){productCreationIntent331='movement';return openEditorBefore331.apply(this,args)};\n// Product creation has a direct form entry. Barcode/photo lookup remains optional.\nfunction openProductCreate331(){\n if(!guardArticleCreate())return false;\n mobileQuickCreate118=window.innerWidth<=820;\n openArticleEditor();\n const modal=document.getElementById('articleModal');\n if(modal?.style.display!=='flex')return false;\n productCreationIntent331='movement';\n if(mobileQuickCreate118)modal.classList.add('mobileCreateMode118');\n (document.getElementById('gaName300')||document.getElementById('aeName'))?.focus();\n return true;\n}\n\n// Every new product continues to quantity after durable article creation.\n// Inventory sessions keep their original count flow; edits never create a receipt.\nconst lookupBefore333=startProductLookup200;\nstartProductLookup200=function(...args){const result=lookupBefore333.apply(this,args);if(productGuide194&&document.getElementById('articleModal')?.style.display==='flex')productCreationIntent331='movement';return result};\n\n// Attach a code to the current draft without launching another product workflow.\nfunction articleBarcodeVisible331(){return String(document.getElementById('aeCode')?.value||articleGuideBookingBarcode194||document.querySelector('#aePacks .aePackBarcode')?.value||'').trim()}\nfunction setArticleBarcode331(value){\n const field=document.getElementById('aeCode');if(!field||editingArticleCode)return false;\n field.value=normalizeBarcode306(value);field.dispatchEvent(new Event('input',{bubbles:true}));\n const visible=document.getElementById('gaBarcode331');if(visible)visible.value=field.value;\n return checkMainBarcodeLive();\n}\nfunction scanArticleBarcode331(){\n if(!guardArticleCreate()||editingArticleCode)return false;\n if(typeof captureGuidedArticle300==='function')captureGuidedArticle300();\n const serial=articleContinuationSerial420,field=document.getElementById('aeCode');\n const alive=()=>serial===articleContinuationSerial420&&!editingArticleCode&&document.getElementById('articleModal')?.style.display==='flex'&&document.getElementById('aeCode')===field;\n const accept=code=>{if(!alive())return;setArticleBarcode331(code);if(guidedArticle300)renderGuidedArticle300()};\n const photo=()=>{\n  if(!alive())return;const input=document.createElement('input');input.type='file';input.accept='image/*';input.setAttribute('capture','environment');input.hidden=true;document.body.appendChild(input);\n  input.onchange=async()=>{const file=input.files?.[0];input.remove();if(!file)return;try{const code=await zReadBarcode166(file);if(!alive())return;if(!code){showFeedback(stockText420('Barcode nicht lesbar','Barcode unreadable','Nie można odczytać kodu'),'warn');return}accept(code)}catch{if(alive())showFeedback(stockText420('Barcode nicht lesbar','Barcode unreadable','Nie można odczytać kodu'),'warn')}};\n  input.oncancel=()=>input.remove();input.click();\n };\n void startLiveScan391(accept,photo);return true;\n}\n\n// A known barcode identifies the existing article and pack. A different draft\n// name must never replace its master data or create a second stock record.\nlet articleBarcodeChoice427=null,articleBarcodeDeclined427='';\nfunction articleBarcodeOwner427(){\n if(editingArticleCode||typeof resolveBarcode306!=='function')return null;\n const barcode=normalizeBarcode306(document.getElementById('aeCode')?.value||'');\n if(!barcode||typeof restrictedCirculationBarcode235==='function'&&restrictedCirculationBarcode235(barcode))return null;\n const hit=resolveBarcode306(barcode),article=hit&&state.products?.[hit.productCode];if(!article)return null;\n const pack=getPacks(article,hit.productCode)[hit.packIndex];if(!pack)return null;\n // Older inconsistent data must not silently select the first of two owners.\n const matches=Object.entries(state.products||{}).flatMap(([code,a])=>getPacks(a,code).filter(p=>{const stored=normalizeBarcode306(p?.[2]);return stored===barcode||typeof validGtin179==='function'&&validGtin179(stored)&&validGtin179(barcode)&&stored.padStart(14,'0')===barcode.padStart(14,'0')}));\n if(matches.length!==1)return null;\n return {barcode,productCode:hit.productCode,packIndex:hit.packIndex,name:article.name||hit.productCode,packName:hit.packIndex>0?pack[0]||'':'',signature:JSON.stringify([hit.productCode,article.name,article.unit,hit.packIndex,pack])};\n}\nfunction renderArticleBarcodeChoice427(reveal=false){\n const modal=document.getElementById('articleModal'),guided=typeof guidedArticle300!=='undefined'&&guidedArticle300;\n const host=guided?.step==='barcode'?document.getElementById('guidedArticle300'):!guided?document.getElementById('aeCodeStatus'):null;\n const owner=modal?.style.display==='flex'&&host?articleBarcodeOwner427():null;\n let panel=document.getElementById('articleBarcodeChoice427');\n if(!owner){panel?.remove();articleBarcodeChoice427=null;articleBarcodeDeclined427='';return false}\n const key=JSON.stringify([articleContinuationSerial420,owner.barcode,owner.signature]);\n if(reveal||articleBarcodeChoice427?.key!==key)articleBarcodeDeclined427='';\n articleBarcodeChoice427={...owner,key,serial:articleContinuationSerial420};\n const field=document.getElementById('aeCode'),status=document.getElementById('aeCodeStatus');field?.classList?.remove('barcodeOk');field?.classList?.add('barcodeConflict');if(status){status.className='barcodeStatus bad';status.textContent=stockText420('Dieser Barcode gehört zu: ','This barcode belongs to: ','Ten kod należy do: ')+owner.name}\n if(!panel){panel=document.createElement('section');panel.id='articleBarcodeChoice427';panel.className='barcodeCollision195 barcodeChoice427';panel.setAttribute('role','status');if(guided)host.appendChild(panel);else host.after(panel)}\n const declined=articleBarcodeDeclined427===key;\n panel.innerHTML=`<b>${esc(stockText420('Barcode bereits vergeben','Barcode already assigned','Kod jest już przypisany'))}</b><p>${esc(stockText420('Dieser Barcode gehört zu:','This barcode belongs to:','Ten kod należy do:'))}<strong>${esc(owner.name)}</strong>${owner.packName?`<small>${esc(owner.packName)}</small>`:''}</p>${declined?`<p>${esc(stockText420('Bitte den Barcode ändern oder das Feld leer lassen.','Change the barcode or leave the field empty.','Zmień kod albo pozostaw pole puste.'))}</p>`:`<p>${esc(stockText420('Vorhandenes Produkt übernehmen?','Use the existing product?','Użyć istniejącego produktu?'))}</p><div class=\"guidedChoice300\"><button type=\"button\" class=\"primary\" onclick=\"answerArticleBarcode427(true)\">${esc(guidedText300('yes'))}</button><button type=\"button\" class=\"soft\" onclick=\"answerArticleBarcode427(false)\">${esc(guidedText300('no'))}</button></div>`}`;\n return true;\n}\nfunction answerArticleBarcode427(accept){\n const choice=articleBarcodeChoice427,modal=document.getElementById('articleModal');\n if(typeof busyOperation275==='function'&&busyOperation275()||typeof articleSaving257!=='undefined'&&articleSaving257)return false;\n if(!choice||editingArticleCode||modal?.style.display!=='flex'||choice.serial!==articleContinuationSerial420||normalizeBarcode306(document.getElementById('aeCode')?.value||'')!==choice.barcode)return false;\n if(!accept){articleBarcodeDeclined427=choice.key;renderArticleBarcodeChoice427();const field=document.getElementById('gaBarcode331')||document.getElementById('aeCode');field?.focus();field?.select?.();return true}\n const owner=articleBarcodeOwner427();if(!owner||owner.signature!==choice.signature){renderArticleBarcodeChoice427(true);return false}\n if(!guardArticleCreate())return false;\n if(!openResolvedBarcode306(owner.barcode))return false;\n if(productGuide194?.existing?.productCode!==owner.productCode||productGuide194.existing.packIndex!==owner.packIndex)return false;\n articleBarcodeChoice427=null;articleBarcodeDeclined427='';document.getElementById('articleBarcodeChoice427')?.remove();\n if(hasPermission('stockAdd'))chooseRecognized198('in');\n return true;\n}\n"},{"src":"product-families336.js?v=338","code":"'use strict';\n// Catalog identity is separate from a barcode and from a stock movement.\nfunction familyNorm336(value){return String(value||'').normalize('NFD').replace(/[\\u0300-\\u036f]/g,'').toLocaleLowerCase('de').replace(/ß/g,'ss').replace(/[^a-z0-9]+/g,' ').trim()}\nfunction familyMeasure336(name){\n const matches=[...String(name||'').matchAll(/\\b(\\d+(?:[.,]\\d+)?)\\s*(kg|kilogramm|kilo|g|gramm|ml|milliliter|liter|l)\\b/gi)];\n if(matches.length!==1||/\\d\\s*[x×]\\s*\\d/i.test(name))return null;\n const m=matches[0],qty=Number(m[1].replace(',','.')),raw=m[2].toLowerCase(),unit=/^(kg|kilo|kilogramm)$/.test(raw)?'kg':/^(g|gramm)$/.test(raw)?'g':/^(ml|milliliter)$/.test(raw)?'ml':'Liter';\n return qty>0?{qty,unit,text:m[0]}:null;\n}\nfunction familyBase336(name){return String(name||'').replace(/\\b\\d+(?:[.,]\\d+)?\\s*(?:kg|kilogramm|kilo|g|gramm|ml|milliliter|liter|l)\\b/gi,' ').replace(/\\b(?:packungen?|packung|k[uü]beln?|k[uü]bel)\\s*$/i,'').replace(/\\s*[-·,()]\\s*$/g,'').replace(/\\s+/g,' ').trim()}\nfunction familyNameKey336(name){return familyNorm336(familyBase336(name)).replace(/\\bchia\\s+samen\\b/g,'chiasamen')}\n// Search aliases broaden proposals only; they never merge group identities.\nfunction familyQuery338(value){return familyNorm336(value).replace(/\\bchia\\s+samen\\b|\\bchiasamen\\b/g,'chia')}\nfunction familyDistance336(a,b){\n const d=Array.from({length:a.length+1},()=>Array(b.length+1).fill(0));\n for(let i=0;i<=a.length;i++)d[i][0]=i;for(let j=0;j<=b.length;j++)d[0][j]=j;\n for(let i=1;i<=a.length;i++)for(let j=1;j<=b.length;j++){d[i][j]=Math.min(d[i-1][j]+1,d[i][j-1]+1,d[i-1][j-1]+(a[i-1]===b[j-1]?0:1));if(i>1&&j>1&&a[i-1]===b[j-2]&&a[i-2]===b[j-1])d[i][j]=Math.min(d[i][j],d[i-2][j-2]+1)}return d[a.length][b.length];\n}\nfunction familyScore336(query,text){\n const q=familyNorm336(query),hay=familyNorm336(text);if(!q)return 0;if(hay===q)return 100;\n const words=hay.split(' '),tokens=q.split(' ');let score=0;\n for(const t of tokens){if(hay.includes(t)){score+=12;continue}if(t.length>=3&&words.some(w=>w.length>=3&&(familyDistance336(t,w)<=(t.length>=7?2:1)||w.length>t.length&&familyDistance336(t,w.slice(0,t.length))<=1))){score+=7;continue}return 0}return score+(hay.startsWith(q)?8:0);\n}\nfunction familyDirect337(query,text){const tokens=familyNorm336(query).split(' ').filter(Boolean),hay=familyNorm336(text);return tokens.length>0&&tokens.every(t=>hay.includes(t))}\nfunction familyPreferDirect337(rows){return rows.some(x=>x.direct)?rows.filter(x=>x.direct):rows}\nconst searchBefore336=mobileProductSearch;\nmobileProductSearch=function(query,limit=12){\n if(!String(query||'').trim())return searchBefore336(query,limit);\n const exact=findScanCode(query);if(exact)return[[exact.productCode,state.products[exact.productCode]]];\n // An unknown barcode must never fall through to fuzzy product selection.\n if(/^GS[-_]|^\\d{6,}$/i.test(String(query).trim()))return[];\n const searchQuery=familyQuery338(query),rows=Object.entries(state.products||{}).map(([code,a])=>{const main=typeof mainGroup==='function'?mainGroup(a.mainArticleId):null,hay=familyQuery338([a.name,a.productGroup336?.name,main?.name,code,a.manufacturer,a.brand,a.supplier,a.storage,a.cat,a.overviewGroup,...(a.packs||[]).flat(),...(Array.isArray(a.synonyms)?a.synonyms:[a.synonyms||''])].join(' '));return{code,a,score:familyScore336(searchQuery,hay),direct:familyDirect337(searchQuery,hay)}}).filter(x=>x.score>0);\n return familyPreferDirect337(rows).sort((a,b)=>b.score-a.score||String(a.a.name).localeCompare(String(b.a.name),'de')).slice(0,limit).map(x=>[x.code,x.a]);\n};\nfunction productFamilies336(){\n const families=new Map();\n for(const [code,a] of Object.entries(state.products||{})){\n  const main=typeof mainGroup==='function'?mainGroup(a.mainArticleId):null,explicit=main&&mainMembers(main.id||a.mainArticleId).length>1;\n  const name=a.productGroup336?.name||(explicit?familyBase336(main.name):familyBase336(a.name)),key=a.productGroupSeparate336?'separate:'+code:a.productGroup336?.key||(explicit?'main:'+a.mainArticleId:'name:'+familyNameKey336(name)+':'+familyNorm336(a.overviewGroup||a.cat));\n  if(!families.has(key))families.set(key,{key,name,members:[],mainId:explicit?a.mainArticleId:''});families.get(key).members.push([code,a]);\n }\n return [...families.values()];\n}\nfunction familyCandidates336(query){const base=familyQuery338(familyBase336(query)),rows=productFamilies336().map(g=>({...g,score:familyScore336(base,familyQuery338(g.name)),direct:familyDirect337(base,familyQuery338(g.name))})).filter(g=>g.score>0&&!g.key.startsWith('separate:'));return familyPreferDirect337(rows).sort((a,b)=>b.score-a.score||a.name.localeCompare(b.name,'de')).slice(0,6)}\nfunction familyPolicy336(a){return typeof recognizedMhdPolicy256==='function'?recognizedMhdPolicy256(a):(a.mhdPolicy144|| (a.mhd?'required':'review'))}\nfunction familySettings336(a){return JSON.stringify([familyPolicy336(a),a.overviewGroup||a.cat||'',a.storage||'',a.shoppingArea173||'',[...(a.allergens||[])].sort(),familyPolicy336(a)==='required'?a.mhdDays242||'':''])}\nfunction familyFactor336(code,a,unit){\n const direct=convertUnit(1,a.unit,unit);if(direct!==null&&direct>0)return direct;\n const content=a.stockContent?.stockUnit===a.unit?a.stockContent:null;\n if(content){const f=convertUnit(Number(content.qty),content.unit,unit);if(f>0)return f}\n if(typeof isCountLikeUnit==='function'&&isCountLikeUnit(a.unit)){const m=familyMeasure336(a.name);if(m){const f=convertUnit(m.qty,m.unit,unit);if(f>0)return f}}\n return null;\n}\nfunction familySummary336(g){\n const dimensions=g.members.map(([code,a])=>familyFactor336(code,a,'kg')>0?'kg':familyFactor336(code,a,'Liter')>0?'Liter':'');\n const unit=dimensions.every(x=>x==='kg')?'kg':dimensions.every(x=>x==='Liter')?'Liter':g.members.every(([,a])=>a.unit===g.members[0][1].unit)?g.members[0][1].unit:'';\n // Counting dissimilar packs as one physical quantity is not a valid conversion.\n const physicalMissing=dimensions.some(Boolean)&&dimensions.some(x=>!x),factors=g.members.map(([code,a])=>!physicalMissing&&unit?familyFactor336(code,a,unit):null),safe=factors.every(f=>f>0);\n return {id:g.key,name:g.name,unit,totalSafe:safe,total:safe?g.members.reduce((n,[,a],i)=>n+(Number(a.stock)||0)*factors[i],0):null,min:0,members:g.members,unsafeMembers:safe?[]:g.members.map(([code,a])=>({code,a}))};\n}\nconst poolsBefore336=stockPools301;\nstockPools301=function(){const oldPools=poolsBefore336();return productFamilies336().map(g=>{const old=oldPools.find(p=>p.members.some(([code])=>code===g.members[0][0]));if(g.members.length===1)return old;const exactOld=old?.grouped&&old.members.length===g.members.length&&old.members.every(([code])=>g.members.some(([c])=>c===code));if(exactOld)return old;const summary=familySummary336(g);return{key:'family:'+g.key,grouped:true,summary,members:g.members,category:stockPoolCategory301(g.members),storage:stockPoolLocation301(g.members),code:'',article:null}}).filter(Boolean)};\n\nlet familyDraft336=null;\nfunction familyEl336(id){return document.getElementById(id)}\nfunction familyText336(key){const l=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de',t={de:{suggest:'Vorhandene Produktgruppe übernehmen',own:'Als eigenes Produkt anlegen',selected:'Produktgruppe',inherited:'MHD-Regel, Lager, Einkaufsbereich und Allergene übernommen. Allergene für diese Variante prüfen.',choose:'Mehrere Gruppen passen. Bitte eine auswählen.',conflict:'Angaben unterscheiden sich. Passende Variante als Vorlage auswählen.',required:'MHD erforderlich',optional:'MHD optional',none:'Kein MHD',review:'MHD-Regel noch auswählen'},en:{suggest:'Use an existing product group',own:'Create a separate product',selected:'Product group',inherited:'Expiry policy, storage, shopping area and allergens copied. Check allergens for this variant.',choose:'Several groups match. Please select one.',conflict:'Details differ. Select a variant as a template.',required:'Expiry required',optional:'Expiry optional',none:'No expiry',review:'Select expiry policy'},pl:{suggest:'Użyj istniejącej grupy produktów',own:'Utwórz osobny produkt',selected:'Grupa produktów',inherited:'Skopiowano zasady daty, magazyn, dział zakupów i alergeny. Sprawdź alergeny wariantu.',choose:'Pasuje kilka grup. Wybierz jedną.',conflict:'Dane różnią się. Wybierz wariant jako wzór.',required:'Data wymagana',optional:'Data opcjonalna',none:'Bez daty',review:'Wybierz zasady daty'}};return(t[l]||t.de)[key]}\nfunction familySuggestHtml336(query){\n const groups=familyCandidates336(query);if(!groups.length||familyDraft336?.separate)return'';\n if(familyDraft336?.key)return `<div class=\"familySelected336\"><b>${esc(familyText336('selected'))}: ${esc(familyDraft336.name)}</b><small>${esc(familyText336('inherited'))}</small></div><button type=\"button\" class=\"soft\" onclick=\"familySeparate336()\">${esc(familyText336('own'))}</button>`;\n return `<b>${esc(familyText336('suggest'))}</b>${groups.map(g=>{const conflict=new Set(g.members.map(([,a])=>familySettings336(a))).size>1,rows=conflict?g.members:[g.members[0]];return `<div class=\"familyCandidate336\"><strong>${esc(g.name)}</strong>${conflict?`<small>${esc(familyText336('conflict'))}</small>`:''}${rows.map(([code,a])=>`<button type=\"button\" class=\"soft\" onclick=\"chooseFamily336('${escJs(code)}')\"><span>${esc(conflict?a.name:g.name)}</span><small>${esc(familyText336(familyPolicy336(a)))}${a.storage?' · '+esc(a.storage):''}</small></button>`).join('')}</div>`}).join('')}<button type=\"button\" class=\"soft\" onclick=\"familySeparate336()\">${esc(familyText336('own'))}</button>`;\n}\nfunction familySizeInput336(name){const m=familyMeasure336(name);if(!m)return;const qty=familyEl336('aeContentQty'),unit=familyEl336('aeContentUnit'),stockUnit=familyEl336('aeUnit');if(qty&&unit&&stockUnit&&isCountLikeUnit(stockUnit.value)){qty.value=String(m.qty);unit.value=m.unit;if(typeof updateEditorStockContent==='function')updateEditorStockContent()}}\nfunction chooseFamily336(code,automatic=false){\n if(editingArticleCode)return false;const g=productFamilies336().find(g=>g.members.some(([c])=>c===code)),a=state.products[code];if(!g||!a)return false;\n const name=familyEl336('aeName'),typed=name?.value||'',measure=familyMeasure336(typed);\n familyDraft336={key:g.key,name:g.name,source:code};\n if(name&&!automatic)name.value=g.name+(measure?' '+measure.text:'');\n const cat=familyEl336('aeCategory');if(cat)cat.value=a.overviewGroup||a.cat||cat.value;\n if(typeof refreshArticleSubgroups==='function')refreshArticleSubgroups(a.storage||'');\n const storage=familyEl336('aeSubgroup');if(storage)storage.value=a.storage||storage.value;\n const shopping=familyEl336('aeShoppingArea173');if(shopping){if(typeof shoppingAreaOptions173==='function')shopping.innerHTML=shoppingAreaOptions173(a.shoppingArea173||(typeof shoppingAreaFor173==='function'?shoppingAreaFor173(a):''));shopping.value=a.shoppingArea173||(typeof shoppingAreaFor173==='function'?shoppingAreaFor173(a):shopping.value);shopping.dataset.manual='true'}\n const policy=familyEl336('aeMhdPolicy144');if(policy)policy.value=familyPolicy336(a);const mhd=familyEl336('aeMhd');if(mhd)mhd.checked=familyPolicy336(a)==='required';\n const days=familyEl336('aeMhdDays242');if(days)days.value=familyPolicy336(a)==='required'&&Number(a.mhdDays242)>0?String(a.mhdDays242):'';\n // Copy only shared data. No barcode, price, stock, brand, photos or batches.\n if(typeof renderAllergenEditor128==='function')renderAllergenEditor128({allergens:[...(a.allergens||[])]});\n if(typeof updateArticleFoodOptions==='function')updateArticleFoodOptions();if(typeof renderExistingMhdEditor==='function')renderExistingMhdEditor();\n familySizeInput336(familyEl336('aeName')?.value||typed);renderFamilySuggestions336();if(typeof scheduleArticleDraft249==='function')scheduleArticleDraft249();\n if(typeof guidedArticle300!=='undefined'&&guidedArticle300){guidedArticle300.groupConfirmed=true;if(!automatic)renderGuidedArticle300()}\n return true;\n}\n// One explicit main-stock decision shared by guided and full article forms.\n// Keep it in the existing draft marker so save does not repeat fuzzy matching.\nfunction confirmMainFamily429(id){\n if(editingArticleCode)return false;\n const target=familyEl336('aeMainArticle'),main=id&&typeof mainGroup==='function'?mainGroup(id):null;\n if(!target||target.value!==id||id&&!main)return false;\n familyDraft336={name:familyEl336('aeName')?.value||'',mainChoice429:id,...(!id?{separate:true}:{})};\n if(typeof guidedArticle300!=='undefined'&&guidedArticle300)guidedArticle300.groupConfirmed=true;\n if(typeof scheduleArticleDraft249==='function')scheduleArticleDraft249();\n return true;\n}\nfunction familySeparate336(){\n const target=familyEl336('aeMainArticle');if(target){target.value='';if(typeof articleMainGroupChanged==='function')articleMainGroupChanged()}\n familyDraft336={separate:true,name:familyEl336('aeName')?.value||'',mainChoice429:''};\n if(typeof guidedArticle300!=='undefined'&&guidedArticle300)guidedArticle300.groupConfirmed=true;\n if(typeof clearArticleFeedback316==='function')clearArticleFeedback316();\n renderFamilySuggestions336();if(typeof scheduleArticleDraft249==='function')scheduleArticleDraft249();if(typeof guidedArticle300!=='undefined'&&guidedArticle300)renderGuidedArticle300();\n}\nfunction familyNameChanged336(value){\n const name=familyEl336('aeName');if(name&&value!==undefined)name.value=value;const raw=name?.value||'';\n if(familyDraft336&&Object.prototype.hasOwnProperty.call(familyDraft336,'mainChoice429')){\n  const id=familyDraft336.mainChoice429,target=familyEl336('aeMainArticle');\n  if(familyNameKey336(raw)===familyNameKey336(familyDraft336.name)&&(!target&&!id||target?.value===id)&&(!id||typeof mainGroup==='function'&&mainGroup(id))){renderFamilySuggestions336();return}\n  if(target)target.value='';if(typeof articleMainGroupChanged==='function')articleMainGroupChanged();\n  if(typeof guidedArticle300!=='undefined'&&guidedArticle300)guidedArticle300.groupConfirmed=false;\n  familyDraft336=null;renderFamilySuggestions336();return;\n }\n if(familyDraft336&&!familyDraft336.separate&&!(typeof familyNameMatches340==='function'?familyNameMatches340(raw,familyDraft336):familyNameKey336(raw)===familyNameKey336(familyDraft336.name)))familyDraft336=null;\n if(familyDraft336?.separate&&familyNameKey336(raw)!==familyNameKey336(familyDraft336.name))familyDraft336=null;\n familySizeInput336(raw);\n if(!familyDraft336&&!editingArticleCode){const exact=familyCandidates336(raw).filter(g=>familyNameKey336(g.name)===familyNameKey336(raw));if(exact.length===1&&new Set(exact[0].members.map(([,a])=>familySettings336(a))).size===1)chooseFamily336(exact[0].members[0][0],true)}\n renderFamilySuggestions336();\n}\nfunction renderFamilySuggestions336(){const box=familyEl336('familySuggestions336');if(box){box.innerHTML=editingArticleCode?'':familySuggestHtml336(familyEl336('aeName')?.value||'');box.hidden=!box.innerHTML}if(!editingArticleCode){const legacy=familyEl336('aeMainArticle')?.closest?.('.field');if(legacy)legacy.hidden=!!familyDraft336?.key;const factor=familyEl336('aeMainFactorField');if(factor&&familyDraft336?.key)factor.hidden=true}}\nfunction installFamilyEditor336(){const input=familyEl336('aeName');if(!input)return;if(!familyEl336('familySuggestions336'))input.insertAdjacentHTML('afterend','<div id=\"familySuggestions336\" class=\"familySuggestions336\" aria-live=\"polite\"></div>');if(!familyEl336('aeFamily336'))input.insertAdjacentHTML('afterend','<input type=\"hidden\" id=\"aeFamily336\" value=\"\">');if(!input.dataset.family336){input.dataset.family336='true';input.addEventListener('input',()=>familyNameChanged336())}const main=familyEl336('aeMainArticle');if(main&&!main.dataset.family429){main.dataset.family429='true';main.addEventListener('change',()=>{confirmMainFamily429(main.value);renderFamilySuggestions336()})}renderFamilySuggestions336()}\nconst editorBefore336=openArticleEditor;\nopenArticleEditor=function(...args){familyDraft336=null;installFamilyEditor336();const marker=familyEl336('aeFamily336');if(marker)marker.value='';const out=editorBefore336.apply(this,args);if(!editingArticleCode&&marker?.value){try{const d=JSON.parse(marker.value);if((typeof familyNameMatches340==='function'?familyNameMatches340(familyEl336('aeName')?.value,d):familyNameKey336(d.name)===familyNameKey336(familyEl336('aeName')?.value))&&(d.separate||Object.prototype.hasOwnProperty.call(d,'mainChoice429')&&d.mainChoice429===familyEl336('aeMainArticle')?.value&&typeof mainGroup==='function'&&mainGroup(d.mainChoice429)||productFamilies336().some(g=>g.key===d.key))){familyDraft336=d;if(typeof renderAllergenEditor128==='function'&&Array.isArray(d.allergens))renderAllergenEditor128({allergens:d.allergens})}}catch{}}renderFamilySuggestions336();return out};\nif(typeof saveArticleDraft249==='function'){const draftSaveBefore336=saveArticleDraft249;saveArticleDraft249=function(...args){const marker=familyEl336('aeFamily336');if(marker)marker.value=familyDraft336?JSON.stringify({...familyDraft336,allergens:typeof selectedAllergens128==='function'?selectedAllergens128():[]}):'';return draftSaveBefore336.apply(this,args)}}\nconst nameBefore336=articleNameInputChanged;\narticleNameInputChanged=function(...args){const out=nameBefore336.apply(this,args);familyNameChanged336();return out};\n\n// Run inside the existing transactional save, before persistState and its rollback.\nconst syncMainBefore336=syncMainArticleAfterSave;\nsyncMainArticleAfterSave=function(code,item){\n const id=syncMainBefore336(code,item);if(editingArticleCode)return id;\n if(typeof selectedAllergens128==='function')item.allergens=[...selectedAllergens128()];\n if(familyDraft336?.separate){item.productGroupSeparate336=true;return id}\n if(familyDraft336?.mainChoice429){const main=mainGroup(familyDraft336.mainChoice429);if(main&&id===familyDraft336.mainChoice429)item.productGroup336={key:'main:'+id,name:main.name};return id}\n if(familyDraft336?.key)item.productGroup336={key:familyDraft336.key,name:familyDraft336.name};\n const group=productFamilies336().find(g=>g.members.some(([c])=>c===code));if(!group||group.members.length<2)return id;\n const summary=familySummary336(group);if(!summary.totalSafe)return id;\n const draft=clone(state),rows=group.members.map(([c,a])=>({code:c,factor:familyFactor336(c,a,summary.unit)})),target=group.mainId||'family336-'+familyNorm336(group.name).replace(/ /g,'-')+'-'+familyNorm336(item.overviewGroup||item.cat).replace(/ /g,'-');\n try{applyVariantGroup206(draft,target,group.name,summary.unit,rows)}catch(error){return id}\n state.mainArticles=draft.mainArticles;state.recipeGroups=draft.recipeGroups;\n for(const r of rows){const a=state.products[r.code];a.mainArticleId=target;a.mainFactor=r.factor;a.mainFactorConfirmed=true;a.productGroup336={key:group.key,name:group.name}}\n return target;\n};\nconst saveBefore336=saveArticleEditor;\nsaveArticleEditor=function(...args){\n if(!editingArticleCode){familyNameChanged336();const choices=familyCandidates336(familyEl336('aeName')?.value||'');if(!familyDraft336&&choices.length){if(typeof guidedArticle300!=='undefined'&&guidedArticle300){guidedArticle300.step='group';renderGuidedArticle300()}showFeedback(familyText336('selected'),'warn',4500,familyText336('choose'));familyEl336(typeof guidedArticle300!=='undefined'&&guidedArticle300?'guidedArticle300':'familySuggestions336')?.scrollIntoView?.({block:'center'});return}}\n return saveBefore336.apply(this,args);\n};\n\n// Guided mobile creation uses the very same underlying fields and selected group.\nconst guidedStepsBefore336=guidedArticleSteps300;\nguidedArticleSteps300=function(){const steps=guidedStepsBefore336();return familyDraft336?.key?steps.filter(x=>x!=='group'&&(x!=='mhd'||!['required','optional','none'].includes(familyEl336('aeMhdPolicy144')?.value))):steps};\nconst guidedCaptureBefore336=captureGuidedArticle300;\ncaptureGuidedArticle300=function(){const step=guidedArticle300?.step;const out=guidedCaptureBefore336();if(step==='name')familyNameChanged336();return out};\nconst guidedRenderBefore336=renderGuidedArticle300;\nrenderGuidedArticle300=function(){const out=guidedRenderBefore336();if(!guidedArticle300)return out;const input=familyEl336('gaName300');if(input){input.addEventListener('input',()=>{familyNameChanged336(input.value);const box=familyEl336('gaFamilies336');if(box)box.innerHTML=familySuggestHtml336(input.value);if(typeof scheduleArticleDraft249==='function')scheduleArticleDraft249()});input.insertAdjacentHTML('afterend',`<div id=\"gaFamilies336\" class=\"familySuggestions336\">${familySuggestHtml336(input.value)}</div>`)}if(guidedArticle300.step==='review'){for(const row of familyEl336('guidedArticle300')?.querySelectorAll?.('dl > div')||[]){const title=row.querySelector('dt'),value=row.querySelector('dd');if(title?.textContent==='MHD'&&value)value.textContent=familyText336(familyEl336('aeMhdPolicy144')?.value||'review');if(title?.textContent==='Gesamtvorrat'&&value&&familyDraft336?.key){title.textContent=familyText336('selected');value.textContent=familyDraft336.name}}if(familyDraft336?.key)familyEl336('guidedArticle300')?.insertAdjacentHTML('beforeend',`<button type=\"button\" class=\"soft\" onclick=\"endGuidedArticle300();document.getElementById('aeAllergenBlock')?.scrollIntoView({block:'center'})\">${esc(familyText336('inherited'))}</button>`)}return out};\n"},{"src":"desktop-search338.js?v=339","code":"'use strict';\n// Enter submits name searches into the main results area, not the typeahead.\nlet desktopSearch338=null,desktopMovement338=false,desktopReturn339=null;\nfunction desktopSearchWords338(key){const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';const t={de:{results:'Suchergebnisse',empty:'Keine passenden Produkte gefunden.',create:'Produkt anlegen',back:'Zurück zu den Suchergebnissen'},en:{results:'Search results',empty:'No matching products found.',create:'Create product',back:'Back to search results'},pl:{results:'Wyniki wyszukiwania',empty:'Nie znaleziono pasujących produktów.',create:'Utwórz produkt',back:'Wróć do wyników wyszukiwania'}};return(t[lang]||t.de)[key]}\nfunction rememberDesktopSearch339(){if(!desktopSearch338)return;const card=document.getElementById(desktopSearch338.cardId);desktopReturn339={...desktopSearch338,folds:[...(card?.querySelectorAll?.('details')||[])].map(d=>!!d.open)}}\nfunction paintDesktopBack339(){if(desktopSearch338||!desktopReturn339||window.innerWidth<=820)return;const card=document.getElementById(desktopReturn339.cardId);if(card&&!card.querySelector?.('.desktopBack339'))card.insertAdjacentHTML?.('afterbegin',`<button type=\"button\" class=\"soft desktopBack339\" onclick=\"returnDesktopSearch339()\" style=\"margin-bottom:16px\">← ${esc(desktopSearchWords338('back'))}</button>`)}\nfunction returnDesktopSearch339(){if(!desktopReturn339)return;desktopSearch338={...desktopReturn339};const input=document.getElementById(desktopSearch338.inputId);if(input)input.value=desktopSearch338.query;hideArticleSuggestions(desktopSearch338.boxId);paintDesktopSearch338();const card=document.getElementById(desktopSearch338.cardId);[...(card?.querySelectorAll?.('details')||[])].forEach((d,i)=>{if(desktopReturn339.folds[i]!==undefined)d.open=desktopReturn339.folds[i]});card?.scrollIntoView?.({block:'nearest',behavior:'smooth'})}\nfunction paintDesktopSearch338(){\n if(!desktopSearch338||window.innerWidth<=820)return;\n const {query,inputId,boxId,cardId,allowCreate}=desktopSearch338,card=document.getElementById(cardId);if(!card)return;\n const pools=stockPoolsForSearch301(query),count=pools.reduce((n,p)=>n+p.members.length,0);\n const body=pools.length?`<div class=\"stockList stockPools301\">${pools.map(p=>stockPoolHtml301(p).replace('<details class=','<details open class=')).join('')}</div>`:`<p class=\"muted\">${esc(desktopSearchWords338('empty'))}</p>${allowCreate&&canCreateArticles()?`<button type=\"button\" class=\"soft\" onclick=\"offerCreateArticleFromSearch('${escJs(inputId)}','${escJs(boxId)}')\">${esc(desktopSearchWords338('create'))}</button>`:''}`;\n card.innerHTML=`<h2>${esc(desktopSearchWords338('results'))}: ${esc(query)} <small>(${count})</small></h2>${body}`;\n}\nconst resolveBefore338=resolveArticleSearch;\nresolveArticleSearch=function(inputId,boxId,allowCreate=false){\n const input=document.getElementById(inputId),query=input?.value.trim()||'',cardId=inputId==='barcode'?'productCard':inputId==='dashBarcode'?'dashProductCard':'';\n if(window.innerWidth<=820||!cardId)return resolveBefore338(inputId,boxId,allowCreate);\n if(!query||findScanCode(query)||/^GS[-_]|^\\d{6,}$/i.test(query)){const hadResults=!!desktopSearch338;desktopSearch338=null;desktopReturn339=null;const out=resolveBefore338(inputId,boxId,allowCreate);if(!query&&hadResults)render();return out}\n desktopReturn339=null;\n desktopSearch338={query,inputId,boxId,cardId,allowCreate};hideArticleSuggestions(boxId);input?.blur?.();paintDesktopSearch338();\n};\nconst renderBefore338=render;\nrender=function(...args){const out=renderBefore338.apply(this,args);paintDesktopSearch338();paintDesktopBack339();return out};\nconst dashboardBefore338=renderDashboard;\nrenderDashboard=function(...args){const out=dashboardBefore338.apply(this,args);if(desktopSearch338?.cardId==='dashProductCard')paintDesktopSearch338();if(desktopReturn339?.cardId==='dashProductCard')paintDesktopBack339();return out};\nconst selectBefore338=selectArticleSearchResult;\nselectArticleSearchResult=function(...args){rememberDesktopSearch339();desktopSearch338=null;return selectBefore338.apply(this,args)};\nconst existingBefore338=openExistingStock301;\nopenExistingStock301=function(...args){if(!desktopMovement338){rememberDesktopSearch339();desktopSearch338=null}return existingBefore338.apply(this,args)};\nconst movementBefore338=openStockMovement301;\nopenStockMovement301=function(...args){desktopMovement338=true;try{return movementBefore338.apply(this,args)}finally{desktopMovement338=false}};\n"},{"src":"cloud-panel339.js?v=1","code":"'use strict';\n// Presentation only. All original fields, handlers and auth/session guards remain.\nfunction cloudPanelText339(key){const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de',t={de:{login:'Anmelden',live:'Gemeinsamer Datenstand',intro:'Dein Lager auf Handy und PC.',account:'Mit deinem Zapasio-Zugang anmelden.'},en:{login:'Sign in',live:'Shared inventory',intro:'Your inventory on phone and PC.',account:'Sign in with your Zapasio account.'},pl:{login:'Zaloguj się',live:'Wspólny stan magazynu',intro:'Twój magazyn na telefonie i komputerze.',account:'Zaloguj się na swoje konto Zapasio.'}};return(t[lang]||t.de)[key]}\nconst opsBefore339=openOpsPanel;\nopenOpsPanel=function(...args){const result=opsBefore339.apply(this,args);const dialog=document.getElementById('opsDialog92');dialog?.classList.remove('cloudPanel339');const heading=dialog?.querySelector(':scope > header h2');if(heading){heading.id='opsTitle339';dialog.setAttribute('aria-labelledby',heading.id)}return result};\nconst cloudBefore339=openCloudSettings;\nopenCloudSettings=function(...args){\n const result=cloudBefore339.apply(this,args),dialog=document.getElementById('opsDialog92'),body=dialog?.querySelector('.opsBody92'),header=dialog?.querySelector(':scope > header'),heading=header?.querySelector('h2');\n if(!body||!header||!heading||!body.querySelector('#cloudUrl92'))return result;\n const c=cloudRuntime();dialog.classList.add('cloudPanel339');\n heading.textContent=cloudPanelText339(c.user?'live':'login');heading.id='cloudTitle339';dialog.setAttribute('aria-labelledby',heading.id);\n header.insertAdjacentHTML('afterbegin','<div class=\"cloudBrand339\"><img src=\"assets/zapasio-main-approved-lossless424.webp\" alt=\"Zapasio\"></div>');\n heading.insertAdjacentHTML('afterend',`<p class=\"cloudIntro339\">${esc(cloudPanelText339(c.user?'intro':'account'))}</p>`);\n const login=body.querySelector('#cloudPassword92')?.closest('.opsGrid92');login?.classList.add('cloudLoginFields339');\n const status=body.querySelector('#cloudBadge92');status?.classList.add('cloudConnection339');\n const technical=body.querySelector('#cloudUrl92')?.closest('details');if(technical){if(cloudConfig()?.url)technical.open=false;technical.classList.add('cloudTechnical339');body.appendChild(technical)}\n return result;\n};\n"},{"src":"family-choice340.js?v=2","code":"'use strict';\n// Choose the product family first. Existing variants are not parent products.\nconst familyTextBefore340=familyText336;\nfunction familyWords340(key){const l=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de',t={de:{pick:'Produktgruppe auswählen',add:'Neue Packungsgröße hinzufügen',independent:'Neues, unabhängiges Produkt',newSize:'Neue Packungsgröße für',content:'Packungsinhalt',unit:'Einheit',shared:'Gemeinsame Angaben übernommen. Allergene für die neue Variante prüfen.',different:'Nur diese Angaben unterscheiden sich. Bitte für die neue Variante auswählen.',select:'Bitte auswählen',category:'Artikelart',storage:'Lagerbereich',shopping:'Einkaufsbereich',policy:'MHD-Regel',allergens:'Allergene',days:'Haltbarkeit in Tagen',missing:'Bitte die unterschiedlichen Angaben für die neue Variante auswählen.',sizeMissing:'Bitte einen Packungsinhalt größer als 0 eingeben und die Einheit auswählen, zum Beispiel 50 Stück oder 750 g.',none:'Keine',noDays:'Keine feste Tagesvorgabe',variants:'Vorhandene Größen'},en:{pick:'Select product group',add:'Add a new pack size',independent:'New independent product',newSize:'New pack size for',content:'Pack contents',unit:'Unit',shared:'Shared details copied. Check allergens for the new variant.',different:'Only these details differ. Select them for the new variant.',select:'Please select',category:'Product type',storage:'Storage area',shopping:'Shopping area',policy:'Expiry policy',allergens:'Allergens',days:'Shelf life in days',missing:'Select the differing details for the new variant.',sizeMissing:'Enter pack contents greater than 0 and select the unit, for example 50 pieces or 750 g.',none:'None',noDays:'No fixed shelf life',variants:'Existing sizes'},pl:{pick:'Wybierz grupę produktów',add:'Dodaj nową wielkość opakowania',independent:'Nowy niezależny produkt',newSize:'Nowa wielkość opakowania dla',content:'Zawartość opakowania',unit:'Jednostka',shared:'Wspólne dane skopiowane. Sprawdź alergeny nowego wariantu.',different:'Tylko te dane różnią się. Wybierz je dla nowego wariantu.',select:'Wybierz',category:'Rodzaj produktu',storage:'Miejsce magazynowania',shopping:'Dział zakupów',policy:'Zasady daty ważności',allergens:'Alergeny',days:'Trwałość w dniach',missing:'Wybierz różniące się dane nowego wariantu.',sizeMissing:'Podaj zawartość opakowania większą niż 0 i wybierz jednostkę, np. 50 sztuk lub 750 g.',none:'Brak',noDays:'Brak stałej liczby dni',variants:'Istniejące wielkości'}};return(t[l]||t.de)[key]}\nfamilyText336=function(key){return key==='own'?familyWords340('independent'):familyTextBefore340(key)};\nfunction sharedFields340(group){\n const getters={category:a=>a.overviewGroup||a.cat||'',storage:a=>a.storage||'',shopping:a=>a.shoppingArea173||(typeof shoppingAreaFor173==='function'?shoppingAreaFor173(a):''),policy:a=>familyPolicy336(a),allergens:a=>JSON.stringify([...(a.allergens||[])].sort()),days:a=>familyPolicy336(a)==='required'&&Number(a.mhdDays242)>0?String(a.mhdDays242):''};\n return Object.entries(getters).map(([key,get])=>{const values=[...new Set(group.members.map(([,a])=>get(a)))];return{key,values,chosen:values.length===1&&(key!=='policy'||['required','optional','none'].includes(values[0]))?values[0]:null}});\n}\nfunction sharedValue340(key,value){\n if(key==='policy')return familyText336(value);\n if(key==='allergens'){const keys=JSON.parse(value||'[]');if(!keys.length)return familyWords340('none');return keys.map(key=>{const a=typeof ALLERGENS128!=='undefined'?ALLERGENS128.find(a=>a.key===key):null;return a?a[(typeof recognizedLanguage278==='function'?recognizedLanguage278():'de')]||a.de:key}).join(', ')}\n if(key==='days')return value?value+' '+({de:'Tage',en:'days',pl:'dni'}[typeof recognizedLanguage278==='function'?recognizedLanguage278():'de']||'Tage'):familyWords340('noDays');\n if(key==='shopping'){const el=familyEl336('aeShoppingArea173');return [...(el?.options||[])].find(o=>o.value===value)?.textContent||value||familyWords340('none')}\n return value||familyWords340('none');\n}\nfunction setSharedValue340(key,value){\n const id={category:'aeCategory',storage:'aeSubgroup',shopping:'aeShoppingArea173',policy:'aeMhdPolicy144',days:'aeMhdDays242'}[key],el=id?familyEl336(id):null;if(el)el.value=value;\n if(key==='category'&&typeof refreshArticleSubgroups==='function'){refreshArticleSubgroups(familyDraft336?.shared340?.find(x=>x.key==='storage')?.chosen||'')}\n if(key==='shopping'&&el)el.dataset.manual='true';\n if(key==='policy'){const mhd=familyEl336('aeMhd');if(mhd)mhd.checked=value==='required';const days=familyEl336('aeMhdDays242');if(days)days.value=value==='required'?(familyDraft336?.shared340?.find(f=>f.key==='days')?.chosen||''):''}\n if(key==='allergens'&&typeof renderAllergenEditor128==='function')renderAllergenEditor128({allergens:JSON.parse(value||'[]')});\n}\nconst prototypeBefore340=chooseFamily336;\nfunction chooseProductGroup340(key,automatic=false){\n const group=productFamilies336().find(g=>g.key===key);if(!group||editingArticleCode)return false;\n const fields=sharedFields340(group);prototypeBefore340(group.members[0][0],automatic);\n familyDraft336.shared340=fields;delete familyDraft336.source;\n for(const f of fields)setSharedValue340(f.key,f.chosen===null?(f.key==='policy'?'review':f.key==='allergens'?'[]':''):f.chosen);\n // The new variant's recognized/manual contents belong to that variant. Shared\n // settings must neither clear them nor copy the template's pack size.\n if(typeof renderExistingMhdEditor==='function')renderExistingMhdEditor();renderFamilySuggestions336();if(typeof scheduleArticleDraft249==='function')scheduleArticleDraft249();\n if(typeof guidedArticle300!=='undefined'&&guidedArticle300){guidedArticle300.groupConfirmed=true;if(!automatic)renderGuidedArticle300()}\n return true;\n}\nchooseFamily336=function(code,automatic=false){const group=productFamilies336().find(g=>g.members.some(([c])=>c===code));return group?chooseProductGroup340(group.key,automatic):false};\nfunction chooseShared340(key,value){\n const field=familyDraft336?.shared340?.find(f=>f.key===key);if(!field)return;\n const allowed=key==='policy'?['required','optional','none']:field.values;if(!allowed.includes(value))return;\n field.chosen=value;setSharedValue340(key,value);\n if(typeof updateArticleFoodOptions==='function')updateArticleFoodOptions();if(typeof renderExistingMhdEditor==='function')renderExistingMhdEditor();\n renderFamilySuggestions336();if(typeof scheduleArticleDraft249==='function')scheduleArticleDraft249();if(typeof guidedArticle300!=='undefined'&&guidedArticle300)renderGuidedArticle300();\n}\nfunction setFamilySize340(qty,unit){\n if(!familyDraft336?.key)return;const q=Number(String(qty).replace(',','.'));if(!['','Stück','g','kg','ml','Liter'].includes(unit))return;\n const content=familyEl336('aeContentQty'),contentUnit=familyEl336('aeContentUnit'),name=familyEl336('aeName');if(content)content.value=String(qty).replace(',','.');if(contentUnit)contentUnit.value=unit;\n if(name)name.value=familyDraft336.name+(Number.isFinite(q)&&q>0&&unit?' '+String(q)+' '+unit:'');\n const guidedName=familyEl336('gaName300');if(guidedName&&name)guidedName.value=name.value;\n if(typeof updateEditorStockContent==='function')updateEditorStockContent();if(typeof scheduleArticleDraft249==='function')scheduleArticleDraft249();\n}\n// Preserve an explicitly selected family when its new variant has a piece\n// suffix. Do not change name-based grouping for existing catalogue records.\nfunction familyNameMatches340(name,draft){\n if(!draft)return false;if(familyNameKey336(name)===familyNameKey336(draft.name))return true;\n const qty=Number(String(familyEl336('aeContentQty')?.value||'').replace(',','.'));\n return familyEl336('aeContentUnit')?.value==='Stück'&&Number.isSafeInteger(qty)&&qty>0&&String(name).trim()===String(draft.name).trim()+' '+qty+' Stück';\n}\nfunction groupSizes340(group){return [...new Set(group.members.map(([code,a])=>{const m=familyMeasure336(a.name)||a.stockContent;return m?.qty>0?String(m.qty).replace('.',',')+' '+m.unit:a.unit||''}))].join(' · ')}\nfunction sharedPending340(){return (familyDraft336?.shared340||[]).filter(f=>f.chosen===null&&(f.key!=='days'||familyEl336('aeMhdPolicy144')?.value==='required'))}\nfamilySuggestHtml336=function(query){\n if(familyDraft336?.mainChoice429){const main=typeof mainGroup==='function'?mainGroup(familyDraft336.mainChoice429):null;return `<div class=\"familySelected336\"><b>${esc(familyText336('selected'))}: ${esc(main?.name||familyDraft336.name)}</b></div><button type=\"button\" class=\"soft\" onclick=\"familySeparate336()\">${esc(familyWords340('independent'))}</button>`}\n if(familyDraft336?.separate)return `<div class=\"familySelected336\"><b>${esc(familyWords340('independent'))}</b></div>`;\n if(familyDraft336?.key){\n  const qty=familyEl336('aeContentQty')?.value||'',unit=familyEl336('aeContentUnit')?.value||'',pending=sharedPending340();\n  const conflicts=(familyDraft336.shared340||[]).filter(f=>(f.values.length>1||f.key==='policy'&&f.values.some(v=>!['required','optional','none'].includes(v)))&&(f.key!=='days'||familyEl336('aeMhdPolicy144')?.value==='required'));\n  return `<div class=\"familySelected336\"><b>${esc(familyWords340('newSize'))} ${esc(familyDraft336.name)}</b><small>${esc(familyWords340('shared'))}</small><label class=\"familySizeLabel340\">${esc(familyWords340('content'))}</label><div class=\"familySize340\"><input data-familyqty340 inputmode=\"decimal\" value=\"${esc(qty)}\" aria-label=\"${esc(familyWords340('content'))}\" oninput=\"setFamilySize340(this.value,this.parentElement.querySelector('select').value)\"><select data-familyunit340 aria-label=\"${esc(({de:'Einheit',en:'Unit',pl:'Jednostka'}[typeof recognizedLanguage278==='function'?recognizedLanguage278():'de']||'Einheit'))}\" onchange=\"setFamilySize340(this.parentElement.querySelector('input').value,this.value)\">${['','Stück','g','kg','ml','Liter'].map(u=>`<option value=\"${u}\" ${u===unit?'selected':''}>${u?esc(u):esc(familyWords340('unit'))}</option>`).join('')}</select></div>${conflicts.length?`<p class=\"tiny\">${esc(familyWords340('different'))}</p>`:''}${conflicts.map(f=>`<label class=\"familyConflict340\">${esc(familyWords340(f.key))}<select aria-label=\"${esc(familyWords340(f.key))}\" onchange=\"chooseShared340('${f.key}',this.value)\"><option value=\"\" ${f.chosen===null?'selected':''} disabled>${esc(familyWords340('select'))}</option>${(f.key==='policy'?['required','optional','none']:f.values).map(v=>`<option value=\"${esc(v)}\" ${v===f.chosen?'selected':''}>${esc(sharedValue340(f.key,v))}</option>`).join('')}</select></label>`).join('')}</div><button type=\"button\" class=\"soft\" onclick=\"familySeparate336()\">${esc(familyWords340('independent'))}</button>`;\n }\n const groups=familyCandidates336(query);if(!groups.length)return'';\n return `<b>${esc(familyWords340('pick'))}</b>${groups.map(g=>`<div class=\"familyCandidate336\"><strong>${esc(g.name)}</strong><small>${esc(familyWords340('variants'))}: ${esc(groupSizes340(g))}</small><button type=\"button\" class=\"soft\" onclick=\"chooseProductGroup340('${escJs(g.key)}')\">${esc(familyWords340('add'))}</button></div>`).join('')}<button type=\"button\" class=\"soft\" onclick=\"familySeparate336()\">${esc(familyWords340('independent'))}</button>`;\n};\nconst renderFamiliesBefore340=renderFamilySuggestions336;\nrenderFamilySuggestions336=function(...args){if(!editingArticleCode&&familyDraft336?.key&&!familyDraft336.shared340){const group=productFamilies336().find(g=>g.key===familyDraft336.key);if(group){familyDraft336.shared340=sharedFields340(group);for(const f of familyDraft336.shared340)if(f.chosen===null)setSharedValue340(f.key,f.key==='policy'?'review':f.key==='allergens'?'[]':'')}}const out=renderFamiliesBefore340.apply(this,args);if(!editingArticleCode){const field=familyEl336('aeMhdPolicy144')?.closest?.('.field');if(field)field.hidden=!!familyDraft336?.key}return out};\nfunction missingSharedMessage341(fields){\n const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';\n const words={de:{policy:'Bitte eine MHD-Regel auswählen: erforderlich, optional oder keines.',allergens:'Bitte die Allergene auswählen, auch wenn keine enthalten sind.',category:'Bitte eine Artikelart auswählen.',storage:'Bitte einen Lagerbereich auswählen.',shopping:'Bitte einen Einkaufsbereich auswählen.',days:'Bitte die Haltbarkeit in Tagen auswählen.'},en:{policy:'Select an expiry policy: required, optional or none.',allergens:'Select the allergens, including none when applicable.',category:'Select a product type.',storage:'Select a storage area.',shopping:'Select a shopping area.',days:'Select the shelf life in days.'},pl:{policy:'Wybierz zasady daty ważności: wymagane, opcjonalne lub brak.',allergens:'Wybierz alergeny, także brak, jeśli ich nie ma.',category:'Wybierz rodzaj produktu.',storage:'Wybierz miejsce magazynowania.',shopping:'Wybierz dział zakupów.',days:'Wybierz trwałość w dniach.'}};\n return fields.map(f=>(words[lang]||words.de)[f.key]).join(' ');\n}\nfunction groupReady340(){\n if(editingArticleCode||!familyDraft336?.key)return true;\n const pending=sharedPending340();if(pending.length){showFeedback(pending.map(f=>familyWords340(f.key)).join(', '),'warn',7000,missingSharedMessage341(pending));return false}\n const size=Number(String(familyEl336('aeContentQty')?.value||'').replace(',','.')),unit=familyEl336('aeContentUnit')?.value;\n if(!(Number.isFinite(size)&&size>0)||!['Stück','g','kg','ml','Liter'].includes(unit)||(unit==='Stück'&&!Number.isSafeInteger(size))){showFeedback(familyWords340('content'),'warn',4500,familyWords340('sizeMissing'));return false}\n return true;\n}\nconst saveFamilyBefore340=saveArticleEditor;\nsaveArticleEditor=function(...args){if(!groupReady340())return;if(!editingArticleCode)familyNameChanged336();if(!groupReady340())return;return saveFamilyBefore340.apply(this,args)};\nconst guidedGoBefore340=guidedArticleGo300;\nguidedArticleGo300=function(direction){if(direction>0&&guidedArticle300?.step==='name'&&familyDraft336?.key&&!groupReady340())return;return guidedGoBefore340(direction)};\nconst saveGuidedBefore340=saveGuidedArticle300;\nsaveGuidedArticle300=function(...args){if(!groupReady340())return;return saveGuidedBefore340.apply(this,args)};\n"},{"src":"stock-polish342.js?v=1","code":"'use strict';\n// Sort display copies, preserving product identity, stock, lots and stored order.\nfunction compareProductRows342(x,y){return String(x[1].name||'').localeCompare(String(y[1].name||''),'de',{numeric:true,sensitivity:'base'})||String(x[0]).localeCompare(String(y[0]));}\nconst poolsBefore342=stockPools301;\nstockPools301=function(...args){return poolsBefore342.apply(this,args).map(p=>({...p,members:p.members.slice().sort(compareProductRows342)})).sort((a,b)=>String(a.summary?.name||a.article?.name||a.members[0]?.[1].name||'').localeCompare(String(b.summary?.name||b.article?.name||b.members[0]?.[1].name||''),'de',{numeric:true,sensitivity:'base'})||String(a.key).localeCompare(String(b.key)));};\n// Core polling now runs every two seconds while visible. Existing single-flight,\n// revision and open-form guards remain in charge of every request and apply.\nconst endGuideBefore342=endGuide194;\nendGuide194=function(...args){const result=endGuideBefore342.apply(this,args);requestCloudCheck326();return result;};\n"},{"src":"admin-storage343.js?v=1","code":"'use strict';\n// View-only storage filters scoped to the selected category. Never alter articles.\nglobalThis.adminStorageFilter343='';\nfunction adminStorageWords343(){const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';return({de:{label:'Lagerbereich',all:'Alle Lagerbereiche'},en:{label:'Storage area',all:'All storage areas'},pl:{label:'Miejsce magazynowania',all:'Wszystkie miejsca'}})[lang]||{label:'Lagerbereich',all:'Alle Lagerbereiche'};}\nfunction adminStorageAreas343(rows){const counts=new Map();for(const [,a] of rows){const area=a.storage||'Allgemein';counts.set(area,(counts.get(area)||0)+1);}return [...counts].sort(([a],[b])=>a.localeCompare(b,'de',{numeric:true,sensitivity:'base'}));}\nfunction renderAdminStorageFilters343(rows){\n const areas=adminStorageAreas343(rows),host=document.getElementById('adminStorageFilters343');\n if(globalThis.adminStorageFilter343&&!areas.some(([area])=>area===globalThis.adminStorageFilter343))globalThis.adminStorageFilter343='';\n if(!host)return;host.hidden=!areas.length;if(!areas.length){host.innerHTML='';return;}\n const words=adminStorageWords343(),selected=globalThis.adminStorageFilter343;\n const button=(value,label,count)=>`<button type=\"button\" class=\"adminStorageButton343${selected===value?' active':''}\" data-storage343=\"${esc(value)}\" aria-pressed=\"${selected===value}\" onclick=\"selectAdminStorage343('${escJs(value)}')\"><span>${esc(label)}</span><small>${count}</small></button>`;\n host.innerHTML=`<small class=\"adminStorageLabel343\">${esc(words.label)}</small><div class=\"adminStorageBar343\" role=\"group\" aria-label=\"${esc(words.label)}\">${button('',words.all,rows.length)}${areas.map(([area,count])=>button(area,area,count)).join('')}</div>`;\n}\nfunction selectAdminStorage343(value){\n const rows=Object.entries(state.products||{}).filter(([,a])=>adminFilter==='Alle'||(a.overviewGroup||a.cat||'Sonstiges')===adminFilter);\n if(value&&!adminStorageAreas343(rows).some(([area])=>area===value))return;\n globalThis.adminStorageFilter343=value;renderAdminArticles();\n const host=document.getElementById('adminStorageFilters343');if(host)for(const b of host.querySelectorAll('[data-storage343]'))if(b.dataset.storage343===value){b.focus({preventScroll:true});break;}\n}\nconst categoryFilterBefore343=setAdminFilter;\nsetAdminFilter=function(...args){globalThis.adminStorageFilter343='';return categoryFilterBefore343.apply(this,args);};\n"},{"src":"shopping-mobile344.js?v=2","code":"'use strict';\n// Mobile presentation adapters retain all original row keys and action handlers.\nfunction shoppingMobileWords344(){const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';return ({de:{actions:'Artikelaktionen',route:'Bereichsreihenfolge',add:'Artikel hinzufügen',hint:'Nur Einkaufsliste, keine Lagerbuchung.',tick:'Abhaken bucht noch keinen Wareneingang.'},en:{actions:'Item actions',route:'Area order',add:'Add item',hint:'Shopping list only, no stock booking.',tick:'Checking items does not book stock.'},pl:{actions:'Działania dla pozycji',route:'Kolejność działów',add:'Dodaj pozycję',hint:'Tylko lista zakupów, bez zmiany stanu.',tick:'Zaznaczenie nie przyjmuje towaru do magazynu.'}})[lang]||{actions:'Artikelaktionen',route:'Bereichsreihenfolge',add:'Artikel hinzufügen',hint:'Nur Einkaufsliste, keine Lagerbuchung.',tick:'Abhaken bucht noch keinen Wareneingang.'};}\nfunction shoppingMenu344(kind,label,buttons){return `<details class=\"shoppingMenu344 ${kind} shoppingNoPrint\"><summary aria-label=\"${esc(label)}\"><span aria-hidden=\"true\">⋯</span></summary><div class=\"shoppingMenuButtons344\">${buttons}</div></details>`;}\nconst shoppingRowBefore344=shoppingAreaRow173;\nshoppingAreaRow173=function(row){const html=shoppingRowBefore344(row);if(!row.manual174)return html;const buttons=html.match(/<span class=\"shoppingManualActions174 shoppingNoPrint\">([\\s\\S]*?)<\\/span>/)?.[1];return buttons?html.replace(/<\\/div>$/,shoppingMenu344('shoppingItemMenu344',shoppingMobileWords344().actions+' '+row.name,buttons)+'</div>'):html;};\nconst shoppingAreaBefore344=shoppingAreaHtml173;\nshoppingAreaHtml173=function(g,area,index){let html=shoppingAreaBefore344(g,area,index);const controls=html.match(/<span class=\"shoppingNoPrint shoppingMove173\">([\\s\\S]*?)<\\/span>/)?.[1];if(controls)html=html.replace('</summary><div class=\"shoppingAreaBody173\">','</summary>'+shoppingMenu344('shoppingAreaMenu344',shoppingMobileWords344().route+' '+area.label,controls)+'<div class=\"shoppingAreaBody173\">');return html;};\nconst shoppingFormBefore344=shoppingManualForm174;\nshoppingManualForm174=function(...args){const w=shoppingMobileWords344();return shoppingFormBefore344.apply(this,args).replace('<summary>+ Freien Artikel auf die Einkaufsliste setzen</summary>',`<summary><span class=\"shoppingDesktopText344\">+ Freien Artikel auf die Einkaufsliste setzen</span><span class=\"shoppingMobileText344\">+ ${esc(w.add)}</span></summary>`).replace('<form onsubmit=',`<p class=\"shoppingMobileText344 shoppingManualHint344\">${esc(w.hint)}</p><form onsubmit=`);};\nconst shoppingRenderBefore344=renderSelfShopping;\nrenderSelfShopping=function(...args){const result=shoppingRenderBefore344.apply(this,args),hint=document.querySelector('#purchasingListPage .shoppingMobileIntro344');if(hint)hint.textContent=shoppingMobileWords344().tick;return result;};\n// Supplier-only mobile view: area order remains authoritative but headings disappear.\nfunction flattenShoppingAreas345(){if(typeof window==='undefined'||window.innerWidth>820)return;for(const area of document.querySelectorAll('#selfShoppingInline .shoppingArea173,#selfShoppingBody .shoppingArea173'))area.open=true;}\nconst shoppingRenderBefore345=renderSelfShopping;\nrenderSelfShopping=function(...args){const result=shoppingRenderBefore345.apply(this,args);flattenShoppingAreas345();return result;};\nif(typeof window!=='undefined')window.addEventListener('resize',flattenShoppingAreas345);\n"},{"src":"recipe-portion-preview346.js?v=1","code":"'use strict';\n// Show the existing economic calculation; never calculate a weight cost as a portion cost.\nfunction recipePreviewWords346(){const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';return ({de:{cost:'Kosten/Portion',missing:'Portionskosten offen',undefined:'Portionsgröße nicht festgelegt'},en:{cost:'Cost/portion',missing:'Portion cost incomplete',undefined:'Portion size not set'},pl:{cost:'Koszt/porcję',missing:'Koszt porcji niepełny',undefined:'Wielkość porcji nieustalona'}})[lang]||{cost:'Kosten/Portion',missing:'Portionskosten offen',undefined:'Portionsgröße nicht festgelegt'};}\nfunction recipePortionPreview346(recipe,meta){const w=recipePreviewWords346(),portions=recipePortioning108(recipe)||/^(Portion(?:en)?|portion(?:s)?|porcj[ae])$/i.test(String(recipe.yieldUnit||'Portionen'));if(!portions)return w.undefined;if(!meta.complete||meta.portionInvalid||!(Number(meta.basis)>0)||!Number.isFinite(Number(meta.unitCost))||Number(meta.unitCost)<0)return w.missing;return w.cost+': '+moneyFmt(Number(meta.unitCost));}\n"},{"src":"recipe-general348.js?v=407&release=411","code":"'use strict';\n// General recipe ingredients use confirmed group identity, never fuzzy stock merging.\nfunction ingredientText348(de,en,pl){const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';return lang==='en'?en:lang==='pl'?pl:de}\nfunction ingredientFood348(a){return !!a&&(!a.cat||a.cat==='Lebensmittel')}\nfunction ingredientStandalone348(code){\n const a=state.products?.[code];if(!a)return false;if(!a.mainArticleId)return true;\n // Loading/restoring legacy products assigns each standalone article its own group.\n // That technical identity must not break an already saved standalone average.\n const own='main-'+String(a.internalId||code).toLowerCase().replace(/[^a-z0-9]+/g,'-');\n return a.mainArticleId===own&&mainMembers(own).length===1;\n}\nfunction ingredientGroups348(ing){\n const key=ingredientNameKey80(ing.name||''),ids=new Set();\n if(ing.mainArticleId)ids.add(ing.mainArticleId);\n const direct=(ing.productId&&productCodeByInternalId(ing.productId))||ing.productCode;\n if(state.products[direct]?.mainArticleId)ids.add(state.products[direct].mainArticleId);\n for(const [code,a] of Object.entries(state.products||{})){\n  const g=mainGroup(a.mainArticleId);\n  if(ingredientFood348(a)&&g&&(ingredientNameKey80(g.name)===key||ingredientNameKey80(a.name)===key))ids.add(g.id||a.mainArticleId);\n }\n return [...ids].map(id=>({id,g:mainGroup(id),codes:mainMembers(id).map(([code])=>code).filter(code=>ingredientFood348(state.products[code]))})).filter(x=>x.g&&x.codes.length);\n}\nconst ingredientCandidatesBefore348=ingredientCandidates;\ningredientCandidates=function(ing){\n if(ing.valuation==='average'&&ing.mainArticleId)return mainMembers(ing.mainArticleId).map(([code])=>code).filter(code=>ingredientFood348(state.products[code]));\n const codes=ingredientCandidatesBefore348(ing);\n return ing.averageFamily348?codes.filter(ingredientStandalone348):codes;\n};\nconst ingredientReferenceValueBefore348=ingredientReferenceValue;\ningredientReferenceValue=function(ing){return ing.valuation==='average'&&ing.mainArticleId?'average-group:'+ing.mainArticleId:ing.valuation==='average'&&ing.averageFamily348?'average-family348':ingredientReferenceValueBefore348(ing)};\nconst ingredientReferenceOptionsBefore348=ingredientReferenceOptions;\ningredientReferenceOptions=function(ing,currentRecipeId){\n if(ing.kind==='packaging'||ing.recipeId)return ingredientReferenceOptionsBefore348(ing,currentRecipeId);\n if(ing.valuation==='average'&&ing.mainArticleId&&!mainMembers(ing.mainArticleId).length)return `<option value=\"average-group:${esc(ing.mainArticleId)}\" selected>${ingredientText348('Produktgruppe fehlt','Product group missing','Brak grupy produktów')} · ${esc(ing.name||'Zutat')}</option>`;\n if((ing.mainArticleId&&!mainMembers(ing.mainArticleId).length)||(ing.productCode&&!state.products[ing.productCode])||(ing.productId&&!productCodeByInternalId(ing.productId)))return ingredientReferenceOptionsBefore348(ing,currentRecipeId);\n const groups=ingredientGroups348(ing),plain=ingredientCandidatesBefore348(ing).filter(ingredientStandalone348);\n const codes=[...new Set([...groups.flatMap(x=>x.codes),...plain])],current=ingredientReferenceValue(ing);\n if(!codes.length)return ingredientReferenceOptionsBefore348(ing,currentRecipeId);\n const general=groups.filter(x=>ing.mainArticleId===x.id||!x.codes.every(ingredientStandalone348)).map(x=>({value:'average-group:'+x.id,name:x.g.name}));\n if(plain.length)general.push({value:'average-family348',name:recipeProductBaseName(state.products[plain[0]])});\n if(ing.valuation==='average'&&!ing.mainArticleId&&!ing.averageFamily348)general.push({value:'average',name:ing.name});\n // Preserve explicit choices in saved recipes. Defaults apply to newly chosen ingredients.\n const selected=ing.productCode||ing.productId||ing.mainArticleId||ing.valuation?(ing.mainArticleId&&!ing.valuation&&!ing.productCode&&!ing.productId?'average-group:'+ing.mainArticleId:current):general.length===1?general[0].value:'';\n const old=ingredientReferenceOptionsBefore348({...ing,mainArticleId:'',valuation:'',productCode:'',productId:''},currentRecipeId);\n const recipeBlock=old.match(/<optgroup label=\"Vorbereitete Rezeptbestandteile\">[\\s\\S]*?<\\/optgroup>/)?.[0]||'';\n return (general.length>1?`<option value=\"\" ${!selected?'selected':''}>${ingredientText348('Zutat auswählen','Choose ingredient','Wybierz składnik')}</option>`:'')+\n `<optgroup label=\"${ingredientText348('Allgemeine Zutat','General ingredient','Składnik ogólny')}\">${general.map(x=>`<option value=\"${esc(x.value)}\" ${selected===x.value?'selected':''}>${esc(x.name)} · ${ingredientText348('alle Varianten','all variants','wszystkie warianty')}</option>`).join('')}</optgroup>`+\n `<optgroup label=\"${ingredientText348('Bestimmte Variante (optional)','Specific variant (optional)','Konkretny wariant (opcjonalnie)')}\">${codes.map(code=>`<option value=\"product:${esc(code)}\" ${selected==='product:'+code?'selected':''}>${esc(state.products[code].name)}</option>`).join('')}</optgroup>`+recipeBlock.replace(/ selected/g,'');\n};\nfunction ingredientSuggestions348(query){\n const key=mobileNormalize(query.trim());if(key.length<2)return[];\n const rows=new Map();\n for(const [code,a] of Object.entries(state.products||{})){\n  if(!ingredientFood348(a))continue;\n  const g=mainGroup(a.mainArticleId),name=g?.name||recipeProductBaseName(a),hay=[a.name,name,...(Array.isArray(a.synonyms)?a.synonyms:[])].join(' ');\n  if(!mobileNormalize(hay).includes(key))continue;\n  const ref=g?'average-group:'+a.mainArticleId:'average-family348',id=g?a.mainArticleId:'name:'+ingredientNameKey80(name);\n  if(!rows.has(id))rows.set(id,{name,ref,count:g?mainMembers(a.mainArticleId).length:ingredientCandidatesBefore348({name}).length});\n }\n return [...rows.values()].sort((a,b)=>Number(!mobileNormalize(a.name).startsWith(key))-Number(!mobileNormalize(b.name).startsWith(key))||a.name.localeCompare(b.name,'de')).slice(0,8);\n}\nconst updateIngredientMatchHintBefore348=updateIngredientMatchHint;\nupdateIngredientMatchHint=function(input,gi,ri){\n const row=input.closest('.ingredientRow');\n if(row?.dataset.kind==='packaging')return updateIngredientMatchHintBefore348(input,gi,ri);\n const ref=row?.querySelector('.riRef'),menu=row?.querySelector('.ingredientSuggestions175');if(!ref||!menu)return;\n ref.innerHTML=ingredientReferenceOptions({name:input.value.trim(),kind:'food'},'');\n row.dataset.pin='false';row.dataset.legacyGroup='';const supplier=row.querySelector('.riSupplier');if(supplier)supplier.value='';\n const hint=row.querySelector('.ingredientMatchHint');if(hint)hint.innerHTML='';\n const hits=ingredientSuggestions348(input.value);\n menu.innerHTML=hits.map(x=>`<button type=\"button\" role=\"option\" aria-selected=\"false\" data-general348=\"${esc(x.ref)}\" data-name348=\"${esc(x.name)}\" onmousedown=\"event.preventDefault()\" onclick=\"chooseIngredient175(this,${gi},${ri})\">${esc(x.name)}<small>${ingredientText348('Alle Varianten · Durchschnittspreis','All variants · average cost','Wszystkie warianty · średni koszt')}${x.count>1?' · '+x.count:''}</small></button>`).join('');menu.hidden=!hits.length;\n scheduleRecipeAutosave(gi,ri);\n};\nconst chooseIngredientBefore348=chooseIngredient175;\nchooseIngredient175=function(button,gi,ri){\n const target=button.dataset.general348;if(!target)return chooseIngredientBefore348(button,gi,ri);\n const row=button.closest('.ingredientRow'),input=row?.querySelector('.riName'),ref=row?.querySelector('.riRef');if(!input||!ref)return;\n input.value=button.dataset.name348;\n const ing={name:input.value,valuation:'average',averageFamily348:target==='average-family348',mainArticleId:target.startsWith('average-group:')?target.slice(14):''};\n ref.innerHTML=ingredientReferenceOptions(ing,'');ref.value=target;\n row.querySelector('.ingredientSuggestions175').hidden=true;\n changeRecipeVariant(ref,gi,ri);\n};\nconst syncIngredientReferenceBefore348=syncIngredientReference;\nsyncIngredientReference=function(sel,gi,ri){\n if(sel.value==='average-family348'){const row=sel.closest('.ingredientRow');if(!row)return;const name=row.querySelector('.riName'),supplier=row.querySelector('.riSupplier');name.value=recipeProductBaseName({name:name.value});if(supplier){supplier.value='';supplier.disabled=false}return}\n if(!sel.value.startsWith('average-group:'))return syncIngredientReferenceBefore348(sel,gi,ri);\n const g=mainGroup(sel.value.slice(14)),row=sel.closest('.ingredientRow');if(!g||!row)return;\n row.querySelector('.riName').value=g.name;const supplier=row.querySelector('.riSupplier');if(supplier){supplier.value='';supplier.disabled=false}\n};\n\n// The reference selector must follow the ingredient query, including subrecipes.\nconst ingredientReferenceOptionsBefore349=ingredientReferenceOptions;\ningredientReferenceOptions=function(ing,currentRecipeId){\n const html=ingredientReferenceOptionsBefore349(ing,currentRecipeId);\n const words=mobileNormalize(ing.name||'').trim().split(/\\s+/).filter(Boolean);\n return html.replace(/<optgroup label=\"Vorbereitete Rezeptbestandteile\">[\\s\\S]*?<\\/optgroup>/g,()=>{\n  if(!words.length)return '';\n  const matches=(state.recipeGroups||[]).flatMap(g=>g.recipes||[]).filter(r=>r.id!==currentRecipeId&&words.every(word=>mobileNormalize(r.name||'').includes(word)));\n  if(!matches.length)return '';\n  return `<optgroup label=\"${ingredientText348('Passende Rezeptbestandteile','Matching subrecipes','Pasujące podreceptury')}\">${matches.map(r=>`<option value=\"recipe:${esc(r.id)}\" ${ing.recipeId===r.id?'selected':''}>${esc(r.name)} (${esc(r.yieldUnit||'Einheit')})</option>`).join('')}</optgroup>`;\n });\n};\n"},{"src":"planning-work353.js?v=406","code":"'use strict';\nfunction workText353(de,en,pl){const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';return lang==='en'?en:lang==='pl'?pl:de}\nfunction recipeCourse353(id){\n const f=findRecipeById(id);if(!f)return null;\n const direct=f.recipe?.course||f.recipe?.mealCourse;\n if(['starter','main','dessert'].includes(direct))return direct;\n const group=String(f.group?.name||f.group||'').toLowerCase();\n if(/dessert|nachspeis|süßspeis|deser/.test(group))return 'dessert';\n if(/vorspeis|starter|appetizer|przystawk/.test(group))return 'starter';\n return 'main';\n}\nfunction courseRecipeOptions353(course,selected){\n const all=allRecipeOptions(),matches=all.filter(x=>recipeCourse353(x.id)===course);\n let out=`<option value=\"\">${workText353('Rezept auswählen','Choose recipe','Wybierz przepis')}</option>`;\n if(selected&&!matches.some(x=>x.id===selected)){\n  const old=all.find(x=>x.id===selected);\n  out+=`<option value=\"${esc(selected)}\" selected>${esc(old?.label||selected)} · ${workText353('Kategorie prüfen','Check category','Sprawdź kategorię')}</option>`;\n }\n return out+matches.map(x=>`<option value=\"${esc(x.id)}\" ${x.id===selected?'selected':''}>${esc(x.label)}</option>`).join('');\n}\nconst courseRowBefore353=courseRow106;\ncourseRow106=function(l,i){return courseRowBefore353(l,i).replace(recipeSelectOptions(l.recipeId||''),courseRecipeOptions353(course106(l),l.recipeId||''))};\nconst renderDraftBefore353=renderCateringDraftRows;\nrenderCateringDraftRows=function(){\n const el=document.getElementById('cateringRecipeRows');if(!el)return;\n // Retain draft semantics and manual quantities; change presentation only.\n const rows=draftCateringLines.map((l,i)=>({l,i}));\n el.innerHTML=courses106.map(([id,label])=>{\n  const group=rows.filter(x=>course106(x.l)===id);\n  if(!group.length)return '';\n  return `<section class=\"courseCompact353\"><h3>${esc(label)}</h3>${group.map(({l,i})=>courseRow106(l,i)).join('')}</section>`;\n }).join('')+`<div class=\"courseAdd353\">${courses106.map(([id,label])=>`<button type=\"button\" class=\"soft\" onclick=\"addCateringRecipeLine('${id}')\">+ ${esc(label)}</button>`).join('')}</div>`;\n};\nfunction cancelPlanning353(){askConfirm(workText353('Ungespeicherte Änderungen verwerfen?','Discard unsaved changes?','Odrzucić niezapisane zmiany?'),()=>{clearCateringDraft(false);setPlanningSection('overview')},workText353('Änderungen verwerfen','Discard changes','Odrzuc zmiany'),workText353('Einsatzplanung','Event planning','Planowanie wydarzenia'))}\nfunction simplifyPlan353(){\n const host=document.getElementById('planningSection_entry');if(!host||host.dataset.simple353)return;\n host.dataset.simple353='true';\n const folds=host.querySelectorAll('.planFold'),basic=folds[0];if(basic)basic.open=true;\n const intro=host.querySelector('.sectionIntro');if(intro)intro.textContent=workText353('Grunddaten eintragen, Speisen auswählen und speichern.','Enter details, choose dishes and save.','Wpisz dane, wybierz dania i zapisz.');\n const body=basic?.querySelector('.planFoldBody');\n if(body){\n  const more=document.createElement('details');more.className='planMore353';more.innerHTML=`<summary>${workText353('Ort, Fahrt & Umsatz (optional)','Location, travel & revenue (optional)','Miejsce, dojazd i przychód (opcjonalnie)')}</summary><div class=\"planMoreBody353\"></div>`;\n  const target=more.lastElementChild;\n  for(const id of ['planAddress','planReadyTime','planTravelMinutes','planTravelConfirmed','planBufferMinutes','planRevenue']){\n   const input=document.getElementById(id),field=input?.closest('.field');if(field&&!target.contains(field))target.appendChild(field);\n  }\n  const route=document.getElementById('routeTools148');if(route)target.appendChild(route);\n  const offer=document.getElementById('planOfferArea');if(offer)target.appendChild(offer);\n  body.appendChild(more);\n }\n const costs=document.querySelector('#planCostsFold>summary>span');if(costs)costs.textContent=workText353('3 · Weitere Angaben & Kosten','3 · More details & costs','3 · Dodatkowe dane i koszty');\n const calc=folds[folds.length-1];if(calc)calc.open=true;\n const reset=host.querySelector('button[onclick=\"clearCateringDraft()\"]');if(reset){reset.textContent=workText353('Abbrechen','Cancel','Anuluj');reset.setAttribute('onclick','cancelPlanning353()')}\n const distribute=host.querySelector('[onclick=\"resetDistribution104()\"]');if(distribute){distribute.className='soft';const details=document.createElement('details');details.className='planMore353';details.innerHTML=`<summary>${workText353('Mengenoptionen','Quantity options','Opcje ilości')}</summary>`;distribute.before(details);details.appendChild(distribute)}\n for(const hint of host.querySelectorAll('.drinkSwitchText small'))hint.hidden=true;\n}\nconst applyPlanningBefore353=applyPlanningSection;\napplyPlanningSection=function(...args){const r=applyPlanningBefore353(...args);simplifyPlan353();if(planningSection==='entry'){const first=document.querySelector('#planningSection_entry .planFold');if(first)first.open=true}return r};\nconst newPlanningBefore353=newPlanningEntry;\nnewPlanningEntry=function(...args){const r=newPlanningBefore353(...args);const basic=document.querySelector('#planningSection_entry .planFold');if(basic)basic.open=true;return r};\nfunction compactQuantity353(q,u){\n let unit=normalizeUnit(u),qty=Number(q)||0;\n if(unit==='kg'&&qty>0&&qty<1){qty*=1000;unit='g'}\n return formatQtySmart(qty,unit==='l'?'l':u&&unit!=='g'?u:unit);\n}\nfunction ingredientShort353(n){\n let text=workText353('Bedarf: ','Needed: ','Potrzeba: ')+compactQuantity353(n.need,n.unit);\n const a=state.products[n.productCode];if(!a||!(n.need>0))return text;\n const packs=getPacks(a,n.productCode).map((p,i)=>({i,size:packMeasureForNeed(p,a,n.unit,true),orderable:!!p[3]})).filter(x=>x.i>0&&x.size>0);\n let pool=packs.filter(x=>x.orderable);if(!pool.length)pool=packs;\n const best=pool.map(x=>({...x,count:Math.max(1,Math.ceil((n.need-1e-9)/x.size))})).sort((a,b)=>(a.count*a.size-n.need)-(b.count*b.size-n.need)||a.count-b.count||b.size-a.size)[0];\n if(best&&best.count*best.size>n.need+1e-7)text+=' · '+workText353('Bereitstellen: ','Set out: ','Przygotuj: ')+best.count+' × '+compactQuantity353(best.size,n.unit);\n return text;\n}\nfunction taskSection353(name){const labels={'Produktion':['Production','Produkcja'],'Zutaten bereitstellen':['Set out ingredients','Przygotuj składniki'],'Unterrezepte herstellen':['Prepare subrecipes','Przygotuj podreceptury'],'Speisen':['Dishes','Dania'],'Getränke':['Drinks','Napoje'],'Ausstattung':['Equipment','Wyposażenie']};const pair=labels[name];return pair?workText353(name,...pair):name}\nfunction taskPresentation353(c,kind){\n const tasks=kind==='preparation'?preparationAutoTasks(c):loadListAutoTasks(c),needs=kind==='preparation'?cateringNeedDetails(c).needs:[];\n return tasks.map(t=>{\n  const n=t.id.startsWith('ingredient:')?needs.find(n=>'ingredient:'+n.productCode===t.id):null;\n  return {...t,shortMeta353:n?ingredientShort353(n):String(t.meta||'').split(' · ')[0],detailMeta353:t.meta};\n });\n}\nfunction workTaskHtml353(c,t,kind,manual=false){\n const list=kind==='preparation'?ensurePreparation(c):ensureLoadList(c),done=manual?!!t.done:!!list.done[t.id],fn=kind==='preparation'?(manual?'togglePrepManual':'setPrepDone'):(manual?'toggleLoadManual':'setLoadDone');\n const name=t.label||t.name||'Aufgabe',short=t.shortMeta353||t.meta||'';\n return `<article class=\"workTask353 ${done?'done':''}\"><label><input type=\"checkbox\" ${done?'checked':''} aria-label=\"${esc(name)}\" onchange=\"${fn}('${escJs(c.id)}','${escJs(t.id)}',this.checked)\"><span><strong>${esc(name)}</strong>${short?`<small>${esc(short)}</small>`:''}</span></label>${t.detailMeta353&&t.detailMeta353!==short?`<details><summary>${workText353('Details','Details','Szczegóły')}</summary><p>${esc(t.detailMeta353)}</p></details>`:''}${t.missingArticle?`<button type=\"button\" class=\"soft\" onclick=\"mobileOpenPlanning('preparation')\">${workText353('Zuordnung prüfen','Check link','Sprawdź powiązanie')}</button>`:''}</article>`;\n}\nfunction compactWebTasks353(c,kind){\n const host=document.getElementById(kind==='preparation'?'preparationPreview':'loadPreview');if(!host||!c)return;\n for(const t of taskPresentation353(c,kind)){\n  for(const row of host.querySelectorAll('.prepTask')){\n   const check=row.querySelector('input[type=\"checkbox\"]');if(!check?.getAttribute('onchange')?.includes(\"'\"+escJs(t.id)+\"'\"))continue;\n   const meta=row.querySelector('.prepTaskMeta');if(!meta)continue;\n   meta.textContent=t.shortMeta353;\n   if(t.detailMeta353!==t.shortMeta353&&!row.querySelector('.taskDetails353')){\n    const details=document.createElement('details');details.className='taskDetails353';details.innerHTML=`<summary>${workText353('Details','Details','Szczegóły')}</summary><p>${esc(t.detailMeta353)}</p>`;\n    // Details must be outside a checkbox label so opening them never marks done.\n    if(row.tagName==='LABEL')row.after(details);else row.appendChild(details);\n   }\n  }\n }\n}\nconst prepRenderBefore353=renderPreparation;\nrenderPreparation=function(c){const r=prepRenderBefore353(c);compactWebTasks353(c,'preparation');return r};\nconst loadRenderBefore353=renderLoadList;\nrenderLoadList=function(c){const r=loadRenderBefore353(c);compactWebTasks353(c,'loadList');return r};\nlet mobileWorkPhase353='preparation';\nfunction mobileWorkSelect353(phase){if(!['preparation','load','setup','onsite'].includes(phase))return;mobileWorkPhase353=phase;renderMobileTasks()}\nfunction mobilePhaseTasks353(c,phase){\n if(phase==='preparation'||phase==='load'){\n  const kind=phase==='preparation'?'preparation':'loadList',list=kind==='preparation'?ensurePreparation(c):ensureLoadList(c),tasks=taskPresentation353(c,kind),manual=list.manual||[],sections=new Map();\n  for(const t of tasks){if(!sections.has(t.section))sections.set(t.section,[]);sections.get(t.section).push(t)}\n  const total=tasks.length+manual.length,done=tasks.filter(t=>list.done[t.id]).length+manual.filter(t=>t.done).length;\n  return `<p class=\"workProgress353\">${done} / ${total} · ${total?Math.round(done/total*100):0}%</p>`+[...sections].map(([name,rows])=>`<section class=\"workGroup353\"><h3>${esc(taskSection353(name))}</h3>${rows.map(t=>workTaskHtml353(c,t,kind)).join('')}</section>`).join('')+(manual.length?`<section class=\"workGroup353\"><h3>${workText353('Eigene Aufgaben','Own tasks','Własne zadania')}</h3>${manual.map(t=>workTaskHtml353(c,t,kind,true)).join('')}</section>`:'');\n }\n const w=initWorkflowSuggestions(c),blocks=(state.timeBlocks||[]).filter(b=>b.phase===phase&&w.enabledBlocks[b.id]),done=blocks.filter(b=>w.done[b.id]).length;\n return `<p class=\"workProgress353\">${done} / ${blocks.length}</p>`+blocks.map(b=>`<article class=\"workTask353 ${w.done[b.id]?'done':''}\"><label><input type=\"checkbox\" ${w.done[b.id]?'checked':''} onchange=\"setWorkflowBlockDone('${escJs(c.id)}','${escJs(b.id)}',this.checked)\"><span><strong>${esc(b.name)}</strong><small>${workflowBlockMinutes(c,b,false)} min</small></span></label></article>`).join('')+`<button class=\"soft\" onclick=\"mobileOpenPlanning('${phase}')\">${workText353('Weitere Angaben','More details','Dodatkowe dane')}</button>`;\n}\nconst mobileTasksBefore353=renderMobileTasks;\nrenderMobileTasks=function(){\n const c=mobileWorkingPlan(),host=document.getElementById('mobileTasksContent');if(!c||!host)return mobileTasksBefore353();\n const phases=[['preparation',workText353('Vorbereitung','Preparation','Przygotowanie')],['load',workText353('Ladeliste','Loading','Załadunek')],['setup',workText353('Aufbau','Setup','Montaż')],['onsite',workText353('Vor Ort','On site','Na miejscu')]].filter(([id])=>hasPermission(id==='load'?'load':id));\n if(!phases.some(([id])=>id===mobileWorkPhase353))mobileWorkPhase353=phases[0]?.[0]||'';\n host.innerHTML=mobilePlanPicker()+`<section class=\"mobileWorkCard workMobile353\"><h2>${esc(c.event)}</h2><p>${c.date?formatDate(c.date):''}</p><nav class=\"workPhase353\">${phases.map(([id,label])=>`<button class=\"${id===mobileWorkPhase353?'primary':'soft'}\" aria-pressed=\"${id===mobileWorkPhase353}\" onclick=\"mobileWorkSelect353('${id}')\">${label}</button>`).join('')}</nav>${mobileWorkPhase353?mobilePhaseTasks353(c,mobileWorkPhase353):workText353('Keine Aufgabenberechtigung','No task permission','Brak uprawnień do zadań')}</section>`;\n};\n// These existing checkbox actions need the same durable guarantee as prep/load.\nsetWorkflowBlockDone=function(cateringId,blockId,value){\n const b=(state.timeBlocks||[]).find(b=>b.id===blockId);if(!b||!['setup','onsite'].includes(b.phase))return false;\n const result=operationTransaction(workText353('Aufgabenstatus gespeichert','Task status saved','Status zadania zapisany'),b.phase==='setup'?'setup':'onsite',()=>{\n  const c=state.planning.caterings.find(c=>c.id===cateringId);if(!c)throw Error('Einsatz nicht mehr vorhanden.');const w=initWorkflowSuggestions(c);if(!w.enabledBlocks[blockId])throw Error('Aufgabe ist nicht aktiv.');w.done[blockId]=!!value;\n });\n return afterOperation275(result,ok=>{if(!ok){renderPlanning();renderMobileWorkApp()}return ok});\n};\n// Replace fixed business-specific copy in UI only. Stored records and internal\n// movement identifiers remain untouched; user-entered names/inputs are excluded.\nfunction neutralText353(text){return String(text).replace(/Loading list Trude/g,'Loading list').replace(/Load Trude|Load into Trude|Into Trude/g,'Load').replace(/Return from Trude/g,'Return to stock').replace(/Załaduj Trude|Do Trude/g,'Załaduj').replace(/Powrót z Trude/g,'Powrót do magazynu').replace(/In Trude buchen/g,'Beladung buchen').replace(/Ladeliste Trude/g,'Ladeliste').replace(/Trude beladen|In Trude eingeräumt/g,'Beladen').replace(/Trude aufbauen|Aufbau Trude/g,'Aufbau').replace(/Fahrt & Aufbau Trude/g,'Fahrt & Aufbau').replace(/→ In Trude|In Trude einräumen|In Trude/g,'Beladen').replace(/← Aus Trude zurück|Aus Trude zurück ins Lager|Aus Trude zurück/g,'Zurück ins Lager').replace(/Ware in Trude|Ware, die bereits in Trude eingeräumt wurde/g,'bereits geladene Ware').replace(/normaler Verkaufstag mit Trude/g,'normaler Verkaufstag').replace(/Trude-Einräumungen/g,'Beladungen')}\nfunction neutralUi353(){\n if(typeof document==='undefined'||!document.createTreeWalker)return;\n const tree=document.createTreeWalker(document.body,4);let n;while((n=tree.nextNode())){\n  if(n.parentElement?.closest('script,style,input,textarea,#log,.articleInfoIntro,.mobileProductSummary,.recipeCardText,#cateringPlans .cateringMeta'))continue;\n  const next=neutralText353(n.nodeValue);if(next!==n.nodeValue)n.nodeValue=next;\n }\n}\nif(typeof document!=='undefined'&&document.body){simplifyPlan353();neutralUi353();if(typeof MutationObserver==='function'){let queued=false;new MutationObserver(()=>{if(queued)return;queued=true;queueMicrotask(()=>{queued=false;neutralUi353()})}).observe(document.body,{childList:true,subtree:true,characterData:true})}}\n\nconst taskReadyBefore353=taskReady185;\ntaskReady185=function(t,c){const view=taskPresentation353(c,'preparation').find(x=>x.id===t.id);return taskReadyBefore353(view?{...t,meta:view.shortMeta353}:t,c)};\nconst previewBefore353=updateCateringPreview;\nupdateCateringPreview=function(...args){\n const r=previewBefore353(...args),host=document.getElementById('cateringCalcPreview');if(!host)return r;\n const full=host.innerHTML,c=cateringDraftObject(),calc=cateringCalc(c);\n host.innerHTML=`<div class=\"planningMetrics353\"><div><small>${workText353('Kosten','Costs','Koszty')}</small><strong>${moneyFmt(calc.total)}</strong></div><div><small>${workText353('Umsatz netto','Net revenue','Przychód netto')}</small><strong>${moneyFmt(calc.revenue)}</strong></div><div><small>${workText353('Deckungsbeitrag','Contribution','Marża kwotowa')}</small><strong>${moneyFmt(calc.contribution)}</strong></div></div>${calc.free?.length||calc.costIssues?.length||calc.costedIngredients<calc.totalIngredients?`<p class=\"warnText\">${workText353('Kalkulation noch unvollständig. Details prüfen.','Calculation incomplete. Check details.','Kalkulacja niepełna. Sprawdź szczegóły.')}</p>`:''}<details class=\"planMore353\"><summary>${workText353('Kalkulation & Zeitplan ansehen','View calculation & schedule','Zobacz kalkulację i harmonogram')}</summary>${full}</details>`;\n return r;\n};\n\nif(typeof document!=='undefined'&&document.body){\n renderCateringDraftRows();updateCateringPreview();\n const selected=state.planning?.caterings?.find(c=>c.id===activeCateringId);\n if(selected){compactWebTasks353(selected,'preparation');compactWebTasks353(selected,'loadList')}\n if(typeof innerWidth!=='undefined'&&innerWidth<=820)renderMobileTasks();\n neutralUi353();\n}\n\n// Neutral fixed copy also applies to newly generated print documents.\nif(typeof eventPrintDocument135==='function'){\n const printBefore353=eventPrintDocument135;\n eventPrintDocument135=function(...args){\n  const html=printBefore353(...args);if(typeof DOMParser==='undefined')return html;\n  const doc=new DOMParser().parseFromString(html,'text/html'),walk=doc.createTreeWalker(doc.body,4);let node;\n  while((node=walk.nextNode()))if(!node.parentElement?.closest('h1,script,style'))node.nodeValue=neutralText353(node.nodeValue);\n  return '<!doctype html>'+doc.documentElement.outerHTML;\n };\n}\n"},{"src":"kitchen-flow355.js?v=407&release=411","code":"'use strict';\nfunction kitchenAmounts355(n){\n const need=Number(n?.need),raw=n?.stock,stock=raw!==null&&raw!==undefined&&raw!==''?Number(raw):NaN;\n const known=Number.isFinite(stock)&&Number.isFinite(need)&&need>=0;\n return {need:Number.isFinite(need)?Math.max(0,need):null,stock:Number.isFinite(stock)?Math.max(0,stock):null,buy:known?Math.max(0,need-Math.max(0,stock)):null,unit:n?.unit||''};\n}\nfunction kitchenQuantity355(q,unit,need){if(q===null)return workText353('Prüfen','Check','Sprawdź');return compactQuantity353(q,q===0&&normalizeUnit(unit)==='kg'&&need<1?'g':unit)}\nfunction kitchenMetrics355(n){\n const v=kitchenAmounts355(n),labels=[n.availability374?workText353('Verfügbar zum Termin','Available for date','Dostępne w terminie'):workText353('Auf Lager','In stock','Na stanie'),workText353('Benötigt','Needed','Potrzeba'),workText353('Besorgen','To buy','Dokupić')];\n return `<span class=\"kitchenMetrics355 ${v.buy>0?'shortage':''}\">${[v.stock,v.need,v.buy].map((q,i)=>`<span><small>${labels[i]}</small><b>${esc(kitchenQuantity355(q,v.unit,v.need))}</b></span>`).join('')}</span>`;\n}\nfunction kitchenPack355(n){\n // Keep the display concise; original task metadata remains unchanged.\n const full=ingredientShort353(n),parts=full.split(' · ');\n return parts.slice(1).join(' · ');\n}\nfunction kitchenIngredientHtml355(c,t,n){\n n=kitchenAvailableNeed355(c,n);\n const list=ensurePreparation(c),done=!!list.done[t.id],v=kitchenAmounts355(n),pack=kitchenPack355(n);\n return `<article class=\"workTask353 kitchenTask355 ${done?'done':''}\"><label><input type=\"checkbox\" ${done?'checked':''} aria-label=\"${esc(t.label)}\" onchange=\"setPrepDone('${escJs(c.id)}','${escJs(t.id)}',this.checked)\"><span><strong>${esc(t.label)}</strong></span></label>${kitchenMetrics355(n)}${v.buy>0&&hasPermission('shopping')?`<button type=\"button\" class=\"soft kitchenBuy355\" onclick=\"openPurchasingOverview('list')\">${workText353('Zur Einkaufsliste','Shopping list','Lista zakupów')}</button>`:''}${pack?`<details><summary>${workText353('Details','Details','Szczegóły')}</summary><p>${esc(pack)}</p></details>`:''}</article>`;\n}\nfunction kitchenAvailableNeed355(c,n){\n if(typeof availabilityPlan374!=='function'||!c?.date)return n;\n const rows=availabilityPlan374(c).target?.rows||[],group=typeof mainSummaryForProduct==='function'?mainSummaryForProduct(n.productCode):null;\n const row=rows.find(r=>r.productCode===n.productCode||(group?.id&&r.key==='group:'+group.id));\n if(!row)return n;\n const available=convertUnit(Number(row.available),row.unit,n.unit);\n return {...n,stock:available===null?null:available,availability374:true};\n}\nfunction kitchenRecipeBlock356(c,id,subrecipe=false){\n const found=findRecipeById(id);if(!found)return `<p role=\"status\">${workText353('Rezept nicht mehr vorhanden.','Recipe no longer available.','Przepis nie jest już dostępny.')}</p>`;\n const recipe=found.recipe;\n let lines;\n if(subrecipe){\n  lines=(cateringNeedDetails(c).subrecipes||[]).filter(n=>n.recipeId===id).map(n=>({recipeId:id,qty:n.qty,unit:n.unit}));\n }else lines=[...(c.lines||[]),...(c.dayItems||[])].filter(n=>n.recipeId===id&&Number(n.qty)>0);\n if(!lines.length)return `<p role=\"status\">${workText353('Keine geplante Menge vorhanden.','No quantity planned.','Brak zaplanowanej ilości.')}</p>`;\n // Use the existing planning calculation for portions, optional choices, loss and whole batches.\n const d=cateringNeedDetails({lines,dayItems:[]});\n const amounts=lines.map(n=>compactQuantity353(Number(n.qty),n.unit||recipePlanningUnit108(recipe)));\n const row=(name,qty,unit)=>`<li><span>${esc(name)}</span><b>${esc(compactQuantity353(qty,unit))}</b></li>`;\n const ingredients=goodsRows294(d).map(n=>row(n.name,n.need,n.unit)).join('');\n const unresolved=(d.free||[]).map(n=>row(n.name,n.qty,n.unit)+`<li class=\"recipeCheck356\">${workText353('Zuordnung oder Menge prüfen','Check ingredient link or quantity','Sprawdź powiązanie lub ilość')}</li>`).join('');\n const batches=(d.batchPlans||[]).filter(n=>n.recipeId===id).map(n=>`<p>${esc(compactQuantity353(n.produced,n.unit))} · ${n.batches} ${workText353('Ansätze','batches','partii')}</p>`).join('');\n return `<section class=\"recipeBlock356\"><h3>${esc(recipe.name)}</h3><p>${esc(amounts.join(' + '))}</p>${batches}<ul>${ingredients}${unresolved}</ul>${!(ingredients||unresolved)?`<p>${workText353('Keine Zutaten hinterlegt.','No ingredients saved.','Brak zapisanych składników.')}</p>`:''}${(d.errors||[]).length?`<p class=\"recipeCheck356\" role=\"status\">${workText353('Rezeptberechnung prüfen.','Check recipe calculation.','Sprawdź obliczenia przepisu.')}</p>`:''}</section>`;\n}\nfunction kitchenProductionHtml355(c,t){\n const id=t.id.replace(/^(?:recipe|subrecipe):/,''),name=findRecipeById(id)?.recipe?.name||t.label;\n const amount=/Charge/.test(t.meta||'')?String(t.meta).split(' · ').slice(0,2).join(' · '):t.shortMeta353;\n const html=workTaskHtml353(c,{...t,label:name,shortMeta353:workText353('Fertig: ','Prepared: ','Gotowe: ')+amount,detailMeta353:/Charge/.test(t.meta||'')?t.meta:''},'preparation');\n return html+(hasPermission('recipes')?`<details class=\"kitchenRecipe356\"><summary>${workText353('Rezept ansehen','View recipe','Zobacz przepis')}</summary>${kitchenRecipeBlock356(c,id,t.id.startsWith('subrecipe:'))}</details>`:'');\n}\nfunction kitchenStep355(number,title,done,total,body,open){\n return `<details class=\"kitchenStep355\" ${open?'open':''}><summary><span><b>${number}</b>${title}</span><small>${done} / ${total}</small></summary><div class=\"kitchenStepBody355\">${body}</div></details>`;\n}\nconst mobilePhaseBefore355=mobilePhaseTasks353;\nmobilePhaseTasks353=function(c,phase){\n if(phase!=='preparation')return mobilePhaseBefore355(c,phase);\n const list=ensurePreparation(c),tasks=taskPresentation353(c,'preparation'),needs=cateringNeedDetails(c).needs||[],manual=list.manual||[];\n const ingredients=tasks.filter(t=>t.id.startsWith('ingredient:')||t.id.startsWith('free:'));\n const production=tasks.filter(t=>!ingredients.includes(t));\n const count=rows=>rows.filter(t=>list.done[t.id]).length,ingredientsDone=count(ingredients),productionDone=count(production);\n const ready=ingredients.length>0&&ingredientsDone===ingredients.length,prepared=production.length>0&&productionDone===production.length;\n const total=tasks.length+manual.length,done=count(tasks)+manual.filter(t=>t.done).length;\n const rows=ingredients.map(t=>{\n  const n=needs.find(n=>'ingredient:'+n.productCode===t.id);\n  return n?kitchenIngredientHtml355(c,t,n):workTaskHtml353(c,{...t,shortMeta353:workText353('Zuordnung prüfen','Check ingredient link','Sprawdź powiązanie składnika'),detailMeta353:t.meta},'preparation');\n }).join('')||`<p class=\"warnText\">${workText353('Zutaten im Rezept prüfen.','Check recipe ingredients.','Sprawdź składniki przepisu.')}</p>`;\n let html=`<p class=\"workProgress353\">${done} / ${total} · ${total?Math.round(done/total*100):0}%</p>`;\n html+=kitchenStep355(1,workText353('Zutaten bereitstellen','Set out ingredients','Przygotuj składniki'),ingredientsDone,ingredients.length,rows,!ready);\n html+=kitchenStep355(2,workText353('Zubereiten','Prepare dishes','Przygotuj dania'),productionDone,production.length,production.map(t=>kitchenProductionHtml355(c,t)).join(''),ready&&!prepared);\n if(hasPermission('load')){\n  const load=ensureLoadList(c),loadTasks=loadListAutoTasks(c),loadManual=load.manual||[],loadTotal=loadTasks.length+loadManual.length,loadDone=loadTasks.filter(t=>load.done[t.id]).length+loadManual.filter(t=>t.done).length;\n  html+=kitchenStep355(3,workText353('Beladen','Load','Załaduj'),loadDone,loadTotal,`<button class=\"primary\" onclick=\"mobileWorkSelect353('load')\">${workText353('Zur Ladeliste','Open loading list','Otwórz listę załadunku')}</button>`,prepared&&loadDone<loadTotal);\n }\n if(manual.length)html+=`<section class=\"workGroup353\"><h3>${workText353('Eigene Aufgaben','Own tasks','Własne zadania')}</h3>${manual.map(t=>workTaskHtml353(c,t,'preparation',true)).join('')}</section>`;\n return html;\n};\nconst prepRenderBefore355=renderPreparation;\nrenderPreparation=function(c){\n const result=prepRenderBefore355(c),host=document.getElementById('preparationPreview');if(!host||!c)return result;\n const needs=cateringNeedDetails(c).needs||[],tasks=taskPresentation353(c,'preparation');\n for(const t of tasks){const n=needs.find(n=>'ingredient:'+n.productCode===t.id);if(!n)continue;\n  for(const row of host.querySelectorAll('.prepTask')){\n   if(!row.querySelector('input')?.getAttribute('onchange')?.includes(\"'\"+escJs(t.id)+\"'\"))continue;\n   if(row.nextElementSibling?.classList.contains('taskDetails353'))row.nextElementSibling.remove();\n   row.outerHTML=kitchenIngredientHtml355(c,t,n);\n  }\n }\n // Move the shared ingredient section before production without changing checks.\n const sections=[...host.querySelectorAll('.prepSection')],ingredients=sections.find(s=>s.querySelector('.kitchenTask355')||s.querySelector('input[onchange*=\"free:\"]'));\n if(ingredients&&sections[0]!==ingredients)host.insertBefore(ingredients,sections[0]);\n if(typeof renderOperationsEntry==='function')renderOperationsEntry();\n return result;\n};\nif(typeof document!=='undefined'&&document.body){\n const c=state.planning?.caterings?.find(c=>c.id===activeCateringId);if(c)renderPreparation(c);\n if(typeof innerWidth!=='undefined'&&innerWidth<=820)renderMobileTasks();\n}\n"},{"src":"mobile-event357.js?v=1","code":"'use strict';\n// View-only accordion state: never part of the shared operating data.\nconst mobileEventOpen357=new Map();\nfunction mobileEventPhases357(c){\n return [\n  ['preparation',workText353('Vorbereiten','Prepare','Przygotuj')],\n  ['load',workText353('Beladen','Load','Załaduj')],\n  ['setup',workText353('Aufbauen','Set up','Rozstaw')],\n  ['onsite',workText353('Vor Ort','On site','Na miejscu')]\n ].filter(([id])=>hasPermission(id)).map(([id,label])=>{\n  let tasks,done;\n  if(id==='preparation'||id==='load'){\n   const kind=id==='preparation'?'preparation':'loadList',list=id==='preparation'?ensurePreparation(c):ensureLoadList(c);\n   tasks=taskPresentation353(c,kind);const manual=list.manual||[];\n   done=tasks.filter(t=>list.done[t.id]).length+manual.filter(t=>t.done).length;\n   return {id,label,done,total:tasks.length+manual.length};\n  }\n  const w=initWorkflowSuggestions(c);\n  tasks=(state.timeBlocks||[]).filter(b=>b.phase===id&&w.enabledBlocks[b.id]);\n  done=tasks.filter(b=>w.done[b.id]).length;\n  return {id,label,done,total:tasks.length};\n });\n}\nfunction mobileEventPreparation357(c){\n const list=ensurePreparation(c),tasks=taskPresentation353(c,'preparation'),needs=cateringNeedDetails(c).needs||[];\n const ingredients=tasks.filter(t=>t.id.startsWith('ingredient:')||t.id.startsWith('free:'));\n const production=tasks.filter(t=>!ingredients.includes(t));\n const section=(title,rows)=>rows?`<section class=\"eventTasks357\"><h3>${title}</h3>${rows}</section>`:'';\n const rows=ingredients.map(t=>{const n=needs.find(n=>'ingredient:'+n.productCode===t.id);return n?kitchenIngredientHtml355(c,t,n):workTaskHtml353(c,{...t,shortMeta353:workText353('Zuordnung prüfen','Check ingredient link','Sprawdź powiązanie składnika'),detailMeta353:t.meta},'preparation')}).join('');\n return section(workText353('Zutaten','Ingredients','Składniki'),rows)+\n section(workText353('Zubereiten','Prepare dishes','Przygotuj dania'),production.map(t=>kitchenProductionHtml355(c,t)).join(''))+\n section(workText353('Eigene Aufgaben','Own tasks','Własne zadania'),(list.manual||[]).map(t=>workTaskHtml353(c,t,'preparation',true)).join(''));\n}\nfunction mobileEventPhaseBody357(c,phase){\n const html=phase==='preparation'?mobileEventPreparation357(c):mobilePhaseTasks353(c,phase).replace(/^<p class=\"workProgress353\">[\\s\\S]*?<\\/p>/,'');\n return html||`<p class=\"eventEmpty357\">${workText353('Keine Aufgaben','No tasks','Brak zadań')}</p>`;\n}\nfunction mobileEventSelected357(c,phases){\n const saved=mobileEventOpen357.get(c.id);\n if(saved===''||phases.some(p=>p.id===saved))return saved;\n return (phases.find(p=>p.total>p.done)||phases.find(p=>p.total>0)||phases[0])?.id||'';\n}\nfunction mobileEventToggle357(event,phase){\n event.preventDefault();const c=mobileWorkingPlan();if(!c||!hasPermission(phase))return false;\n const host=document.getElementById('mobileTasksContent');if(!host)return false;\n const rows=[...host.querySelectorAll('details[data-event-phase357]')],row=rows.find(el=>el.dataset.eventPhase357===phase);\n if(!row)return false;\n const next=!row.open;for(const el of rows){el.open=next&&el===row;el.querySelector('summary')?.setAttribute('aria-expanded',String(el.open));}\n mobileEventOpen357.set(c.id,next?phase:'');return false;\n}\n// Retain existing callers that request a particular phase, including permission checks.\nmobileWorkSelect353=function(phase){if(!['preparation','load','setup','onsite'].includes(phase)||!hasPermission(phase))return;const c=mobileWorkingPlan();if(!c)return;mobileEventOpen357.set(c.id,phase);renderMobileTasks()};\nconst mobileTasksBefore357=renderMobileTasks;\nrenderMobileTasks=function(){\n const c=mobileWorkingPlan(),host=document.getElementById('mobileTasksContent');if(!c||!host)return mobileTasksBefore357();\n const phases=mobileEventPhases357(c),selected=mobileEventSelected357(c,phases);\n host.innerHTML=`<details class=\"eventPicker357\"><summary>${workText353('Einsatz wechseln','Change event','Zmień wydarzenie')}</summary>${mobilePlanPicker()}</details><section class=\"mobileWorkCard workMobile353 eventMobile357\"><header><h2>${esc(c.event)}</h2><p>${c.date?esc(formatDate(c.date)):''}</p></header>${phases.map(p=>{\n  const open=p.id===selected,complete=p.total>0&&p.done===p.total;\n  return `<details class=\"eventPhase357 ${complete?'complete':''}\" data-event-phase357=\"${p.id}\" ${open?'open':''}><summary aria-expanded=\"${open}\" onclick=\"return mobileEventToggle357(event,'${p.id}')\"><span>${complete?'<span class=\"eventCheck357\" aria-hidden=\"true\">✓</span>':''}${p.label}</span><span class=\"eventPhaseEnd357\">${p.total?`<small>${p.done}/${p.total}</small>`:''}<span class=\"eventChevron357\" aria-hidden=\"true\">›</span></span></summary><div class=\"eventPhaseBody357\">${mobileEventPhaseBody357(c,p.id)}</div></details>`;\n }).join('')||`<p>${workText353('Keine Aufgabenberechtigung','No task permission','Brak uprawnień do zadań')}</p>`}</section>`;\n};\nif(typeof document!=='undefined'&&document.body&&typeof innerWidth!=='undefined'&&innerWidth<=820)renderMobileTasks();\n"},{"src":"mobile-workflow358.js?v=411&release=411","code":"'use strict';\nfunction mobileWorkflowTimes358(c,phase){\n const p=timePlanFor(c,false);if(!p)return `<p class=\"workflowHint358\">${workText353('Zeit noch offen','Time not set','Godzina nieustalona')}</p>`;\n const pair=(label,value)=>`<div><small>${label}</small><b>${esc(value)}</b></div>`;\n const key=phase==='setup'?pair(workText353('Ankunft bis','Arrive by','Przyjazd do'),clockFmt(p.arrival)):pair(workText353('Ausgabe ab','Service from','Wydawanie od'),clockFmt(p.ready));\n const departure=p.travelConfirmed?clockFmt(p.departure):workText353('Noch offen','Not set','Nieustalona');\n return `<div class=\"workflowKey358\">${key}</div><details class=\"workflowMore358\"><summary>${workText353('Zeitplan','Schedule','Harmonogram')}</summary><div class=\"workflowTimes358\">${pair(workText353('Abfahrt','Departure','Wyjazd'),departure)}${pair(workText353('Ankunft bis','Arrive by','Przyjazd do'),clockFmt(p.arrival))}${pair(workText353('Aufbau','Setup','Montaż'),p.setup+' min')}${pair(workText353('Vor Ort','On site','Na miejscu'),p.onsite+' min')}${pair(workText353('Ausgabe ab','Service from','Wydawanie od'),clockFmt(p.ready))}</div>${!p.travelConfirmed?`<p class=\"workflowHint358\">${workText353('Fahrzeit noch nicht bestätigt.','Travel time not confirmed.','Czas podróży niepotwierdzony.')}</p>`:''}${c.address?`<p class=\"workflowHint358\">${esc(c.address)}</p>`:''}</details>`;\n}\nfunction mobileWorkflowAdjust358(c,phase){\n const w=initWorkflowSuggestions(c),blocks=(state.timeBlocks||[]).filter(b=>b.phase===phase);\n if(!blocks.length)return '';\n return `<details class=\"workflowMore358 workflowAdjust358\"><summary>${workText353('Anpassen','Adjust','Dostosuj')}</summary><div data-workflow-adjust358=\"${phase}\" data-event358=\"${esc(c.id)}\">${blocks.map(b=>`<div class=\"workflowAdjustRow358\" data-block358=\"${esc(b.id)}\"><label><input type=\"checkbox\" class=\"workflowUse358\" ${w.enabledBlocks[b.id]?'checked':''}><span>${esc(b.name)}</span></label><label class=\"workflowMinutes358\"><span>${workText353('Minuten','Minutes','Minuty')}</span><input type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" class=\"workflowMinutesInput358\" value=\"${workflowBlockMinutes(c,b,false)}\" aria-label=\"${esc(b.name+' · '+workText353('Minuten','Minutes','Minuty'))}\"></label></div>`).join('')}<button type=\"button\" class=\"primary\" onclick=\"saveWorkflowAdjust358('${escJs(c.id)}','${phase}')\">${workText353('Übernehmen','Apply','Zastosuj')}</button></div></details>`;\n}\nfunction mobileWorkflowBody358(c,phase){\n if(!['setup','onsite'].includes(phase)||!hasPermission(phase))return '';\n const w=initWorkflowSuggestions(c),blocks=(state.timeBlocks||[]).filter(b=>b.phase===phase&&w.enabledBlocks[b.id]);\n const tasks=blocks.map(b=>`<article class=\"workTask353 ${w.done[b.id]?'done':''}\"><label><input type=\"checkbox\" ${w.done[b.id]?'checked':''} onchange=\"setWorkflowBlockDone('${escJs(c.id)}','${escJs(b.id)}',this.checked)\"><span><strong>${esc(b.name)}</strong></span></label></article>`).join('')||`<p class=\"workflowHint358\">${workText353('Keine Aufgaben ausgewählt','No tasks selected','Nie wybrano zadań')}</p>`;\n const flag=phase==='setup'?'travelDone':'serviceDone';\n const label=phase==='setup'?(w.travelDone?workText353('✓ Angekommen','✓ Arrived','✓ Na miejscu'):workText353('Ankunft bestätigen','Confirm arrival','Potwierdź przyjazd')):(w.serviceDone?workText353('✓ Ausgabe gestartet','✓ Service started','✓ Wydawanie rozpoczęte'):workText353('Ausgabe starten','Start service','Rozpocznij wydawanie'));\n const action=`<button type=\"button\" class=\"${w[flag]?'soft':'primary'}\" onclick=\"mobileWorkflowFlag358('${escJs(c.id)}','${flag}',${!w[flag]})\">${label}</button>`;\n const close=phase==='onsite'&&hasPermission('calc')?`<button type=\"button\" class=\"soft\" onclick=\"toggleWorkflowFlag('${escJs(c.id)}','closed',${!w.closed})\">${w.closed?workText353('Einsatz wieder öffnen','Reopen event','Otwórz ponownie wydarzenie'):workText353('Einsatz abschließen','Close event','Zakończ wydarzenie')}</button>`:'';\n return `<div class=\"workflowCompact358\">${mobileWorkflowTimes358(c,phase)}${phase==='setup'?`<div class=\"workflowActions358\">${action}</div>`:''}${tasks}${phase==='onsite'?`<div class=\"workflowActions358\">${action}${close}</div>`:''}${mobileWorkflowAdjust358(c,phase)}</div>`;\n}\nfunction workflowChanges358(rows){\n return rows.map(r=>{const minutes=Number(String(r.minutes).replace(',','.'));if(r.enabled&&(String(r.minutes).trim()===''||!Number.isFinite(minutes)||minutes<0))throw Error(workText353('Bitte gültige Minuten ab 0 eingeben.','Enter valid minutes of 0 or more.','Wpisz poprawną liczbę minut od 0.'));return {id:r.id,enabled:!!r.enabled,minutes};});\n}\nfunction applyWorkflowChanges358(c,phase,rows){\n const blocks=(state.timeBlocks||[]).filter(b=>b.phase===phase),allowed=new Set(blocks.map(b=>b.id));\n if(rows.length!==blocks.length||new Set(rows.map(r=>r.id)).size!==rows.length||rows.some(r=>!allowed.has(r.id)))throw Error(workText353('Aufgaben wurden geändert. Bitte erneut öffnen.','Tasks changed. Please reopen.','Zadania zmieniono. Otwórz ponownie.'));\n const w=initWorkflowSuggestions(c);for(const r of rows){w.enabledBlocks[r.id]=r.enabled;if(r.enabled)w.blockMinutes[r.id]=r.minutes;else delete w.done[r.id];}\n}\nfunction saveWorkflowAdjust358(id,phase){\n if(!['setup','onsite'].includes(phase)||!guardPermission(phase,workText353('Aufgaben anpassen','Adjust tasks','Dostosuj zadania')))return false;\n const panels=[...document.querySelectorAll('[data-workflow-adjust358]')],panel=panels.find(el=>el.dataset.workflowAdjust358===phase&&el.dataset.event358===id);if(!panel)return false;\n let rows;try{rows=workflowChanges358([...panel.querySelectorAll('[data-block358]')].map(el=>({id:el.dataset.block358,enabled:el.querySelector('.workflowUse358').checked,minutes:el.querySelector('.workflowMinutesInput358').value})));}catch(e){showFeedback(e.message,'warn');return false;}\n const result=operationTransaction(workText353('Aufgaben angepasst','Tasks adjusted','Zadania dostosowano'),phase,()=>{\n  const c=state.planning.caterings.find(c=>c.id===id);if(!c)throw Error(workText353('Einsatz nicht mehr vorhanden.','Event no longer available.','Wydarzenie nie jest już dostępne.'));\n  applyWorkflowChanges358(c,phase,rows);\n });\n return afterOperation275(result,ok=>{renderMobileTasks();return ok});\n}\nfunction mobileWorkflowFlag358(id,key,value){return afterOperation275(toggleWorkflowFlag(id,key,value),ok=>{renderMobileTasks();return ok})}\nconst mobilePhaseBodyBefore358=mobileEventPhaseBody357;\nmobileEventPhaseBody357=function(c,phase){return ['setup','onsite'].includes(phase)?mobileWorkflowBody358(c,phase):mobilePhaseBodyBefore358(c,phase)};\nconst mobilePlanningBefore358=mobileOpenPlanning;\nmobileOpenPlanning=function(section='overview'){\n if(typeof innerWidth!=='undefined'&&innerWidth<=820&&['setup','onsite'].includes(section)){\n  if(!guardPermission(section,section))return;const c=mobileWorkingPlan();return afterOperation275(mobileShow('tasks'),ok=>{if(ok===false)return false;if(c)mobileEventOpen357.set(c.id,section);renderMobileTasks();return true});\n }\n return mobilePlanningBefore358(section);\n};\n// Direct planning phase links on a phone lead to the same compact task view.\nconst planningSectionBefore358=setPlanningSection;\nsetPlanningSection=function(section,...args){if(typeof innerWidth!=='undefined'&&innerWidth<=820&&['setup','onsite'].includes(section))return mobileOpenPlanning(section);return planningSectionBefore358(section,...args)};\nif(typeof document!=='undefined'&&document.body&&typeof innerWidth!=='undefined'&&innerWidth<=820)renderMobileTasks();\n"},{"src":"mobile-close359.js?v=1","code":"'use strict';\nlet mobileCloseDraft359=null;\nfunction mobileCloseCargo359(c){return (c.stockFlow?.cargo||[]).filter(r=>r.location==='Trude')}\nfunction mobileCloseStamp359(c){return JSON.stringify({closed:!!c.workflow?.closed,cargo:mobileCloseCargo359(c)})}\nfunction mobileCloseRows359(c,values){\n const cargo=mobileCloseCargo359(c);if(values.length!==cargo.length||new Set(values.map(v=>v.id)).size!==values.length)throw Error(workText353('Beladung wurde geändert. Bitte erneut öffnen.','Load changed. Please reopen.','Ładunek zmieniono. Otwórz ponownie.'));\n return cargo.map(r=>{const v=values.find(v=>v.id===r.id);if(!v)throw Error(workText353('Beladung wurde geändert. Bitte erneut öffnen.','Load changed. Please reopen.','Ładunek zmieniono. Otwórz ponownie.'));\n const parse=x=>{const s=String(x).trim();return /^\\d+(?:[.,]\\d+)?$/.test(s)?Number(s.replace(',','.')):NaN};\n const returned=parse(v.returned),waste=parse(v.waste);\n if(![returned,waste].every(n=>Number.isFinite(n)&&n>=0)||returned+waste>Number(r.qty)+1e-8)throw Error(r.name+': '+workText353('Rückware und Ausschuss müssen zusammen zwischen 0 und der geladenen Menge liegen.','Returns plus waste must be between 0 and the loaded quantity.','Zwroty i odpady łącznie muszą mieścić się od 0 do załadowanej ilości.'));\n if(isWholeQuantityUnit(r.unit)&&(!Number.isInteger(returned)||!Number.isInteger(waste)))throw Error(r.name+': '+workText353('Bitte ganze Stückzahlen eingeben.','Enter whole quantities.','Wpisz pełne sztuki.'));\n if(r.kind==='article'&&new Set((r.lots||[]).map(l=>l.mhd)).size>1&&returned>0&&returned<r.qty)throw Error(workText353('Mehrere MHD-Chargen: Rückware zuerst im Lager eindeutig zuordnen.','Multiple expiry batches: assign returned stock to its batch first.','Wiele partii z terminem: najpierw przypisz zwrot do właściwej partii.'));\n return {id:r.id,name:r.name,unit:r.unit,qty:r.qty,returned,waste,sold:Math.max(0,r.qty-returned-waste)};\n });\n}\nfunction mobileCloseContent359(c,draft,review=false){\n const rows=review?draft.rows:mobileCloseCargo359(c).map(r=>({...r,...draft.values.find(v=>v.id===r.id)}));\n const qty=(q,u)=>esc(compactQuantity353(q,u));\n const warning=!rows.length?`<p class=\"closeNotice359\">${workText353('Keine Beladung erfasst. Rückware und Ausschuss werden nicht automatisch gebucht.','No load recorded. Returns and waste will not be booked automatically.','Brak zarejestrowanego załadunku. Zwroty i odpady nie zostaną zaksięgowane automatycznie.')}</p>`:'';\n const body=rows.map(r=>review?`<article class=\"closeItem359\"><h3>${esc(r.name)}</h3><dl><div><dt>${workText353('Zurück','Returned','Zwrot')}</dt><dd>${qty(r.returned,r.unit)}</dd></div><div><dt>${workText353('Ausschuss','Waste','Odpady')}</dt><dd>${qty(r.waste,r.unit)}</dd></div><div><dt>${workText353('Verkauft / verbraucht','Sold / used','Sprzedano / zużyto')}</dt><dd>${qty(r.sold,r.unit)}</dd></div></dl></article>`:`<article class=\"closeItem359\" data-close-cargo359=\"${esc(r.id)}\"><h3>${esc(r.name)}</h3><p>${workText353('Geladen: ','Loaded: ','Załadowano: ')}${qty(r.qty,r.unit)}</p><label>${workText353('Was kommt zurück?','What is returned?','Co wraca?')}<input class=\"closeReturn359\" inputmode=\"decimal\" value=\"${esc(r.returned??0)}\" aria-label=\"${esc(r.name+' · '+workText353('Rückware','Returns','Zwrot'))}\"></label><label>${workText353('Was wurde entsorgt?','What was discarded?','Co wyrzucono?')}<input class=\"closeWaste359\" inputmode=\"decimal\" value=\"${esc(r.waste??0)}\" aria-label=\"${esc(r.name+' · '+workText353('Ausschuss','Waste','Odpady'))}\"></label></article>`).join('');\n return `<div class=\"mobileClose359\"><p class=\"closeEvent359\">${esc(c.event)}</p>${review?`<h3>${workText353('Abschluss prüfen','Review closure','Sprawdź zakończenie')}</h3>`:''}${warning}${body}<p class=\"closeHint359\">${workText353('Kosten, Kilometer und Personal später im Web prüfen.','Review costs, mileage and staff later on the web.','Koszty, kilometry i personel sprawdź później w wersji web.')}</p>${review?`<p class=\"closeHint359\">${workText353('Der Einsatz wird abgeschlossen. Kostenangaben bleiben unverändert.','The event will be closed. Cost entries stay unchanged.','Wydarzenie zostanie zakończone. Dane kosztów pozostaną bez zmian.')}</p>`:''}<p id=\"closeError359\" role=\"alert\"></p><div class=\"closeActions359\">${review?`<button type=\"button\" class=\"soft\" onclick=\"mobileCloseBack359()\">${workText353('Zurück','Back','Wstecz')}</button><button type=\"button\" class=\"primary\" onclick=\"mobileCloseConfirm359()\">${workText353('Jetzt abschließen','Close now','Zakończ teraz')}</button>`:`<button type=\"button\" class=\"soft\" onclick=\"document.getElementById('opsDialog92').close()\">${workText353('Abbrechen','Cancel','Anuluj')}</button><button type=\"button\" class=\"primary\" onclick=\"mobileCloseReview359()\">${workText353('Abschluss prüfen','Review closure','Sprawdź zakończenie')}</button>`}</div></div>`;\n}\nfunction mobileCloseDisplay359(review=false){\n const d=mobileCloseDraft359,c=state.planning?.caterings?.find(c=>c.id===d?.id);if(!c)return;\n openOpsPanel(workText353('Einsatz abschließen','Close event','Zakończ wydarzenie'),mobileCloseContent359(c,d,review));\n const dialog=document.getElementById('opsDialog92');dialog?.classList.add('mobileCloseDialog359');const close=dialog?.querySelector(':scope > header button');close?.setAttribute('aria-label',workText353('Schließen','Close','Zamknij'));\n}\nfunction mobileCloseError359(error){const el=document.getElementById('closeError359');if(el)el.textContent=error.message;else showFeedback(error.message,'warn')}\nfunction mobileCloseReview359(){\n try{const d=mobileCloseDraft359,c=state.planning.caterings.find(c=>c.id===d?.id);if(!c||mobileCloseStamp359(c)!==d.stamp)throw Error(workText353('Beladung oder Einsatz wurde geändert. Bitte erneut öffnen.','Load or event changed. Please reopen.','Zmieniono ładunek lub wydarzenie. Otwórz ponownie.'));\n d.values=[...document.querySelectorAll('#opsDialog92 [data-close-cargo359]')].map(el=>({id:el.dataset.closeCargo359,returned:el.querySelector('.closeReturn359').value,waste:el.querySelector('.closeWaste359').value}));\n d.rows=mobileCloseRows359(c,d.values);d.reviewed=true;mobileCloseDisplay359(true);\n }catch(e){mobileCloseError359(e)}\n}\nfunction mobileCloseBack359(){if(!mobileCloseDraft359)return;mobileCloseDraft359.reviewed=false;mobileCloseDisplay359(false)}\nfunction mobileCloseConfirm359(){\n const d=mobileCloseDraft359;if(!d?.reviewed||!guardPermission('calc',workText353('Einsatzabschluss','Close event','Zakończ wydarzenie')))return false;\n const c=state.planning.caterings.find(c=>c.id===d.id);if(!c||mobileCloseStamp359(c)!==d.stamp){mobileCloseError359(Error(workText353('Beladung oder Einsatz wurde geändert. Bitte erneut öffnen.','Load or event changed. Please reopen.','Zmieniono ładunek lub wydarzenie. Otwórz ponownie.')));return false;}\n return afterOperation275(completeEvent(d.id,null,d.rows,{deferActual:true}),ok=>{if(ok){document.getElementById('opsDialog92')?.close();mobileCloseDraft359=null;renderMobileTasks()}return ok});\n}\nconst openEventCloseBefore359=openEventClose;\nopenEventClose=function(id){\n if(typeof innerWidth==='undefined'||innerWidth>820)return openEventCloseBefore359(id);\n if(!guardPermission('calc',workText353('Einsatzabschluss','Close event','Zakończ wydarzenie')))return;\n const c=state.planning.caterings.find(c=>c.id===id);if(!c||c.workflow?.closed){showFeedback(workText353('Einsatz bereits abgeschlossen.','Event already closed.','Wydarzenie już zakończono.'),'warn');return;}\n mobileCloseDraft359={id,stamp:mobileCloseStamp359(c),values:[],rows:[],reviewed:false};mobileCloseDisplay359();\n document.getElementById('opsDialog92')?.addEventListener('close',()=>{document.getElementById('opsDialog92')?.classList.remove('mobileCloseDialog359');mobileCloseDraft359=null},{once:true});\n};\n"},{"src":"web-workspace360.js?v=408&release=411","code":"'use strict';\nfunction eventNumber397(v){return Number(String(v??'').replace(',','.'))||0}\nfunction eventQuantityText397(c){if(!c?.day390)return String(c?.guests??'')+' '+workText353('Personen','people','osób');if(c.day390.status!=='closed')return workText353('Verkaufstag','Sales day','Dzien sprzedazy');const sold=c.day390.rows.reduce((sum,r)=>sum+eventNumber397(r.sold),0);return sold+' '+(c.day390.closing405&&c.day390.extraSales405?.length?workText353('Portionen / Stück aus Rezepten · weitere Verkäufe erfasst','recipe portions / items · additional sales recorded','porcji / sztuk z przepisów · zapisano dodatkową sprzedaż'):workText353('Portionen / Stück verkauft','portions / items sold','porcji / sztuk sprzedanych'))}\nfunction eventSummaryRows397(c){if(!c?.day390)return [...(c?.lines||[]),...(c?.dayItems||[])];const closed=c.day390.status==='closed';return c.day390.rows.map(r=>{const recipe=findRecipeById(r.recipeId)?.recipe,m=r.snapshot||{name:recipe?.name,unit:recipePlanningUnit108(recipe)};return {recipeId:r.recipeId,label:m.name,qty:closed?eventNumber397(r.sold):eventNumber397(r.start)+(r.additions||[]).reduce((n,a)=>n+eventNumber397(a.qty),0),unit:closed?m.unit:r.unit}})}\nconst webMainSpecs360=[\n ['start','Start','Home','Start',['tabDashboardBtn'],''],\n ['stock','Lager','Stock','Magazyn',['tabStockBtn','tabLagerBtn','tabAdminBtn','navInventory185'],'stock'],\n ['recipes','Rezepte','Recipes','Przepisy',['tabRecipesBtn'],'recipes'],\n ['buy','Einkauf','Purchasing','Zakupy',['navShoppingListBtn','navOrder155','navNeed155','navSuppliers155'],'shopping'],\n ['events','Einsätze','Events','Wydarzenia',['tabPlanningBtn','navPreparationBtn','navLoadBtn','navSetupBtn','navOnsiteBtn','navPlanCalc155'],'planning'],\n ['analytics','Auswertungen','Reports','Raporty',['navCalcBtn','tabRankingBtn','navCalcFood155','navCalcDb155','navCalcQuick155','navCalcDaily155'],'calc']\n];\nlet webMain360='start';\nconst webActualDrafts360=new Map();\nconst webOfferButtons360=new Set();\nfunction webDesktop360(){return typeof innerWidth!=='undefined'&&innerWidth>820}\nfunction webAllowed360(id){const spec=webMainSpecs360.find(s=>s[0]===id);if(!spec)return false;if(id==='start')return true;if(id==='recipes')return typeof canViewRecipes164==='function'?canViewRecipes164():hasPermission('recipes');const permissions={stock:['stock','scan','articles','inventory'],recipes:['recipes'],buy:['shopping','suppliers'],events:['planning','preparation','load','setup','onsite','calc'],analytics:['calc','stock']};return permissions[id].some(p=>hasPermission(p))}\nfunction webOriginalClick360(id){const button=document.getElementById(id);if(!button||button.disabled||button.style.display==='none')return false;button.click();return true}\nfunction webMainOpen360(id){\n if(!webAllowed360(id))return;\n if(id==='events'){const phase=hasPermission('planning')?'overview':['preparation','load','setup','onsite','calc'].find(p=>hasPermission(p));if(phase)openPlanningFromNav(phase);return;}\n const spec=webMainSpecs360.find(s=>s[0]===id);for(const action of spec?.[4]||[])if(webOriginalClick360(action))return;\n}\nfunction webIcon360(id){const names={stock:'stock',buy:'cart',events:'calendar',analytics:'prep',recipes:'prep',start:'stock'};return typeof zIcon158==='function'?zIcon158(names[id]):''}\nfunction installWebNav360(){\n const nav=document.getElementById('customSideNav');if(!nav)return;\n let box=document.getElementById('webMainNav360');if(!box){box=document.createElement('div');box.id='webMainNav360';nav.appendChild(box);}\n box.innerHTML=webMainSpecs360.filter(s=>webAllowed360(s[0])).map(s=>`<button type=\"button\" class=\"webMainButton360 ${webMain360===s[0]?'active':''}\" onclick=\"webMainOpen360('${s[0]}')\" ${webMain360===s[0]?'aria-current=\"page\"':''}><span>${webIcon360(s[0])}</span>${workText353(s[1],s[2],s[3])}</button>`).join('')+`<div class=\"webMainSystem360\">${hasPermission('settings')?`<button type=\"button\" class=\"webMainButton360\" onclick=\"showTab('settings')\">${workText353('Einstellungen','Settings','Ustawienia')}</button>`:''}<button type=\"button\" class=\"webMainButton360\" onclick=\"showTab('help')\">${workText353('Hilfe','Help','Pomoc')}</button></div>`;\n const brand=document.querySelector('.sidebarBrand');if(brand&&!brand.querySelector('.webLogo360')){const img=document.createElement('img');img.src='assets/zapasio-main-approved-lossless424.webp';img.alt='Zapasio';img.className='webLogo360';brand.appendChild(img);}\n}\nfunction webArea360(which,section){if(which==='planning')return section==='suppliers'?'buy':'events';return ({dashboard:'start',stock:'stock',lager:'stock',admin:'stock',inventory185:'stock',recipes:'recipes',purchasing:'buy',ranking:'analytics',calculations:'analytics'})[which]||''}\nconst webActiveBefore360=setActiveSideNav;\nsetActiveSideNav=function(which,section=''){const r=webActiveBefore360(which,section);webMain360=webArea360(which,section);installWebNav360();webLocalNav360();return r};\nconst webNavBuildBefore360=buildWorkshopNav155;\nbuildWorkshopNav155=function(...args){const r=webNavBuildBefore360(...args);installWebNav360();return r};\nconst webPermissionBefore360=applyPermissionsUI;\napplyPermissionsUI=function(...args){const r=webPermissionBefore360(...args);installWebNav360();return r};\nfunction webLocalNav360(){\n if(!webDesktop360())return;\n const active=document.querySelector('.tabPanel.active');if(!active||active.id==='tabPlanning')return;\n const stock=['tabStock','tabLager','tabAdmin','tabInventory185'].includes(active.id),analytics=['tabCalculations','tabRanking'].includes(active.id),buy=active.id==='tabPurchasing';\n const ids=stock?['tabStockBtn','tabLagerBtn','tabAdminBtn','navInventory185']:analytics?['navCalcBtn','tabRankingBtn','navCalcFood155','navCalcDb155','navCalcQuick155','navCalcDaily155']:buy?['navSuppliers155']:[];\n let tools=active.querySelector(':scope>.webLocal360');if(!ids.length){tools?.remove();return}if(!tools){tools=document.createElement('details');tools.className='webLocal360';active.prepend(tools)}\n tools.innerHTML=`<summary>${workText353('Weitere Aktionen','More actions','Więcej działań')}</summary><div>${ids.map(id=>{const b=document.getElementById(id);if(!b||b.disabled||b.style.display==='none')return '';return `<button type=\"button\" class=\"soft\" onclick=\"webOriginalClick360('${id}')\">${esc(b.textContent.trim())}</button>`}).join('')}${buy&&typeof canSaveDocuments240==='function'&&canSaveDocuments240()?`<button class=\"soft\" onclick=\"openInboundAuto183()\">${workText353('Beleg erfassen','Capture document','Dodaj dokument')}</button>`:''}</div>`;\n}\nfunction webEventTab360(section){return ['shopping','preparation','load','setup','onsite'].includes(section)?'perform':section==='calc'?'close':'plan'}\nfunction webStageAllowed360(stage){return stage==='plan'?hasPermission('planning'):stage==='close'?hasPermission('calc'):['shopping','preparation','load','setup','onsite'].some(p=>hasPermission(p))}\nfunction webEventStage360(stage){\n if(!webStageAllowed360(stage))return;\n const section=stage==='plan'?(editingCateringId?'entry':'overview'):stage==='close'?'calc':['shopping','preparation','load','setup','onsite'].find(p=>hasPermission(p));\n setPlanningSection(section);\n}\nfunction webEventList360(){document.body.classList.toggle('webEventList360');setPlanningSection('overview')}\nfunction webEventHeader360(){\n const shell=document.querySelector('#tabPlanning .planningShell');if(!shell)return;\n const hero=shell.querySelector('.planningHero')?.closest('.card');hero?.classList.add('webOldHero360');\n let head=document.getElementById('webEventHeader360');if(!head){head=document.createElement('section');head.id='webEventHeader360';shell.prepend(head)}\n const previousPicker=head.querySelector('#planningEntryPicker');\n const c=activePlanningEntry(),newEntry=planningSection==='entry'&&!editingCateringId,title=newEntry?workText353('Neuer Einsatz','New event','Nowe wydarzenie'):c?.event||workText353('Einsätze','Events','Wydarzenia'),stage=webEventTab360(planningSection);\n for(const button of head.querySelectorAll('#webOfferActions360 button'))webOfferButtons360.add(button);\n head.innerHTML=`<p class=\"webCrumb360\">${workText353('Einsätze','Events','Wydarzenia')}${c&&!newEntry?' / '+esc(c.event):''}</p><div class=\"webEventTitle360\"><div><h1>${esc(title)}</h1>${c&&!newEntry?`<p class=\"webEventMeta360\"><span>${c.date?esc(formatDate(c.date)):''}</span><span>${esc(eventQuantityText397(c))}</span><span class=\"webStatus360\">${c.workflow?.closed?workText353('Abgeschlossen','Closed','Zakończono'):c.confirmed374===true?workText353('Fixiert','Confirmed','Potwierdzone'):workText353('Entwurf','Draft','Szkic')}</span></p>`:''}</div><details class=\"webEventPicker360\"><summary>${workText353('Einsatz wechseln','Change event','Zmień wydarzenie')}</summary><div class=\"webEventPickerPopover360\"><div id=\"webEventPickerSlot360\"></div><button type=\"button\" class=\"soft\" onclick=\"webEventList360()\">${workText353('Alle Einsätze','All events','Wszystkie wydarzenia')}</button></div></details><details class=\"webEventMenu360\"><summary aria-label=\"${workText353('Weitere Aktionen','More actions','Więcej działań')}\">⋯</summary><div>${hasPermission('planning')?`<button class=\"soft\" onclick=\"newPlanningEntry()\">${workText353('Neuer Einsatz','New event','Nowe wydarzenie')}</button>${c?`<button class=\"soft\" onclick=\"editCateringPlan('${escJs(c.id)}')\">${workText353('Bearbeiten','Edit','Edytuj')}</button><button class=\"danger\" onclick=\"removeCateringPlan('${escJs(c.id)}')\">${workText353('Löschen','Delete','Usuń')}</button>`:''}`:''}${hasPermission('load')?`<button class=\"soft\" onclick=\"webOriginalClick360('navQuickLoadBtn')\">${workText353('Schnelle Ladeliste','Quick loading list','Szybka lista załadunku')}</button>`:''}<div id=\"webOfferActions360\"></div></div></details></div><nav class=\"webEventTabs360\">${[['plan','Planen','Plan','Planuj'],['perform','Durchführen','Carry out','Realizuj'],['close','Abschließen','Close','Zakończ']].filter(t=>webStageAllowed360(t[0])).map(t=>`<button type=\"button\" class=\"${stage===t[0]?'active':''}\" ${stage===t[0]?'aria-current=\"page\"':''} onclick=\"webEventStage360('${t[0]}')\">${workText353(t[1],t[2],t[3])}</button>`).join('')}</nav>`;\n const picker=document.querySelector('#planningOverviewBar #planningEntryPicker')||previousPicker;if(picker)document.getElementById('webEventPickerSlot360').appendChild(picker);\n else{const select=document.createElement('select');select.id='planningEntryPicker';select.setAttribute('aria-label',workText353('Einsatz wählen','Choose event','Wybierz wydarzenie'));select.innerHTML=planningEntryOptions(c?.id||'');select.onchange=()=>switchPlanningFromHeader(select.value);document.getElementById('webEventPickerSlot360').appendChild(select)}\n // Keep the existing offer action and its handler, without a duplicate trigger.\n for(const button of hero?.querySelectorAll('.planningHeroActions button')||[]){if(button.getAttribute('onclick')?.includes('newPlanningEntry'))continue;webOfferButtons360.add(button)}\n for(const button of webOfferButtons360)document.getElementById('webOfferActions360').appendChild(button);\n}\nconst webMoved360=new Map();\nfunction webMove360(node,destination){if(!node||!destination||node.parentNode===destination)return;if(!webMoved360.has(node)){const marker=document.createComment('web360-original-position');node.before(marker);webMoved360.set(node,marker)}destination.appendChild(node)}\nfunction webRestore360(){for(const [node,marker] of webMoved360){if(marker.parentNode)marker.after(node)}webMoved360.clear()}\nfunction webEventPanels360(){\n const shell=document.querySelector('#tabPlanning .planningShell');if(!shell)return;\n let perform=document.getElementById('webPerform360');if(!perform){perform=document.createElement('section');perform.id='webPerform360';shell.appendChild(perform)}\n const specs=[['shopping','Bedarf & Einkauf','Needs & purchasing','Zapotrzebowanie i zakupy'],['preparation','Vorbereiten','Prepare','Przygotuj'],['load','Beladen','Load','Załaduj'],['setup','Aufbauen','Set up','Rozstaw'],['onsite','Vor Ort','On site','Na miejscu']];\n for(const [id,de,en,pl] of specs){const panel=document.getElementById('planningSection_'+id);if(!panel)continue;let fold=perform.querySelector(`[data-web-phase360=\"${id}\"]`);if(!fold){fold=document.createElement('details');fold.dataset.webPhase360=id;fold.className='webPhase360';const summary=document.createElement('summary');summary.textContent=workText353(de,en,pl);fold.appendChild(summary);perform.appendChild(fold)}fold.hidden=!hasPermission(id);webMove360(panel,fold);if(planningSection===id)fold.open=true;}\n let costFold=document.getElementById('webPlanCosts360');if(!costFold){costFold=document.createElement('details');costFold.id='webPlanCosts360';costFold.className='webPhase360';costFold.innerHTML=`<summary>${workText353('Geplante Kosten','Planned costs','Planowane koszty')}</summary>`;document.getElementById('planningSection_overview')?.appendChild(costFold)}\n costFold.hidden=!hasPermission('calc');webMove360(document.getElementById('selectedCalcPreview'),costFold);\n const stage=webEventTab360(planningSection);perform.hidden=stage!=='perform';\n for(const panel of shell.querySelectorAll('.planningPanel')){const id=panel.id.replace('planningSection_','');if(!specs.some(s=>s[0]===id))panel.classList.toggle('active',id===planningSection);}\n}\nfunction webMoveMoney360(input,destination){destination.appendChild(input);if(input.formAdapter231?.box)destination.appendChild(input.formAdapter231.box);}\nfunction webActualFields360(id){return ['actRev_','actGoods_','actPers_','actTravel_','actRental_','actOther_'].map(prefix=>document.getElementById(prefix+id)).filter(Boolean)}\nfunction webCaptureActual360(){\n const host=document.querySelector('#selectedPostCalcPreview .webActual360');if(!host)return;\n const id=host.dataset.event360;webActualDrafts360.set(id,Object.fromEntries(webActualFields360(id).map(el=>[el.id,el.value])));\n}\nfunction webActualLayout360(c){\n const host=document.getElementById('selectedPostCalcPreview'),post=host?.querySelector('.postCalc');if(!post||post.classList.contains('webActual360')||!hasPermission('calc'))return;\n const body=post.querySelector('.postCalcBody');if(!body)return;\n const saved=Object.keys(c.actual||{}).length>0,draft=webActualDrafts360.get(c.id),summary=post.querySelector(':scope>summary');post.open=true;post.classList.add('webActual360');post.dataset.event360=c.id;\n if(summary)summary.innerHTML=`<span>${workText353('Tatsächliche Kosten','Actual costs','Rzeczywiste koszty')}</span><span class=\"webStatus360 ${saved?'':'pending'}\">${saved?workText353('Gespeichert','Saved','Zapisano'):workText353('Noch offen','Still open','Otwarte')}</span>`;\n const note=document.createElement('p');note.className='webActualHint360';note.textContent=workText353('Planwerte sind noch keine bestätigten Ist-Werte.','Planned figures are not confirmed actuals.','Wartości planowane nie są potwierdzonymi rzeczywistymi.');body.prepend(note);\n const table=document.createElement('table');table.className='webActualTable360';table.innerHTML=`<thead><tr><th>${workText353('Position','Item','Pozycja')}</th><th>${workText353('Geplant','Planned','Planowane')}</th><th>${workText353('Tatsächlich','Actual','Rzeczywiste')}</th></tr></thead><tbody></tbody>`;\n body.insertBefore(table,note.nextSibling);const calc=cateringCalc(c),co=c.costs||{};\n const rows=[['actGoods_',workText353('Wareneinsatz','Cost of goods','Koszt produktów'),Number(calc.autoCost||0)+Number(calc.drinkCost||0)+Number(co.extraGoods||0)],['actPers_',workText353('Personal · zusätzlich','Staff · additional','Personel · dodatkowo'),co.personnel||0],['actTravel_',workText353('Fahrt · zusätzlich','Travel · additional','Przejazd · dodatkowo'),co.travel||0]];\n for(const [prefix,label,planned] of rows){const input=document.getElementById(prefix+c.id);if(!input)continue;const tr=document.createElement('tr');tr.innerHTML=`<td>${label}</td><td>${moneyFmt(planned)}</td><td></td>`;webMoveMoney360(input,tr.lastElementChild);input.setAttribute('aria-label',label+' (€)');input.formAdapter231?.display?.setAttribute('aria-label',label);table.querySelector('tbody').appendChild(tr);}\n const advanced=document.createElement('details');advanced.className='webActualMore360';advanced.innerHTML=`<summary>${workText353('Weitere Kosten, Umsatz & Details','More costs, revenue & details','Pozostałe koszty, przychód i szczegóły')}</summary>`;\n const keep=new Set([note,table]);const save=[...body.querySelectorAll('button')].find(b=>b.getAttribute('onclick')?.includes('saveActualCalc('));\n for(const node of [...body.childNodes])if(!keep.has(node)&&node!==save)advanced.appendChild(node);\n body.appendChild(advanced);if(save){save.textContent=workText353('Ist-Werte speichern','Save actuals','Zapisz wartości rzeczywiste');body.appendChild(save)}\n for(const field of webActualFields360(c.id)){if(draft&&Object.hasOwn(draft,field.id))field.value=draft[field.id];else if(!saved){field.value='';field.placeholder=workText353('Betrag','Amount','Kwota')}field.dataset.webActual360='true';}\n // Strip empty old wrappers left behind by moving the original inputs.\n for(const wrapper of advanced.querySelectorAll('.costGrid>.field'))if(!wrapper.querySelector('input,select,textarea'))wrapper.remove();\n if(typeof installForms231==='function')installForms231(post);\n for(const box of table.querySelectorAll('.money231')){const options=box.querySelector('.moneyOptions231');if(!options)continue;const fold=options.closest('details.webMoneyOptions360');if(fold)fold.replaceWith(options);}\n}\nfunction webCloseLayout360(c){\n const panel=document.getElementById('planningSection_calc'),card=panel?.querySelector(':scope>.card');if(!card||!c)return;\n card.classList.add('webCloseCard360');let title=document.getElementById('webCloseTitle360');if(!title){title=document.createElement('h2');title.id='webCloseTitle360';card.prepend(title)}title.textContent=workText353('Abschluss & Nachkalkulation','Closure & actual costs','Zakończenie i koszty rzeczywiste');\n let status=document.getElementById('webCloseStatus360');if(!status){status=document.createElement('section');status.id='webCloseStatus360';title.after(status)}\n const closed=!!c.workflow?.closed,loaded=(c.stockFlow?.cargo||[]).length>0;\n status.innerHTML=`<h3>${workText353('Warenabschluss','Stock settlement','Rozliczenie towarów')} <span class=\"webStatus360 ${closed?'':'pending'}\">${closed?workText353('✓ Abgeschlossen','✓ Closed','✓ Zakończono'):workText353('Noch offen','Still open','Otwarte')}</span></h3>${!loaded?`<p>${workText353('Keine Beladung erfasst.','No load recorded.','Brak zarejestrowanego załadunku.')}</p>`:''}${!closed&&hasPermission('calc')?`<button type=\"button\" class=\"soft\" onclick=\"openEventClose('${escJs(c.id)}')\">${workText353('Rückware & Ausschuss prüfen','Review returns & waste','Sprawdź zwroty i odpady')}</button>`:''}`;\n webActualLayout360(c);\n}\nfunction webEventApply360(){\n const shell=document.querySelector('#tabPlanning .planningShell');if(!shell)return;\n if(!webDesktop360()){webRestore360();document.body.classList.remove('webWorkspace360');return;}\n document.body.classList.add('webWorkspace360');if(!activePlanningEntry())document.body.classList.add('webEventList360');if(webEventTab360(planningSection)!=='plan')document.body.classList.remove('webEventList360');webEventHeader360();webEventPanels360();const c=activePlanningEntry();if(c)webCloseLayout360(c);\n const overview=document.getElementById('planningSection_overview');if(overview){let intro=document.getElementById('webPlanSummary360');if(!intro){intro=document.createElement('section');intro.id='webPlanSummary360';overview.prepend(intro)}intro.innerHTML=c?`<h2>${c.day390?workText353(c.day390.status==='closed'?'Verkaufte Mengen':'Vorbereitete Mengen',c.day390.status==='closed'?'Quantities sold':'Prepared quantities',c.day390.status==='closed'?'Sprzedane ilosci':'Przygotowane ilosci'):workText353('Speisen & Mengen','Dishes & quantities','Dania i ilosci')}</h2>${eventSummaryRows397(c).map(l=>`<div><span>${esc(findRecipeById(l.recipeId)?.recipe?.name||l.name||l.label||workText353('Rezept prüfen','Check recipe','Sprawdź przepis'))}</span><b>${esc(formatQtySmart(l.qty,l.unit||recipePlanningUnit108(findRecipeById(l.recipeId)?.recipe)))}</b></div>`).join('')}<button class=\"soft\" onclick=\"editCateringPlan('${escJs(c.id)}')\" ${hasPermission('planning')?'':'disabled'}>${workText353('Einsatz bearbeiten','Edit event','Edytuj wydarzenie')}</button>`:'';}\n}\nconst webPlanningApplyBefore360=applyPlanningSection;\napplyPlanningSection=function(...args){const r=webPlanningApplyBefore360(...args);webEventApply360();return r};\nconst webPlanningRenderBefore360=renderPlanning;\nrenderPlanning=function(...args){webCaptureActual360();const r=webPlanningRenderBefore360(...args);webEventApply360();return r};\nconst webShowBefore360=showTab;\nshowTab=function(...args){const r=webShowBefore360(...args);webLocalNav360();return r};\nconst webRenderBefore360=render;\nrender=function(...args){const r=webRenderBefore360(...args);installWebNav360();webEventApply360();webLocalNav360();return r};\nconst webSaveActualBefore360=saveActualCalc;\nsaveActualCalc=function(id){\n const host=document.querySelector('#selectedPostCalcPreview .webActual360');if(!webDesktop360()||host?.dataset.event360!==id||!document.getElementById('tabPlanning')?.classList.contains('active'))return webSaveActualBefore360(id);\n if(!guardPermission('calc',workText353('Nachkalkulation','Actual costing','Koszty rzeczywiste')))return false;\n const c=state.planning.caterings.find(c=>c.id===id);if(!c)return false;\n const actualFields=webActualFields360(id);if(actualFields.length!==6){showFeedback(workText353('Kostenansicht bitte erneut öffnen.','Please reopen the cost view.','Otwórz ponownie widok kosztów.'),'warn');return false;}\n const invalid=[...host.querySelectorAll?.('[data-invalid231]')||[]].find(el=>el.dataset.invalid231);if(invalid){for(let p=invalid.parentElement;p&&p!==host;p=p.parentElement)if(p.tagName==='DETAILS')p.open=true;invalid.querySelector('input:not([type=hidden])')?.focus();showFeedback(invalid.dataset.invalid231,'warn');return false;}\n const missing=actualFields.find(el=>el.value.trim()==='');\n if(missing){missing.closest('details.webActualMore360')?.setAttribute('open','');(missing.formAdapter231?.display||missing).focus();showFeedback(workText353('Ist-Beträge ergänzen, auch 0 € ausdrücklich eintragen.','Enter actual amounts, including an explicit 0 where appropriate.','Wpisz rzeczywiste kwoty, również wyraźnie 0.'),'warn');return false;}\n if(typeof validateForms231==='function'&&!validateForms231(host))return false;\n if(!validTypeFeeFields(c.type,'actFee_'+id))return false;const a=actualDraftFromInputs(c);\n if(!validResourceCosts(a.resourceCosts)||['revenue','goods','personnel','travel','rental','other'].some(k=>!Number.isFinite(a[k])||a[k]<0)){showFeedback(workText353('Bitte gültige Ist-Kosten eingeben.','Enter valid actual costs.','Wpisz poprawne koszty rzeczywiste.'),'warn');return false;}\n const snapshot=clone(a),result=operationTransaction(workText353('Nachkalkulation gespeichert','Actual costing saved','Zapisano koszty rzeczywiste'),'calc',()=>{const current=state.planning.caterings.find(c=>c.id===id);if(!current)throw Error('Einsatz nicht mehr vorhanden.');current.actual={...snapshot,savedAt:new Date().toISOString()};});\n return afterOperation275(result,ok=>{if(ok){webActualDrafts360.delete(id);delete actualResourceDrafts[id];renderPlanning()}return ok});\n};\nif(typeof renderEventCalcs143==='function'){\n const reportBefore360=renderEventCalcs143;\n renderEventCalcs143=function(...args){const r=reportBefore360(...args);if(webDesktop360())for(const form of document.querySelectorAll('#calc143_event .postCalc')){const note=document.createElement('p');note.className='webReportNote360';note.textContent=workText353('Ist-Kosten im Einsatz prüfen und bearbeiten.','Review and edit actual costs in the event.','Sprawdź i edytuj koszty rzeczywiste w wydarzeniu.');form.replaceWith(note)}webLocalNav360();return r};\n}\nif(typeof document!=='undefined'&&document.body){installWebNav360();webEventApply360();webLocalNav360();if(typeof addEventListener==='function')addEventListener('resize',()=>{webEventApply360();webLocalNav360()})}\n\nfunction closeEventPicker376(focus=false){for(const picker of document.querySelectorAll('.webEventPicker360[open]')){picker.open=false;if(focus)picker.querySelector('summary')?.focus()}}\nif(typeof document!=='undefined'&&document.body){\n document.addEventListener('click',event=>{if(!event.target?.closest?.('.webEventPicker360'))closeEventPicker376()},true);\n document.addEventListener('change',event=>{if(event.target?.id==='planningEntryPicker')closeEventPicker376()},true);\n document.addEventListener('keydown',event=>{if(event.key==='Escape')closeEventPicker376(true)});\n}\n"},{"src":"waste362.js?v=406","code":"'use strict';\n// Read existing ledgers; never re-book an event settlement into warehouse stock.\nfunction wasteText362(de,en,pl){return workText353(de,en,pl)}\nfunction wasteRows362(s=state){\n const rows=[],seen=new Set();\n for(const m of s.movements||[]){\n  if(m.type!=='waste'||!Number.isFinite(Number(m.qty))||!(Math.abs(Number(m.qty))>0))continue;\n  const key='movement:'+m.id;if(seen.has(key))continue;seen.add(key);\n  const known=m.costKnown362===true||(m.costKnown362!==false&&Number(m.unitCost)>0);\n  rows.push({id:key,date:m.date||String(m.ts?new Date(m.ts).toISOString():'').slice(0,10),ts:Number(m.ts)||0,name:m.productName||s.products?.[m.productCode]?.name||m.productCode,code:m.productCode||'',qty:Math.abs(Number(m.qty)),unit:m.unit||'',reason:m.reason||wasteText362('Nicht hinterlegt','Not recorded','Nie zapisano'),value:known&&Number.isFinite(Number(m.value))?Math.abs(Number(m.value)):null,batch:m.batchLabel362||m.batchId||'',source:wasteText362('Lager','Warehouse','Magazyn')});\n }\n for(const c of s.planning?.caterings||[])for(const r of c.stockFlow?.settlements||[]){\n  if(!Number.isFinite(Number(r.waste))||!(Number(r.waste)>0))continue;\n  // A future explicitly linked ledger row replaces this view row, never duplicates it.\n  if((s.movements||[]).some(m=>m.type==='waste'&&m.eventId362===c.id&&m.cargoId362===r.cargoId))continue;\n  const key='event:'+c.id+':'+r.cargoId;if(seen.has(key))continue;seen.add(key);\n  rows.push({id:key,date:String(r.at||c.closedAt||'').slice(0,10),ts:Date.parse(r.at||c.closedAt)||0,name:r.name||'',code:'',qty:Number(r.waste),unit:r.unit||'',reason:wasteText362('Ausschuss beim Einsatzabschluss','Waste at event closing','Odpad przy zamknięciu wydarzenia'),value:Number(r.unitCost)>0?Number(r.waste)*Number(r.unitCost):null,batch:'',source:c.event||wasteText362('Einsatz','Event','Wydarzenie')});\n }\n // Daily closings are economic snapshots, not another warehouse movement.\n const dailySeen=new Set();\n for(const closing of (s.dailyClosings||[]).slice().sort((a,b)=>String(b.savedAt||'').localeCompare(String(a.savedAt||'')))){\n  if(!closing.planId390)continue;\n  if(dailySeen.has(closing.planId390))continue;dailySeen.add(closing.planId390);\n  const entry=(s.planning?.caterings||[]).find(c=>c.id===closing.planId390);\n  for(const [index,item] of (closing.items||[]).entries()){\n   const original=entry?.day390?.rows?.[index],meta=original?.snapshot;\n   const w=item.waste406||(original&&original.recipeId===item.recipeId?{rowId:original.id,recipeId:item.recipeId,name:meta?.name,qty:Number(String(original.waste||0).replace(',','.')),unit:meta?.unit,unitCost:meta?.unitCost,known:meta?.complete}:null);\n   if(!w||!Number.isFinite(w.qty)||!(w.qty>0))continue;\n   const key='day:'+closing.planId390+':'+(w.rowId||index);if(seen.has(key))continue;seen.add(key);\n   if((s.movements||[]).some(m=>m.type==='waste'&&m.dayClosingId406===closing.id&&m.dayRowId406===w.rowId))continue;\n   rows.push({id:key,date:closing.date||'',ts:Date.parse(closing.savedAt)||0,name:w.name||item.recipeId,code:'recipe:'+item.recipeId,qty:w.qty,unit:w.unit||'Portionen',reason:wasteText362('Ausschuss beim Tagesabschluss','Waste at daily closing','Odpad przy zamknieciu dnia'),value:w.known&&Number.isFinite(w.unitCost)&&w.unitCost>=0?w.qty*w.unitCost:null,batch:wasteText362('Rezeptansatz · keine zusätzliche Lagerbuchung','Recipe estimate · no additional stock movement','Koszt przepisu · bez dodatkowego ruchu magazynu'),source:closing.location||entry?.event||'Tagesgeschäft'});\n  }\n }\n return rows.sort((a,b)=>b.ts-a.ts||b.date.localeCompare(a.date));\n}\nfunction wasteSummary362(rows){\n const units={},reasons={};let value=0,unknown=0;\n for(const r of rows){units[r.unit]=(units[r.unit]||0)+r.qty;reasons[r.reason]=(reasons[r.reason]||0)+1;if(r.value===null)unknown++;else value+=r.value}\n return {count:rows.length,value,unknown,units,reasons};\n}\nfunction wasteLots362(code){\n const a=state.products?.[code];if(!a)throw Error(wasteText362('Artikel fehlt.','Product missing.','Brak produktu.'));\n const stock=Number(a.stock)||0,commission=Number(a.consignmentQty168)||0,batches=(state.batches?.[code]||[]).filter(b=>Number(b.qty)>0),out=[];\n if(stock<0||commission<0||commission>stock+1e-8)throw Error(wasteText362('Bestand prüfen.','Check stock.','Sprawdź stan.'));\n for(const owned of [false,true]){\n  const available=owned?commission:stock-commission,matched=batches.filter(b=>!!b.consignment168===owned),tracked=matched.reduce((n,b)=>n+Number(b.qty),0);\n  if(tracked>available+1e-8||new Set(batches.map(b=>b.id)).size!==batches.length||batches.some(b=>!b.id))throw Error(wasteText362('Bestand und Chargen stimmen nicht überein. Bitte zuerst prüfen.','Stock and lots disagree. Review them first.','Stan i partie są niespójne. Najpierw je sprawdź.'));\n  for(const b of matched)out.push({id:b.id,qty:Number(b.qty),mhd:b.mhd||'',commission:owned,label:(b.mhd?formatDate(b.mhd):stockInfoText311('unassigned'))+(owned?' · '+wasteText362('Kommission','Consignment','Komis'):'')});\n  if(available-tracked>1e-8)out.push({id:owned?'unassigned-commission362':'unassigned-owned362',qty:available-tracked,mhd:'',commission:owned,unassigned:true,label:stockInfoText311('unassigned')+(owned?' · '+wasteText362('Kommission','Consignment','Komis'):'')});\n }\n return out;\n}\nfunction wasteSnapshot362(code){const a=state.products?.[code];return JSON.stringify({stock:a?.stock,commission:a?.consignmentQty168,unit:a?.unit,price:a?.price,batches:state.batches?.[code]||[]})}\nlet wasteDraft362=null;\nfunction openWasteProduct362(code){\n if(!guardPermission('scan','Waste Management')||!state.products?.[code])return;\n try{const a=state.products[code],lots=wasteLots362(code);if(!lots.length)throw Error(wasteText362('Kein Lagerbestand verfügbar.','No warehouse stock available.','Brak stanu magazynowego.'));\n wasteDraft362={code,snapshot:wasteSnapshot362(code),busy:false,done:false};\n openOpsPanel(wasteText362('Verwurf erfassen','Record waste','Zapisz odpad'),`<form id=\"wasteForm362\" onsubmit=\"event.preventDefault();reviewWaste362()\"><h3>${esc(a.name)}</h3><label>${wasteText362('Menge','Quantity','Ilość')} (${esc(a.unit)})<input id=\"wasteQty362\" inputmode=\"decimal\" required placeholder=\"${wasteText362('Menge eingeben','Enter quantity','Wpisz ilość')}\"></label><label>${wasteText362('Charge / Bestand','Lot / stock','Partia / stan')}<select id=\"wasteLot362\" required><option value=\"\">${wasteText362('Bitte wählen','Please choose','Wybierz')}</option>${lots.map(l=>`<option value=\"${esc(l.id)}\">${esc(l.label)} · ${esc(formatQtySmart(l.qty,a.unit))}</option>`).join('')}</select></label><label>${wasteText362('Grund','Reason','Powód')}<select id=\"wasteReason362\" required>${[['','Bitte wählen','Please choose','Wybierz'],['expired','MHD überschritten','Best-before exceeded','Po terminie'],['spoiled','Verdorben','Spoiled','Zepsute'],['excess','Zu viel produziert','Overproduction','Nadprodukcja'],['damaged','Beschädigt','Damaged','Uszkodzone'],['other','Sonstiger Grund','Other reason','Inny powód']].map(([id,...w])=>`<option value=\"${id}\">${wasteText362(...w)}</option>`).join('')}</select></label><label>${wasteText362('Notiz (optional)','Note (optional)','Notatka (opcjonalnie)')}<input id=\"wasteNote362\" maxlength=\"300\"></label><p class=\"muted\">${wasteText362('Der Bestand wird erst nach deiner Bestätigung verringert.','Stock is reduced only after confirmation.','Stan zmniejszy się dopiero po potwierdzeniu.')}</p><div class=\"hubActions362\"><button type=\"button\" class=\"soft\" onclick=\"document.getElementById('opsDialog92').close()\">${wasteText362('Abbrechen','Cancel','Anuluj')}</button><button type=\"submit\" class=\"primary\">${wasteText362('Verwurf prüfen','Review waste','Sprawdź odpad')}</button></div></form>`);\n }catch(e){showFeedback(wasteText362('Verwurf prüfen','Review waste','Sprawdź odpad'),'warn',5000,e.message)}\n}\nfunction wastePayload362(d,raw,lotId,reason,note=''){\n if(!d||d.done||wasteSnapshot362(d.code)!==d.snapshot)throw Error(wasteText362('Bestand wurde geändert. Artikel erneut öffnen.','Stock changed. Reopen the product.','Stan się zmienił. Otwórz produkt ponownie.'));\n const a=state.products[d.code],qty=inventoryQuantity245(raw,a.unit),lot=wasteLots362(d.code).find(l=>l.id===lotId);\n if(!(qty>0)||!lot||qty>lot.qty+1e-8)throw Error(wasteText362('Menge übersteigt die gewählte Charge oder ist ungültig.','Quantity exceeds the selected lot or is invalid.','Ilość przekracza wybraną partię lub jest nieprawidłowa.'));\n if(!['expired','spoiled','excess','damaged','other'].includes(reason)||(reason==='other'&&!String(note).trim()))throw Error(wasteText362('Grund angeben. Bei „Sonstiger Grund“ bitte eine Notiz ergänzen.','Choose a reason; add a note for Other.','Wybierz powód; dla Inny dopisz notatkę.'));\n return {qty,lot,reason,note:String(note).trim().slice(0,300)};\n}\nfunction reviewWaste362(){\n const d=wasteDraft362;if(!d||d.busy||d.done)return;\n try{const p=wastePayload362(d,document.getElementById('wasteQty362').value,document.getElementById('wasteLot362').value,document.getElementById('wasteReason362').value,document.getElementById('wasteNote362').value);askConfirm(`${state.products[d.code].name}: ${formatQtySmart(p.qty,state.products[d.code].unit)} · ${p.lot.label}\\n${wasteReasonLabel362(p.reason)}${p.note?' · '+p.note:''}`,()=>saveWaste362(d,p),wasteText362('Verwurf buchen','Book waste','Zaksięguj odpad'),wasteText362('Verwurf bestätigen','Confirm waste','Potwierdź odpad'))}catch(e){showFeedback(wasteText362('Verwurf prüfen','Review waste','Sprawdź odpad'),'warn',5000,e.message)}\n}\nfunction wasteReasonLabel362(id){return ({expired:wasteText362('MHD überschritten','Best-before exceeded','Po terminie'),spoiled:wasteText362('Verdorben','Spoiled','Zepsute'),excess:wasteText362('Zu viel produziert','Overproduction','Nadprodukcja'),damaged:wasteText362('Beschädigt','Damaged','Uszkodzone'),other:wasteText362('Sonstiger Grund','Other reason','Inny powód')})[id]||id}\nfunction saveWaste362(d,p){\n if(d!==wasteDraft362||d.done||d.busy||!document.getElementById('wasteForm362'))return false;d.busy=true;\n const result=operationTransaction(wasteText362('Verwurf gespeichert','Waste saved','Odpad zapisany'),'scan',()=>{\n  const checked=wastePayload362(d,String(p.qty),p.lot.id,p.reason,p.note),a=state.products[d.code],lot=checked.lot;\n  if(!lot.unassigned){const b=state.batches[d.code].find(b=>b.id===lot.id);b.qty-=p.qty;if(Number(b.packSize)>0)b.packCount=b.qty/Number(b.packSize);state.batches[d.code]=state.batches[d.code].filter(b=>Number(b.qty)>1e-8)}\n  a.stock=Number(a.stock)-p.qty;if(lot.commission)a.consignmentQty168=Number(a.consignmentQty168)-p.qty;\n  const reason=wasteReasonLabel362(p.reason)+(p.note?' · '+p.note:'');\n  addMovement('waste',d.code,-p.qty,{reason,unitCost:Number(a.price)||0,source:'waste362',batchId:lot.unassigned?'':lot.id,batchLabel362:lot.label,costKnown362:Number(a.price)>0});\n  state.log??=[];state.log.push(`${stamp()} | ${a.name} | −${p.qty} ${a.unit} | ${reason} | ${lot.label}`);\n });\n return afterOperation275(result,ok=>{d.busy=false;if(ok){d.done=true;document.getElementById('opsDialog92')?.close();if(typeof refreshHub362==='function')refreshHub362()}return ok});\n}\n// Retain provenance inside the same atomic transaction as existing callers.\nconst movementBefore362=addMovement;\naddMovement=function(type,code,qty,extra={},...rest){const r=movementBefore362(type,code,qty,extra,...rest);if(type==='waste'){const m=state.movements?.at(-1);if(m&&m.type==='waste'&&m.productCode===code){m.costKnown362=extra.costKnown362??(Number(extra.unitCost??state.products?.[code]?.price)>0);for(const k of ['source','batchId','batchLabel362','eventId362','cargoId362','dayClosingId406','dayRowId406'])if(extra[k]!==undefined)m[k]=extra[k]}}return r};\n// Existing Ausschuss buttons use the same explicit lot selection.\nif(typeof openModal==='function'){const modalBeforeWaste362=openModal;openModal=function(kind,...args){if(kind==='waste')return openWasteProduct362(current);return modalBeforeWaste362(kind,...args)}}\n"},{"src":"waste-charts363.js?v=406","code":"'use strict';\n// Views only: grouping historical labels never rewrites the original ledger.\nfunction wasteReasonGroup363(reason){\n const head=String(reason||'').split(' · ')[0].trim().toLocaleLowerCase('de');\n const labels=[[/^(mhd überschritten|abgelaufen|best-before exceeded|po terminie)$/,['MHD überschritten','Best-before exceeded','Po terminie']],[/^(verdorben|spoiled|zepsute)$/,['Verdorben','Spoiled','Zepsute']],[/^(beschädigt|defekt|damaged|uszkodzone)$/,['Beschädigt','Damaged','Uszkodzone']],[/^(zu viel produziert|overproduction|nadprodukcja)$/,['Zu viel produziert','Overproduction','Nadprodukcja']],[/^(produktionsverlust|fehlproduktion)$/,['Produktionsverlust','Production loss','Strata produkcyjna']],[/^(rest nach einsatz entsorgt|ausschuss beim einsatzabschluss|ausschuss beim tagesabschluss|waste at daily closing|waste at event closing|odpad przy zamknięciu wydarzenia)$/,['Einsatz-Ausschuss','Event waste','Odpady z wydarzeń']]];\n return wasteText362(...(labels.find(([pattern])=>pattern.test(head))?.[1]||['Sonstige Angaben','Other entries','Pozostałe wpisy']));\n}\nfunction wasteChartData363(rows){\n const reasons=new Map(),products=new Map(),days=new Map(),units=new Map();let unknown=0;\n for(const r of rows){\n  const reason=wasteReasonGroup363(r.reason);reasons.set(reason,(reasons.get(reason)||0)+1);\n  units.set(r.unit,(units.get(r.unit)||0)+r.qty);\n  if(r.value===null)unknown++;\n  else{const key=r.code?'code:'+r.code:'name:'+r.name,old=products.get(key)||{label:r.name,value:0};old.value+=r.value;products.set(key,old)}\n  if(/^\\d{4}-\\d{2}-\\d{2}$/.test(r.date))days.set(r.date,(days.get(r.date)||0)+1);\n }\n return {reasons:[...reasons].map(([label,value])=>({label,value})).sort((a,b)=>b.value-a.value),products:[...products.values()].sort((a,b)=>b.value-a.value).slice(0,10),days:[...days].sort((a,b)=>a[0].localeCompare(b[0])).slice(-14).map(([label,value])=>({label,value})),units:[...units].map(([unit,qty])=>({unit,qty})),unknown};\n}\nfunction wasteBars363(items,kind='count'){\n if(!items.length)return `<p class=\"chartEmpty363\">${wasteText362('Noch keine auswertbaren Einträge.','No records to analyse yet.','Brak wpisów do analizy.')}</p>`;\n const max=Math.max(...items.map(x=>x.value),1e-9);\n return `<div class=\"wasteBars363\" role=\"list\">${items.map((item,i)=>{const label=kind==='date'?formatDate(item.label):item.label,value=kind==='money'?moneyFmt(item.value):`${item.value} ${item.value===1?wasteText362('Eintrag','record','wpis'):wasteText362('Einträge','records','wpisów')}`,percent=Math.max(0,Math.min(100,item.value/max*100));return `<div class=\"wasteBarRow363\" role=\"listitem\"><div class=\"wasteBarLabel363\"><span>${esc(label)}</span><b>${esc(value)}</b></div><div class=\"wasteBarTrack363\" aria-hidden=\"true\"><span class=\"tone${i%3}\" style=\"width:${percent.toFixed(3)}%\"></span></div></div>`}).join('')}</div>`;\n}\nfunction wasteChartPanel363(title,caption,body){return `<section class=\"wasteChartPanel363\"><h2>${esc(title)}</h2><p class=\"wasteChartCaption363\">${esc(caption)}</p>${body}</section>`}\nfunction wasteAnalysis363(rows){\n const d=wasteChartData363(rows);\n const quantities=typeof wasteQuantityPanel364==='function'?wasteQuantityPanel364(rows):`<section class=\"wasteUnitsPanel363\"><h2>${wasteText362('Verworfene Mengen','Wasted quantities','Ilości odpadów')}</h2><div class=\"wasteUnits363\">${d.units.map(x=>`<div><strong>${esc(formatQtySmart(x.qty,x.unit))}</strong></div>`).join('')||'<span>—</span>'}</div><p class=\"wasteChartCaption363\">${wasteText362('Einheiten bleiben getrennt.','Units are kept separate.','Jednostki pozostają rozdzielone.')}</p></section>`;\n return `<div class=\"wasteCharts363\">${quantities}${wasteChartPanel363(wasteText362('Warum wird verworfen?','Why is food discarded?','Dlaczego powstają odpady?'),wasteText362('Anzahl der Buchungen nach Grund','Number of records by reason','Liczba wpisów według powodu'),wasteBars363(d.reasons))}${wasteChartPanel363(wasteText362('Größter Warenverlust','Largest goods losses','Największa wartość strat'),wasteText362('Bis zu 10 Artikel nach bekanntem Warenwert','Up to 10 products by known goods value','Do 10 produktów według znanej wartości')+(d.unknown?` · ${d.unknown} `+wasteText362('Einträge ohne Preis nicht enthalten','unpriced records excluded','wpisów bez ceny pominięto'):''),wasteBars363(d.products,'money'))}${wasteChartPanel363(wasteText362('Verlauf der Verwürfe','Waste over time','Odpady w czasie'),wasteText362('Buchungen an den letzten 14 Tagen mit Einträgen im gewählten Zeitraum','Records on the last 14 dates with entries in this period','Wpisy z ostatnich 14 dni z odpadami w wybranym okresie'),wasteBars363(d.days,'date'))}</div><details class=\"wasteDetail363\"><summary>${wasteText362('Einzelne Buchungen und Originalangaben','Individual records and original details','Poszczególne wpisy i oryginalne dane')}</summary>${hubWasteTable362(rows)}</details>`;\n}\n"},{"src":"workspace-hubs362.js?v=406","code":"'use strict';\nfunction stockPhoto368(article){\n const src=String(article?.photo||'').trim();\n const safe=src&&(/^(?:data:image\\/(?:png|jpe?g|webp|gif|avif);base64,|https?:\\/\\/|blob:|\\.?\\.?\\/|assets\\/)/i.test(src));\n return `<span class=\"stockPhoto368\" aria-hidden=\"true\">${safe?`<img src=\"${esc(src)}\" alt=\"\" loading=\"lazy\" decoding=\"async\" onerror=\"this.hidden=true\">`:''}</span>`;\n}\nfunction stockColumns368(){return `<div class=\"hubStockColumns362\"><span aria-hidden=\"true\"></span><span>${hubText362('Artikel','Product','Produkt')}</span><span>${hubText362('Bestand','Stock','Stan')}</span><span>${hubText362('Nächstes MHD','Next best-before','Najbliższy termin')}</span><span>${hubText362('Aktionen','Actions','Działania')}</span></div>`}\n\n// Navigation views contain no copied forms and do not persist operating data.\nlet hub362=null,hubOrigin362=null,hubRequest362=0;\nconst hubView362={stockQuery:'',storage:'',eventQuery:'',eventFilter:'all',eventPhase:'overview',wasteDays:'30',wastePage:'home',recipeQuery:''};\nfunction hubText362(...w){return workText353(...w)}\nfunction hubTitle362(id){return ({stock:hubText362('Lager','Stock','Magazyn'),recipes:hubText362('Rezepte','Recipes','Przepisy'),buy:hubText362('Einkauf','Purchasing','Zakupy'),events:hubText362('Einsätze','Events','Wydarzenia'),analytics:hubText362('Auswertungen','Reports','Raporty'),waste:'Waste Management'})[id]||''}\nfunction hubAllowed362(id){return id==='waste'?hasPermission('stock')||hasPermission('scan'):webAllowedBefore362(id)}\nconst webAllowedBefore362=webAllowed360;\nwebAllowed360=function(id){return hubAllowed362(id)};\nwebMainSpecs360.splice(2,0,['waste','Waste Management','Waste Management','Waste Management',[],'waste']);\nconst iconBefore362=webIcon360;\nwebIcon360=function(id){return id==='waste'?'<svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><path d=\"M3 6h18M8 6V3h8v3M6 6l1 15h10l1-15M10 10v7M14 10v7\"/></svg>':iconBefore362(id)};\nfunction hubButton362(action,title,subtitle='',icon='stock',allowed=true){if(!allowed)return '';return `<button type=\"button\" class=\"hubTile362${action==='withdraw'?' stockWithdraw445':''}\" onclick=\"hubAction362('${escJs(action)}')\"><span class=\"hubIcon362\" aria-hidden=\"true\">${webIcon360(icon)}</span><strong>${esc(title)}</strong><small>${esc(subtitle)}</small><span class=\"hubArrow362\" aria-hidden=\"true\">→</span></button>`}\nfunction hubMetric362(label,value,note=''){return `<div class=\"hubMetric362\"><small>${esc(label)}</small><strong>${esc(value)}</strong>${note?`<span>${esc(note)}</span>`:''}</div>`}\nfunction hubHead362(id,subtitle,action=''){return `<header class=\"hubHead362\"><div><p class=\"hubCrumb362\">${hubText362('Start','Home','Start')} / ${esc(hubTitle362(id))}</p><h1>${esc(hubTitle362(id))}</h1><p>${esc(subtitle)}</p></div>${action}</header>`}\nfunction hubRoot362(){let n=document.getElementById('tabHub362');if(!n){n=document.createElement('div');n.id='tabHub362';n.className='tabPanel';const anchor=document.getElementById('tabDashboard');if(!anchor)return null;anchor.parentNode.insertBefore(n,anchor)}return n}\nasync function openHub362(id){\n if(!hubAllowed362(id))return false;\n if(!webDesktop360()&&!['waste','events'].includes(id))return webMainBefore362(id);\n const request=++hubRequest362;\n // The established tab transition flushes recipe drafts and honors form guards.\n const transitioned=await showTab('hub362');\n if(transitioned===false||request!==hubRequest362||[...document.querySelectorAll('.tabPanel.active')].some(n=>n.id!=='tabHub362')||!hubAllowed362(id))return false;\n const root=hubRoot362();if(!root)return false;\n document.querySelectorAll('.tabPanel.active').forEach(n=>n.classList.remove('active'));root.classList.add('active');\n if(!webDesktop360())document.body.classList.add('mobile-detail-mode');\n hub362=id;hubOrigin362=null;webMain360=id;installWebNav360();refreshHub362();closeSidebar();window.scrollTo({top:0,behavior:'smooth'});return true;\n}\nconst webMainBefore362=webMainOpen360;\nwebMainOpen360=function(id){hubOrigin362=null;if(['stock','recipes','buy','events','analytics','waste'].includes(id))return openHub362(id);return webMainBefore362(id)};\nfunction hubBackBar362(){\n document.querySelectorAll('.hubBack362').forEach(n=>n.remove());\n if(!hubOrigin362)return;const active=document.querySelector('.tabPanel.active');if(!active||active.id==='tabHub362')return;\n const bar=document.createElement('div');bar.className='hubBack362';bar.innerHTML=`<button type=\"button\" class=\"soft\" onclick=\"openHub362('${hubOrigin362}')\">← ${hubText362('Zur Übersicht','Back to overview','Wróć do przeglądu')}: ${esc(hubTitle362(hubOrigin362))}</button>`;active.prepend(bar);\n webMain360=hubOrigin362;installWebNav360();\n}\nconst showBeforeHubs362=showTab;\nshowTab=function(...args){const before=hub362;if(['dashboard','settings','help'].includes(args[0]))hubOrigin362=null;const r=showBeforeHubs362(...args);const finish=()=>{const other=[...document.querySelectorAll('.tabPanel.active')].some(n=>n.id!=='tabHub362');if(other){restoreShopping362();hub362=null;document.getElementById('tabHub362')?.classList.remove('active')}else hub362=before;hubBackBar362()};if(r?.then)return r.then(v=>{finish();return v});finish();return r};\nasync function hubRoute362(fn){const origin=hub362||hubOrigin362;hubOrigin362=origin;const out=await fn();if(document.querySelector('.tabPanel.active')?.id!=='tabHub362'){restoreShopping362();hub362=null;document.getElementById('tabHub362')?.classList.remove('active');hubBackBar362()}return out}\nfunction hubAction362(action){\n const [kind,...tail]=action.split(':');const value=tail.join(':');\n if(kind==='hub')return openHub362(value);\n if(kind==='legacy'){if(!document.getElementById(value)||document.getElementById(value).disabled||document.getElementById(value).style.display==='none')return;return hubRoute362(()=>webOriginalClick360(value))}\n if(kind==='phase'){if(!['overview','preparation','load','setup','onsite','calc'].includes(value)||!hasPermission(value==='overview'?'planning':value))return;hubView362.eventPhase=value;return hubRoute362(()=>hubOpenPlanning366(value))}\n if(kind==='event')return hubOpenEvent362(value);\n if(kind==='newEvent'&&hasPermission('planning'))return hubRoute362(async()=>{if(await hubOpenPlanning366('overview'))newPlanningEntry()});\n if(kind==='receive'||kind==='withdraw'){if(!hasPermission(kind==='receive'?'stockAdd':'scan'))return;hubView362.stockIntent=kind==='receive'?'in':'out';refreshHub362();document.getElementById('hubStockQuery362')?.focus();return}\n if(kind==='create'&&canCreateArticles())return openArticleEditor();\n if(kind==='document'&&canSaveDocuments240())return openInboundAuto183();\n if(kind==='recipe')return hubOpenRecipe362(value);\n if(kind==='newRecipe'&&hasPermission('recipes'))return hubRoute362(async()=>{await showTab('recipes');const input=document.querySelector('#recipeAdmin .recipeAddRow input');input?.scrollIntoView({block:'center'});input?.focus()});\n if(kind==='book')return hubRoute362(async()=>{await showTab('recipes');if(value==='chapters')document.getElementById('cookbookGroup')?.focus()});\n if(kind==='wastePage'){if(!hubAllowed362('waste'))return;hubView362.wastePage=value;refreshHub362();if(value==='capture')document.getElementById('hubWasteSearch362')?.focus();return}\n if(kind==='mhd')return hubRoute362(()=>{showTab('stock');document.getElementById('stockWarnings')?.scrollIntoView({block:'start'})});\n}\nasync function hubOpenPlanning366(phase){\n const permissions={overview:'planning',preparation:'preparation',load:'load',setup:'setup',onsite:'onsite',calc:'calc'};\n if(!permissions[phase]||!guardPermission(permissions[phase],phase))return false;\n // Await the existing draft/storage transition before selecting a phase.\n const result=await showTab('planning',true);\n if(result===false||!document.getElementById('tabPlanning')?.classList.contains('active')||!hasPermission(permissions[phase]))return false;\n setPlanningSection(phase);setActiveSideNav('planning',phase);return true;\n}\nasync function hubOpenEvent362(id){\n const phase=hubView362.eventPhase,p=phase==='overview'?'planning':phase;\n if(!hasPermission(p)||!(state.planning?.caterings||[]).some(c=>c.id===id))return;\n return hubRoute362(async()=>{if(!await hubOpenPlanning366(phase))return false;selectPlanningEntry(id,phase);return true});\n}\nasync function hubOpenRecipe362(id){\n if(!canViewRecipes164())return;let found=null;for(const [gi,g] of (state.recipeGroups||[]).entries())for(const [ri,r] of (g.recipes||[]).entries())if(r.id===id)found={gi,ri};if(!found)return;\n return hubRoute362(async()=>{await showTab('recipes');if(!document.getElementById('tabRecipes')?.classList.contains('active'))return;activeRecipeGroup=found.gi;activeRecipeIndex=found.ri;renderRecipes();document.querySelector('#recipeAdmin .recipeVariant[open]')?.scrollIntoView({block:'start'})});\n}\nfunction hubStockAction362(code,kind){\n if(!state.products?.[code])return;\n if(kind==='edit'){if(guardPermission('articles','Artikel bearbeiten'))return openArticleEditor(code);return}\n if(kind==='info'){if(hasPermission('stock')||hasPermission('scan'))openArticleInfo127(code);return}\n if(!['in','out'].includes(kind)||!hasPermission(kind==='in'?'stockAdd':'scan'))return;\n openVariant322(code,kind);\n}\nfunction hubStockRows362(query=hubView362.stockQuery,storage=hubView362.storage,suppliedPools=null){\n const pools=(suppliedPools||(query?stockPoolsForSearch301(query):stockPools301())).filter(p=>!storage||p.members.some(([,a])=>a.storage===storage)).sort((a,b)=>typeof compareStockPools383==='function'?compareStockPools383(a,b,storage):(a.summary?.name||a.members[0][1].name).localeCompare(b.summary?.name||b.members[0][1].name,'de'));\n return pools.map(p=>{const members=p.members.filter(([,a])=>!storage||a.storage===storage).slice().sort((a,b)=>typeof compareStockMembers383==='function'?compareStockMembers383(a,b):a[1].name.localeCompare(b[1].name,'de')),rows=members.map(([code,a])=>{const next=(state.batches?.[code]||[]).filter(b=>Number(b.qty)>0&&b.mhd).sort((a,b)=>a.mhd.localeCompare(b.mhd))[0],warning=next?expiryWarning322(next.mhd):null;return `<div class=\"hubStockRow362\">${stockPhoto368(a)}<div class=\"stockName368\"><b>${esc(a.name)}</b><small>${esc(a.manufacturer||a.brand||a.supplier||'')}${a.storage?' · '+esc(a.storage):''}</small>${typeof productMeasuresHtml433==='function'?productMeasuresHtml433(a):''}</div><strong>${esc(formatQtySmart(Number(a.stock)||0,a.unit))}</strong><span class=\"${warning?.className||''}\">${next?esc(formatDate(next.mhd)):'—'}${warning?`<small>${esc(warning.label)}</small>`:''}</span><div class=\"hubActions362\">${hasPermission('stockAdd')?`<button type=\"button\" class=\"soft\" aria-label=\"${esc(stockMovementText301('in')+' '+a.name)}\" onclick=\"hubStockAction362('${escJs(code)}','in')\">+</button>`:''}${hasPermission('scan')?`<button type=\"button\" class=\"soft\" aria-label=\"${esc(stockMovementText301('out')+' '+a.name)}\" onclick=\"hubStockAction362('${escJs(code)}','out')\">−</button>`:''}<button type=\"button\" class=\"soft\" aria-label=\"${esc(stockInfoText311('info')+' '+a.name)}\" onclick=\"hubStockAction362('${escJs(code)}','info')\">ⓘ</button>${hasPermission('articles')?`<button type=\"button\" class=\"soft\" title=\"${esc(hubText362('Bearbeiten','Edit','Edytuj'))}\" aria-label=\"${esc(hubText362('Bearbeiten','Edit','Edytuj')+' '+a.name)}\" onclick=\"hubStockAction362('${escJs(code)}','edit')\">✎</button>`:''}</div></div>`}).join('');\n if(!p.grouped)return rows;const s=p.summary;return `<details class=\"hubPool362\" data-pool435=\"${esc(p.key)}\" ${query?'open':''}><summary><span class=\"stockPhoto368\" aria-hidden=\"true\"></span><div class=\"stockName368\"><b>${esc(s.name)}</b><small>${p.members.length} ${groupText322('variants')}${storage?' · '+hubText362('Gruppensumme aller Lagerorte','Group total across locations','Suma grupy ze wszystkich lokalizacji'):''}</small></div><strong>${s.totalSafe?esc(formatQtySmart(s.total,s.unit))+' '+hubText362('gesamt','total','razem'):hubText362('Umrechnung prüfen','Check conversion','Sprawdź przeliczenie')}</strong><span></span><span>${typeof groupMenuButton435==='function'?groupMenuButton435(p):''}</span></summary>${typeof groupTools433==='function'?groupTools433(p):''}${rows}</details>`;\n }).join('')||`<p class=\"muted\">${hubText362('Keine passenden Produkte.','No matching products.','Brak pasujących produktów.')}</p>`;\n}\nfunction renderHubStock362(){\n const storages=[...new Set(Object.values(state.products||{}).map(a=>a.storage).filter(Boolean))].sort((a,b)=>a.localeCompare(b,'de'));\n return hubHead362('stock',hubText362('Bestand und Haltbarkeit im Blick','Stock and best-before dates at a glance','Stan i terminy ważności w jednym miejscu'),canCreateArticles()?`<button class=\"primary\" onclick=\"hubAction362('create')\">+ ${hubText362('Neues Produkt','New product','Nowy produkt')}</button>`:'')+\n `<div class=\"hubSearch362\"><label class=\"srOnly\" for=\"hubStockQuery362\">${hubText362('Produkt suchen','Search product','Szukaj produktu')}</label><input id=\"hubStockQuery362\" type=\"search\" placeholder=\"${hubText362('Produkt oder Barcode suchen','Search product or barcode','Szukaj produktu lub kodu')}\" value=\"${esc(hubView362.stockQuery)}\" oninput=\"hubView362.stockQuery=this.value;document.getElementById('hubStockRows362').innerHTML=hubStockRows362()\"><select aria-label=\"${hubText362('Lagerort','Storage location','Lokalizacja')}\" onchange=\"hubView362.storage=this.value;document.getElementById('hubStockRows362').innerHTML=hubStockRows362()\"><option value=\"\">${hubText362('Alle Lagerorte','All locations','Wszystkie lokalizacje')}</option>${storages.map(s=>`<option ${s===hubView362.storage?'selected':''}>${esc(s)}</option>`).join('')}</select></div>`+\n `<div class=\"hubTiles362 three\">${hubButton362('receive',stockMovementText301('in'),hubView362.stockIntent==='in'?hubText362('Produkt unten auswählen','Choose a product below','Wybierz produkt poniżej'):hubText362('Wareneingang buchen','Receive goods','Przyjmij towar'),'stock',hasPermission('stockAdd'))}${hubButton362('withdraw',stockMovementText301('out'),hubView362.stockIntent==='out'?hubText362('Produkt unten auswählen','Choose a product below','Wybierz produkt poniżej'):hubText362('Ware aus dem Lager nehmen','Withdraw warehouse goods','Wydaj towar'),'stock',hasPermission('scan'))}${hubButton362('mhd',hubText362('MHD im Blick','Best-before dates','Terminy ważności'),hubText362('Chargen und Warnungen prüfen','Review lots and warnings','Sprawdź partie i ostrzeżenia'),'events',hasPermission('stock'))}</div>`+\n `<div class=\"hubLinks362\">${hubButton362('legacy:tabLagerBtn',hubText362('Barcode & Fotosuche','Barcode & photo search','Kod kreskowy i zdjęcia'),'','stock',hasPermission('scan'))}${hubButton362('legacy:navInventory185',hubText362('Inventur','Inventory','Inwentaryzacja'),'','stock',hasPermission('inventory'))}${hubButton362('legacy:tabAdminBtn',hubText362('Produkte verwalten','Manage products','Zarządzaj produktami'),'','stock',hasPermission('articles'))}</div><section class=\"hubPanel362\"><h2>${hubText362('Bestandsübersicht','Stock overview','Przegląd stanu')}</h2>${stockColumns368()}<div id=\"hubStockRows362\">${hubStockRows362()}</div></section>`;\n}\nfunction hubEventRows362(){const q=hubView362.eventQuery.toLocaleLowerCase(),f=hubView362.eventFilter;return (state.planning?.caterings||[]).filter(c=>(!q||String(c.event).toLocaleLowerCase().includes(q))&&(f==='all'||(f==='closed')===!!c.workflow?.closed)).slice().sort((a,b)=>String(b.date).localeCompare(String(a.date))).map(c=>`<button type=\"button\" class=\"hubListRow362\" onclick=\"hubAction362('event:${escJs(c.id)}')\"><span><b>${esc(c.event)}</b><small>${esc(c.date?formatDate(c.date):'')} · ${esc(eventQuantityText397(c))}</small></span><span>${c.workflow?.closed?hubText362('Abgeschlossen','Closed','Zakończono'):hubText362('Offen','Open','Otwarte')}</span><span aria-hidden=\"true\">→</span></button>`).join('')||`<p class=\"muted\">${hubText362('Keine passenden Einsätze.','No matching events.','Brak pasujących wydarzeń.')}</p>`}\nfunction renderHubEvents362(){\n const options=[['overview','Planen','Plan','Planuj','Menü, Mengen und Bedarf','Menu, quantities and needs','Menu, ilości i zapotrzebowanie','planning'],['preparation','Vorbereiten','Prepare','Przygotuj','Zutaten und Aufgaben','Ingredients and tasks','Składniki i zadania','preparation'],['load','Durchführen','Carry out','Realizuj','Beladen, Aufbau und vor Ort','Loading, setup and service','Załadunek, rozstawienie i obsługa','load'],['calc','Abschließen','Close','Zamknij','Rückware, Verwurf und Kosten','Returns, waste and costs','Zwroty, odpady i koszty','calc']];\n // Employees without loading rights still have an entry to their permitted phase.\n if(!hasPermission('load')){const phase=['setup','onsite'].find(p=>hasPermission(p));if(phase){options[2][0]=phase;options[2][7]=phase}}\n const permitted=options.filter(o=>hasPermission(o[7]));if(!permitted.some(o=>o[0]===hubView362.eventPhase))hubView362.eventPhase=permitted[0]?.[0]||'overview';\n return hubHead362('events',hubText362('Planen und im Alltag durchführen','Plan and carry out your work','Planuj i realizuj pracę'),hasPermission('planning')?`<button class=\"primary\" onclick=\"hubAction362('newEvent')\">+ ${hubText362('Neuer Einsatz','New event','Nowe wydarzenie')}</button>`:'')+`<div class=\"hubTiles362 four\">${permitted.map(([id,de,en,pl,sd,se,sp])=>hubButton362('phase:'+id,hubText362(de,en,pl),hubText362(sd,se,sp),'events')).join('')}</div><section class=\"hubPanel362\" id=\"hubEvents362\"><h2>${hubText362('Meine Einsätze','My events','Moje wydarzenia')}</h2><p class=\"muted\">${hubText362('Einsatz auswählen','Choose an event','Wybierz wydarzenie')} · ${esc(permitted.find(o=>o[0]===hubView362.eventPhase)?.slice(1,4)?hubText362(...permitted.find(o=>o[0]===hubView362.eventPhase).slice(1,4)):'')}</p><div class=\"hubSearch362\"><input type=\"search\" aria-label=\"${hubText362('Einsatz suchen','Search events','Szukaj wydarzeń')}\" placeholder=\"${hubText362('Einsatz suchen','Search events','Szukaj wydarzeń')}\" value=\"${esc(hubView362.eventQuery)}\" oninput=\"hubView362.eventQuery=this.value;document.getElementById('hubEventRows362').innerHTML=hubEventRows362()\"><select aria-label=\"Status\" onchange=\"hubView362.eventFilter=this.value;document.getElementById('hubEventRows362').innerHTML=hubEventRows362()\">${[['all','Alle','All','Wszystkie'],['open','Offen','Open','Otwarte'],['closed','Abgeschlossen','Closed','Zakończone']].map(([id,...w])=>`<option value=\"${id}\" ${id===hubView362.eventFilter?'selected':''}>${hubText362(...w)}</option>`).join('')}</select></div><div id=\"hubEventRows362\">${hubEventRows362()}</div></section>`;\n}\nfunction renderHubBuy362(){return hubHead362('buy',hubText362('Was fehlt und wo du es besorgst','What you need and where to buy it','Czego brakuje i gdzie to kupić'))+`<div class=\"hubTiles362 four\">${hubButton362('legacy:navShoppingListBtn',hubText362('Einkaufsliste','Shopping list','Lista zakupów'),hubText362('Nach Lieferant sortiert','Sorted by supplier','Według dostawcy'),'buy',hasPermission('shopping'))}${hubButton362('legacy:navNeed155',hubText362('Bedarf prüfen','Check needs','Sprawdź zapotrzebowanie'),hubText362('Lager und Planung abgleichen','Compare stock and plans','Porównaj stan i plan'),'stock',hasPermission('shopping'))}${hubButton362('legacy:navSuppliers155',hubText362('Lieferanten','Suppliers','Dostawcy'),hubText362('Bezugsquellen verwalten','Manage suppliers','Zarządzaj dostawcami'),'buy',hasPermission('suppliers'))}${hubButton362('document',hubText362('Beleg erfassen','Capture document','Dodaj dokument'),hubText362('Rechnung oder Lieferschein','Invoice or delivery note','Faktura lub dokument dostawy'),'recipes',canSaveDocuments240())}</div><div id=\"hubShoppingSlot362\"></div><section class=\"hubPanel362\"><div class=\"hubLinks362\">${hubButton362('legacy:navOrder155',hubText362('Bestellungen','Orders','Zamówienia'),'','buy',hasPermission('shopping'))}</div></section>`}\nfunction hubRecipeRows362(){const q=hubView362.recipeQuery.toLocaleLowerCase(),cards=[];for(const g of state.recipeGroups||[])for(const r of g.recipes||[]){if(!r.id||q&&!String(r.name).toLocaleLowerCase().includes(q))continue;const meta=recipeEconomicMeta(r);cards.push(`<button type=\"button\" class=\"hubRecipe362\" onclick=\"hubAction362('recipe:${escJs(r.id)}')\"><span class=\"hubIcon362\" aria-hidden=\"true\">${webIcon360('recipes')}</span><small>${esc(g.name)}</small><h3>${esc(r.name)}</h3><span class=\"hubRecipeOpen362\">${hubText362('Rezept öffnen','Open recipe','Otwórz przepis')} →</span><footer><span>${esc(recipePortionPreview346(r,meta))}</span><span>${hubText362('Wareneinsatz','Ingredient cost','Koszt składników')}: ${meta.complete?esc(moneyFmt(meta.cost)):hubText362('Kosten offen','Costs incomplete','Koszty niepełne')}</span></footer></button>`)}return cards.join('')||`<p>${hubText362('Keine passenden Rezepte.','No matching recipes.','Brak pasujących przepisów.')}</p>`}\nfunction renderHubRecipes362(){return hubHead362('recipes',hubText362('Rezepte finden und kalkulieren','Find and cost recipes','Znajdź przepisy i oblicz koszty'),hasPermission('recipes')?`<button type=\"button\" class=\"primary\" onclick=\"hubAction362('newRecipe')\">+ ${hubText362('Neues Rezept','New recipe','Nowy przepis')}</button>`:'')+`<div class=\"hubSearch362\"><input type=\"search\" aria-label=\"${hubText362('Rezept suchen','Search recipes','Szukaj przepisu')}\" placeholder=\"${hubText362('Rezept suchen','Search recipes','Szukaj przepisu')}\" value=\"${esc(hubView362.recipeQuery)}\" oninput=\"hubView362.recipeQuery=this.value;document.getElementById('hubRecipeRows362').innerHTML=hubRecipeRows362()\"></div><div class=\"hubTiles362 three\">${hubButton362('book:all',hubText362('Meine Rezepte','My recipes','Moje przepisy'),hubText362('Zutaten und Zubereitung','Ingredients and preparation','Składniki i przygotowanie'),'recipes')}${hubButton362('book:chapters',hubText362('Kapitel & Bausteine','Chapters & components','Rozdziały i składniki'),hubText362('Vorhandene Rezeptsammlung','Existing recipe collection','Istniejąca kolekcja przepisów'),'recipes')}${hubButton362('legacy:navCalcFood155',hubText362('Kalkulation','Cost calculation','Kalkulacja'),hubText362('Portionskosten prüfen','Review portion costs','Sprawdź koszt porcji'),'analytics',hasPermission('calc'))}</div><div class=\"hubRecipes362\" id=\"hubRecipeRows362\">${hubRecipeRows362()}</div>`}\nfunction analyticsActuals406(s=state){\n const entries=[],seen=new Set();let unknown=0;\n for(const c of s.planning?.caterings||[]){\n  if(c.day390)continue;const a=c.actual;\n  if(a&&['revenue','goods'].every(k=>a[k]!==''&&a[k]!==null&&a[k]!==undefined&&Number.isFinite(Number(a[k]))))entries.push({revenue:Number(a.revenue),goods:Number(a.goods)});\n }\n for(const c of (s.dailyClosings||[]).slice().sort((a,b)=>String(b.savedAt||'').localeCompare(String(a.savedAt||'')))){\n  const key=c.planId390||c.id;if(key&&seen.has(key))continue;if(key)seen.add(key);\n  const x=c.totals390;\n  if(x){const goods=[x.food,x.waste,x.own,x.additionalFood??0].reduce((sum,n)=>sum+Number(n),0);if(x.known&&Number.isFinite(x.revenue)&&Number.isFinite(goods))entries.push({revenue:x.revenue,goods});else unknown++;}\n  else if(typeof dailyEconomics255==='function'){const x=dailyEconomics255(c.items||[]);if(x.complete&&Number.isFinite(x.revenue)&&Number.isFinite(x.cost))entries.push({revenue:x.revenue,goods:x.cost});else unknown++;}\n }\n return {entries,unknown,revenue:entries.reduce((n,x)=>n+x.revenue,0),goods:entries.reduce((n,x)=>n+x.goods,0)};\n}\nfunction renderHubAnalytics362(){\n const totals=analyticsActuals406(),entries=totals.entries,rev=totals.revenue,goods=totals.goods,note=hubText362('Gespeicherte Abschlüsse und Ist-Werte','Saved closings and actuals','Zapisane zamkniecia i koszty rzeczywiste')+(totals.unknown?' · '+totals.unknown+' '+hubText362('unvollständig, nicht eingerechnet','incomplete, excluded','niepelne, pominiete'):'');\n const metrics=hasPermission('calc')?`<div class=\"hubMetrics362\">${hubMetric362(hubText362('Umsatz','Revenue','Obrót'),entries.length?moneyFmt(rev):'—',note)}${hubMetric362(hubText362('Wareneinsatz','Ingredient cost','Koszt składników'),entries.length?moneyFmt(goods):'—',note)}${hubMetric362(hubText362('Deckungsbeitrag nach Warenkosten','Contribution after goods','Marża po kosztach towaru'),entries.length?moneyFmt(rev-goods):'—',note)}</div>`:'';\n const specs=[['navCalcBtn','Einsatzkalkulationen','Event costing','Kalkulacje wydarzeń','Plan und Ist vergleichen','Compare plan and actual','Porównaj plan i wykonanie','calc'],['navCalcFood155','Wareneinsatz','Ingredient cost','Koszt składników','Kosten der eingesetzten Waren','Cost of ingredients used','Koszt zużytych składników','calc'],['navCalcDb155','Deckungsbeitrag','Contribution margin','Marża pokrycia','Kosten und Erlöse vergleichen','Compare costs and revenue','Porównaj koszty i przychody','calc'],['tabRankingBtn','Ranking','Ranking','Ranking','Produkte und Rezepte vergleichen','Compare products and recipes','Porównaj produkty i przepisy','stock'],['navCalcQuick155','Schnellkalkulation','Quick costing','Szybka kalkulacja','Eine Idee direkt durchrechnen','Calculate an idea','Przelicz pomysł','calc'],['navCalcDaily155','Tagesabschlüsse','Daily closing','Zamknięcie dnia','Ergebnisse pro Tag ansehen','See daily results','Wyniki dzienne','calc']];\n return hubHead362('analytics',hubText362('Zahlen auf einen Blick','Numbers at a glance','Liczby w jednym miejscu'))+metrics+`<div class=\"hubTiles362 three\">${specs.map(([id,de,en,pl,sd,se,sp,p])=>hubButton362('legacy:'+id,hubText362(de,en,pl),hubText362(sd,se,sp),'analytics',hasPermission(p))).join('')}</div>`;\n}\nfunction hubWasteFiltered362(){const today=localTodayISO(),cutoff=hubView362.wasteDays==='0'?'':new Date(Date.parse(today+'T12:00:00Z')-(Number(hubView362.wasteDays)-1)*86400000).toISOString().slice(0,10);return wasteRows362().filter(r=>!cutoff||r.date>=cutoff&&r.date<=today)}\nfunction hubWasteCaptureRows362(query=''){return (query?stockPoolsForSearch301(query):stockPools301()).flatMap(p=>p.members).filter(([,a])=>Number(a.stock)>0).sort((a,b)=>a[1].name.localeCompare(b[1].name,'de')).map(([code,a])=>`<button class=\"hubListRow362\" type=\"button\" onclick=\"openWasteProduct362('${escJs(code)}')\"><span><b>${esc(a.name)}</b><small>${esc(a.storage||'')} · ${esc(code)}</small></span><strong>${esc(formatQtySmart(Number(a.stock),a.unit))}</strong><span>→</span></button>`).join('')||`<p>${hubText362('Kein passender Lagerbestand.','No matching warehouse stock.','Brak pasującego stanu.')}</p>`}\nfunction hubWasteTable362(rows){return `<div class=\"hubTableScroll362\"><table><thead><tr>${['Datum|Date|Data','Artikel / Charge|Product / lot|Produkt / partia','Menge|Quantity|Ilość','Grund / Quelle|Reason / source|Powód / źródło','Warenwert|Goods value|Wartość'].map(x=>`<th>${hubText362(...x.split('|'))}</th>`).join('')}</tr></thead><tbody>${rows.map(r=>`<tr><td>${esc(r.date?formatDate(r.date):'—')}</td><td><b>${esc(r.name)}</b><small>${esc(r.batch||hubText362('Charge nicht dokumentiert','Lot not recorded','Partia niezapisana'))}</small></td><td>${esc(formatQtySmart(r.qty,r.unit))}</td><td>${esc(r.reason)}<small>${esc(r.source)}</small></td><td>${r.value===null?hubText362('Warenwert offen','Value unknown','Wartość nieznana'):esc(moneyFmt(r.value))}</td></tr>`).join('')||`<tr><td colspan=\"5\">${hubText362('Keine Einträge im gewählten Zeitraum.','No records in the selected period.','Brak wpisów w wybranym okresie.')}</td></tr>`}</tbody></table></div>`}\nfunction renderHubWaste362(){const rows=hubWasteFiltered362(),s=wasteSummary362(rows),page=hubView362.wastePage,period=`<select aria-label=\"${hubText362('Zeitraum','Period','Okres')}\" onchange=\"hubView362.wasteDays=this.value;refreshHub362()\">${[['1','Heute','Today','Dzisiaj'],['30','Letzte 30 Tage','Last 30 days','Ostatnie 30 dni'],['90','Letzte 90 Tage','Last 90 days','Ostatnie 90 dni'],['0','Gesamter Zeitraum','All time','Cały okres']].map(([id,...w])=>`<option value=\"${id}\" ${id===hubView362.wasteDays?'selected':''}>${hubText362(...w)}</option>`).join('')}</select>`;\n const top=Object.entries(s.reasons).sort((a,b)=>b[1]-a[1])[0];\n let content='';if(page==='capture')content=`<section class=\"hubPanel362\"><h2>${hubText362('Produkt auswählen','Choose product','Wybierz produkt')}</h2><input id=\"hubWasteSearch362\" type=\"search\" aria-label=\"${hubText362('Artikel oder Barcode suchen','Search product or barcode','Szukaj produktu lub kodu')}\" placeholder=\"${hubText362('Artikel oder Barcode suchen','Search product or barcode','Szukaj produktu lub kodu')}\" oninput=\"document.getElementById('hubWasteCapture362').innerHTML=hubWasteCaptureRows362(this.value)\"><div id=\"hubWasteCapture362\">${hubWasteCaptureRows362()}</div></section>`;\n else if(page==='analysis'&&typeof wasteAnalysis363==='function')content=wasteAnalysis363(rows);\n else if(page==='analysis')content=`<section class=\"hubPanel362\"><h2>${hubText362('Mengen nach Einheit','Quantities by unit','Ilości według jednostek')}</h2>${Object.entries(s.units).map(([u,n])=>`<p><b>${esc(formatQtySmart(n,u))}</b></p>`).join('')||'—'}<h2>${hubText362('Gründe','Reasons','Powody')}</h2>${Object.entries(s.reasons).sort((a,b)=>b[1]-a[1]).map(([reason,n])=>`<div class=\"hubAnalysisRow362\"><span>${esc(reason)}</span><b>${n} ${hubText362('Einträge','records','wpisów')}</b></div>`).join('')||'—'}</section>`;\n else content=`<section class=\"hubPanel362\"><h2>${page==='list'?hubText362('Tagesliste','Daily records','Lista dzienna'):hubText362('Letzte Verwürfe','Recent waste','Ostatnie odpady')}</h2>${hubWasteTable362(page==='list'?rows:rows.slice(0,10))}</section>`;\n return hubHead362('waste',hubText362('Verwurf sehen und Ursachen erkennen','See waste and understand causes','Zobacz odpady i ich przyczyny'),`<div class=\"hubActions362\">${period}${hasPermission('scan')?`<button class=\"primary\" onclick=\"hubAction362('wastePage:capture')\">+ ${hubText362('Verwurf erfassen','Record waste','Zapisz odpad')}</button>`:''}</div>`)+`<div class=\"hubMetrics362\">${hubMetric362(hubText362('Verworfener Warenwert','Wasted goods value','Wartość odpadów'),s.unknown?(s.count>s.unknown?moneyFmt(s.value)+' '+hubText362('bekannt','known','znane'):hubText362('Offen','Unknown','Nieznane')):moneyFmt(s.value),s.unknown?`${s.unknown} ${hubText362('Einträge ohne gesicherten Preis','records without verified price','wpisów bez potwierdzonej ceny')}`:'')}${hubMetric362(hubText362('Erfasste Verwürfe','Waste records','Wpisy odpadów'),String(s.count))}${hubMetric362(hubText362('Häufigster Grund','Most common reason','Najczęstszy powód'),top?.[0]||'—')}</div><div class=\"hubTiles362 three\">${hubButton362('wastePage:capture',hubText362('Schnell erfassen','Quick capture','Szybki zapis'),hubText362('Artikel, Menge und Grund','Product, quantity and reason','Produkt, ilość i powód'),'waste',hasPermission('scan'))}${hubButton362('wastePage:list',hubText362('Tagesliste','Daily records','Lista dzienna'),hubText362('Alle erfassten Verwürfe','All waste records','Wszystkie odpady'),'recipes')}${hubButton362('wastePage:analysis',hubText362('Auswertung','Analysis','Analiza'),hubText362('Ursachen und Warenwert','Causes and value','Powody i wartość'),'analytics')}</div>${page!=='home'?`<button type=\"button\" class=\"soft hubInlineBack362\" onclick=\"hubAction362('wastePage:home')\">← ${hubText362('Zur Übersicht','Back to overview','Wróć do przeglądu')}</button>`:''}${content}<div class=\"hubLinks362\">${hubButton362('mhd',hubText362('MHD zuerst','Best-before first','Najpierw terminy'),hubText362('Bald ablaufende Chargen prüfen','Review expiring lots','Sprawdź kończące się terminy'),'events',hasPermission('stock'))}</div>`;\n}\nfunction refreshHub362(){const root=document.getElementById('tabHub362');if(!root?.classList.contains('active')||!hub362)return;if(!hubAllowed362(hub362)){hub362=null;showTab('dashboard');return}restoreShopping362();root.innerHTML=({stock:renderHubStock362,events:renderHubEvents362,buy:renderHubBuy362,recipes:renderHubRecipes362,analytics:renderHubAnalytics362,waste:renderHubWaste362})[hub362]?.()||'';if(hub362==='buy')mountShopping362();webMain360=hub362;installWebNav360()}\nconst renderBeforeHubs362=render;\nrender=function(...args){const r=renderBeforeHubs362(...args);refreshHub362();hubBackBar362();installWasteMobile362();return r};\nfunction installWasteMobile362(){const grid=document.querySelector('#mobileScreenMore .mobileMoreGrid');if(!grid)return;let b=grid.querySelector('[data-waste362]');if(!b){b=document.createElement('button');b.type='button';b.dataset.waste362='true';b.setAttribute('onclick',\"openHub362('waste')\");grid.appendChild(b)}b.hidden=!hubAllowed362('waste');b.textContent='Waste Management'}\nconst mobileNavBefore362=configureMobileWorkNav01;\nconfigureMobileWorkNav01=function(...args){const r=mobileNavBefore362(...args);installWasteMobile362();return r};\nconst permissionBeforeHubs362=applyPermissionsUI;\napplyPermissionsUI=function(...args){const r=permissionBeforeHubs362(...args);if(hub362&&!hubAllowed362(hub362)){hub362=null;showTab('dashboard')}installWasteMobile362();return r};\nif(typeof document!=='undefined'&&document.body){installWebNav360();installWasteMobile362();if(typeof addEventListener==='function')addEventListener('resize',()=>{if(!webDesktop360()&&hub362&&hub362!=='waste')showTab('dashboard')})}\n\nlet shoppingMoved362=null;\nfunction restoreShopping362(){if(!shoppingMoved362)return;const {node,marker}=shoppingMoved362;if(marker.parentNode){marker.before(node);marker.remove()}shoppingMoved362=null}\nfunction mountShopping362(){\n if(!hasPermission('shopping'))return;const slot=document.getElementById('hubShoppingSlot362'),node=document.getElementById('purchasingListPage');if(!slot||!node)return;\n setPurchasingSection('list');const marker=document.createComment('shopping-original362');node.before(marker);shoppingMoved362={node,marker};slot.appendChild(node);node.hidden=false;\n}\n"},{"src":"stock-entry363.js?v=365","code":"'use strict';\nlet stockPicker363=null;\nfunction stockPickerRows363(query=''){\n const kind=stockPicker363?.kind;if(!['in','out'].includes(kind))return '';\n const pools=query?stockPoolsForSearch301(query):stockPools301();\n return pools.flatMap(p=>p.members).filter(([,a])=>kind==='in'||Number(a.stock)>0).map(([code,a])=>`<button type=\"button\" class=\"hubListRow362\" onclick=\"chooseStockPicker363('${escJs(code)}')\"><span><b>${esc(a.name)}</b><small>${esc(a.storage||'')} · ${esc(code)}</small></span><strong>${esc(formatQtySmart(Number(a.stock)||0,a.unit))}</strong><span aria-hidden=\"true\">→</span></button>`).join('')||`<p>${hubText362('Keine passenden Produkte gefunden.','No matching products.','Brak pasujących produktów.')}</p>`;\n}\nfunction openStockPicker363(kind){\n if(!['in','out'].includes(kind)||!guardPermission(kind==='in'?'stockAdd':'scan',stockMovementText301(kind)))return;\n stockPicker363={kind};const query=hubView362.stockQuery||'';\n openOpsPanel(stockMovementText301(kind),`<div class=\"stockPicker363\"><p>${hubText362('Welches Produkt möchtest du buchen?','Which product would you like to book?','Który produkt chcesz zaksięgować?')}</p><label for=\"stockPickerQuery363\">${hubText362('Produkt oder Barcode suchen','Search product or barcode','Szukaj produktu lub kodu')}</label><input id=\"stockPickerQuery363\" type=\"search\" value=\"${esc(query)}\" oninput=\"document.getElementById('stockPickerRows363').innerHTML=stockPickerRows363(this.value)\"><div id=\"stockPickerRows363\">${stockPickerRows363(query)}</div></div>`);\n document.getElementById('stockPickerQuery363')?.focus();\n}\nfunction chooseStockPicker363(code){\n const kind=stockPicker363?.kind,a=state.products?.[code];if(!a||!['in','out'].includes(kind)||!guardPermission(kind==='in'?'stockAdd':'scan',stockMovementText301(kind)))return;\n if(kind==='out'&&!(Number(a.stock)>0)){showFeedback(hubText362('Kein Lagerbestand verfügbar.','No warehouse stock available.','Brak stanu magazynowego.'),'warn');return}\n document.getElementById('opsDialog92')?.close();stockPicker363=null;openVariant322(code,kind);\n}\nfunction stockExpiryRows363(query='',filter='all'){\n const firstDate=code=>(state.batches?.[code]||[]).filter(b=>Number(b.qty)>0&&b.mhd).map(b=>b.mhd).sort()[0]||'9999-99-99';\n const rows=(query?stockPoolsForSearch301(query):stockPools301()).flatMap(p=>p.members).filter(([code,a])=>a.mhd||(state.batches?.[code]||[]).some(b=>Number(b.qty)>0)).sort(([ac,a],[bc,b])=>firstDate(ac).localeCompare(firstDate(bc))||a.name.localeCompare(b.name,'de'));\n return rows.map(([code,a])=>{\n  const batches=(state.batches?.[code]||[]).filter(b=>Number(b.qty)>0),dated=batches.filter(b=>b.mhd),unassigned=Math.max(0,(Number(a.stock)||0)-dated.reduce((n,b)=>n+Number(b.qty),0));\n  const shown=dated.filter(b=>filter==='all'||filter==='expired'&&b.mhd<localTodayISO()||filter==='soon'&&b.mhd>=localTodayISO()&&!!expiryWarning322(b.mhd));\n  if(filter!=='all'&&!shown.length)return '';\n  const lines=shown.slice().sort((a,b)=>a.mhd.localeCompare(b.mhd)).map(b=>{const warning=expiryWarning322(b.mhd),review=b.reviewUntil243?`<small>${hubText362('Weiterverwendung bestätigt bis','Continued use confirmed until','Dalsze użycie potwierdzone do')} ${esc(formatDate(b.reviewUntil243))}</small>`:'';return `<tr><td class=\"${warning?.className||''}\"><b>${esc(formatDate(b.mhd))}</b>${review}</td><td>${esc(formatQtySmart(Number(b.qty),a.unit))}</td><td class=\"${warning?.className||''}\">${warning?esc(warning.label):'—'}</td></tr>`}).join('');\n  return `<section class=\"hubPanel362 stockExpiryProduct363\"><div class=\"stockExpiryHead363\"><div><h2>${esc(a.name)}</h2><small>${esc(a.storage||'')} · ${esc(code)}</small></div><button type=\"button\" class=\"soft\" onclick=\"hubStockAction362('${escJs(code)}','info')\">${hubText362('Produktinfo','Product info','Informacje')}</button></div><div class=\"hubTableScroll362\"><table><thead><tr><th>${hubText362('Ablaufdatum','Best-before date','Termin ważności')}</th><th>${hubText362('Menge','Quantity','Ilość')}</th><th>${hubText362('Warnung','Warning','Ostrzeżenie')}</th></tr></thead><tbody>${lines}${filter==='all'&&unassigned>0?`<tr><td>${esc(stockInfoText311('unassigned'))}</td><td>${esc(formatQtySmart(unassigned,a.unit))}</td><td>—</td></tr>`:''}${!lines&&!(filter==='all'&&unassigned>0)?`<tr><td colspan=\"3\">${hubText362('Keine vorhandenen Chargen.','No current lots.','Brak aktualnych partii.')}</td></tr>`:''}</tbody></table></div></section>`;\n }).join('')||`<section class=\"hubPanel362\"><p>${hubText362('Keine passenden MHD-Chargen.','No matching best-before lots.','Brak pasujących partii.')}</p></section>`;\n}\nfunction renderStockExpiry363(){return hubHead362('stock',hubText362('MHD im Blick · Chargen und Warnungen','Best-before dates · lots and warnings','Terminy ważności · partie i ostrzeżenia'))+`<button type=\"button\" class=\"soft\" onclick=\"hubAction362('stockOverview')\">← ${hubView362.stockExpiryOrigin==='waste'?hubText362('Zurück zu Waste Management','Back to Waste Management','Wróć do Waste Management'):hubText362('Zur Lagerübersicht','Back to stock overview','Wróć do magazynu')}</button><div class=\"hubSearch362\"><input id=\"stockExpiryQuery363\" type=\"search\" aria-label=\"${hubText362('Produkt suchen','Search product','Szukaj produktu')}\" placeholder=\"${hubText362('Produkt suchen','Search product','Szukaj produktu')}\" oninput=\"updateStockExpiry363()\"><select id=\"stockExpiryFilter363\" aria-label=\"${hubText362('MHD-Filter','Best-before filter','Filtr terminów')}\" onchange=\"updateStockExpiry363()\"><option value=\"all\">${hubText362('Alle Chargen','All lots','Wszystkie partie')}</option><option value=\"expired\">${hubText362('MHD überschritten','Expired','Po terminie')}</option><option value=\"soon\">${hubText362('In den nächsten 7 Tagen','Within 7 days','W ciągu 7 dni')}</option></select></div><div id=\"stockExpiryRows363\">${stockExpiryRows363()}</div>`}\nfunction updateStockExpiry363(){const box=document.getElementById('stockExpiryRows363');if(box)box.innerHTML=stockExpiryRows363(document.getElementById('stockExpiryQuery363')?.value||'',document.getElementById('stockExpiryFilter363')?.value||'all')}\nconst stockHubBefore363=renderHubStock362;\nrenderHubStock362=function(){return hubView362.stockExpiry?renderStockExpiry363():stockHubBefore363()};\nconst hubActionBefore363=hubAction362;\nhubAction362=function(action){if(action==='receive'||action==='withdraw')return openStockPicker363(action==='receive'?'in':'out');if(action==='mhd'){if(!hasPermission('stock'))return;hubView362.stockExpiryOrigin=hub362==='waste'?'waste':'stock';hubView362.stockExpiry=true;return openHub362('stock')}if(action==='stockOverview'){const origin=hubView362.stockExpiryOrigin;hubView362.stockExpiry=false;if(origin==='waste')return openHub362('waste');return refreshHub362()}return hubActionBefore363(action)};\nconst mainHubBefore363=webMainOpen360;\nwebMainOpen360=function(id){if(id==='stock')hubView362.stockExpiry=false;return mainHubBefore363(id)};\n"},{"src":"inventory-desktop363.js?v=1","code":"'use strict';\n// Desktop counting is read-only until an existing explicit count/save action.\nconst inventoryFolds363=new Map();\nfunction groupInventoryDesktop363(){\n if(!webDesktop360())return;\n const panel=document.querySelector('#tabInventory185 .manualInventoryPanel246');if(!panel)return;\n for(const section of panel.querySelectorAll('.manualInventoryGroup246')){\n  if(section.querySelector('details.inventoryGroup363'))continue;const title=section.querySelector('h3');if(!title)continue;\n  const name=title.textContent,fold=document.createElement('details'),summary=document.createElement('summary');fold.className='inventoryGroup363';fold.open=inventoryFolds363.get(name)??false;summary.textContent=name;fold.appendChild(summary);\n  for(const row of [...section.children])if(row!==title)fold.appendChild(row);title.remove();section.appendChild(fold);\n  fold.addEventListener('toggle',()=>inventoryFolds363.set(name,fold.open));\n }\n}\nconst manualRenderBefore363=manualInventoryRender246;\nmanualInventoryRender246=function(...args){const r=manualRenderBefore363(...args);groupInventoryDesktop363();return r};\nconst inventoryDesktopBefore363=openInventoryDesktop185;\nopenInventoryDesktop185=function(...args){\n if(!webDesktop360())return inventoryDesktopBefore363(...args);\n if(!guardPermission('inventory',hubText362('Inventur','Inventory','Inwentaryzacja')))return;\n const r=showTab('inventory185');const show=()=>{if(!document.getElementById('tabInventory185')?.classList.contains('active'))return;manualInventoryVisible246=true;renderInventory185();manualInventoryRender246();hubBackBar362()};if(r?.then)return r.then(show);show();return r;\n};\n"},{"src":"shopping-create363.js?v=385","code":"'use strict';\n// Explicit shopping requests reference existing variants; no stock booking here.\nlet shoppingProductDraft363=null;\nconst channelsBefore363=shoppingChannels;\nshoppingChannels=function(data){\n const result=channelsBefore363(data);\n for(const item of state.shoppingChecks?._requested363||[]){\n  const a=state.products?.[item.code];if(!a||!(Number(item.qty)>0)||a.unit!==item.unit)continue;\n  const profile=supplierProfileFor(item.supplier),name=item.supplier||hubText362('Geschäft noch wählen','Choose store','Wybierz sklep');\n  const row={key:'requested363:'+item.id,productCode:item.code,name:a.name,qty:typeof shoppingRequestRemaining456==='function'?shoppingRequestRemaining456(item):Number(item.qty),unit:a.unit,supplier:name,initial:Number(a.stock)||0,reasons:[hubText362('Manuell auf die Einkaufsliste gesetzt','Added to shopping list','Dodano do listy zakupów')],signature:JSON.stringify([item.id,item.code,item.qty,item.unit,name]),requested363:true};\n  const isOrder=profile&&(profile.method!=='pickup'||profile.preorder),target=isOrder?result.orders:result.pickup;\n  let group=target.find(g=>g.name===name);if(!group){group={name,method:isOrder?profile.method:'pickup',profile:profile||{name,method:'pickup',address:''},rows:[]};target.push(group)}group.rows.push(row);\n }\n result.pickup.sort((a,b)=>a.name.localeCompare(b.name,'de'));return result;\n};\nfunction shoppingRequestPayload363(code,raw,supplier){\n const a=state.products?.[code];if(!a)throw Error(hubText362('Produkt erneut auswählen.','Choose product again.','Wybierz produkt ponownie.'));\n const qty=inventoryQuantity245(raw,a.unit);if(qty<=0||qty>100000)throw Error(hubText362('Positive Menge eingeben.','Enter positive quantity.','Wpisz dodatnią ilość.'));\n const name=String(supplier||'').trim();if(!name||name.length>100)throw Error(hubText362('Geschäft oder Lieferant angeben.','Enter store or supplier.','Podaj sklep lub dostawcę.'));\n const calculated=channelsBefore363(),existing=calculated.pickup.concat(calculated.orders).flatMap(g=>g.rows).some(r=>r.productCode===code&&!r.manual174);\n if(existing)throw Error(hubText362('Dieses Produkt steht bereits aus dem berechneten Bedarf auf der Liste. Bitte den vorhandenen Eintrag verwenden.','This product is already listed from calculated demand. Use that entry.','Produkt jest już na liście z obliczonego zapotrzebowania. Użyj istniejącego wpisu.'));\n return {id:'req-'+(globalThis.crypto?.randomUUID?.()||Date.now()+'-'+Math.random().toString(36).slice(2)),code,qty,unit:a.unit,supplier:name};\n}\nfunction shoppingProductRows363(query=''){return (query?stockPoolsForSearch301(query):stockPools301()).flatMap(p=>p.members).map(([code,a])=>`<button class=\"hubListRow362\" type=\"button\" onclick=\"shoppingChooseProduct363('${escJs(code)}')\"><span><b>${esc(a.name)}</b><small>${esc(a.supplier||'')} · ${esc(code)}</small></span><span>${esc(formatQtySmart(Number(a.stock)||0,a.unit))}</span><span>→</span></button>`).join('')}\nfunction shoppingAddProduct363(){if(!guardPermission('shopping','Einkaufsliste'))return;openOpsPanel(hubText362('Artikel hinzufügen','Add product','Dodaj produkt'),`<input type=\"search\" aria-label=\"${hubText362('Produkt suchen','Search product','Szukaj produktu')}\" placeholder=\"${hubText362('Produkt oder Barcode suchen','Search product or barcode','Szukaj produktu lub kodu')}\" oninput=\"document.getElementById('shoppingProductRows363').innerHTML=shoppingProductRows363(this.value)\"><div id=\"shoppingProductRows363\">${shoppingProductRows363()}</div>`)}\nfunction shoppingChooseProduct363(code){\n if(!guardPermission('shopping','Einkaufsliste'))return;const a=state.products?.[code];if(!a)return;shoppingProductDraft363={code,busy:false};\n openOpsPanel(hubText362('Zur Einkaufsliste hinzufügen','Add to shopping list','Dodaj do listy zakupów'),`<form onsubmit=\"event.preventDefault();shoppingSaveProduct363()\"><h3>${esc(a.name)}</h3><p>${hubText362('Auf Lager','In stock','Na stanie')}: ${esc(formatQtySmart(Number(a.stock)||0,a.unit))}</p><label>${hubText362('Zu besorgen','To buy','Do kupienia')} (${esc(a.unit)})<input id=\"shoppingQty363\" inputmode=\"decimal\" required placeholder=\"${hubText362('Menge eingeben','Enter quantity','Wpisz ilość')}\"></label><label>${hubText362('Geschäft / Lieferant','Store / supplier','Sklep / dostawca')}<input id=\"shoppingSupplier363\" required maxlength=\"100\" list=\"shoppingSuppliers363\" value=\"${esc(a.supplier||'')}\"></label><datalist id=\"shoppingSuppliers363\">${(state.suppliers||[]).map(s=>`<option value=\"${esc(s)}\">`).join('')}</datalist><p class=\"muted\">${hubText362('Dies ergänzt die Einkaufsliste und bucht keinen Lagerbestand.','This updates the shopping list, not stock.','To zmienia listę zakupów, nie stan magazynu.')}</p><button class=\"primary\" type=\"submit\">${hubText362('Zur Liste hinzufügen','Add to list','Dodaj do listy')}</button></form>`);\n}\nfunction shoppingSaveProduct363(){const d=shoppingProductDraft363;if(!d||d.busy)return false;let item;try{item=shoppingRequestPayload363(d.code,document.getElementById('shoppingQty363')?.value,document.getElementById('shoppingSupplier363')?.value)}catch(e){showFeedback(hubText362('Einkaufsliste prüfen','Check shopping list','Sprawdź listę zakupów'),'warn',5000,e.message);return false}d.busy=true;\n const result=operationTransaction(hubText362('Einkaufsliste ergänzt','Shopping list updated','Lista zakupów uzupełniona'),'shopping',()=>{shoppingRequestPayload363(item.code,String(item.qty),item.supplier);const a=state.products?.[item.code];if(!a||a.unit!==item.unit)throw Error(hubText362('Produkt wurde geändert. Bitte erneut auswählen.','Product changed. Choose it again.','Produkt się zmienił. Wybierz ponownie.'));state.shoppingChecks??={};const rows=state.shoppingChecks._requested363??=[];const i=rows.findIndex(x=>x.code===item.code);if(i<0)rows.push(item);else rows[i]=item;for(const key of Object.keys(state.shoppingChecks))if(key.startsWith('requested363:')&&key==='requested363:'+rows[i]?.id)delete state.shoppingChecks[key]});return afterOperation275(result,ok=>{d.busy=false;if(ok){shoppingProductDraft363=null;document.getElementById('opsDialog92')?.close();renderSelfShopping()}return ok});\n}\nfunction shoppingRemoveProduct363(code){if(!guardPermission('shopping','Einkaufsliste'))return;askConfirm(hubText362('Artikel von der Einkaufsliste entfernen?','Remove product from shopping list?','Usunąć produkt z listy zakupów?'),()=>afterOperation275(operationTransaction(hubText362('Eintrag entfernt','Entry removed','Wpis usunięty'),'shopping',()=>{state.shoppingChecks._requested363=(state.shoppingChecks._requested363||[]).filter(r=>r.code!==code);for(const key of Object.keys(state.shoppingChecks))if(key==='requested363:'+code)delete state.shoppingChecks[key]}),ok=>{if(ok)renderSelfShopping();return ok}),hubText362('Entfernen','Remove','Usuń'))}\nconst areaRowBefore363=shoppingAreaRow173;\nshoppingAreaRow173=function(row){const h=areaRowBefore363(row);if(!row.requested363)return h;return h.replace('shoppingCheckRow','shoppingCheckRow shoppingRequested455').replace(/<\\/div>$/,`<button type=\"button\" class=\"shoppingRemove455 shoppingNoPrint\" aria-label=\"${esc(hubText362('Von der Einkaufsliste entfernen','Remove from shopping list','Usuń z listy zakupów'))}: ${esc(row.name)}\" title=\"${esc(hubText362('Von der Einkaufsliste entfernen','Remove from shopping list','Usuń z listy zakupów'))}\" onclick=\"shoppingRemoveProduct363('${escJs(row.productCode)}')\"><svg viewBox=\"0 0 24 24\" width=\"19\" height=\"19\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7M14 10v7\"/></svg></button></div>`)};\nfunction shoppingSuggestions363(){\n const data=purchasingOverviewData(),rows=[...(data.total||[]).map(r=>({...r,low:false})),...(data.soon||[]).map(r=>({...r,low:true}))];\n return `<details class=\"shoppingSuggestions363\"><summary>${hubText362('Einkaufsvorschläge','Shopping suggestions','Propozycje zakupów')} · ${rows.length}</summary>${rows.map(r=>`<div class=\"shoppingSuggestionRow363\"><div><b>${esc(r.name)}</b><small>${r.low?hubText362('Nahe am Mindestbestand','Near minimum stock','Blisko stanu minimalnego'):esc((r.reasons||[]).join(' · '))}</small></div><span>${hubText362('Auf Lager','In stock','Na stanie')}<b>${esc(formatQtySmart(r.initial,r.unit))}</b></span><span>${r.low?hubText362('Mindestbestand','Minimum stock','Stan minimalny'):hubText362('Zu besorgen','To buy','Do kupienia')}<b>${esc(!r.low&&typeof purchaseText379==='function'?purchaseText379(r):formatQtySmart(r.low?r.min:r.qty,r.unit))}</b></span><button class=\"soft\" type=\"button\" onclick=\"${r.low?`shoppingChooseProduct363('${escJs(r.productCode)}')`:`shoppingOpenItem385('${escJs(r.productCode)}')`}\">${r.low?hubText362('Auf Liste setzen','Add to list','Dodaj do listy'):hubText362('Zum Listeneintrag','Go to list item','Przejdź do wpisu')}</button></div>`).join('')||`<p>${hubText362('Kein zusätzlicher Bedarf aus Mindestbeständen oder offenen Einsätzen.','No additional need from minimum stock or open events.','Brak dodatkowych potrzeb ze stanów minimalnych i otwartych wydarzeń.')}</p>`}</details>`;\n}\nfunction shoppingControls363(){return `<section class=\"shoppingCreate363 shoppingNoPrint\"><div class=\"hubActions362\"><button type=\"button\" class=\"primary\" onclick=\"shoppingAddProduct363()\">+ ${hubText362('Artikel hinzufügen','Add item','Dodaj artykuł')}</button></div>${shoppingSuggestions363()}</section>`}\nfunction shoppingFree363(){const form=[...document.querySelectorAll('.shoppingManualAdd174')].find(n=>n.getClientRects().length);if(form){form.open=true;form.scrollIntoView({block:'center'});form.querySelector('[name=\"name\"]')?.focus()}}\nconst shoppingRenderBefore363=renderSelfShopping;\nrenderSelfShopping=function(...args){const r=shoppingRenderBefore363(...args);for(const id of ['selfShoppingBody','selfShoppingInline']){const el=document.getElementById(id);if(!el)continue;el.querySelector('.shoppingCreate363')?.remove();el.insertAdjacentHTML('afterbegin',shoppingControls363())}return r};\n// Capture fulfilled request keys inside the original receipt's atomic transaction.\nconst shoppingMovementBefore363=addMovement;\naddMovement=function(type,code,qty,extra={},...rest){const r=shoppingMovementBefore363(type,code,qty,extra,...rest);if(type==='in'&&typeof shoppingReceiptDraft!=='undefined'&&shoppingReceiptDraft&&extra.reason==='Einkaufsliste '+shoppingReceiptDraft.id){const keys=(shoppingReceiptDraft.lines||[]).filter(l=>l.got&&String(l.sourceKey).startsWith('requested363:')&&shoppingReceiptOption(l)?.code===code).map(l=>l.sourceKey),m=state.movements?.at(-1);if(m&&keys.length)m.shoppingRequestKeys363=keys}return r};\nfunction shoppingRequestFulfilled363(row){if(typeof shoppingRequestRemaining456==='function'){const item=(state.shoppingChecks?._requested363||[]).find(x=>'requested363:'+x.id===row.key);if(item)return shoppingRequestRemaining456(item)<=1e-8;}return (state.shoppingReceipts||[]).some(r=>!r.reversedAt&&(r.sourceKeys||[]).includes(row.key)&&(state.movements||[]).some(m=>m.type==='in'&&m.reason==='Einkaufsliste '+r.id&&(m.shoppingRequestKeys363||[]).includes(row.key)))}\n// Fulfilled requests disappear by their unique immutable source key. Undo restores them.\n\nconst requestChannels363=shoppingChannels;\nshoppingChannels=function(data){const ch=requestChannels363(data);for(const group of [...ch.pickup,...ch.orders])group.rows=group.rows.filter(row=>!row.requested363||!shoppingRequestFulfilled363(row));ch.pickup=ch.pickup.filter(g=>g.rows.length);ch.orders=ch.orders.filter(g=>g.rows.length);return ch};\n"},{"src":"purchasing-summary363.js?v=385","code":"'use strict';\nfunction purchaseShortRows363(rows,soon=false){return rows.map(r=>`<div class=\"purchaseShortRow363\"><div><b>${esc(r.name)}</b><small>${esc(r.supplier||hubText362('Lieferant offen','Supplier missing','Brak dostawcy'))}</small></div><div><small>${hubText362('Auf Lager','In stock','Na stanie')}</small><b>${esc(formatQtySmart(r.initial,r.unit))}</b></div><div><small>${soon?hubText362('Mindestbestand','Minimum stock','Stan minimalny'):hubText362('Zu besorgen','To buy','Do kupienia')}</small><b>${esc(!soon&&typeof purchaseText379==='function'?purchaseText379(r):formatQtySmart(soon?r.min:r.qty,r.unit))}</b></div><button type=\"button\" class=\"soft\" onclick=\"${soon?`shoppingChooseProduct363('${escJs(r.productCode)}')`:`shoppingOpenItem385('${escJs(r.productCode)}')`}\">${soon?hubText362('Auf Liste setzen','Add to list','Dodaj do listy'):hubText362('Zum Listeneintrag','Go to list item','Przejdź do wpisu')}</button></div>`).join('')||`<p class=\"muted\">${hubText362('Aktuell nichts offen.','Nothing open currently.','Obecnie brak potrzeb.')}</p>`}\nconst detailsBefore363=renderPurchasingDetails;\nrenderPurchasingDetails=function(...args){\n const r=detailsBefore363(...args),host=document.getElementById('purchasingDetails');if(!host)return r;\n const data=purchasingOverviewData(),old=[...host.childNodes],fold=document.createElement('details');fold.className='purchaseMore363';\n const summary=document.createElement('summary');summary.textContent=hubText362('Weitere Details und Berechnung','More details and calculation','Więcej szczegółów i obliczenia');fold.appendChild(summary);for(const n of old)fold.appendChild(n);\n host.innerHTML=`<section class=\"hubPanel362\"><h2>${hubText362('Was muss ich besorgen?','What do I need to buy?','Co trzeba kupić?')}</h2>${purchaseShortRows363(data.total||[])}<p class=\"tiny\">${hubText362('Mindestbestände und offene Einsätze sind gemeinsam berücksichtigt.','Minimum stock and open events are considered together.','Uwzględniono łącznie stany minimalne i otwarte wydarzenia.')}</p></section><details class=\"hubPanel362 purchaseSoon363\"><summary>${hubText362('Demnächst prüfen','Check soon','Sprawdź wkrótce')} · ${(data.soon||[]).length}</summary>${purchaseShortRows363(data.soon||[],true)}</details><section class=\"hubPanel362\"><button type=\"button\" class=\"soft\" onclick=\"hubAction362('mhd')\">${hubText362('MHD und ablaufende Chargen prüfen','Check best-before dates and expiring lots','Sprawdź terminy i partie')}</button></section>`;host.appendChild(fold);return r;\n};\n\nconst purchaseOverviewBefore363=renderPurchasingOverview;\nrenderPurchasingOverview=function(...args){const r=purchaseOverviewBefore363(...args),host=document.getElementById('purchasingDetails'),more=host?.querySelector('.purchaseMore363');if(more)for(const section of host.querySelectorAll(':scope > .card'))more.appendChild(section);return r};\n"},{"src":"suppliers363.js?v=382","code":"'use strict';\nlet supplierDraft363=null;\nfunction supplierCards363(query=''){\n const q=query.trim().toLocaleLowerCase('de');return (state.supplierProfiles||[]).map((s,i)=>({s,i})).filter(({s})=>[s.name,s.contactPerson363,s.address,s.customerNumber363].join(' ').toLocaleLowerCase('de').includes(q)).sort((a,b)=>String(a.s.name).localeCompare(String(b.s.name),'de')).map(({s,i})=>`<article class=\"supplierCard363\"><div><h2>${esc(s.name)}</h2><dl><dt>${hubText362('Ansprechpartner','Contact','Osoba kontaktowa')}</dt><dd>${esc(s.contactPerson363||'—')}</dd><dt>${hubText362('Adresse','Address','Adres')}</dt><dd>${esc(s.address||'—')}</dd><dt>${hubText362('Kundennummer','Customer number','Numer klienta')}</dt><dd>${esc(s.customerNumber363||'—')}</dd><dt>E-Mail</dt><dd>${esc(s.email||'—')}</dd>${s.phone?`<dt>${hubText362('Telefon','Phone','Telefon')}</dt><dd>${esc(s.phone)}</dd>`:''}<dt>${hubText362('Bestellart','Purchasing method','Sposób zakupu')}</dt><dd>${esc(supplierMethodLabel(s.method,!!s.preorder))}</dd></dl>${supplierOrderActions373(s,i)}</div><div class=\"supplierVisual363\">${s.photo363?`<img src=\"${esc(s.photo363)}\" alt=\"${esc(s.name)}\" loading=\"lazy\">`:`<span class=\"supplierPlaceholder363\" aria-hidden=\"true\">${esc(String(s.name||'?').slice(0,1))}</span>`}<button type=\"button\" class=\"soft\" onclick=\"editSupplier363(${i})\">${hubText362('Bearbeiten','Edit','Edytuj')}</button></div></article>`).join('')||`<p>${hubText362('Keine passenden Lieferanten.','No matching suppliers.','Brak pasujących dostawców.')}</p>`;\n}\nfunction renderSuppliers363(){return `<header class=\"hubHead362\"><div><p class=\"hubCrumb362\">${hubText362('Einkauf / Lieferanten','Purchasing / Suppliers','Zakupy / Dostawcy')}</p><h1>${hubText362('Lieferanten','Suppliers','Dostawcy')}</h1></div><button type=\"button\" class=\"primary\" onclick=\"editSupplier363()\">+ ${hubText362('Lieferant hinzufügen','Add supplier','Dodaj dostawcę')}</button></header><button class=\"soft\" type=\"button\" onclick=\"hubAction362('suppliersBack')\">← ${hubText362('Zur Einkaufsübersicht','Back to purchasing','Wróć do zakupów')}</button><div class=\"hubSearch362\"><input type=\"search\" placeholder=\"${hubText362('Lieferant suchen','Search suppliers','Szukaj dostawcy')}\" aria-label=\"${hubText362('Lieferant suchen','Search suppliers','Szukaj dostawcy')}\" oninput=\"document.getElementById('supplierCards363').innerHTML=supplierCards363(this.value)\"></div><div class=\"supplierCards363\" id=\"supplierCards363\">${supplierCards363()}</div>`}\nfunction editSupplier363(index=-1){\n if(!guardPermission('suppliers',hubText362('Lieferanten','Suppliers','Dostawcy')))return;const s=index>=0?state.supplierProfiles?.[index]:null;if(index>=0&&!s)return;\n supplierDraft363={index,snapshot:s?JSON.stringify(s):null,photo:s?.photo363||'',pending:false,busy:false};\n const field=(id,label,value='',type='text')=>`<label>${label}<input id=\"${id}\" type=\"${type}\" maxlength=\"${id==='supplierAddress363'?500:id==='supplierShop363'?2048:160}\" value=\"${esc(value)}\" ${id==='supplierName363'?'required':''}></label>`;\n openOpsPanel(s?hubText362('Lieferant bearbeiten','Edit supplier','Edytuj dostawcę'):hubText362('Lieferant hinzufügen','Add supplier','Dodaj dostawcę'),`<form id=\"supplierForm363\" onsubmit=\"event.preventDefault();saveSupplier363()\"><div class=\"supplierEditGrid363\">${field('supplierName363',hubText362('Firma','Company','Firma'),s?.name)}${field('supplierContact363',hubText362('Ansprechpartner','Contact','Osoba kontaktowa'),s?.contactPerson363)}${field('supplierAddress363',hubText362('Adresse','Address','Adres'),s?.address)}${field('supplierCustomer363',hubText362('Kundennummer','Customer number','Numer klienta'),s?.customerNumber363)}${field('supplierEmail363','E-Mail',s?.email,'email')}${field('supplierPhone363',hubText362('Telefon','Phone','Telefon'),s?.phone)}<label>${hubText362('Bestellart','Purchasing method','Sposób zakupu')}<select id=\"supplierMethod363\" onchange=\"supplierMethodFields373()\">${[['pickup','Selbst einkaufen','Self pickup','Zakupy samodzielne'],['shop','Onlineshop / Portal','Online shop / portal','Sklep internetowy'],['email','Vorbestellen per E-Mail','Preorder by email','Zamówienie e-mail'],['phone','Vorbestellen per Telefon','Preorder by phone','Zamówienie telefoniczne'],['whatsapp','Vorbestellen per WhatsApp','Preorder by WhatsApp','Zamówienie WhatsApp']].map(([id,...w])=>`<option value=\"${id}\" ${id===(s?.method||'pickup')?'selected':''}>${hubText362(...w)}</option>`).join('')}</select></label>${field('supplierShop363',hubText362('Onlineshop (optional)','Online shop (optional)','Sklep internetowy (opcjonalnie)'),s?.shopUrl,'url')}<label>${hubText362('Online-Zugang vorbereiten','Prepare online access','Przygotuj dostęp online')}<select aria-label=\"${hubText362('Shopadresse vorbelegen','Fill shop address','Uzupełnij adres sklepu')}\" onchange=\"applySupplierPortal381(this.value)\"><option value=\"\">${hubText362('Großhändler-Vorlage wählen','Choose wholesaler preset','Wybierz hurtownię')}</option>${supplierPortalPresets381.map((p,i)=>`<option value=\"${i}\">${esc(p.name)}</option>`).join('')}</select><small>${hubText362('Shopadresse vorbelegen und mit Speichern übernehmen.','Fill the shop address, then save.','Uzupełnij adres sklepu i zapisz.')}</small></label></div><section id=\"supplierMailFields373\" ${['email','whatsapp'].includes(s?.method)?'':'hidden'}>${field('supplierSubject373',hubText362('Mail-Betreff','Email subject','Temat wiadomości'),s?.subject||'')}<label>${hubText362('Bestellvorlage','Order template','Szablon zamówienia')}<textarea id=\"supplierTemplate373\" rows=\"8\" maxlength=\"10000\">${esc(s?.template||supplierDefaultTemplate373())}</textarea></label><small>${hubText362('Bei {{Bestellung}} werden die benötigten Produkte eingesetzt.','Required products replace {{Bestellung}}.','Potrzebne produkty zastąpią {{Bestellung}}.')}</small></section>${typeof supplierPriceEdit382==='function'?supplierPriceEdit382(index):''}<label>${hubText362('Lieferantenfoto / Logo','Supplier photo / logo','Zdjęcie / logo dostawcy')}<input type=\"file\" accept=\"image/*\" onchange=\"supplierPhoto363(this.files[0])\"></label><div id=\"supplierPhotoPreview363\">${s?.photo363?`<img src=\"${esc(s.photo363)}\" alt=\"\">`:''}</div><p id=\"supplierPhotoStatus363\" role=\"status\"></p><div class=\"hubActions362\"><button type=\"button\" class=\"soft\" onclick=\"document.getElementById('opsDialog92').close()\">${hubText362('Abbrechen','Cancel','Anuluj')}</button><button type=\"submit\" class=\"primary\">${hubText362('Speichern','Save','Zapisz')}</button></div></form>`);\n}\nasync function supplierPhoto363(file){const d=supplierDraft363;if(!d||!file||d.busy)return;d.pending=true;const status=document.getElementById('supplierPhotoStatus363');if(status)status.textContent=hubText362('Bild wird vorbereitet …','Preparing image …','Przygotowywanie obrazu …');try{if(!file.type.startsWith('image/'))throw Error(hubText362('Bitte eine Bilddatei wählen.','Choose an image file.','Wybierz plik obrazu.'));const photo=await compress144(file);if(photo.length>450000)throw Error(hubText362('Bild ist zu groß. Bitte einen kleineren Ausschnitt wählen.','Image too large. Choose a smaller crop.','Obraz za duży. Wybierz mniejszy fragment.'));if(supplierDraft363!==d)return;d.photo=photo;const preview=document.getElementById('supplierPhotoPreview363');if(preview)preview.innerHTML=`<img src=\"${esc(photo)}\" alt=\"\">`;if(status)status.textContent=''}catch(e){if(status)status.textContent=e.message}finally{d.pending=false}}\nfunction supplierPayload363(d,values){\n const current=d.index>=0?state.supplierProfiles?.[d.index]:null;if(d.index>=0&&JSON.stringify(current)!==d.snapshot)throw Error(hubText362('Lieferant wurde geändert. Bitte erneut öffnen.','Supplier changed. Reopen it.','Dostawca się zmienił. Otwórz ponownie.'));\n const name=String(values.name||'').trim(),method=values.method;if(!name||name.length>160||!['pickup','shop','email','phone','whatsapp'].includes(method))throw Error(hubText362('Firma und Bestellart prüfen.','Check company and purchasing method.','Sprawdź firmę i sposób zakupu.'));\n if((state.supplierProfiles||[]).some((s,i)=>i!==d.index&&(s.name.toLocaleLowerCase('de')===name.toLocaleLowerCase('de')||(s.aliases||[]).some(a=>a.toLocaleLowerCase('de')===name.toLocaleLowerCase('de')))))throw Error(hubText362('Dieser Lieferant ist bereits vorhanden.','Supplier already exists.','Dostawca już istnieje.'));\n const email=String(values.email||'').trim(),shop=String(values.shopUrl||'').trim();if(email&&!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)||shop&&!supplierShopUrl373(shop))throw Error(hubText362('E-Mail oder Shop-Adresse prüfen.','Check email or shop URL.','Sprawdź e-mail lub adres sklepu.'));\n return {...(current||{aliases:[],subject:'',template:''}),name,method,preorder:['email','phone','whatsapp'].includes(method),contactPerson363:String(values.contact||'').trim().slice(0,160),customerNumber363:String(values.customer||'').trim().slice(0,160),address:String(values.address||'').trim().slice(0,500),email,phone:String(values.phone||'').trim().slice(0,160),shopUrl:shop,subject:values.subject===undefined?(current?.subject||''):String(values.subject).trim().slice(0,240),template:values.template===undefined?(current?.template||''):String(values.template).slice(0,10000),photo363:d.photo};\n}\nfunction commitSupplier363(d,values){if(!d||d!==supplierDraft363||d.busy||d.pending)return false;d.busy=true;\n const result=operationTransaction(hubText362('Lieferant gespeichert','Supplier saved','Dostawca zapisany'),'suppliers',()=>{const next=supplierPayload363(d,values),old=d.index>=0?state.supplierProfiles[d.index].name:null;state.supplierProfiles??=[];if(d.index<0)state.supplierProfiles.push(next);else state.supplierProfiles[d.index]=next;if(old&&old!==next.name){next.aliases=[...new Set([...(next.aliases||[]),old])];for(const a of Object.values(state.products||{}))if(a.supplier===old)a.supplier=next.name;for(const g of state.recipeGroups||[])for(const r of g.recipes||[])for(const ing of r.ingredients||[])if(ing.supplier===old)ing.supplier=next.name;for(const item of [...(state.shoppingChecks?._manual174||[]),...(state.shoppingChecks?._requested363||[])])if(item.supplier===old)item.supplier=next.name}syncSupplierNames()});\n return afterOperation275(result,ok=>{d.busy=false;if(ok){supplierDraft363=null;document.getElementById('opsDialog92')?.close();refreshHub362();renderSupplierAdmin()}return ok});\n}\nfunction saveSupplier363(){const d=supplierDraft363;if(!d||!document.getElementById('supplierForm363')?.reportValidity())return false;const get=id=>document.getElementById(id)?.value||'';return commitSupplier363(d,{name:get('supplierName363'),contact:get('supplierContact363'),customer:get('supplierCustomer363'),address:get('supplierAddress363'),email:get('supplierEmail363'),phone:get('supplierPhone363'),method:get('supplierMethod363'),shopUrl:get('supplierShop363'),subject:get('supplierSubject373'),template:get('supplierTemplate373')})}\nfunction supplierPrices363(){if(!hasPermission('suppliers'))return;return hubRoute362(()=>webOriginalClick360('navSuppliers155'))}\nconst buyHubBeforeSuppliers363=renderHubBuy362;\nrenderHubBuy362=function(){return hubView362.suppliers&&hasPermission('suppliers')?renderSuppliers363():buyHubBeforeSuppliers363()};\nconst supplierActionBefore363=hubAction362;\nhubAction362=function(action){if(action==='legacy:navSuppliers155'){if(!hasPermission('suppliers'))return;hubView362.suppliers=true;return openHub362('buy')}if(action==='suppliersBack'){hubView362.suppliers=false;return refreshHub362()}return supplierActionBefore363(action)};\nconst supplierMainBefore363=webMainOpen360;\nwebMainOpen360=function(id){if(id==='buy')hubView362.suppliers=false;return supplierMainBefore363(id)};\n\nfunction supplierDefaultTemplate373(){return hubText362('Hallo,\\n\\nbitte folgende Produkte bestellen:\\n\\n{{Bestellung}}\\n\\nVielen Dank und freundliche Grüße','Hello,\\n\\nplease order the following products:\\n\\n{{Bestellung}}\\n\\nThank you and kind regards','Dzień dobry,\\n\\nproszę o następujące produkty:\\n\\n{{Bestellung}}\\n\\nDziękuję i pozdrawiam')}\nfunction supplierShopUrl373(value){try{const u=new URL(String(value||'').trim());return ['https:','http:'].includes(u.protocol)&&u.hostname&&!u.username&&!u.password?u.href:''}catch{return ''}}\nfunction supplierMethodFields373(){const area=document.getElementById('supplierMailFields373');if(area)area.hidden=!['email','whatsapp'].includes(document.getElementById('supplierMethod363')?.value)}\nfunction editSupplierShop373(index){editSupplier363(index);document.getElementById('supplierShop363')?.focus()}\nfunction supplierOrderActions373(s,index){const url=supplierShopUrl373(s.shopUrl);if(s.method==='shop')return `<div class=\"supplierOrderActions373\">${url?`<a class=\"primary\" href=\"${esc(url)}\" target=\"_blank\" rel=\"noopener noreferrer\">${hubText362('Shop öffnen','Open shop','Otwórz sklep')}</a>`:`<button type=\"button\" class=\"soft\" onclick=\"editSupplierShop373(${index})\">${hubText362('Shop-Link hinterlegen','Add shop link','Dodaj adres sklepu')}</button>`}</div>`;const email=s.method==='email'?`<button type=\"button\" class=\"primary\" onclick=\"supplierMailDraft373(${index})\">${hubText362('Mailvorlage öffnen','Open email draft','Otwórz szkic e-mail')}</button>`:'';return email||url?`<div class=\"supplierOrderActions373\">${email}${url?`<a class=\"soft\" href=\"${esc(url)}\" target=\"_blank\" rel=\"noopener noreferrer\">${hubText362('Online-Zugang öffnen','Open online access','Otwórz dostęp online')}</a>`:''}</div>`:''}\nfunction supplierMailValues373(s,rows){const items=orderItemsText((rows||[]).filter(r=>Number(r.qty)>0),s.name);return {to:s.email||'',subject:s.subject||hubText362('Bestellung','Order','Zamówienie'),body:applyOrderTemplate(s.template||supplierDefaultTemplate373(),{event:'',date:''},items||hubText362('[Produkte und Mengen ergänzen]','[Add products and quantities]','[Uzupełnij produkty i ilości]'))}}\nfunction supplierMailDraft373(index){if(!guardPermission('suppliers',hubText362('Lieferanten','Suppliers','Dostawcy')))return;const s=state.supplierProfiles?.[index];if(!s)return;const group=shoppingChannels().orders.find(g=>g.name===s.name),d=supplierMailValues373(s,group?.rows||[]);openOpsPanel(hubText362('Bestellung prüfen','Review order','Sprawdź zamówienie')+' · '+s.name,`<div class=\"supplierMailDraft373\"><label>${hubText362('An','To','Do')}<input id=\"supplierDraftTo373\" type=\"email\" value=\"${esc(d.to)}\" oninput=\"supplierMailLinks373()\"></label><label>${hubText362('Betreff','Subject','Temat')}<input id=\"supplierDraftSubject373\" value=\"${esc(d.subject)}\" oninput=\"supplierMailLinks373()\"></label><label>${hubText362('Bestellung','Order','Zamówienie')}<textarea id=\"supplierDraftBody373\" rows=\"12\" oninput=\"supplierMailLinks373()\">${esc(d.body)}</textarea></label><p class=\"muted\">${hubText362('Entwurf prüfen. Gesendet wird erst von dir im Mailprogramm.','Review the draft. You send it from your email app.','Sprawdź szkic. Wysyłasz go samodzielnie w programie pocztowym.')}</p><div class=\"supplierOrderActions373\"><a id=\"supplierMailto373\" class=\"primary\">${hubText362('Im Mailprogramm öffnen','Open email app','Otwórz pocztę')}</a><a id=\"supplierOutlook373\" class=\"soft\" target=\"_blank\" rel=\"noopener noreferrer\">${hubText362('In Outlook öffnen','Open Outlook','Otwórz Outlook')}</a></div></div>`);supplierMailLinks373()}\nfunction supplierMailLinks373(){const to=document.getElementById('supplierDraftTo373'),subject=document.getElementById('supplierDraftSubject373')?.value||'',body=document.getElementById('supplierDraftBody373')?.value||'',valid=to?.value&&to.checkValidity();for(const [id,href]of [['supplierMailto373',`mailto:${encodeURIComponent(to?.value||'')}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`],['supplierOutlook373',`https://outlook.office.com/mail/deeplink/compose?to=${encodeURIComponent(to?.value||'')}&subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`]]){const a=document.getElementById(id);if(!a)continue;if(valid){a.href=href;a.removeAttribute('aria-disabled')}else{a.removeAttribute('href');a.setAttribute('aria-disabled','true')}}}\n\n// Public portal addresses only. The user explicitly chooses and saves a preset.\nconst supplierPortalPresets381=[{name:'Herzog / Orderlion',url:'https://orderlion.com/herzogfrische'},{name:'METRO Österreich',url:'https://shop.metro.at/'},{name:'Transgourmet Österreich',url:'https://shop.transgourmet.at/'},{name:'KASTNER',url:'https://shop.kastner.at/'}];\nfunction applySupplierPortal381(value){if(!hasPermission('suppliers')||!/^\\d+$/.test(String(value)))return false;const preset=supplierPortalPresets381[Number(value)],field=document.getElementById('supplierShop363');if(!preset||!field)return false;const url=supplierShopUrl373(preset.url);if(!url)return false;field.value=url;field.focus();return true}\n"},{"src":"waste-quantities364.js?v=1","code":"'use strict';\nlet wasteQuantityUnit364=null;\nfunction wasteQuantityGroups364(rows){\n const units=new Map();\n for(const r of rows){\n  if(!units.has(r.unit))units.set(r.unit,{unit:r.unit,qty:0,products:new Map()});\n  const group=units.get(r.unit),key=r.code?'code:'+r.code:'name:'+r.name;\n  const item=group.products.get(key)||{name:r.name,qty:0,value:0,known:0,unknown:0};\n  group.qty+=r.qty;item.qty+=r.qty;if(r.value===null)item.unknown++;else{item.value+=r.value;item.known++}group.products.set(key,item);\n }\n return [...units.values()].map(g=>({...g,products:[...g.products.values()].sort((a,b)=>b.qty-a.qty||a.name.localeCompare(b.name,'de'))}));\n}\nfunction wasteQuantityPanel364(rows){\n const groups=wasteQuantityGroups364(rows),selected=groups.find(g=>g.unit===wasteQuantityUnit364)||groups[0];\n const buttons=groups.map((g,i)=>`<button type=\"button\" class=\"wasteUnitButton364 ${g===selected?'active':''}\" aria-pressed=\"${g===selected}\" onclick=\"wasteChooseUnit364(${i})\"><strong>${esc(formatQtySmart(g.qty,g.unit))}</strong><small>${g.products.length} ${wasteText362('Artikel anzeigen','show products','pokaż produkty')}</small></button>`).join('');\n const table=selected?`<h3>${wasteText362('Welche Produkte wurden verworfen?','Which products were discarded?','Które produkty wyrzucono?')}</h3><div class=\"hubTableScroll362\"><table><thead><tr><th>${wasteText362('Artikel','Product','Produkt')}</th><th>${wasteText362('Verworfene Menge','Discarded quantity','Wyrzucona ilość')}</th><th>${wasteText362('Warenwert','Goods value','Wartość')}</th></tr></thead><tbody>${selected.products.map(p=>`<tr><td><b>${esc(p.name)}</b></td><td>${esc(formatQtySmart(p.qty,selected.unit))}</td><td>${p.known?esc(moneyFmt(p.value)):wasteText362('Warenwert offen','Value unknown','Wartość nieznana')}${p.known&&p.unknown?`<small>${wasteText362('Zusätzlich Einträge ohne Preis','Additional records without price','Dodatkowe wpisy bez ceny')}</small>`:''}</td></tr>`).join('')}</tbody></table></div>`:`<p>${wasteText362('Noch kein Verwurf im gewählten Zeitraum.','No waste recorded in this period.','Brak odpadów w wybranym okresie.')}</p>`;\n return `<section class=\"wasteUnitsPanel363\" id=\"wasteQuantityPanel364\"><h2>${wasteText362('Was wurde verworfen?','What was discarded?','Co wyrzucono?')}</h2><div class=\"wasteUnitChoices364\" role=\"group\" aria-label=\"${wasteText362('Mengen nach Einheit auswählen','Choose quantities by unit','Wybierz ilości według jednostek')}\">${buttons}</div>${table}</section>`;\n}\nfunction wasteChooseUnit364(index){\n const rows=hubWasteFiltered362(),groups=wasteQuantityGroups364(rows),group=groups[index];if(!group)return;\n wasteQuantityUnit364=group.unit;const panel=document.getElementById('wasteQuantityPanel364');if(panel){panel.outerHTML=wasteQuantityPanel364(rows);document.querySelector(`#wasteQuantityPanel364 .wasteUnitButton364:nth-child(${index+1})`)?.focus()}\n}\n"},{"src":"missing-product365.js?v=420","code":"'use strict';\nlet missingProduct365=null;\nfunction missingProductOffer365(action){return `<div class=\"missingProduct365\"><p>${hubText362('Kein passendes Produkt gefunden.','No matching product found.','Nie znaleziono produktu.')}</p>${canCreateArticles()?`<button type=\"button\" class=\"primary\" onclick=\"${action}()\">+ ${hubText362('Neues Produkt anlegen','Create new product','Dodaj nowy produkt')}</button>`:''}</div>`}\nconst pickerRowsBefore365=stockPickerRows363;\nstockPickerRows363=function(query=''){const all=(query?stockPoolsForSearch301(query):stockPools301()).flatMap(p=>p.members);return all.length?pickerRowsBefore365(query):missingProductOffer365('createMissingStock365')};\nfunction createMissingStock365(){const kind=stockPicker363?.kind;if(!['in','out'].includes(kind))return;return beginMissingProduct365('stock',kind,document.getElementById('stockPickerQuery363')?.value||'')}\nconst shoppingRowsBefore365=shoppingProductRows363;\nshoppingProductRows363=function(query=''){const h=shoppingRowsBefore365(query);return h||missingProductOffer365('createMissingShopping365')};\nfunction createMissingShopping365(){return beginMissingProduct365('shopping','',document.querySelector('#opsDialog92 input[type=\"search\"]')?.value||'')}\nfunction beginMissingProduct365(source,kind,query){\n if(!canCreateArticles()||!guardArticleCreate())return false;\n if(source==='stock'&&!hasPermission(kind==='in'?'stockAdd':'scan')||source==='shopping'&&!hasPermission('shopping'))return false;\n document.getElementById('opsDialog92')?.close();\n const opened=openProductCreate331();if(opened===false||document.getElementById('articleModal')?.style.display!=='flex')return false;\n missingProduct365={source,kind,modal:document.getElementById('articleModal'),busy:false};\n const text=String(query).trim(),barcode=/^\\d{8,14}$/.test(text),input=document.getElementById(barcode?'aeCode':'aeName');if(input){input.value=text;input.dispatchEvent?.(new Event('input',{bubbles:true}))}\n if(!barcode&&typeof familyNameChanged336==='function')familyNameChanged336();\n if(typeof guidedArticle300!=='undefined'&&guidedArticle300){if(!barcode&&text)guidedArticle300.step='unit';renderGuidedArticle300();}\n return true;\n}\nconst openEditorBefore365=openArticleEditor;\nopenArticleEditor=function(...args){missingProduct365=null;return openEditorBefore365.apply(this,args)};\nconst saveEditorBefore365=saveArticleEditor;\nsaveArticleEditor=function(...args){\n const ctx=missingProduct365;if(!ctx)return saveEditorBefore365.apply(this,args);if(ctx.busy)return false;\n const before=new Set(Object.keys(state.products||{})),result=saveEditorBefore365.apply(this,args),code=current;\n if(!state.products?.[code]||before.has(code))return result;\n ctx.busy=true;\n // The existing article saver owns rollback. Wait for its device flush and final UI.\n const finish=async()=>{try{await window.deviceStore257.flush();await new Promise(resolve=>setTimeout(resolve,0));if(missingProduct365!==ctx||!state.products?.[code]||ctx.modal?.style.display==='flex')return false;missingProduct365=null;if(ctx.source==='stock')openVariant322(code,ctx.kind);else shoppingChooseProduct363(code);return true}catch{return false}finally{ctx.busy=false}};\n return Promise.resolve(result).then(finish);\n};\n"},{"src":"action-colors365.js?v=1","code":"'use strict';\n// Presentation only: keep each original node, listener and permission guard.\nfunction actionTone365(button){\n const label=String(button.textContent||'').trim();\n if(/^[+＋](?:\\s|$)/u.test(label))return 'plus';\n if(/^[-−–](?:\\s|$)/u.test(label))return 'minus';\n return '';\n}\nfunction paintAction365(button){const tone=actionTone365(button);button.classList.toggle('actionPlus365',tone==='plus');button.classList.toggle('actionMinus365',tone==='minus')}\nfunction paintActions365(root){if(root.matches?.('button'))paintAction365(root);root.querySelectorAll?.('button').forEach(paintAction365)}\npaintActions365(document);\nnew MutationObserver(records=>{for(const r of records){if(r.type==='characterData'){const b=r.target.parentElement?.closest('button');if(b)paintAction365(b)}else{const b=r.target.closest?.('button');if(b)paintAction365(b);r.addedNodes.forEach(n=>{if(n.nodeType===1)paintActions365(n)})}}}).observe(document.body,{childList:true,subtree:true,characterData:true});\n"},{"src":"event-choice367.js?v=374","code":"'use strict';\nlet eventChoice367=null,eventNew367=null;\nfunction eventPhasePermission367(phase){return {overview:'planning',preparation:'preparation',load:'load',setup:'setup',onsite:'onsite',calc:'calc'}[phase]}\nfunction eventPhaseLabel367(phase){return hubText362(...({overview:['Planen','Plan','Planuj'],preparation:['Vorbereiten','Prepare','Przygotuj'],load:['Durchführen','Carry out','Realizuj'],setup:['Aufbauen','Set up','Rozstaw'],onsite:['Vor Ort','On site','Na miejscu'],calc:['Abschließen','Close','Zamknij']}[phase]||['Einsatz','Event','Wydarzenie']))}\nfunction eventChoices367(query='',date=''){\n const q=String(query).trim().toLocaleLowerCase();return (state.planning?.caterings||[]).filter(c=>!['cancelled','archived'].includes(c.status390)&&(!q||String(c.event||'').toLocaleLowerCase().includes(q)||String(c.date||'').includes(q))&&(!date||c.date===date)).slice().sort((a,b)=>String(b.date||'').localeCompare(String(a.date||''))||String(a.event||'').localeCompare(String(b.event||''),'de')).map(c=>`<button type=\"button\" class=\"hubListRow362\" onclick=\"chooseEvent367('${escJs(c.id)}')\"><span><b>${esc(c.event||hubText362('Ohne Namen','Untitled','Bez nazwy'))}</b><small>${esc(c.date?formatDate(c.date):hubText362('Datum offen','Date not set','Brak daty'))} · ${c.workflow?.closed?hubText362('Abgeschlossen','Closed','Zakończono'):c.confirmed374===true?hubText362('Fixiert','Confirmed','Potwierdzone'):hubText362('Entwurf','Draft','Szkic')}</small></span><span aria-hidden=\"true\">→</span></button>`).join('')||`<p>${hubText362('Keine passenden Einsätze vorhanden.','No matching events.','Brak pasujących wydarzeń.')}</p>`;\n}\nfunction filterEvents367(){const list=document.getElementById('eventChoiceRows367');if(list)list.innerHTML=eventChoices367(document.getElementById('eventChoiceQuery367')?.value||'',document.getElementById('eventChoiceDate367')?.value||'')}\nfunction openEventChoice367(phase){\n const permission=eventPhasePermission367(phase);if(!permission||!guardPermission(permission,eventPhaseLabel367(phase)))return false;\n eventChoice367={phase,busy:false};\n openOpsPanel(eventPhaseLabel367(phase),`<div class=\"eventChoice367\"><h3>${hubText362('Welcher Einsatz?','Which event?','Które wydarzenie?')}</h3>${hasPermission('planning')?`<button class=\"primary\" type=\"button\" onclick=\"newEventChoice367()\">+ ${hubText362('Neuen Auftrag anlegen','Create new event','Dodaj nowe wydarzenie')}</button>`:''}<div class=\"eventChoiceFilters367\"><label>${hubText362('Name','Name','Nazwa')}<input id=\"eventChoiceQuery367\" type=\"search\" placeholder=\"${hubText362('Einsatz suchen','Search events','Szukaj wydarzenia')}\" oninput=\"filterEvents367()\"></label><label>${hubText362('Datum','Date','Data')}<input id=\"eventChoiceDate367\" type=\"date\" onchange=\"filterEvents367()\"></label></div><div id=\"eventChoiceRows367\">${eventChoices367()}</div>${!(state.planning?.caterings||[]).length&&!hasPermission('planning')?`<p>${hubText362('Bitte einen Administrator bitten, einen Einsatz anzulegen.','Ask an administrator to create an event.','Poproś administratora o dodanie wydarzenia.')}</p>`:''}</div>`);return true;\n}\nasync function chooseEvent367(id){\n const ctx=eventChoice367;if(!ctx||ctx.busy||!hasPermission(eventPhasePermission367(ctx.phase))||!(state.planning?.caterings||[]).some(c=>c.id===id))return false;\n ctx.busy=true;document.getElementById('opsDialog92')?.close();hubView362.eventPhase=ctx.phase;\n try{const result=await hubOpenEvent362(id);if(result!==false)eventChoice367=null;return result}finally{ctx.busy=false}\n}\nasync function newEventChoice367(){\n const ctx=eventChoice367;if(!ctx||ctx.busy||!guardPermission('planning','Einsatz anlegen'))return false;ctx.busy=true;\n document.getElementById('opsDialog92')?.close();\n try{return await hubRoute362(async()=>{if(!await hubOpenPlanning366('overview'))return false;newPlanningEntry();eventNew367={phase:ctx.phase,busy:false};eventChoice367=null;return true})}finally{ctx.busy=false}\n}\nconst eventActionBefore367=hubAction362;\nhubAction362=function(action){if(action.startsWith('phase:'))return openEventChoice367(action.slice(6));return eventActionBefore367(action)};\nconst newEntryBefore367=newPlanningEntry;\nnewPlanningEntry=function(...args){eventNew367=null;return newEntryBefore367.apply(this,args)};\nconst saveEventBefore367=saveCateringPlan;\nsaveCateringPlan=function(...args){\n const ctx=eventNew367;if(!ctx)return saveEventBefore367.apply(this,args);if(ctx.busy)return false;\n const ids=new Set((state.planning?.caterings||[]).map(c=>c.id)),result=saveEventBefore367.apply(this,args),id=activeCateringId;\n if(ids.has(id)||!(state.planning?.caterings||[]).some(c=>c.id===id))return result;\n ctx.busy=true;\n return Promise.resolve(result).then(async()=>{try{await window.deviceStore257.flush();if(eventNew367!==ctx||!(state.planning?.caterings||[]).some(c=>c.id===id))return false;eventNew367=null;document.body.classList.remove('webEventList360');selectPlanningEntry(id,hasPermission(eventPhasePermission367(ctx.phase))?ctx.phase:'overview');return true}catch{return false}finally{ctx.busy=false}});\n};\nconst selectEventBefore367=selectPlanningEntry;\nselectPlanningEntry=function(...args){document.body.classList.remove('webEventList360');return selectEventBefore367.apply(this,args)};\n"},{"src":"stock-presentation368.js?v=1","code":"'use strict';\nfunction stockTable368(pools=null,query=''){return `<div class=\"stockTable368\">${stockColumns368()}${hubStockRows362(query,'',pools)}</div>`}\n// Same data and action renderer for the old scanner destination and stock overview.\nlet scannerSelection368=null;\nconst selectScannerBefore368=selectArticleSearchResult;\nselectArticleSearchResult=function(code,...args){if(webDesktop360())scannerSelection368=code;return selectScannerBefore368.call(this,code,...args)};\nconst existingScannerBefore368=openExistingStock301;\nopenExistingStock301=function(code,...args){if(webDesktop360())scannerSelection368=code;return existingScannerBefore368.call(this,code,...args)};\nfunction scannerAllProducts368(){scannerSelection368=null;desktopSearch338=null;desktopReturn339=null;render()}\nconst articleMarkupBefore368=dashboardArticleMarkup;\ndashboardArticleMarkup=function(article){if(!webDesktop360())return articleMarkupBefore368(article);const pools=scannerSelection368?stockPools301().filter(p=>p.members.some(([code])=>code===scannerSelection368)):null;const scanned=typeof lastScanPackCode!=='undefined'&&lastScanPackCode&&scannerSelection368?`<p class=\"muted\">${esc(hubText362('Gescannt','Scanned','Zeskanowano'))}: ${esc(lastScanPackName||lastScanPackCode)} · ${esc(lastScanPackCode)}</p>`:'';return `<h2>${hubText362('Bestandsübersicht','Stock overview','Przegląd stanu')}</h2>${pools?`<button type=\"button\" class=\"soft\" onclick=\"scannerAllProducts368()\">← ${hubText362('Alle Produkte','All products','Wszystkie produkty')}</button>`:''}${scanned}${stockTable368(pools,pools?' ': '')}`};\nconst paintSearchBefore368=paintDesktopSearch338;\npaintDesktopSearch338=function(...args){\n if(!webDesktop360()||!desktopSearch338)return paintSearchBefore368.apply(this,args);\n const {query,inputId,boxId,cardId,allowCreate}=desktopSearch338,card=document.getElementById(cardId);if(!card)return;\n const pools=stockPoolsForSearch301(query),count=pools.reduce((n,p)=>n+p.members.length,0);\n card.innerHTML=`<h2>${esc(desktopSearchWords338('results'))}: ${esc(query)} <small>(${count})</small></h2>${pools.length?stockTable368(pools,query):`<p>${esc(desktopSearchWords338('empty'))}</p>${allowCreate&&canCreateArticles()?`<button type=\"button\" class=\"soft\" onclick=\"offerCreateArticleFromSearch('${escJs(inputId)}','${escJs(boxId)}')\">${esc(desktopSearchWords338('create'))}</button>`:''}`}`;\n};\n"},{"src":"planning-guide369.js?v=375","code":"'use strict';\nlet planningStep369=0,planningSaving369=false,planningOffer369=null,planningOfferSerial369=0,planningOfferMeta369=null;\nfunction planningWebUpload369(){return webDesktop360()&&!globalThis.ZapasioAndroid&&!/ZapasioAndroid\\//.test(globalThis.navigator?.userAgent||'')}\nfunction planningLabels369(){return [hubText362('Was steht an?','What is planned?','Co planujesz?'),hubText362('Speisen & Mengen','Dishes & quantities','Dania i ilości'),hubText362('Bestand & Bedarf','Stock & needs','Stan i potrzeby'),hubText362('Kosten & Angaben','Costs & details','Koszty i dane'),hubText362('Zusammenfassung','Summary','Podsumowanie')]}\n// A fixed wizard step must not depend on an invisible native details toggle.\nfunction planningFoodContainer373(fold){\n if(!fold||fold.tagName!=='DETAILS')return fold;\n const container=document.createElement('div');\n for(const attr of [...fold.attributes])if(!['open','name'].includes(attr.name))container.setAttribute(attr.name,attr.value);\n container.classList.add('planningFood373');\n while(fold.firstChild)container.appendChild(fold.firstChild);\n fold.replaceWith(container);\n return container;\n}\nfunction installPlanningGuide369(){\n const host=document.getElementById('planningSection_entry'),card=host?.querySelector(':scope > .card');if(!card)return;if(card.querySelector('#planningGuide369')){planningFoodContainer373(card.querySelector('[data-planning-step369=\"1\"] > .planFold'));return;}\n const folds=[...card.querySelectorAll(':scope > .planFold')];if(folds.length<4)return;folds.forEach(f=>f.open=true);folds[1]=planningFoodContainer373(folds[1]);\n const guide=document.createElement('div');guide.id='planningGuide369';guide.innerHTML='<p id=\"planningProgress369\" role=\"status\"></p><div id=\"planningSteps369\"></div><p id=\"planningError369\" role=\"alert\" hidden></p><div id=\"planningButtons369\"></div>';card.querySelector('.sectionIntro')?.remove();card.querySelector('h2').textContent=hubText362('Einsatz anlegen oder bearbeiten','Create or edit event','Dodaj lub edytuj wydarzenie');folds[0].before(guide);\n const panels=Array.from({length:5},(_,i)=>{const panel=document.createElement('section');panel.dataset.planningStep369=i;guide.querySelector('#planningSteps369').append(panel);return panel});\n panels[0].append(folds[0]);panels[1].append(folds[1]);\n const needs=document.createElement('div');needs.id='planningNeeds369';panels[2].append(needs);\n const summary=document.createElement('div');summary.id='planningSummary369';panels[4].append(summary);\n const more=document.createElement('details');more.className='planningMore369';more.innerHTML=`<summary>${hubText362('Kosten und weitere Angaben erfassen','Enter costs and other details','Wprowadź koszty i inne dane')}</summary>`;more.append(folds[2]);panels[3].append(more);const calculation=document.createElement('details');calculation.className='planningMore369';calculation.innerHTML=`<summary>${hubText362('Detaillierte Kalkulation','Detailed calculation','Szczegółowa kalkulacja')}</summary>`;calculation.append(folds[3]);panels[3].append(calculation);const costs=document.createElement('div');costs.id='planningCosts374';panels[3].prepend(costs);const approval=document.createElement('label');approval.className='confirmEvent374';approval.innerHTML=`<input id=\"confirmEvent374\" type=\"checkbox\" onchange=\"confirmReview374(this.checked)\"><span>${hubText362('Einsatz geprüft und verbindlich erfassen','Reviewed: confirm this event','Sprawdzono: potwierdzam wydarzenie')}</span>`;panels[4].append(approval);\n const legacyDistribution=card.querySelector('[onclick=\"resetDistribution104()\"]')?.closest('details.planMore353');if(legacyDistribution)legacyDistribution.hidden=true;\n const guests=document.getElementById('planGuests')?.closest('.field');if(guests)panels[1].prepend(guests);\n const address=document.getElementById('planAddress')?.closest('.field');if(address)folds[0].querySelector('.cateringFormGrid')?.append(address);\n const basicMore=folds[0].querySelector('.planMore353');if(basicMore)more.append(basicMore);\n const offerNumber=document.getElementById('planOfferNo')?.closest('.field');if(offerNumber)more.append(offerNumber);\n const upload=document.createElement('div');upload.id='planningUpload369';upload.innerHTML=`<button class=\"soft\" type=\"button\" onclick=\"document.getElementById('planningPdf369').click()\">${hubText362('Angebot aus PDF übernehmen','Read PDF offer','Wczytaj ofertę PDF')}</button><input id=\"planningPdf369\" type=\"file\" accept=\".pdf,application/pdf\" hidden onchange=\"handleOfferPdf(this.files[0]);this.value='' \"><small>${hubText362('PDF auswählen oder hierher ziehen.','Choose a PDF or drop it here.','Wybierz PDF lub upuść tutaj.')}</small><p id=\"planningUploadStatus369\" role=\"status\"></p>`;panels[0].prepend(upload);\n for(const type of ['dragenter','dragover'])upload.addEventListener(type,e=>{e.preventDefault();upload.classList.add('dragging')});upload.addEventListener('dragleave',()=>upload.classList.remove('dragging'));upload.addEventListener('drop',e=>{e.preventDefault();upload.classList.remove('dragging');const files=[...(e.dataTransfer?.files||[])];if(files.length!==1){planningUploadMessage369('Bitte genau eine PDF-Datei auswählen.');return}handleOfferPdf(files[0])});\n const actions=card.querySelector(':scope > .planActions');if(actions){const nav=guide.querySelector('#planningButtons369');const back=document.createElement('button');back.type='button';back.className='soft';back.id='planningBack369';back.textContent=hubText362('Zurück','Back','Wstecz');back.onclick=()=>planningGo369(planningStep369-1);nav.append(back);const next=document.createElement('button');next.type='button';next.className='primary';next.id='planningNext369';next.textContent=hubText362('Weiter','Next','Dalej');next.onclick=()=>planningGo369(planningStep369+1);nav.append(next);nav.append(actions)}\n}\nfunction planningDateLimit375(){const field=document.getElementById('planDate');if(field)field.min=localTodayISO()}\nfunction planningValidation369(step){const value=id=>document.getElementById(id)?.value?.trim()||'';\n if(step>=0){planningDateLimit375();if(!value('planEvent'))return ['planEvent','Bitte einen Namen für den Einsatz eingeben.'];const date=value('planDate');if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(date)||!Number.isFinite(Date.parse(date+'T12:00:00Z'))||new Date(date+'T12:00:00Z').toISOString().slice(0,10)!==date)return ['planDate','Bitte ein gültiges Einsatzdatum auswählen.'];if(date<localTodayISO())return ['planDate','Das Einsatzdatum liegt in der Vergangenheit. Bitte heute oder einen späteren Tag auswählen.']}\n if(step>=1){const n=Number(value('planGuests'));if(!Number.isInteger(n)||n<1)return ['planGuests','Bitte eine ganze Personenanzahl größer als 0 eingeben.'];for(const line of draftCateringLines||[])if(line.recipeId&&(!(Number(line.qty)>0)||!Number.isFinite(Number(line.qty))))return ['cateringRecipeRows','Bitte bei jeder gewählten Speise eine Menge größer als 0 eingeben.']}\n return null;\n}\nfunction planningGo369(step){if(planningSaving369||step<0||step>4)return false;const error=step>planningStep369?planningValidation369(planningStep369):null;const message=document.getElementById('planningError369');if(error){if(message){message.hidden=false;message.textContent=error[1]}document.getElementById(error[0])?.focus();return false}if(message)message.hidden=true;if(step!==planningStep369&&typeof invalidateApproval374==='function')invalidateApproval374();planningStep369=step;paintPlanningGuide369();document.getElementById('planningGuide369')?.scrollIntoView({block:'start',behavior:'smooth'});return true}\nfunction planningNeedsModel369(c){const food=cateringNeedDetails(c),drink=drinkNeedDetails(c),rows=new Map();for(const n of [...(food.needs||[]),...(drink.needs||[])]){const key=n.productCode+'|'+normalizeUnit(n.unit),old=rows.get(key);if(old)old.need+=Number(n.need)||0;else rows.set(key,{...n,need:Number(n.need)||0,stock:Number(n.stock)||0})}return {rows:[...rows.values()],free:[...(food.free||[]),...(drink.free||[])],errors:food.errors||[]}}\nfunction planningNeedsHtml369(c){const data=planningNeedsModel369(c);return `<h3>${hubText362('Was ist schon da?','What is in stock?','Co jest na stanie?')}</h3><div class=\"hubTableScroll362\"><table class=\"planningNeedTable369\"><thead><tr>${['Artikel','Benötigt','Auf Lager','Besorgen'].map(s=>`<th>${esc(s)}</th>`).join('')}</tr></thead><tbody>${data.rows.map(n=>`<tr><th>${esc(n.name)}</th><td>${esc(formatQtySmart(n.need,n.unit))}</td><td>${esc(formatQtySmart(n.stock,n.unit))}</td><td>${esc(formatQtySmart(Math.max(0,n.need-n.stock),n.unit))}</td></tr>`).join('')}</tbody></table></div>${!data.rows.length?`<p>${hubText362('Noch kein verknüpfter Bedarf vorhanden.','No linked needs yet.','Brak powiązanego zapotrzebowania.')}</p>`:''}${data.free.length?`<p class=\"warnText\">${data.free.length} ${hubText362('Positionen noch zuordnen.','items need linking.','pozycji wymaga przypisania.')}</p><ul>${data.free.map(n=>`<li>${esc(n.name)} · ${esc(formatQtySmart(n.qty,n.unit))}</li>`).join('')}</ul>`:''}${data.errors.length?`<p class=\"warnText\">${esc(data.errors.join(' · '))}</p>`:''}<p class=\"muted\">${hubText362('Nach dem Speichern wird der Bedarf im Einkauf berücksichtigt.','After saving, needs are included in purchasing.','Po zapisie potrzeby pojawią się w zakupach.')}</p>`}\nfunction paintPlanningGuide369(){planningDateLimit375();installPlanningGuide369();const guide=document.getElementById('planningGuide369');if(!guide)return;const labels=planningLabels369();guide.querySelectorAll('[data-planning-step369]').forEach((p,i)=>p.hidden=i!==planningStep369);document.getElementById('planningProgress369').textContent=`${planningStep369+1} / 5 · ${labels[planningStep369]}`;const upload=document.getElementById('planningUpload369');if(upload)upload.hidden=!planningWebUpload369();const back=document.getElementById('planningBack369'),next=document.getElementById('planningNext369'),save=guide.querySelector('[onclick=\"saveCateringPlan()\"]');if(back)back.disabled=planningStep369===0||planningSaving369;if(next)next.hidden=planningStep369===4;if(save){save.hidden=planningStep369!==4;save.disabled=planningSaving369||!document.getElementById('confirmEvent374')?.checked;}\n if(planningStep369>=2){const c=cateringDraftObject();if(typeof renderFinalSummary374==='function'){renderFinalSummary374(c);return;}document.getElementById('planningNeeds369').innerHTML=planningNeedsHtml369(c);document.getElementById('planningSummary369').innerHTML=`<h3>${esc(c.event||'')}</h3><p>${esc(c.date?formatDate(c.date):'')} · ${esc(c.guests)} ${hubText362('Personen','people','osób')}${c.address?' · '+esc(c.address):''}</p><ul>${(c.lines||[]).map(l=>`<li>${esc(findRecipeById(l.recipeId)?.recipe?.name||l.name||'Rezept prüfen')} · ${esc(formatQtySmart(l.qty,l.unit||'Portionen'))}</li>`).join('')}</ul>`}\n}\nconst applyGuideBefore369=applyPlanningSection;\napplyPlanningSection=function(...args){const r=applyGuideBefore369.apply(this,args);if(planningSection==='entry')paintPlanningGuide369();return r};\nconst newGuideBefore369=newPlanningEntry;\nnewPlanningEntry=function(...args){planningStep369=0;planningOfferMeta369=null;planningOffer369=null;planningOfferSerial369++;const r=newGuideBefore369.apply(this,args);const guests=document.getElementById('planGuests');if(guests)guests.value='';paintPlanningGuide369();return r};\nconst editGuideBefore369=editCateringPlan;\neditCateringPlan=function(...args){planningStep369=0;planningOfferMeta369=null;planningOffer369=null;planningOfferSerial369++;const r=editGuideBefore369.apply(this,args);paintPlanningGuide369();return r};\nconst draftGuideBefore369=cateringDraftObject;\ncateringDraftObject=function(...args){const c=draftGuideBefore369.apply(this,args);return planningOfferMeta369?{...c,...planningOfferMeta369}:c};\nconst saveGuideBefore369=saveCateringPlan;\nsaveCateringPlan=function(...args){if(planningSection!=='entry')return saveGuideBefore369.apply(this,args);if(planningSaving369)return false;const error=planningValidation369(1);if(error){planningStep369=error[0]==='planGuests'||error[0]==='cateringRecipeRows'?1:0;paintPlanningGuide369();const msg=document.getElementById('planningError369');if(msg){msg.textContent=error[1];msg.hidden=false}return false}if(!validateForms231())return false;const edit=editingCateringId,ids=new Set((state.planning?.caterings||[]).map(c=>c.id)),result=saveGuideBefore369.apply(this,args),id=activeCateringId;if(!id||!(state.planning?.caterings||[]).some(c=>c.id===id)||(edit?editingCateringId!==''||id!==edit:ids.has(id)))return result;planningSaving369=true;return Promise.resolve(result).then(async ok=>{try{if(ok===false)return false;await window.deviceStore257.flush();if(planningSection==='entry')selectPlanningEntry(id,'overview');planningOfferMeta369=null;return true}catch{return false}finally{planningSaving369=false}})};\nfunction planningUploadMessage369(message){const target=document.getElementById('planningUploadStatus369');if(target)target.textContent=message;else showFeedback(message,'warn')}\nasync function readPlanningPdf369(file){const [reader,engine]=await Promise.all([import('./document170.mjs?v=97'),import('./offer170.mjs?v=369')]);const doc=await reader.readOfferFile170(file);if(doc.needsOcr||!doc.text?.trim())throw Error('Diese PDF ist nicht ausreichend lesbar. Bitte eine durchsuchbare PDF verwenden oder die Angaben manuell eingeben.');const analysis=engine.parseOffer170(doc.text,offerRecipes170());if(['supplier_price_list','non_catering_document'].includes(analysis.documentType))throw Error('Die Datei ist kein Einsatzangebot. Bitte ein passendes Angebot auswählen.');return {doc,analysis,price:engine.preferredOfferPrice170(analysis.fields)}}\nfunction planningOfferFields369(review){const find=k=>review.analysis.fields.find(f=>f.kind===k),client=find('client'),date=find('eventDate'),people=find('people'),address=find('eventAddress'),price=review.price;return [{key:'planEvent',label:'Einsatzname',value:client?'Catering · '+client.value:'',source:client?.source||'',type:'text'},{key:'planDate',label:'Einsatzdatum',value:date?offerDate170(date.value):'',source:date?.source||'',type:'date'},{key:'planGuests',label:'Personen',value:people?.value||'',source:people?.source||'',type:'number'},{key:'planAddress',label:'Einsatzort',value:address?.value||'',source:address?.source||'',type:'text'},{key:'planRevenue',label:'Netto-Auftragswert gesamt (€)',value:price?.value?.tax==='net'&&price.value.unit==='total'?String(price.value.amount).replace('.',','):'',source:price?.source||'',type:'text'}]}\nfunction planningOfferReview369(review){const fields=planningOfferFields369(review);return `<p>${esc(review.doc.name)} · ${hubText362('Vorschläge prüfen und auswählen.','Review and select suggestions.','Sprawdź i wybierz propozycje.')}</p><div class=\"offerDraftFields369\">${fields.map(f=>`<label><span><input type=\"checkbox\" data-offer-field369=\"${f.key}\" ${f.value&&!editingCateringId?'checked':''}> ${esc(f.label)}</span><input id=\"offerDraft_${f.key}\" type=\"${f.type}\" value=\"${esc(f.value)}\"><small>${esc(f.source||'Nicht eindeutig erkannt. Bei Bedarf manuell eintragen.')}</small></label>`).join('')}</div><h3>Speisen aus dem Angebot</h3>${(review.analysis.items||[]).filter(item=>item.type==='food'&&!item.bundleTotal).map((item,i)=>`<div class=\"offerDraftRow369\"><label><input type=\"checkbox\" data-offer-line369=\"${i}\"> ${esc(item.name||'Speise')}${item.optional?' (optional)':''}</label><small>${esc(item.source||'')}</small><select data-offer-recipe369=\"${i}\" aria-label=\"Rezept\">${offerRecipeOptions170(item.recipeId||'')}</select><input data-offer-qty369=\"${i}\" type=\"number\" min=\"0\" step=\"any\" value=\"${esc(item.quantity||'')}\" aria-label=\"Menge\"></div>`).join('')||'<p>Keine einzelnen Speisen eindeutig erkannt. Im nächsten Schritt ergänzen.</p>'}<label><input id=\"offerReplace369\" type=\"checkbox\"> Vorhandene Speisen durch die ausgewählten Positionen ersetzen</label>${(review.analysis.items||[]).some(i=>i.type==='alternative'||i.bundleTotal)?'<p class=\"warnText\">Sammelpositionen und Alternativen bitte bei „Speisen & Mengen“ aufteilen.</p>':''}${(review.analysis.items||[]).some(i=>i.type!=='food'||i.bundleTotal)?`<details><summary>Weitere Positionen und Pakete ansehen</summary><ul>${(review.analysis.items||[]).filter(i=>i.type!=='food'||i.bundleTotal).map(i=>`<li>${esc(i.source||i.name||'Position prüfen')}</li>`).join('')}</ul></details>`:''}${review.analysis.uncertainties?.length?`<details><summary>Erkennungshinweise</summary><ul>${review.analysis.uncertainties.map(x=>`<li>${esc(x.reason)}</li>`).join('')}</ul></details>`:''}<details><summary>Gelesenen Originaltext ansehen</summary><pre class=\"offerSource170\">${esc(review.doc.text)}</pre></details><p id=\"offerDraftError369\" role=\"alert\"></p><div class=\"opsActions92\"><button type=\"button\" class=\"soft\" onclick=\"document.getElementById('opsDialog92').close()\">Abbrechen</button><button type=\"button\" class=\"primary\" onclick=\"applyPlanningOffer369()\">Auswahl ins Formular übernehmen</button></div>`}\nhandleOfferPdf=async function(file){\n if(!planningWebUpload369()){showFeedback('Angebote bitte in der Webversion am PC hochladen.','warn');return false}if(!guardPermission('planning','Angebot übernehmen')||!file)return false;\n const serial=++planningOfferSerial369;\n if(!/\\.pdf$/i.test(file.name||'')||file.size>8*1024*1024){planningUploadMessage369('Bitte eine PDF-Datei bis 8 MB auswählen.');return false}\n planningUploadMessage369('PDF wird gelesen …');\n try{const review=await readPlanningPdf369(file);if(serial!==planningOfferSerial369)return false;\n const duplicate=(state.planning?.caterings||[]).find(c=>c.offerHash170===review.doc.hash&&c.id!==editingCateringId);if(duplicate)throw Error('Dieses Angebot gehört bereits zum Einsatz „'+duplicate.event+'“. Bitte diesen Einsatz bearbeiten.');if(planningOfferMeta369?.offerHash170===review.doc.hash)throw Error('Dieses Angebot wurde bereits ins aktuelle Formular übernommen.');\n planningOffer369=review;openOpsPanel('Angebot prüfen',planningOfferReview369(review));planningUploadMessage369('PDF gelesen. Angaben vor der Übernahme prüfen.');return true;\n }catch(error){if(serial===planningOfferSerial369)planningUploadMessage369('PDF konnte nicht übernommen werden: '+error.message);return false}\n};\nfunction applyPlanningOffer369(){\n if(!planningWebUpload369()||!guardPermission('planning','Angebot übernehmen')||!planningOffer369)return false;\n const host=document.getElementById('opsDialog92'),review=planningOffer369,values={};const lines=[];\n try{\n for(const checked of host.querySelectorAll('[data-offer-field369]:checked')){const key=checked.dataset.offerField369,value=document.getElementById('offerDraft_'+key)?.value.trim()||'';if(!value)throw Error('Bitte ausgewählte Angaben ausfüllen oder die Auswahl entfernen.');if(key==='planGuests'&&(!Number.isInteger(Number(value))||Number(value)<1))throw Error('Bitte eine ganze Personenanzahl größer als 0 eingeben.');if(key==='planRevenue'&&(!Number.isFinite(Number(value.replace(',','.')))||Number(value.replace(',','.'))<0))throw Error('Bitte den geprüften Netto-Gesamtbetrag eingeben.');values[key]=value}\n for(const checked of host.querySelectorAll('[data-offer-line369]:checked')){const i=checked.dataset.offerLine369,recipeId=host.querySelector(`[data-offer-recipe369=\"${i}\"]`)?.value,qty=Number(host.querySelector(`[data-offer-qty369=\"${i}\"]`)?.value);if(!findRecipeById(recipeId)||!(qty>0)||!Number.isFinite(qty))throw Error('Bitte bei jeder ausgewählten Speise ein Rezept und eine positive Menge wählen.');lines.push({recipeId,qty,qtyManual:true,course:typeof recipeCourse353==='function'?recipeCourse353(recipeId):'main'})}\n if(lines.length&&(draftCateringLines||[]).some(l=>l.recipeId)&&!document.getElementById('offerReplace369')?.checked)throw Error('Es sind bereits Speisen eingetragen. Bitte das Ersetzen ausdrücklich auswählen oder nur die Grunddaten übernehmen.');\n if(!Object.keys(values).length&&!lines.length)throw Error('Bitte mindestens eine Angabe oder Speise auswählen.');\n }catch(error){const message=document.getElementById('offerDraftError369');if(message)message.textContent=error.message;return false}\n for(const [key,value] of Object.entries(values)){const input=document.getElementById(key);if(input){input.value=value;input.dispatchEvent?.(new Event('input',{bubbles:true}));input.dispatchEvent?.(new Event('change',{bubbles:true}))}}\n if(lines.length){draftCateringLines=lines;for(const l of lines)draftCourseEnabled[l.course]=true;renderCateringDraftRows()}\n draftOfferFileName=review.doc.name;planningOfferMeta369={offerHash170:review.doc.hash,offerFileName:review.doc.name,offerDraftReview369:{checkedAt:new Date().toISOString(),fields:Object.keys(values),source:review.analysis.fields.map(f=>({kind:f.kind,source:f.source})),warnings:(review.analysis.uncertainties||[]).map(x=>x.reason)}};\n planningOffer369=null;updateOfferStatus();updateCateringPreview();host.close();paintPlanningGuide369();planningUploadMessage369('Angaben übernommen. Bitte weiter prüfen und den Einsatz speichern.');return true;\n}\nconst openOfferBefore369=openOfferImport170;\nopenOfferImport170=function(...args){if(!planningWebUpload369()){showFeedback('Angebote bitte in der Webversion am PC hochladen.','warn');return false}return openOfferBefore369.apply(this,args)};\nif(typeof document!=='undefined'&&document.body&&planningSection==='entry')paintPlanningGuide369();\nconst offerButtonBefore369=addOfferButton170;\naddOfferButton170=function(...args){const r=offerButtonBefore369.apply(this,args);const button=document.getElementById('offerImportButton170');if(button)button.hidden=!planningWebUpload369();return r};\nif(typeof document!=='undefined'&&document.body)addOfferButton170();\n"},{"src":"planning-mix370.js?v=1","code":"'use strict';\nlet mixGroup370='',mixTargets370=new Map(),mixErrors370=new Map();\nfunction mixGroups370(){return (state.recipeGroups||[]).map((g,i)=>({id:String(g.id||'group-'+i),name:g.name||hubText362('Rezepte','Recipes','Przepisy'),recipes:(g.recipes||[]).filter(r=>r.id)})).filter(g=>g.recipes.length).sort((a,b)=>a.name.localeCompare(b.name,'de'))}\nfunction mixLines370(group){const ids=new Set(group.recipes.map(r=>r.id));return draftCateringLines.map((line,index)=>({line,index})).filter(x=>ids.has(x.line.recipeId))}\nfunction mixCountUnit370(unit){return /^(?:portion(?:en|s)?|stück|stk|pieces?)$/i.test(String(unit))}\nfunction mixMetrics370(group){const entries=mixLines370(group),lines=entries.map(x=>x.line),units=[...new Set(lines.map(l=>l.unit||recipePlanningUnit108(findRecipeById(l.recipeId)?.recipe)))],countable=units.length===1&&mixCountUnit370(units[0]),qty=lines.reduce((n,l)=>n+Math.max(0,Number(l.qty)||0),0),details=cateringNeedDetails({lines,dayItems:[],drinks:[],drinksEnabled:false}),complete=lines.length>0&&details.totalIngredients>0&&details.costedIngredients===details.totalIngredients&&!details.free?.length&&!details.errors?.length&&lines.every(l=>Number(l.qty)>0);return {entries,lines,units,countable,qty,details,complete,cost:complete?Number(details.autoCost):null,average:complete&&countable&&qty>0?Number(details.autoCost)/qty:null}}\nfunction mixTarget370(group,metrics=mixMetrics370(group)){if(!mixTargets370.has(group.id))mixTargets370.set(group.id,String(metrics.qty>0?metrics.qty:document.getElementById('planGuests')?.value||''));return mixTargets370.get(group.id)}\nfunction mixStatsHtml370(group){const m=mixMetrics370(group),target=Number(mixTarget370(group,m)),diff=Number.isInteger(target)&&target>0&&m.countable?target-m.qty:null;return `<div class=\"mixMetrics370\"><div><small>${hubText362('Ausgewählt','Selected','Wybrano')}</small><strong>${new Set(m.lines.map(l=>l.recipeId)).size} ${hubText362('Sorten','variants','wariantów')}</strong></div><div><small>${hubText362('Aufgeteilt','Allocated','Rozdzielono')}</small><strong>${m.units.length===1?esc(formatQtySmart(m.qty,m.units[0])):'—'}</strong></div><div><small>${hubText362('Rezeptkosten gesamt','Total recipe costs','Koszt przepisów razem')}</small><strong>${m.cost===null?hubText362('Offen','Unknown','Brak danych'):moneyFmt(m.cost)}</strong></div><div><small>${hubText362('Durchschnitt je','Average per','Średnio za')} ${esc(m.countable?m.units[0]:'Portion')}</small><strong>${m.average===null?hubText362('Offen','Unknown','Brak danych'):moneyFmt(m.average)}</strong></div></div>${diff!==null&&diff!==0?`<p class=\"mixNote370\">${diff>0?esc(formatQtySmart(diff,m.units[0]))+' '+hubText362('noch verteilen','still to allocate','pozostało do rozdzielenia'):esc(formatQtySmart(-diff,m.units[0]))+' '+hubText362('über der geplanten Menge','over the planned quantity','ponad plan')}</p>`:''}${m.lines.length&&!m.complete?`<p class=\"mixNote370\">${hubText362('Mengen, Zutatenzuordnung oder Rezeptpreise noch unvollständig.','Quantities, ingredient links or recipe prices are incomplete.','Ilości, składniki lub ceny wymagają uzupełnienia.')}</p>`:''}${m.lines.length&&!m.countable?`<p class=\"mixNote370\">${hubText362('Portionsdurchschnitt nur bei gleicher Stück- oder Portionseinheit. Mengen bleiben in der Rezepteinheit.','Per-portion average needs matching portion or piece units. Quantities keep their recipe units.','Średnia wymaga tej samej jednostki porcji lub sztuk.')}</p>`:''}`}\nfunction mixSelectGroup370(id){if(!mixGroups370().some(g=>g.id===id)&&id!=='')return false;mixGroup370=id;renderCateringDraftRows();return true}\nfunction toggleMixRecipe370(id,checked){const found=findRecipeById(id);if(!found||!hasPermission('planning'))return false;mixErrors370.clear();if(checked){if(!draftCateringLines.some(l=>l.recipeId===id)){draftCateringLines=draftCateringLines.filter(l=>l.recipeId);const course=recipeCourse353(id)||'main';draftCateringLines.push({recipeId:id,qty:0,unit:recipePlanningUnit108(found.recipe),qtyManual:true,course});draftCourseEnabled[course]=true}}else draftCateringLines=draftCateringLines.filter(l=>l.recipeId!==id);renderCateringDraftRows();updateCateringPreview();return true}\nfunction setMixQuantity370(index,raw){const line=draftCateringLines[index],r=line&&findRecipeById(line.recipeId)?.recipe;if(!r||!hasPermission('planning'))return false;const unit=line.unit||recipePlanningUnit108(r),text=String(raw).trim(),qty=text===''?0:Number(text.replace(',','.'));if(!Number.isFinite(qty)||qty<0||mixCountUnit370(unit)&&!Number.isInteger(qty)){mixErrors370.set(index,hubText362('Bitte eine gültige Menge eingeben; Stück und Portionen nur ganzzahlig.','Enter a valid quantity; pieces and portions must be whole numbers.','Wpisz poprawną ilość; sztuki i porcje muszą być całkowite.'));updateMixStats370();return false}mixErrors370.delete(index);line.qty=qty;line.unit=unit;line.qtyManual=true;updateMixStats370();updateCateringPreview();return true}\nfunction distributeMix370(id){const group=mixGroups370().find(g=>g.id===id);if(!group||!hasPermission('planning'))return false;const m=mixMetrics370(group),total=Number(mixTarget370(group,m));if(!m.countable||!m.lines.length||new Set(m.lines.map(l=>l.recipeId)).size!==m.lines.length||!Number.isInteger(total)||total<m.lines.length){showFeedback(hubText362('Bitte Sorten mit gleicher Portionseinheit wählen und mindestens eine Portion je Sorte einplanen.','Choose matching portion units and allow at least one portion per variant.','Wybierz zgodne jednostki i co najmniej jedną porcję na wariant.'),'warn');return false}const apply=()=>{m.lines.forEach((l,i)=>{l.qty=Math.floor(total/m.lines.length)+(i<total%m.lines.length?1:0);l.qtyManual=true});mixErrors370.clear();renderCateringDraftRows();updateCateringPreview()};if(m.lines.some(l=>Number(l.qty)>0))askConfirm(hubText362('Die Mengen dieser Gruppe gleichmäßig neu verteilen?','Redistribute quantities in this group evenly?','Rozdzielić ponownie ilości w tej grupie?'),apply,hubText362('Verteilen','Distribute','Rozdziel'));else apply();return true}\nfunction updateMixStats370(){const group=mixGroups370().find(g=>g.id===mixGroup370),box=document.getElementById('mixStats370'),error=document.getElementById('mixError370');if(group&&box)box.innerHTML=mixStatsHtml370(group);if(error){error.textContent=[...mixErrors370.values()][0]||'';error.hidden=!mixErrors370.size}}\nconst renderMixBefore370=renderCateringDraftRows;\nrenderCateringDraftRows=function(){const host=document.getElementById('cateringRecipeRows');if(!host)return renderMixBefore370();const groups=mixGroups370();if(!mixGroup370){const selected=groups.find(g=>mixLines370(g).length);if(selected)mixGroup370=selected.id}const group=groups.find(g=>g.id===mixGroup370);host.innerHTML=`<div class=\"mixPicker370\"><label for=\"mixGroup370\">${hubText362('Rezeptgruppe','Recipe group','Grupa przepisów')}</label><select id=\"mixGroup370\" onchange=\"mixSelectGroup370(this.value)\"><option value=\"\">${hubText362('Gruppe auswählen','Choose group','Wybierz grupę')}</option>${groups.map(g=>`<option value=\"${esc(g.id)}\" ${g.id===mixGroup370?'selected':''}>${esc(g.name)}</option>`).join('')}</select></div>${group?`<section class=\"mixGroup370\"><h3>${esc(group.name)}</h3><div class=\"mixTarget370\"><label>${hubText362('Geplante Gesamtmenge','Planned total quantity','Planowana ilość łączna')}<input id=\"mixTarget370\" type=\"number\" min=\"1\" step=\"1\" value=\"${esc(mixTarget370(group))}\" oninput=\"mixTargets370.set('${escJs(group.id)}',this.value);updateMixStats370()\"></label><button type=\"button\" class=\"soft\" onclick=\"distributeMix370('${escJs(group.id)}')\">${hubText362('Gleichmäßig verteilen','Distribute evenly','Rozdziel równo')}</button></div>${group.recipes.map(r=>{const entries=draftCateringLines.map((line,index)=>({line,index})).filter(x=>x.line.recipeId===r.id),unit=recipePlanningUnit108(r);return `<div class=\"mixVariant370\"><label><input type=\"checkbox\" ${entries.length?'checked':''} onchange=\"toggleMixRecipe370('${escJs(r.id)}',this.checked)\"><span>${esc(r.name)}</span></label><div>${entries.map(({line,index})=>`<label class=\"mixQty370\"><input type=\"number\" min=\"0\" step=\"${mixCountUnit370(line.unit||unit)?'1':'any'}\" inputmode=\"decimal\" value=\"${Number(line.qty)||0}\" aria-label=\"${esc(r.name+' '+(line.unit||unit))}\" data-line-index=\"${index}\" oninput=\"setMixQuantity370(${index},this.value)\"><span>${esc(line.unit||unit)}</span></label>`).join('')}</div></div>`}).join('')}<p id=\"mixError370\" class=\"warnText\" role=\"alert\" hidden></p><div id=\"mixStats370\">${mixStatsHtml370(group)}</div></section>`:`<p class=\"muted\">${hubText362('Gruppe wählen, Sorten anhaken und Mengen eintragen.','Choose a group, check variants and enter quantities.','Wybierz grupę, warianty i ilości.')}</p>`}<div class=\"mixSelected370\">${groups.filter(g=>mixLines370(g).length).map(g=>`<button type=\"button\" class=\"soft\" onclick=\"mixSelectGroup370('${escJs(g.id)}')\">${esc(g.name)} · ${new Set(mixLines370(g).map(x=>x.line.recipeId)).size} ${hubText362('Sorten','variants','wariantów')}</button>`).join('')}</div>`;const missing=draftCateringLines.map((line,index)=>({line,index})).filter(x=>x.line.recipeId&&!findRecipeById(x.line.recipeId));if(missing.length)host.insertAdjacentHTML('beforeend',`<section class=\"mixMissing370\"><h3>${hubText362('Fehlende Rezepte prüfen','Review missing recipes','Sprawdź brakujące przepisy')}</h3>${missing.map(({line,index})=>`<p>${esc(line.name||line.recipeId)} <button class=\"soft\" onclick=\"removeCateringRecipeLine(${index})\">${hubText362('Entfernen','Remove','Usuń')}</button></p>`).join('')}</section>`);quantitySummary104();updateMixStats370();};\nconst validateMixBefore370=planningValidation369;\nplanningValidation369=function(step){if(step>=1&&mixErrors370.size)return ['cateringRecipeRows',[...mixErrors370.values()][0]];return validateMixBefore370(step)};\nconst newMixBefore370=newPlanningEntry;\nnewPlanningEntry=function(...args){mixGroup370='';mixTargets370.clear();mixErrors370.clear();return newMixBefore370.apply(this,args)};\nconst editMixBefore370=editCateringPlan;\neditCateringPlan=function(...args){mixGroup370='';mixTargets370.clear();mixErrors370.clear();return editMixBefore370.apply(this,args)};\n// PDF review keeps commercial positions separate from recipe quantities.\nconst offerReviewMixBefore370=planningOfferReview369;\nplanningOfferReview369=function(review){const html=offerReviewMixBefore370(review),start=html.indexOf('<h3>Speisen aus dem Angebot</h3>');const marker='<label><input id=\"offerReplace369\"',a=html.indexOf(marker),b=a<0?-1:html.indexOf('</label>',a)+8;if(start<0||a<0||b<8)return html;return html.slice(0,start)+`<h3>${hubText362('Speisen & Sorten','Dishes & variants','Dania i warianty')}</h3><p>${hubText362('Nach der Übernahme im nächsten Schritt die Rezeptgruppe wählen, Sorten anhaken und Portionen aufteilen.','After applying, choose recipe groups and allocate portions in the next step.','Po przejęciu wybierz grupę, warianty i podziel porcje.')}</p><details><summary>${hubText362('Speisenpositionen im Angebot ansehen','View food positions in offer','Zobacz pozycje dań w ofercie')}</summary><ul>${(review.analysis.items||[]).filter(i=>i.type==='food'||i.type==='alternative').map(i=>`<li>${esc(i.source||i.name||'')}</li>`).join('')}</ul></details>`+html.slice(b)};\nif(typeof document!=='undefined'&&document.body)renderCateringDraftRows();\n"},{"src":"article-metadata370.js?v=1","code":"'use strict';\n// Editing price/name/loss is not a new receipt or a retrospective batch assignment.\nfunction articleBatchSignature370(rows){return JSON.stringify((rows||[]).map(b=>Object.fromEntries(Object.keys(b).filter(k=>k!=='id').sort().map(k=>[k,k==='mhd'?(typeof parseMhd147==='function'?parseMhd147(b[k])?.iso||b[k]:b[k]):k==='qty'?Number(b[k]):b[k]]))).sort((a,b)=>JSON.stringify(a).localeCompare(JSON.stringify(b))))}\nfunction unchangedStockMhd370(){\n if(!editingArticleCode)return false;const old=state.products?.[editingArticleCode];if(!old)return false;\n const value=id=>document.getElementById(id)?.value;\n const stock=Number(value('aeStock'));if(!Number.isFinite(stock)||stock!==Number(old.stock)||value('aeUnit')!==old.unit)return false;\n if(value('aeMhdPolicy144')!==(old.mhdPolicy144||'review'))return false;\n const days=String(value('aeMhdDays242')||'').trim(),oldDays=old.mhdDays242?String(old.mhdDays242):'';if(days!==oldDays)return false;\n return articleBatchSignature370(articleBatchDraft)===articleBatchSignature370(state.batches?.[editingArticleCode]||[]);\n}\nconst metadataMhdBefore370=validateExistingMhdEditor;\nvalidateExistingMhdEditor=function(...args){if(unchangedStockMhd370())return true;return metadataMhdBefore370.apply(this,args)};\n"},{"src":"field-feedback370.js?v=375","code":"'use strict';\nlet fieldErrorSequence370=0,mhdQuiet370=false;\nfunction clearFieldError370(el){if(!el)return;el.classList?.remove('fieldInvalid370');el.removeAttribute?.('aria-invalid');const id=el.dataset?.fieldError370;if(id){document.getElementById(id)?.remove();const described=(el.getAttribute?.('aria-describedby')||'').split(/\\s+/).filter(x=>x&&x!==id);if(described.length)el.setAttribute('aria-describedby',described.join(' '));else el.removeAttribute?.('aria-describedby');delete el.dataset.fieldError370}}\nfunction markFieldError370(el,message,reveal=false){if(!el)return;el.classList?.add('fieldInvalid370');el.setAttribute?.('aria-invalid','true');let hint=el.dataset?.fieldError370&&document.getElementById(el.dataset.fieldError370);if(!hint&&el.dataset&&document.createElement){hint=document.createElement('small');hint.id='field-error-370-'+(++fieldErrorSequence370);hint.className='fieldError370';hint.setAttribute('role','alert');el.dataset.fieldError370=hint.id;const old=el.getAttribute('aria-describedby')||'';el.setAttribute('aria-describedby',(old+' '+hint.id).trim());if(el.formAdapter231?.box)el.formAdapter231.box.after(hint);else if(el.matches?.('input,select,textarea'))el.after(hint);else el.appendChild(hint)}if(hint)hint.textContent=message;if(reveal){let p=el.parentElement;while(p){if(p.tagName==='DETAILS')p.open=true;p=p.parentElement}el.scrollIntoView?.({block:'center',behavior:'smooth'});if(el.matches?.('input,select,textarea,button'))el.focus?.()}}\nconst issueBefore370=issue231;\nissue231=function(box,message){const r=issueBefore370.apply(this,arguments);if(message){box.classList?.add('fieldInvalid370');box.querySelectorAll?.('input:not([type=\"hidden\"]),select,textarea').forEach(el=>el.setAttribute('aria-invalid','true'))}else{box.classList?.remove('fieldInvalid370');box.querySelectorAll?.('[aria-invalid=\"true\"]').forEach(el=>el.removeAttribute('aria-invalid'))}return r};\nconst planningFieldBefore370=planningValidation369;\nplanningValidation369=function(step){const error=planningFieldBefore370(step);if(error){const el=document.getElementById(error[0]);markFieldError370(el,error[1]);if(error[0]==='cateringRecipeRows')for(const input of el?.querySelectorAll('input[data-line-index]')||[]){const index=Number(input.dataset.lineIndex);if(!(Number(draftCateringLines[index]?.qty)>0)||mixErrors370.has(index))markFieldError370(input,error[1])}}return error};\nconst mixQuantityBeforeFeedback370=setMixQuantity370;\nsetMixQuantity370=function(index,raw){const out=mixQuantityBeforeFeedback370.apply(this,arguments),input=document.querySelector(`#cateringRecipeRows input[data-line-index=\"${index}\"]`);if(out===false)markFieldError370(input,mixErrors370.get(index)||'Bitte die Menge prüfen.');else{clearFieldError370(input);if(!mixErrors370.size&&draftCateringLines.filter(l=>l.recipeId).every(l=>Number(l.qty)>0))clearFieldError370(document.getElementById('cateringRecipeRows'))}return out};\nconst mhdValidateBefore370=validateExistingMhdEditor;\nvalidateExistingMhdEditor=function(...args){const ok=mhdValidateBefore370.apply(this,args),block=document.getElementById('aeBatchBlock'),stockEl=document.getElementById('aeStock');if(ok){const feedback=document.getElementById('articleModal')?.querySelector('.formFeedback231');if(feedback?.dataset.source370==='mhd')feedback.remove();clearFieldError370(block);clearFieldError370(stockEl);for(const el of document.querySelectorAll('#aeBatchRows .fieldInvalid370'))clearFieldError370(el);return ok}\n const unit=document.getElementById('aeUnit')?.value||'Stück',stock=Number(stockEl?.value)||0,total=(articleBatchDraft||[]).reduce((n,b)=>n+(Number(b.qty)||0),0),diff=stock-total,invalid=(articleBatchDraft||[]).some(b=>!b.mhd||!(Number(b.qty)>0)||isWholeQuantityUnit(unit)&&!Number.isInteger(Number(b.qty))||typeof parseMhd147==='function'&&!parseMhd147(b.mhd));\n let message=invalid?'Bitte jede Charge mit gültigem Datum und positiver Menge ausfüllen oder die unvollständige Zeile entfernen.':`Bestand: ${formatQtySmart(stock,unit)}. Chargen: ${formatQtySmart(total,unit)}. ${diff>0?'Noch zuordnen: '+formatQtySmart(diff,unit)+'.':'Die Chargenmengen liegen um '+formatQtySmart(-diff,unit)+' über dem Bestand.'}`;\n markFieldError370(block,message,!mhdQuiet370);if(!invalid)markFieldError370(stockEl,message);else{clearFieldError370(stockEl);[...document.querySelectorAll('#aeBatchRows .batchRow')].forEach((row,i)=>{const b=articleBatchDraft[i];if(!b)return;const date=row.querySelector('.aeBatchDate'),qty=row.querySelector('.aeBatchQty');if(!b.mhd||typeof parseMhd147==='function'&&!parseMhd147(b.mhd))markFieldError370(date?.formAdapter231?.box||date,'Bitte ein gültiges MHD eintragen.');else clearFieldError370(date?.formAdapter231?.box||date);if(!(Number(b.qty)>0)||isWholeQuantityUnit(unit)&&!Number.isInteger(Number(b.qty)))markFieldError370(qty,'Bitte eine positive '+(isWholeQuantityUnit(unit)?'ganze ':'')+'Menge eintragen.');else clearFieldError370(qty)})}\n const feedback=document.getElementById('articleModal')?.querySelector('.formFeedback231');if(feedback){feedback.dataset.type='error';feedback.dataset.source370='mhd';feedback.textContent=message}return false;\n};\nif(typeof document!=='undefined'&&document.body){\n document.addEventListener('invalid',event=>{const el=event.target;if(el.matches?.('input,select,textarea'))markFieldError370(el,el.validationMessage||'Bitte diese Angabe prüfen.',true)},true);\n document.addEventListener('input',event=>{const el=event.target;if(el.id==='planEvent'&&el.value.trim()||el.id==='planDate'&&/^\\d{4}-\\d{2}-\\d{2}$/.test(el.value)&&el.value>=localTodayISO()&&el.validity?.valid!==false||el.id==='planGuests'&&Number.isInteger(Number(el.value))&&Number(el.value)>0)clearFieldError370(el);else if(el.getAttribute?.('aria-invalid')==='true'&&el.validity?.valid&&!el.closest?.('.money231,.expiry231,#aeBatchBlock')&&!['planEvent','planDate','planGuests','aeStock'].includes(el.id))clearFieldError370(el);\n if((el.id==='aeStock'||el.closest?.('#aeBatchBlock'))&&document.getElementById('aeBatchBlock')?.classList.contains('fieldInvalid370')){const originalFeedback=showFeedback;mhdQuiet370=true;try{showFeedback=()=>{};collectExistingMhdEditor();validateExistingMhdEditor()}finally{showFeedback=originalFeedback;mhdQuiet370=false}}\n });\n}\n"},{"src":"planning-groups371.js?v=386","code":"'use strict';\n// Group UI only: existing recipe/drink lines remain the saved source of quantities.\nfunction foodGroupsSummary371(){return mixGroups370().filter(g=>mixLines370(g).length).map(g=>`<section class=\"groupSummary371\"><button type=\"button\" class=\"soft\" onclick=\"mixSelectGroup370('${escJs(g.id)}')\">${esc(g.name)} · ${hubText362('Bearbeiten','Edit','Edytuj')}</button><p>${mixLines370(g).map(({line})=>esc((findRecipeById(line.recipeId)?.recipe.name||'')+' · '+formatQtySmart(line.qty,line.unit||recipePlanningUnit108(findRecipeById(line.recipeId)?.recipe)))).join('<br>')}</p></section>`).join('')}\nfunction addFoodGroup371(){if(!hasPermission('planning'))return;mixGroup370='__new__';renderCateringDraftRows();document.getElementById('mixGroup370')?.focus()}\nconst foodRenderBefore371=renderCateringDraftRows;\nrenderCateringDraftRows=function(...args){const out=foodRenderBefore371.apply(this,args),host=document.getElementById('cateringRecipeRows');if(host)host.insertAdjacentHTML('beforeend',`<button type=\"button\" class=\"soft\" onclick=\"addFoodGroup371()\">+ ${hubText362('Weitere Rezeptgruppe','Another recipe group','Kolejna grupa przepisów')}</button><div class=\"groupSummaries371\">${foodGroupsSummary371()}</div>`);return out};\nconst drinkGroupNames371=[['Bier','Beer','Piwo'],['Spritzer','Wine spritzer','Szprycer'],['Glühwein','Mulled wine','Grzane wino'],['Mineralwasser','Mineral water','Woda mineralna'],['Bio-Säfte','Organic juices','Soki bio'],['Weitere Getränke','Other drinks','Inne napoje'],['Säfte & Limonaden','Juices & soft drinks','Soki i lemoniady']];\nlet activeDrinkGroup371='',customDrinkGroups371=[],drinkTargets371=new Map();\nfunction drinkGroupLabel371(id){const n=Number(id);return /^\\d$/.test(id)&&drinkGroupNames371[n]?hubText362(...drinkGroupNames371[n]):id}\nfunction classifyDrink371(d){if(d.group371)return String(d.group371);const a=state.products?.[d.productCode]||d,s=String(a.name||d.name||'').toLowerCase();if(/glühwein|gluhwein|mulled|grzane/.test(s))return '2';if(/spritzer|szprycer/.test(s))return '1';if(/mineral|wasser|\\bwater\\b|\\bwoda\\b/.test(s))return '3';if(/\\bbier\\b|\\bbeer\\b|augustiner|pils|lagerbier|weizen|\\bpiwo\\b/.test(s))return '0';if(/saft|säfte|safte|juice|nektar|sok|sprudel|limonade|eistee/.test(s))return /\\bbio\\b|organic/.test(s)?'4':'6';return '5'}\nfunction drinkProducts371(){return Object.entries(state.products||{}).filter(([code,a])=>typeof isBeverageCategory386==='function'?isBeverageCategory386(a):/getränk|getrank|beverage|napoj/i.test((a.overviewGroup||'')+' '+(a.cat||''))).sort((a,b)=>String(a[1].name).localeCompare(String(b[1].name),'de'))}\nfunction drinkGroups371(){return [...new Set([...drinkGroupNames371.map((_,i)=>String(i)),...customDrinkGroups371,...draftDrinkLines.map(classifyDrink371)])]}\nfunction drinkEntries371(id){return draftDrinkLines.map((line,index)=>({line,index})).filter(x=>(x.line.name||x.line.productCode)&&classifyDrink371(x.line)===id)}\nfunction chooseDrinkGroup371(id){if(!drinkGroups371().includes(id))return false;activeDrinkGroup371=id;renderCateringDrinkRows();return true}\nfunction addDrinkGroup371(){if(!hasPermission('planning'))return;activeDrinkGroup371='';renderCateringDrinkRows();document.getElementById('drinkGroup371')?.focus()}\nfunction createDrinkGroup371(){if(!hasPermission('planning'))return false;const el=document.getElementById('customDrinkGroup371'),name=String(el?.value||'').trim();if(!name)return false;const existing=drinkGroups371().find(id=>drinkGroupLabel371(id).toLocaleLowerCase()===name.toLocaleLowerCase());activeDrinkGroup371=existing||name;if(!existing)customDrinkGroups371.push(name);renderCateringDrinkRows();return true}\nfunction toggleDrink371(code,checked){if(!hasPermission('planning')||!state.products?.[code])return false;if(checked&&!draftDrinkLines.some(d=>d.productCode===code)){const a=state.products[code];draftDrinkLines=draftDrinkLines.filter(d=>d.name||d.productCode);draftDrinkLines.push({name:a.name,productCode:code,qty:1,unit:a.unit||'Stück',price:Number(a.price)||0,supplier:a.supplier||'',group371:activeDrinkGroup371||classifyDrink371({productCode:code})})}else if(!checked)draftDrinkLines=draftDrinkLines.filter(d=>d.productCode!==code);renderCateringDrinkRows();updateCateringPreview();return true}\nfunction freeDrink371(){if(!hasPermission('planning'))return;draftDrinkLines.push({name:'',productCode:'',qty:1,unit:'Flasche',price:0,supplier:'',group371:activeDrinkGroup371||'5'});renderCateringDrinkRows();document.getElementById('drinkName371-'+(draftDrinkLines.length-1))?.focus()}\nfunction distributeDrinks371(){if(!hasPermission('planning'))return false;const entries=drinkEntries371(activeDrinkGroup371),units=[...new Set(entries.map(x=>x.line.unit))],total=Number(drinkTargets371.get(activeDrinkGroup371));if(!entries.length||units.length!==1||!Number.isInteger(total)||total<entries.length||!/^(stück|stk|flasche|flaschen|kiste|kisten|packung|dose|dosen)$/i.test(units[0]||'')){showFeedback(hubText362('Gleiche Zähleinheit und mindestens eine Einheit je Sorte wählen.','Choose matching count units and at least one unit per variant.','Wybierz tę samą jednostkę i co najmniej jedną na wariant.'),'warn');return false}const apply=()=>{entries.forEach((x,i)=>x.line.qty=Math.floor(total/entries.length)+(i<total%entries.length?1:0));renderCateringDrinkRows();updateCateringPreview()};askConfirm(hubText362('Mengen dieser Getränkegruppe gleichmäßig verteilen?','Distribute this drink group evenly?','Rozdzielić ilości w tej grupie?'),apply,hubText362('Verteilen','Distribute','Rozdziel'));return true}\nconst drinkRenderBefore371=renderCateringDrinkRows;\nrenderCateringDrinkRows=function(){const area=document.getElementById('cateringDrinkArea'),box=document.getElementById('cateringDrinkRows'),chk=document.getElementById('planDrinksEnabled');if(chk)chk.checked=!!draftDrinksEnabled;if(area)area.style.display=draftDrinksEnabled?'block':'none';if(!box)return;const group=activeDrinkGroup371,products=drinkProducts371().filter(([code])=>classifyDrink371({productCode:code})===group||draftDrinkLines.some(d=>d.productCode===code&&classifyDrink371(d)===group));box.innerHTML=`<div class=\"mixPicker370\"><label for=\"drinkGroup371\">${hubText362('Getränkegruppe','Drink group','Grupa napojów')}</label><select id=\"drinkGroup371\" onchange=\"chooseDrinkGroup371(this.value)\"><option value=\"\">${hubText362('Gruppe auswählen','Choose group','Wybierz grupę')}</option>${drinkGroups371().map(id=>`<option value=\"${esc(id)}\" ${id===group?'selected':''}>${esc(drinkGroupLabel371(id))}</option>`).join('')}</select></div><details class=\"customDrink371\"><summary>${hubText362('Eigene Gruppe','Custom group','Własna grupa')}</summary><label>${hubText362('Name','Name','Nazwa')}<input id=\"customDrinkGroup371\" maxlength=\"80\"></label><button type=\"button\" class=\"soft\" onclick=\"createDrinkGroup371()\">${hubText362('Übernehmen','Apply','Zastosuj')}</button></details>${group?`<section class=\"mixGroup370\"><h3>${esc(drinkGroupLabel371(group))}</h3>${products.map(([code,a])=>`<label class=\"drinkChoice371\"><input type=\"checkbox\" data-code371=\"${esc(code)}\" ${draftDrinkLines.some(d=>d.productCode===code)?'checked':''} onchange=\"toggleDrink371('${escJs(code)}',this.checked)\"><span>${esc(a.name)}</span></label>`).join('')}${!products.length?`<p class=\"muted\">${hubText362('Noch keine passenden Lagerartikel. Getränk manuell ergänzen.','No matching stock products yet. Add a drink manually.','Brak produktów. Dodaj napój ręcznie.')}</p>`:''}<button type=\"button\" class=\"soft\" onclick=\"freeDrink371()\">+ ${hubText362('Getränk manuell','Manual drink','Napój ręcznie')}</button><div class=\"mixTarget370\"><label>${hubText362('Gesamtmenge in gleicher Einheit','Total in matching units','Suma w tej samej jednostce')}<input type=\"number\" min=\"1\" step=\"1\" value=\"${esc(drinkTargets371.get(group)||'')}\" oninput=\"drinkTargets371.set('${escJs(group)}',this.value)\"></label><button type=\"button\" class=\"soft\" onclick=\"distributeDrinks371()\">${hubText362('Gleichmäßig verteilen','Distribute evenly','Rozdziel równo')}</button></div></section>`:''}<div class=\"drinkSelected371\">${draftDrinkLines.map((d,i)=>({d,i})).filter(({d})=>d.name||d.productCode||d.group371).map(({d,i})=>`<section class=\"groupSummary371\"><small>${esc(drinkGroupLabel371(classifyDrink371(d)))}</small><div class=\"drinkEdit371\"><label>${hubText362('Getränk','Drink','Napój')}<input id=\"drinkName371-${i}\" value=\"${esc(d.name||'')}\" oninput=\"setDrinkField(${i},'name',this.value)\"></label><label>${hubText362('Menge','Quantity','Ilość')}<input type=\"number\" ${quantityInputAttrs(d.unit||'Flasche',false)} value=\"${Number(d.qty)||0}\" oninput=\"setDrinkField(${i},'qty',this.value)\"></label><label>${hubText362('Einheit','Unit','Jednostka')}<select onchange=\"setDrinkField(${i},'unit',this.value)\">${[...new Set([d.unit||'Flasche','Flasche','Kiste','Stück','Liter'])].map(u=>`<option ${u===d.unit?'selected':''}>${esc(u)}</option>`).join('')}</select></label></div><details><summary>${hubText362('Preis & Zuordnung','Price & product','Cena i produkt')}</summary><label>${hubText362('Lagerartikel','Stock product','Produkt magazynowy')}<select onchange=\"setDrinkProduct(${i},this.value)\">${beverageProductOptions(d.productCode||'')}</select></label><label>EK netto<input inputmode=\"decimal\" value=\"${Number(d.price)||''}\" oninput=\"setDrinkField(${i},'price',this.value)\"></label><label>${hubText362('Lieferant','Supplier','Dostawca')}<input value=\"${esc(d.supplier||'')}\" oninput=\"setDrinkField(${i},'supplier',this.value)\"></label></details><button type=\"button\" class=\"soft\" onclick=\"removeCateringDrinkLine(${i})\">${hubText362('Entfernen','Remove','Usuń')}</button></section>`).join('')}</div><button type=\"button\" class=\"soft\" onclick=\"addDrinkGroup371()\">+ ${hubText362('Weitere Getränkegruppe','Another drink group','Kolejna grupa napojów')}</button>`};\nconst addDrinkBefore371=addCateringDrinkLine;\naddCateringDrinkLine=function(){if(!hasPermission('planning'))return;draftDrinksEnabled=true;freeDrink371()};\nconst newGroupsBefore371=newPlanningEntry;\nnewPlanningEntry=function(...args){activeDrinkGroup371='';customDrinkGroups371=[];drinkTargets371.clear();return newGroupsBefore371.apply(this,args)};\nconst editGroupsBefore371=editCateringPlan;\neditCateringPlan=function(...args){activeDrinkGroup371='';customDrinkGroups371=[];drinkTargets371.clear();return editGroupsBefore371.apply(this,args)};\nfunction beverageBrand371(a){return String(a?.manufacturer||a?.brand||'').trim()}\nfunction beverageCategory371(a){return /getränk|getrank|beverage|napoj/i.test((a.overviewGroup||'')+' '+(a.cat||''))}\nfunction brandStockSections371(pools){const sections=new Map();for(const p of pools){const keys=p.members.map(([,a])=>beverageCategory371(a)&&beverageBrand371(a)?classifyDrink371(a)+'\\u0000'+beverageBrand371(a).toLocaleLowerCase():'');const key=keys[0]&&keys.every(k=>k===keys[0])?keys[0]:'';if(!key){sections.set('single:'+sections.size,{pools:[p],label:''});continue}if(!sections.has(key)){const a=p.members[0][1];sections.set(key,{pools:[],label:drinkGroupLabel371(classifyDrink371(a))+' · '+beverageBrand371(a)})}sections.get(key).pools.push(p)}return [...sections.values()].sort((a,b)=>(a.label||a.pools[0].summary?.name||a.pools[0].members[0][1].name).localeCompare(b.label||b.pools[0].summary?.name||b.pools[0].members[0][1].name,'de'))}\nconst stockRowsBefore371=hubStockRows362;\nhubStockRows362=function(query=hubView362.stockQuery,storage=hubView362.storage,suppliedPools=null){const pools=(suppliedPools||(query?stockPoolsForSearch301(query):stockPools301())).filter(p=>!storage||p.members.some(([,a])=>a.storage===storage));if(!pools.length)return stockRowsBefore371(query,storage,[]);return brandStockSections371(pools).sort((a,b)=>typeof compareStockSections383==='function'?compareStockSections383(a,b,storage):0).map(s=>s.label&&s.pools.reduce((n,p)=>n+p.members.length,0)>1?`<details class=\"brandStock371\" ${query?'open':''}><summary><span aria-hidden=\"true\"></span><b>${esc(s.label)}</b><small>${s.pools.reduce((n,p)=>n+p.members.filter(([,a])=>!storage||a.storage===storage).length,0)} ${hubText362('Varianten','variants','wariantów')}</small></summary>${stockRowsBefore371(query,storage,s.pools)}</details>`:stockRowsBefore371(query,storage,s.pools)).join('')};\n// Group the existing checkbox nodes without replacing handlers or product IDs.\nconst drinkBrandsBefore371=renderCateringDrinkRows;\nrenderCateringDrinkRows=function(...args){const out=drinkBrandsBefore371.apply(this,args),box=document.getElementById('cateringDrinkRows');if(!box?.querySelectorAll)return out;const brands=new Map();for(const label of box.querySelectorAll('.drinkChoice371')){const code=label.querySelector('input')?.getAttribute('data-code371');const brand=beverageBrand371(state.products?.[code]);if(!brand)continue;const key=brand.toLocaleLowerCase();if(!brands.has(key)){const fold=document.createElement('details'),summary=document.createElement('summary');fold.className='drinkBrand371';fold.open=true;summary.textContent=brand;fold.appendChild(summary);label.before(fold);brands.set(key,fold)}brands.get(key).appendChild(label)}return out};\n"},{"src":"planning-inline372.js?v=377","code":"'use strict';\n// One editable row per product; saved planning lines remain authoritative.\nlet foodShown372=new Set(),drinkShown372=new Set(),groupOpen372=new Map();\nfunction rememberGroupOpen372(kind,id,open){groupOpen372.set(kind+':'+id,!!open)}\nfunction groupIsOpen372(kind,id){return groupOpen372.get(kind+':'+id)!==false}\nfunction chooseFood372(id){if(!hasPermission('planning')||!mixGroups370().some(g=>g.id===id))return false;foodShown372.add(id);mixGroup370=id;groupOpen372.set('food:'+id,true);renderCateringDraftRows();return true}\nfunction chooseDrink372(id){if(!hasPermission('planning')||!drinkGroups371().includes(id))return false;drinkShown372.add(id);activeDrinkGroup371=id;groupOpen372.set('drink:'+id,true);renderCateringDrinkRows();return true}\nmixSelectGroup370=chooseFood372;\nchooseDrinkGroup371=chooseDrink372;\naddFoodGroup371=function(){document.getElementById('foodAdd372')?.focus()};\naddDrinkGroup371=function(){document.getElementById('drinkAdd372')?.focus()};\n// Removing a planning selection never deletes recipe groups, products or saved events.\nfunction removePlanningGroup376(kind,id){\n if(!hasPermission('planning')||!['food','drink'].includes(kind))return false;\n const group=kind==='food'?mixGroups370().find(g=>g.id===id):null;\n if(kind==='food'&&!group||kind==='drink'&&!drinkGroups371().includes(id))return false;\n const name=group?.name||drinkGroupLabel371(id),recipeIds=new Set(group?.recipes.map(r=>r.id)||[]);\n const matches=line=>kind==='food'?recipeIds.has(line.recipeId):classifyDrink371(line)===id;\n const lines=kind==='food'?draftCateringLines:draftDrinkLines;\n const apply=()=>{\n  if(!hasPermission('planning'))return false;\n  if(kind==='food'){\n   const errors=new Map();let next=0;\n   draftCateringLines=draftCateringLines.filter((line,index)=>{if(matches(line))return false;if(mixErrors370.has(index))errors.set(next,mixErrors370.get(index));next++;return true});\n   mixErrors370=errors;foodShown372.delete(id);mixTargets370.delete(id);if(mixGroup370===id)mixGroup370='';\n  }else{draftDrinkLines=draftDrinkLines.filter(line=>!matches(line));drinkShown372.delete(id);drinkTargets371.delete(id);if(activeDrinkGroup371===id)activeDrinkGroup371=''}\n  groupOpen372.delete(kind+':'+id);for(const key of groupOpen372.keys())if(kind==='drink'&&key.startsWith('brand:'+id+'|'))groupOpen372.delete(key);\n  if(typeof invalidateApproval374==='function')invalidateApproval374();\n  if(kind==='food')renderCateringDraftRows();else renderCateringDrinkRows();\n  updateCateringPreview();document.getElementById(kind==='food'?'foodAdd372':'drinkAdd372')?.focus();return true;\n };\n if(lines.some(matches))askConfirm(hubText362('„'+name+'“ mit den gewählten Produkten und Mengen aus diesem Einsatz entfernen?','Remove “'+name+'” and its selected products and quantities from this event?','Usunąć „'+name+'” wraz z wybranymi produktami i ilościami z wydarzenia?'),apply,hubText362('Gruppe entfernen','Remove group','Usuń grupę'));else apply();\n return true;\n}\nfunction planningGroupRemove376(kind,id,name){const label=esc(hubText362('Gruppe entfernen: ','Remove group: ','Usuń grupę: ')+name);return `<button type=\"button\" class=\"soft planGroupRemove376\" aria-label=\"${label}\" title=\"${label}\" onclick=\"removePlanningGroup376('${escJs(kind)}','${escJs(id)}')\"><svg class=\"uiIcon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7M14 10v7\"/></svg></button>`}\nfunction quantityCellFood372(recipe){const entries=draftCateringLines.map((line,index)=>({line,index})).filter(x=>x.line.recipeId===recipe.id),unit=recipePlanningUnit108(recipe);if(!entries.length)return `<input type=\"number\" disabled aria-label=\"${esc(recipe.name+' '+unit)}\" placeholder=\"—\">`;return entries.map(({line,index})=>`<label class=\"inlineQuantity372\"><input id=\"mixQty372-${index}\" type=\"number\" min=\"0\" step=\"${mixCountUnit370(line.unit||unit)?1:'any'}\" value=\"${Number(line.qty)||0}\" data-line-index=\"${index}\" aria-label=\"${esc(recipe.name+' '+(line.unit||unit))}\" oninput=\"setMixQuantity370(${index},this.value)\"><small>${esc(line.unit||unit)}</small></label>`).join('')}\nfunction foodBlock372(group){return `<div class=\"planGroupWrap376\"><details class=\"planGroup372\" data-group372=\"food:${esc(group.id)}\" ${groupIsOpen372('food',group.id)?'open':''} ontoggle=\"rememberGroupOpen372('food','${escJs(group.id)}',this.open)\"><summary>${esc(group.name)}</summary><div class=\"planGroupBody372\"><div class=\"planChoiceHead372\"><span aria-hidden=\"true\">✓</span><span>${hubText362('Produkt','Product','Produkt')}</span><span>${hubText362('Portionen / Menge','Portions / quantity','Porcje / ilość')}</span></div>${group.recipes.map(r=>`<div class=\"planChoiceRow372\"><input type=\"checkbox\" aria-label=\"${esc(r.name)}\" ${draftCateringLines.some(l=>l.recipeId===r.id)?'checked':''} onchange=\"toggleMixRecipe370('${escJs(r.id)}',this.checked)\"><span class=\"choiceName372\">${esc(r.name)}</span><div class=\"choiceQty372\">${quantityCellFood372(r)}</div></div>`).join('')}<div class=\"mixTarget370 planDistribution375\"><label>${hubText362('Geplante Gesamtmenge','Planned total','Planowana suma')}<input type=\"number\" min=\"1\" step=\"1\" value=\"${esc(mixTarget370(group))}\" oninput=\"mixTargets370.set('${escJs(group.id)}',this.value);updateMixStats370()\"></label><button type=\"button\" class=\"soft\" onclick=\"distributeMix370('${escJs(group.id)}')\">${hubText362('Menge verteilen','Distribute quantity','Rozdziel równo')}</button></div><details class=\"planExtras372\"><summary>${hubText362('Kosten ansehen','View costs','Zobacz koszty')}</summary><div id=\"mixStats372-${esc(group.id)}\">${mixStatsHtml370(group)}</div></details></div></details>${planningGroupRemove376('food',group.id,group.name)}</div>`}\nrenderCateringDraftRows=function(){const host=document.getElementById('cateringRecipeRows');if(!host)return;const groups=mixGroups370();for(const g of groups)if(mixLines370(g).length)foodShown372.add(g.id);host.innerHTML=groups.filter(g=>foodShown372.has(g.id)).map(foodBlock372).join('')+`<label class=\"groupAdd372\">+ ${hubText362('Rezeptgruppe hinzufügen','Add recipe group','Dodaj grupę przepisów')}<select id=\"foodAdd372\" onchange=\"chooseFood372(this.value)\"><option value=\"\">${hubText362('Gruppe wählen','Choose group','Wybierz grupę')}</option>${groups.filter(g=>!foodShown372.has(g.id)).map(g=>`<option value=\"${esc(g.id)}\">${esc(g.name)}</option>`).join('')}</select></label><p id=\"mixError370\" class=\"warnText\" role=\"alert\" hidden></p>`;const missing=draftCateringLines.map((line,index)=>({line,index})).filter(x=>x.line.recipeId&&!findRecipeById(x.line.recipeId));if(missing.length)host.insertAdjacentHTML('beforeend',`<section class=\"mixMissing370\">${missing.map(({line,index})=>`<p>${esc(line.name||line.recipeId)} <button type=\"button\" class=\"soft\" onclick=\"removeCateringRecipeLine(${index})\">${hubText362('Entfernen','Remove','Usuń')}</button></p>`).join('')}</section>`);quantitySummary104();updateMixStats370()};\nupdateMixStats370=function(){for(const g of mixGroups370()){const box=document.getElementById('mixStats372-'+g.id);if(box)box.innerHTML=mixStatsHtml370(g)}const error=document.getElementById('mixError370');if(error){error.textContent=[...mixErrors370.values()][0]||'';error.hidden=!mixErrors370.size}};\nfunction toggleDrinkInline372(code,checked,group){activeDrinkGroup371=group;return toggleDrink371(code,checked)}\nfunction drinkRow372(code,product,entries,group){const selected=entries.length>0,name=product?.name||entries[0]?.line.name||'',first=entries[0],label=code?esc(name):`<input id=\"drinkName371-${first.index}\" value=\"${esc(first.line.name||'')}\" placeholder=\"${hubText362('Getränkename','Drink name','Nazwa napoju')}\" aria-label=\"${hubText362('Getränkename','Drink name','Nazwa napoju')}\" oninput=\"setDrinkField(${first.index},'name',this.value)\">`;return `<div class=\"planChoiceRow372\"><input type=\"checkbox\" aria-label=\"${esc(name||hubText362('Getränk','Drink','Napój'))}\" ${selected?'checked':''} onchange=\"${code?`toggleDrinkInline372('${escJs(code)}',this.checked,'${escJs(group)}')`:`removeCateringDrinkLine(${first.index})`}\"><div class=\"choiceName372\">${label}</div><div class=\"choiceQty372\">${selected?entries.map(({line,index})=>`<label class=\"inlineQuantity372\"><input type=\"number\" ${quantityInputAttrs(line.unit||'Flasche',false)} value=\"${Number(line.qty)||0}\" aria-label=\"${esc(name+' '+line.unit)}\" oninput=\"setDrinkField(${index},'qty',this.value)\"><small>${esc(line.unit||'Flasche')}</small></label>`).join(''):`<input type=\"number\" disabled placeholder=\"—\" aria-label=\"${esc(name+' '+(product?.unit||''))}\">`}</div>${selected?`<details class=\"inlineDrinkExtras372\"><summary>${hubText362('Einheit & Preis','Unit & price','Jednostka i cena')}</summary>${entries.map(({line,index})=>`<label>${hubText362('Einheit','Unit','Jednostka')}<select onchange=\"setDrinkField(${index},'unit',this.value)\">${[...new Set([line.unit||'Flasche','Flasche','Kiste','Stück','Liter'])].map(u=>`<option ${u===line.unit?'selected':''}>${esc(u)}</option>`).join('')}</select></label><label>${hubText362('EK netto','Net purchase price','Cena zakupu netto')}<input inputmode=\"decimal\" value=\"${Number(line.price)||''}\" oninput=\"setDrinkField(${index},'price',this.value)\"></label><label>${hubText362('Lieferant','Supplier','Dostawca')}<input value=\"${esc(line.supplier||'')}\" oninput=\"setDrinkField(${index},'supplier',this.value)\"></label>`).join('')}</details>`:''}</div>`}\nfunction drinkBlock372(id){const byBrand=new Map(),products=drinkProducts371().filter(([code])=>{const selected=draftDrinkLines.find(d=>d.productCode===code);return (selected?classifyDrink371(selected):classifyDrink371({productCode:code}))===id});for(const [code,a] of products){const brand=beverageBrand371(a),entries=draftDrinkLines.map((line,index)=>({line,index})).filter(x=>x.line.productCode===code);if(!byBrand.has(brand))byBrand.set(brand,[]);byBrand.get(brand).push(drinkRow372(code,a,entries,id))}const free=draftDrinkLines.map((line,index)=>({line,index})).filter(x=>classifyDrink371(x.line)===id&&(!x.line.productCode||!state.products?.[x.line.productCode])).map(entry=>drinkRow372('',null,[entry],id)).join('');return `<div class=\"planGroupWrap376\"><details class=\"planGroup372\" data-group372=\"drink:${esc(id)}\" ${groupIsOpen372('drink',id)?'open':''} ontoggle=\"rememberGroupOpen372('drink','${escJs(id)}',this.open)\"><summary>${esc(drinkGroupLabel371(id))}</summary><div class=\"planGroupBody372\"><div class=\"planChoiceHead372\"><span aria-hidden=\"true\">✓</span><span>${hubText362('Produkt','Product','Produkt')}</span><span>${hubText362('Menge','Quantity','Ilość')}</span></div>${[...byBrand].map(([brand,rows])=>brand?`<details class=\"inlineBrand372\" ${groupIsOpen372('brand',id+'|'+brand)?'open':''} ontoggle=\"rememberGroupOpen372('brand','${escJs(id+'|'+brand)}',this.open)\"><summary>${esc(brand)}</summary>${rows.join('')}</details>`:rows.join('')).join('')}${free}${!products.length&&!free?`<p class=\"muted\">${hubText362('Noch keine passenden Lagerartikel.','No matching stock products yet.','Brak pasujących produktów.')}</p>`:''}<button type=\"button\" class=\"soft\" onclick=\"activeDrinkGroup371='${escJs(id)}';freeDrink371()\">+ ${hubText362('Getränk manuell','Manual drink','Napój ręcznie')}</button><div class=\"mixTarget370 planDistribution375\"><label>${hubText362('Gesamtmenge in gleicher Einheit','Total in matching units','Suma w tej samej jednostce')}<input type=\"number\" min=\"1\" step=\"1\" value=\"${esc(drinkTargets371.get(id)||'')}\" oninput=\"drinkTargets371.set('${escJs(id)}',this.value)\"></label><button type=\"button\" class=\"soft\" onclick=\"activeDrinkGroup371='${escJs(id)}';distributeDrinks371()\">${hubText362('Menge verteilen','Distribute quantity','Rozdziel równo')}</button></div></div></details>${planningGroupRemove376('drink',id,drinkGroupLabel371(id))}</div>`}\nrenderCateringDrinkRows=function(){const box=document.getElementById('cateringDrinkRows'),area=document.getElementById('cateringDrinkArea'),chk=document.getElementById('planDrinksEnabled');if(chk)chk.checked=!!draftDrinksEnabled;if(area)area.style.display=draftDrinksEnabled?'block':'none';if(!box)return;for(const d of draftDrinkLines)if(d.name||d.productCode||d.group371)drinkShown372.add(classifyDrink371(d));if(activeDrinkGroup371)drinkShown372.add(activeDrinkGroup371);box.innerHTML=drinkGroups371().filter(id=>drinkShown372.has(id)).map(drinkBlock372).join('')+`<label class=\"groupAdd372\">+ ${hubText362('Getränkegruppe hinzufügen','Add drink group','Dodaj grupę napojów')}<select id=\"drinkAdd372\" onchange=\"chooseDrink372(this.value)\"><option value=\"\">${hubText362('Gruppe wählen','Choose group','Wybierz grupę')}</option>${drinkGroups371().filter(id=>!drinkShown372.has(id)).map(id=>`<option value=\"${esc(id)}\">${esc(drinkGroupLabel371(id))}</option>`).join('')}</select></label><details class=\"customDrink371\"><summary>${hubText362('Eigene Gruppe anlegen','Create custom group','Utwórz własną grupę')}</summary><label>${hubText362('Name','Name','Nazwa')}<input id=\"customDrinkGroup371\" maxlength=\"80\"></label><button type=\"button\" class=\"soft\" onclick=\"createDrinkGroup371()\">${hubText362('Übernehmen','Apply','Zastosuj')}</button></details>`};\naddCateringDrinkLine=function(){if(!hasPermission('planning'))return;draftDrinksEnabled=true;if(!activeDrinkGroup371)activeDrinkGroup371='5';freeDrink371()};\nfunction resetInlineGroups372(){foodShown372.clear();drinkShown372.clear();groupOpen372.clear()}\nconst newInlineBefore372=newPlanningEntry;\nnewPlanningEntry=function(...args){resetInlineGroups372();return newInlineBefore372.apply(this,args)};\nconst editInlineBefore372=editCateringPlan;\neditCateringPlan=function(...args){resetInlineGroups372();return editInlineBefore372.apply(this,args)};\n"},{"src":"event-availability374.js?v=407&release=411","code":"'use strict';\n// Read-only lot allocation. No batch or inventory is reserved/booked in state.\nlet shoppingChoices374={};\nfunction assertProductionAvailable407(c){if(!validDate374(c.date))throw Error('Einsatzdatum vor der Produktion prüfen.');const plan=availabilityPlan374(c).target;if(!plan||plan.checks?.length||plan.free?.length)throw Error('Zutaten und verfügbaren Bestand vor der Produktion prüfen.');const missing=plan.rows.find(r=>r.qty>1e-8);if(missing)throw Error(missing.unknown>1e-8?'MHD oder Chargenbestand für '+missing.name+' vor der Produktion prüfen.':'Für '+missing.name+' fehlt verfügbarer Bestand zum Einsatztermin.');}\nfunction validDate374(s){return /^\\d{4}-\\d{2}-\\d{2}$/.test(String(s||''))&&!Number.isNaN(Date.parse(s+'T00:00:00Z'))&&new Date(s+'T00:00:00Z').toISOString().slice(0,10)===s}\nfunction openEvent374(c){return !c.workflow?.closed&&!/abgeschlossen|abgesagt|storniert|cancelled|closed/i.test(c.status||'')}\nfunction datedLots374(code,a,factor){const stock=Math.max(0,Number(a.stock)||0),tracked=a.mhdPolicy144?a.mhdPolicy144!=='none':!!a.mhd;if(!tracked)return [{code,qty:stock*factor,left:stock*factor,date:null,unknown:false}];const batches=(state.batches?.[code]||[]).filter(b=>Number(b.qty)>0),sum=batches.reduce((n,b)=>n+Number(b.qty),0);if(sum>stock+1e-7)return [{code,qty:stock*factor,left:stock*factor,date:null,unknown:true,reason:'Chargenbestand prüfen'}];const lots=batches.map(b=>({code,qty:Number(b.qty)*factor,left:Number(b.qty)*factor,date:validDate374(b.mhd)?b.mhd:null,unknown:!validDate374(b.mhd)}));if(stock>sum+1e-7)lots.push({code,qty:(stock-sum)*factor,left:(stock-sum)*factor,date:null,unknown:true});return lots}\nfunction usableLots374(pool,date){const today=localTodayISO(),day=validDate374(date)&&date>today?date:today;return validDate374(date)?pool.lots.filter(l=>!l.unknown&&(!l.date||l.date>=day)):[]}\nfunction allocatePool374(pool,date,need){const eligible=usableLots374(pool,date).sort((a,b)=>(a.date||'9999').localeCompare(b.date||'9999')||a.code.localeCompare(b.code)),available=eligible.reduce((n,l)=>n+l.left,0);let left=need;for(const l of eligible){const take=Math.min(l.left,left);l.left-=take;left-=take;if(left<=1e-8)break}const today=localTodayISO(),expired=pool.lots.filter(l=>l.date&&l.date<today).reduce((n,l)=>n+l.qty,0),tooEarly=pool.lots.filter(l=>l.date&&l.date>=today&&validDate374(date)&&l.date<date).reduce((n,l)=>n+l.qty,0),unknown=pool.lots.filter(l=>l.unknown).reduce((n,l)=>n+l.qty,0);return {available,allocated:need-left,qty:Math.max(0,left),expired,tooEarly,unknown,held:pool.lots.reduce((n,l)=>n+l.qty-l.left,0)-(need-left)}}\nfunction remainingDrinks374(c,details){const loaded=new Map();for(const r of c.stockFlow?.cargo||[])if(r.kind==='article'&&r.location==='Trude')loaded.set(r.code,(loaded.get(r.code)||0)+(convertUnit(Number(r.qty)||0,r.unit,state.products[r.code]?.unit)||0));return {...details,needs:(details.needs||[]).map(n=>{const amount=convertUnit(Number(n.need),n.unit,state.products[n.productCode]?.unit);if(amount===null||!(amount>0))return n;const used=Math.min(amount,loaded.get(n.productCode)||0);loaded.set(n.productCode,Math.max(0,(loaded.get(n.productCode)||0)-used));return {...n,need:Number(n.need)*(amount-used)/amount}}).filter(n=>n.need>1e-8)}}\n// Round only procurement; recipe consumption and FEFO allocation stay exact.\nfunction purchaseQuantity379(row,quantity=row.qty){\n const qty=Math.max(0,Number(quantity)||0),unit=row.unit;\n if(qty<=1e-8)return {qty:0,unit,covered:0};\n if(isCountLikeUnit(unit)){const count=Math.ceil(qty-1e-9);return {qty:count,unit,covered:count}}\n const mix=typeof purchaseMix105==='function'?purchaseMix105(row.productCode,qty,unit):null;\n if(mix?.items?.length&&mix.total>=qty-1e-8&&mix.items.every(i=>(i.divisible380||Number.isInteger(i.count))&&i.count>0))return {qty:mix.total,unit,covered:mix.total,items:mix.items.map(i=>({code:i.code,name:i.name,count:i.count,size:i.size,supplier:i.supplier||'',divisible380:!!i.divisible380}))};\n const article=state.products?.[row.productCode],group=mainSummaryForProduct(row.productCode);\n if(article&&isCountLikeUnit(article.unit)&&!(group?.members?.length>1)){\n  const bridge=articlePhysicalBridge(row.productCode,unit);\n  if(bridge?.size>0&&!bridge.ambiguous){const count=Math.ceil(qty/bridge.size-1e-9);return {qty:count,unit:article.unit,covered:count*bridge.size}}\n }\n return {qty,unit,covered:qty};\n}\nfunction purchaseText379(row){const buy=row.purchase379||purchaseQuantity379(row);return buy.items?.length?buy.items.map(i=>`${i.count} × ${i.name}`).join(' + '):formatQtySmart(buy.qty,buy.unit)}\nfunction availabilityPlan374(target=null){\n const pools=new Map(),byCode=new Map(),events=[],unlinked=[],refill=[],soon=[],missingNow=[];\n for(const [code,a]of Object.entries(state.products||{})){const candidate=mainSummaryForProduct(code),group=candidate?.members?.length&&candidate.totalSafe!==false?candidate:null;let key=group?'group:'+group.id:'product:'+code,unit=group?.unit||a.unit,factor=group?(Number(a.mainFactor)||1):1;if(!group&&isCountLikeUnit(a.unit))for(const u of ['kg','Liter']){const b=articlePhysicalBridge(code,u);if(b?.size>0&&!b.ambiguous){unit=u;factor=b.size;break}}if(!pools.has(key))pools.set(key,{key,productCode:code,name:group?.name||a.name,unit,initial:0,min:group?Number(group.min)||0:Math.max(0,Number(a.min)||0)*factor,qty:0,reasons:[],supplier:purchasingSupplierName(a,group),lots:[]});const pool=pools.get(key);pool.initial+=Math.max(0,Number(a.stock)||0)*factor;pool.lots.push(...datedLots374(code,a,factor));byCode.set(code,{pool,factor})}\n const saved=(state.planning?.caterings||[]).filter(c=>openEvent374(c)&&c.confirmed374===true&&(!target||c.id!==target.id)),sort=(a,b)=>(a.date||'9999').localeCompare(b.date||'9999')||String(a.id).localeCompare(String(b.id));let plans=saved.sort(sort);if(target&&openEvent374(target))plans=target.confirmed374?[...plans,target].sort(sort):[...plans,target];let targetEvent=null;\n for(const c of plans){const food=remainingEventNeed(c,cateringNeedDetails(c)),drink=remainingDrinks374(c,drinkNeedDetails(c)),needs=new Map(),free=[...(food.free||[]),...(drink.free||[])],checks=[...(food.errors||[])],rows=[];if(!validDate374(c.date))checks.push(hubText362('Einsatzdatum fehlt oder ist ungültig.','Event date is missing or invalid.','Brak poprawnej daty wydarzenia.'));for(const n of [...(food.needs||[]),...(drink.needs||[])]){const a=state.products[n.productCode],entry=byCode.get(n.productCode);if(!a||!entry)continue;let base=convertUnit(Number(n.need),n.unit,a.unit);if(base===null){const b=articlePhysicalBridge(n.productCode,n.unit);if(b?.size>0&&!b.ambiguous)base=Number(n.need)/b.size}if(base===null||!Number.isFinite(base)){free.push({name:n.name,qty:n.need,unit:n.unit,reason:'Einheit prüfen'});continue}needs.set(entry.pool.key,(needs.get(entry.pool.key)||0)+Math.max(0,base)*entry.factor)}\n for(const [key,need]of needs){const pool=pools.get(key),d=allocatePool374(pool,c.date,need),selected=c.shopping374?.[key]===true;rows.push({key,productCode:pool.productCode,name:pool.name,supplier:pool.supplier,unit:pool.unit,need,initial:pool.initial,...d,selected,purchase379:purchaseQuantity379(pool,d.qty)});if(c.confirmed374===true&&selected&&d.qty>1e-8){pool.qty+=d.qty;pool.reasons.push(`${c.event||'Einsatz'}: ${formatQtySmart(d.qty,pool.unit)}`)}}const item={id:c.id,name:c.event||'Einsatz',type:c.type||'Catering',date:c.date,rows,free,checks};events.push(item);if(target&&c===target)targetEvent=item;for(const f of free)unlinked.push({...f,event:c.event||'Einsatz'})}\n const lastDate=plans.filter(c=>c.confirmed374===true&&validDate374(c.date)).map(c=>c.date).sort().at(-1)||localTodayISO();\n for(const pool of pools.values()){pool.remaining=usableLots374(pool,lastDate).reduce((n,l)=>n+l.left,0);const current=pool.lots.filter(l=>!l.unknown&&(!l.date||l.date>=localTodayISO())).reduce((n,l)=>n+l.qty,0),now=Math.max(0,pool.min-current);if(now>1e-8||current<=0)missingNow.push({...pool,qty:now,targetKnown:pool.min>0});const qty=Math.max(0,pool.min-pool.remaining);if(qty>1e-8){refill.push({...pool,qty});pool.qty+=qty;pool.reasons.push('Vorrat nach den Einsätzen: '+formatQtySmart(qty,pool.unit))}else if(pool.min>0&&pool.remaining<=pool.min*1.25)soon.push({...pool,qty:0})}\n const total=[...pools.values()].filter(p=>p.qty>1e-8).map(({lots,...pool})=>{const onOrder=purchasePendingForPool(pool),exact=Math.max(0,pool.qty-onOrder),buy=purchaseQuantity379(pool,exact);return {...pool,required:pool.qty,onOrder,qty:buy.covered,purchase379:buy}});return {events,target:targetEvent,unlinked,refill,soon,missingNow,total:total.filter(p=>p.qty>1e-8),onOrder:total.filter(p=>p.onOrder>0)};\n}\npurchasingOverviewData=function(){return availabilityPlan374()};\nfunction availabilityHints374(row,c){const parts=[];if(row.expired>1e-8)parts.push(hubText362('Bereits abgelaufen: ','Already expired: ','Już po terminie: ')+formatQtySmart(row.expired,row.unit));if(row.tooEarly>1e-8)parts.push(hubText362('MHD vor Einsatzdatum: ','Best-before before event: ','Termin przed wydarzeniem: ')+formatQtySmart(row.tooEarly,row.unit));if(row.unknown>1e-8)parts.push(hubText362('MHD prüfen: ','Check best-before: ','Sprawdź termin: ')+formatQtySmart(row.unknown,row.unit));if(row.held>1e-8)parts.push(hubText362('Für andere fixierte Einsätze eingeplant: ','Allocated to other confirmed events: ','Zaplanowano na inne wydarzenia: ')+formatQtySmart(row.held,row.unit));return parts}\nfunction setShoppingChoice374(key,checked){if(!hasPermission('shopping'))return false;shoppingChoices374[key]=!!checked;return true}\nplanningNeedsHtml369=function(c){const plan=availabilityPlan374(c),event=plan.target,rows=event?.rows||[];return `<h3>${hubText362('Reicht es für den Einsatz?','Enough for this event?','Czy wystarczy na wydarzenie?')}</h3><p class=\"muted\">${esc(c.date?formatDate(c.date):hubText362('Datum fehlt','Date missing','Brak daty'))} · ${hubText362('Fixierte Einsätze und MHD berücksichtigt.','Confirmed events and best-before dates included.','Uwzględniono potwierdzone wydarzenia i terminy.')}</p><div class=\"hubTableScroll362\"><table class=\"planningNeedTable369 availabilityTable374\"><thead><tr>${['Artikel','Benötigt','Verfügbar zum Termin','Besorgen','Einkaufsliste'].map((s,i)=>`<th>${esc(hubText362(s,['Product','Needed','Available for date','Buy','Shopping list'][i],['Produkt','Potrzeba','Dostępne w terminie','Kupić','Lista zakupów'][i]))}</th>`).join('')}</tr></thead><tbody>${rows.map(r=>{const hints=availabilityHints374(r,c),warning=hints.length>0;return `<tr><th><div class=\"availabilityProduct375\"><span class=\"availabilityIconSlot375\">${warning?`<span class=\"availabilityWarning374\" aria-label=\"${hubText362('Bitte prüfen','Review','Sprawdź')}\">!</span>`:''}</span><div><span class=\"availabilityName375\">${esc(r.name)}</span>${warning?`<details class=\"availabilityHints374\"><summary>${hubText362('Hinweise','Details','Szczegóły')}</summary><div class=\"availabilityHintList375\">${hints.map(s=>`<p>${esc(s)}</p>`).join('')}<p>${hubText362('Gesamtbestand: ','Total stock: ','Stan całkowity: ')}${esc(formatQtySmart(r.initial,r.unit))}</p></div></details>`:''}</div></div></th><td>${esc(formatQtySmart(r.need,r.unit))}</td><td>${esc(formatQtySmart(r.available,r.unit))}</td><td class=\"${r.qty>1e-8?'availabilityMissing374':''}\">${esc(purchaseText379(r))}</td><td>${r.qty>1e-8?`<input type=\"checkbox\" aria-label=\"${esc(r.name+' '+hubText362('auf Einkaufsliste','to shopping list','na listę zakupów'))}\" ${c.shopping374?.[r.key]?'checked':''} ${hasPermission('shopping')?'':'disabled'} onchange=\"setShoppingChoice374('${escJs(r.key)}',this.checked)\">`:`<span class=\"availabilityNoPurchase378\">${hubText362('Nicht nötig','Not needed','Nie potrzeba')}</span>`}</td></tr>`}).join('')}</tbody></table></div>${!rows.length?`<p>${hubText362('Noch kein verknüpfter Bedarf.','No linked needs yet.','Brak powiązanego zapotrzebowania.')}</p>`:''}${event?.checks.length?`<div class=\"availabilityChecks375\">${event.checks.map(s=>`<p><span class=\"availabilityWarning374\" aria-hidden=\"true\">!</span><span>${esc(s)}</span></p>`).join('')}</div>`:''}${event?.free.length?`<p class=\"availabilityMissing374\">${hubText362('Zuordnung prüfen','Review product links','Sprawdź przypisania')}: ${event.free.map(n=>esc(n.name)).join(', ')}</p>`:''}<p class=\"muted\">${hubText362('Markierte Fehlmengen werden mit dem verbindlichen Speichern auf die Einkaufsliste übernommen. MHD ohne Datum bitte prüfen; diese Mengen sind nicht als verfügbar eingeplant.','Selected shortages enter the shopping list when you confirm and save. Check undated stock; it is not counted as available.','Wybrane braki trafią na listę po potwierdzeniu i zapisie. Sprawdź towar bez daty; nie jest liczony jako dostępny.')}</p>`};\nconst draftAvailabilityBefore374=cateringDraftObject;\ncateringDraftObject=function(...args){const draft=draftAvailabilityBefore374.apply(this,args),existing=(state.planning?.caterings||[]).find(c=>c.id===draft.id);return {...draft,...(existing?{stockFlow:existing.stockFlow,workflow:existing.workflow}:{}),confirmed374:!!document.getElementById('confirmEvent374')?.checked,shopping374:{...shoppingChoices374}}};\nconst newAvailabilityBefore374=newPlanningEntry;\nnewPlanningEntry=function(...args){shoppingChoices374={};const r=newAvailabilityBefore374.apply(this,args);const cb=document.getElementById('confirmEvent374');if(cb)cb.checked=false;return r};\nconst editAvailabilityBefore374=editCateringPlan;\neditCateringPlan=function(id,...args){shoppingChoices374={...(state.planning?.caterings||[]).find(c=>c.id===id)?.shopping374};const r=editAvailabilityBefore374.call(this,id,...args);const cb=document.getElementById('confirmEvent374');if(cb)cb.checked=false;return r};\nlet approvedReview374='';\nfunction reviewSignature374(){const c=cateringDraftObject(),x=cateringCalc(c);return JSON.stringify({c,rows:availabilityPlan374(c).target?.rows,cost:{total:x.total,revenue:x.revenue,costed:x.costedIngredients,ingredients:x.totalIngredients}})}\nfunction invalidateApproval374(){approvedReview374='';const cb=document.getElementById('confirmEvent374');if(cb)cb.checked=false;const save=document.querySelector('#planningButtons369 [onclick=\"saveCateringPlan()\"]');if(save)save.disabled=true}\nfunction confirmReview374(checked){const cb=document.getElementById('confirmEvent374');if(cb)cb.checked=!!checked;approvedReview374=checked?reviewSignature374():'';if(checked)renderFinalSummary374(cateringDraftObject());const save=document.querySelector('#planningButtons369 [onclick=\"saveCateringPlan()\"]');if(save)save.disabled=!checked||planningSaving369}\nconst shoppingChoiceBefore374=setShoppingChoice374;\nsetShoppingChoice374=function(...args){const ok=shoppingChoiceBefore374.apply(this,args);if(ok)invalidateApproval374();return ok};\nfunction costOverview374(c){const x=cateringCalc(c),drinkPricesKnown=!(c.drinksEnabled?c.drinks||[]:[]).some(d=>Number(d.qty)>0&&!(Number(state.products[d.productCode]?.price)||Number(d.price))),known=x.costedIngredients>=x.totalIngredients&&!x.free?.length&&drinkPricesKnown,open=hubText362('Offen','Unknown','Otwarte');return `<div class=\"reviewMetrics374\"><div><small>${hubText362('Netto-Auftragswert','Net order value','Wartość netto')}</small><strong>${Number(c.revenue)>0?moneyFmt(x.revenue):open}</strong></div><div><small>${known?hubText362('Erfasste Kosten','Recorded costs','Zapisane koszty'):hubText362('Bisher bekannte Kosten','Known costs so far','Dotychczas znane koszty')}</small><strong>${moneyFmt(x.total)}</strong></div><div><small>${hubText362('Nach erfassten Kosten übrig','After recorded costs','Po zapisanych kosztach')}</small><strong>${known&&Number(c.revenue)>0?moneyFmt(x.contribution):open}</strong></div></div>${!known?`<p class=\"availabilityMissing374\">${hubText362('Preise oder Zutatenzuordnungen noch unvollständig.','Prices or ingredient links are incomplete.','Niepełne ceny lub przypisania składników.')}</p>`:''}`}\nfunction affectedEvents374(c){if(!c.confirmed374)return '';const base=availabilityPlan374(),next=availabilityPlan374(c),before=new Map(base.events.filter(e=>e.id!==c.id).map(e=>[e.id,e]));const affected=next.events.filter(e=>e.id!==c.id&&e.rows.some(r=>r.qty>((before.get(e.id)?.rows||[]).find(b=>b.key===r.key)?.qty||0)+1e-7));return affected.length?`<section class=\"availabilityConflict374\"><h3>${hubText362('Andere fixierte Einsätze prüfen','Review other confirmed events','Sprawdź inne potwierdzone wydarzenia')}</h3>${affected.map(e=>`<p>${esc(e.name)} · ${esc(formatDate(e.date))}: ${e.rows.filter(r=>r.qty>1e-8).map(r=>esc(r.name+' '+formatQtySmart(r.qty,r.unit))).join(', ')}</p>`).join('')}</section>`:''}\nfunction finalSummary374(c){const x=cateringCalc(c);return `<h2>${hubText362('Alles prüfen, dann erfassen','Review, then confirm','Sprawdź i potwierdź')}</h2><section class=\"reviewSection374\"><h3>${esc(c.event)}</h3><dl><dt>${hubText362('Art','Type','Typ')}</dt><dd>${esc(c.type)}</dd><dt>${hubText362('Datum / Uhrzeit','Date / time','Data / godzina')}</dt><dd>${esc(c.date?formatDate(c.date):'—')}${c.readyTime?' · '+esc(c.readyTime):''}</dd><dt>${hubText362('Ort','Location','Miejsce')}</dt><dd>${esc(c.address||'—')}</dd><dt>${hubText362('Personen','People','Osoby')}</dt><dd>${esc(c.guests)}</dd>${c.offerFileName?`<dt>PDF</dt><dd>${esc(c.offerFileName)}</dd>`:''}</dl></section><section class=\"reviewSection374\"><h3>${hubText362('Speisen','Dishes','Dania')}</h3><ul>${(c.lines||[]).map(l=>{const r=findRecipeById(l.recipeId)?.recipe;return `<li><span>${esc(r?.name||l.name||hubText362('Rezept prüfen','Review recipe','Sprawdź przepis'))}</span><strong>${esc(formatQtySmart(l.qty,l.unit||(r?recipePlanningUnit108(r):'Portionen')))}</strong></li>`}).join('')||`<li>${hubText362('Keine Speisen gewählt','No dishes selected','Nie wybrano dań')}</li>`}</ul></section><section class=\"reviewSection374\"><h3>${hubText362('Getränke','Drinks','Napoje')}</h3><ul>${(c.drinksEnabled?c.drinks||[]:[]).map(d=>`<li><span>${esc(d.name||state.products[d.productCode]?.name||'')}</span><strong>${esc(formatQtySmart(d.qty,d.unit))}</strong></li>`).join('')||`<li>${hubText362('Keine Getränke geplant','No drinks planned','Nie zaplanowano napojów')}</li>`}</ul></section>${planningNeedsHtml369({...c,confirmed374:true})}${affectedEvents374({...c,confirmed374:true})}<section class=\"reviewSection374\"><h3>${hubText362('Kostenübersicht','Cost overview','Podsumowanie kosztów')}</h3>${costOverview374(c)}<details><summary>${hubText362('Kostenaufteilung ansehen','View cost breakdown','Zobacz podział kosztów')}</summary>${feeSummaryHtml(c,x)}${resourceBreakdownHtml(c,x)}</details></section><p class=\"muted\">${hubText362('Mit der Bestätigung wird der Einsatz fixiert und bei anderen Einsätzen berücksichtigt. Lagerbestand wird dabei nicht abgebucht.','Confirmation fixes the event for availability planning. No stock is booked.','Potwierdzenie uwzględnia wydarzenie w planowaniu. Stan nie jest pomniejszany.')}</p>`}\nfunction renderFinalSummary374(c){const needs=document.getElementById('planningNeeds369');if(needs)needs.innerHTML=planningNeedsHtml369(c);const costs=document.getElementById('planningCosts374');if(costs)costs.innerHTML=costOverview374(c);const summary=document.getElementById('planningSummary369');if(summary)summary.innerHTML=finalSummary374(c);const cb=document.getElementById('confirmEvent374');if(cb?.checked&&approvedReview374!==reviewSignature374())invalidateApproval374()}\nconst saveAvailabilityBefore374=saveCateringPlan;\nsaveCateringPlan=function(...args){if(planningSection==='entry'&&(planningStep369!==4||!document.getElementById('confirmEvent374')?.checked||!approvedReview374||approvedReview374!==reviewSignature374())){invalidateApproval374();planningStep369=4;paintPlanningGuide369();const msg=document.getElementById('planningError369');if(msg){msg.hidden=false;msg.textContent=hubText362('Bitte die Zusammenfassung prüfen und „Einsatz geprüft und verbindlich erfassen“ anhaken.','Review the summary and confirm this event.','Sprawdź podsumowanie i potwierdź wydarzenie.')}return false}return saveAvailabilityBefore374.apply(this,args)};\nif(typeof document!=='undefined'&&document.addEventListener)document.addEventListener('input',e=>{if(e.target?.id!=='confirmEvent374'&&e.target?.closest?.('#planningGuide369'))invalidateApproval374()},true);\n\nconst procurementBefore374=procurementFor;\nprocurementFor=function(c){if(!c?.date)return procurementBefore374(c).map(r=>({...r,qty:purchaseQuantity379(r).covered}));const result=availabilityPlan374(c).target;if(!result)return [];return [...result.rows.filter(r=>r.qty>1e-8).map(r=>({supplier:r.supplier||'Lieferant prüfen',name:r.name,qty:(r.purchase379||purchaseQuantity379(r)).covered,unit:r.unit,stock:r.available,need:r.need,productCode:r.productCode,linked:true})),...result.free.map(f=>({...f,supplier:f.supplier||'Lieferant prüfen',qty:purchaseQuantity379(f).covered,stock:null,need:f.qty,linked:false}))]};\nconst bottleneckBefore374=bottleneckHtml;\nbottleneckHtml=function(c,compact=false){if(!c?.date)return bottleneckBefore374(c,compact);const result=availabilityPlan374(c).target;if(!result)return '';const missing=result.rows.filter(r=>r.qty>1e-8),unknown=result.free.length||result.checks.length||result.rows.some(r=>r.unknown>1e-8),label=missing.length?hubText362('Ware fehlt','Stock missing','Brakuje towaru'):unknown?hubText362('Prüfung offen','Review pending','Wymaga sprawdzenia'):hubText362('Zum Termin ausreichend','Sufficient for date','Wystarczy w terminie');return `<section class=\"reviewSection374\"><h3>${hubText362('Verfügbarkeit zum Einsatzdatum','Availability for event date','Dostępność w terminie')}</h3><p class=\"${missing.length||unknown?'availabilityMissing374':''}\">${esc(label)}</p>${missing.length?`<ul>${missing.map(r=>`<li><span>${esc(r.name)}</span><strong>${esc(formatQtySmart(r.qty,r.unit))}</strong></li>`).join('')}</ul>`:''}<small>${hubText362('MHD und andere fixierte Einsätze berücksichtigt.','Includes best-before dates and other confirmed events.','Uwzględnia terminy i inne potwierdzone wydarzenia.')}</small></section>`};\n"},{"src":"supplier-price-ui380.js?v=382","code":"'use strict';\nlet priceDraft380=null,priceEngine380=null;\nfunction priceWeb380(){return webDesktop360()&&!globalThis.ZapasioAndroid&&!/ZapasioAndroid\\//.test(globalThis.navigator?.userAgent||'')}\nfunction priceAllowed380(){return priceWeb380()&&canSaveDocuments240()&&hasPermission('suppliers')}\nfunction priceSupplier380(name){return supplierProfileFor(name)?.name||name}\nfunction priceActive380(list){const today=localTodayISO();return !!(list?.confirmed380&&validCatalogDate380(list.validFrom)&&validCatalogDate380(list.validUntil380)&&list.validFrom<=today&&list.validUntil380>=today)}\nfunction priceButton380(index){const name=state.supplierProfiles?.[index]?.name;return priceAllowed380()&&state.planning?.supplierPriceLists?.[name]?.items?.length?`<button type=\"button\" class=\"soft\" onclick=\"openSupplierPrice380(${index})\">${hubText362('Preisliste','Price list','Cennik')}</button>`:''}\nconst supplierActionsBefore380=supplierOrderActions373;\nsupplierOrderActions373=function(s,index){return supplierActionsBefore380(s,index)+priceButton380(index)};\nsupplierPrices363=function(){if(!priceAllowed380())return false;return openSupplierPrice380()};\nasync function openSupplierPrice380(index=-1){\n if(!priceAllowed380())return false;priceEngine380=await import('./supplier-price380.mjs?v=380');if(!priceAllowed380())return false;\n const s=state.supplierProfiles?.[index];if(!s){openPricePanel382(hubText362('Preisliste hochladen','Upload price list','Wczytaj cennik'),`<label>${hubText362('Lieferant wählen','Choose supplier','Wybierz dostawcę')}<select onchange=\"openSupplierPrice380(Number(this.value))\"><option value=\"\">${hubText362('Bitte wählen','Choose','Wybierz')}</option>${(state.supplierProfiles||[]).map((a,i)=>({a,i})).sort((a,b)=>a.a.name.localeCompare(b.a.name,'de')).map(({a,i})=>`<option value=\"${i}\">${esc(a.name)}</option>`).join('')}</select></label>`);return true}\n const old=state.planning?.supplierPriceLists?.[s.name],snapshot=JSON.stringify(old||null);\n priceDraft380={supplier:s.name,index,snapshot,items:clone(old?.items||[]).map(i=>({...i,include:true,measure:i.measure||priceEngine380.pricePack380(i.pack),productCode:i.productCode||confirmedPriceLink380(s.name,i.sku)})),validFrom:old?.validFrom||'',validUntil380:old?.validUntil380||'',hash:old?.hash380||'',source:old?.source||'',net:old?.net380===true,editing:true,busy:false,query:'',offset:0,approved:false};paintPrice380();return true;\n}\nfunction confirmedPriceLink380(supplier,sku){return Object.entries(state.products||{}).find(([,a])=>(a.supplierArticles||[]).some(l=>priceSupplier380(l.supplier)===supplier&&l.confirmed&&String(l.sku)===String(sku)))?.[0]||''}\nfunction priceStatus380(text,error=false){const el=document.getElementById('priceStatus380');if(el){el.textContent=text;el.classList.toggle('priceError380',error)}}\nfunction paintPrice380(){\n const d=priceDraft380;if(!d)return;const known=d.items.length,active=priceActive380({confirmed380:!!d.hash,validFrom:d.validFrom,validUntil380:d.validUntil380});\n openPricePanel382(hubText362('Preisliste','Price list','Cennik')+' · '+d.supplier,`<div id=\"priceImport380\"><div id=\"priceDrop380\" class=\"priceDrop380\"><button type=\"button\" class=\"soft\" onclick=\"document.getElementById('priceFile380').click()\">${hubText362('PDF oder Angebotsmail auswählen','Choose PDF or offer email','Wybierz PDF lub wiadomość')}</button><input id=\"priceFile380\" type=\"file\" accept=\".pdf,.msg\" hidden onchange=\"loadSupplierPrice380(this.files[0]);this.value=''\"><small>${hubText362('Hierher ziehen · PDF oder Outlook-Datei bis 8 MB','Drop here · PDF or Outlook file up to 8 MB','Upuść tutaj · PDF lub Outlook do 8 MB')}</small></div><p id=\"priceStatus380\" role=\"status\">${known?esc(d.source)+' · '+known+' '+hubText362('Artikel','items','artykułów')+' · '+(active?hubText362('Preise verwendbar','Prices usable','Ceny aktywne'):hubText362('Gültigkeit prüfen','Check validity','Sprawdź ważność')):''}</p>${known?`<div class=\"priceDates380\"><label>${hubText362('Preisliste vom / gültig ab','List date / valid from','Data cennika / ważny od')}<input id=\"priceFrom380\" type=\"date\" value=\"${esc(d.validFrom)}\" oninput=\"priceDateChange380('validFrom',this.value)\"></label><label>${hubText362('Preise verwendbar bis','Use prices until','Używaj cen do')}<input id=\"priceUntil380\" type=\"date\" value=\"${esc(d.validUntil380)}\" oninput=\"priceDateChange380('validUntil380',this.value)\"></label></div><small>${hubText362('Tagespreise brauchen eine bestätigte Gültigkeit. Abgelaufene Listen werden nicht als aktuelle Preise verwendet.','Confirm daily-price validity. Expired lists are not used as current prices.','Potwierdź ważność cen dziennych. Nieaktualne cenniki nie są używane.')}</small><label class=\"priceCheck380\"><input id=\"priceNet380\" type=\"checkbox\" ${d.net?'checked':''} onchange=\"priceDraft380.net=this.checked;priceUnapprove380()\">${hubText362('Preise sind netto','Prices are net','Ceny netto')}</label><p class=\"tiny\">${hubText362('Nur zugeordnete Produkte werden verglichen. Unterschiedliche Qualitäten getrennt zuordnen.','Only linked products are compared. Keep different qualities separate.','Porównywane są tylko przypisane produkty. Rozdziel różne jakości.')}</p><input type=\"search\" value=\"${esc(d.query)}\" placeholder=\"${hubText362('Artikel oder Bestellnummer suchen','Search product or order number','Szukaj artykułu lub numeru')}\" oninput=\"priceDraft380.query=this.value;priceDraft380.offset=0;paintPriceRows380()\"><div id=\"priceRows380\"></div><div class=\"pricePager380\"><button type=\"button\" class=\"soft\" onclick=\"pricePage380(-1)\">←</button><span id=\"priceCount380\"></span><button type=\"button\" class=\"soft\" onclick=\"pricePage380(1)\">→</button></div><label class=\"priceCheck380\"><input id=\"priceApprove380\" type=\"checkbox\" ${d.approved?'checked':''} onchange=\"priceDraft380.approved=this.checked;document.getElementById('priceSave380').disabled=!this.checked\">${hubText362('Preise, Gebinde und Zuordnungen geprüft','Prices, packs and links reviewed','Ceny, opakowania i powiązania sprawdzone')}</label><div class=\"hubActions362\"><button type=\"button\" class=\"soft\" onclick=\"document.getElementById('priceDialog382').close()\">${hubText362('Schließen','Close','Zamknij')}</button><button id=\"priceSave380\" type=\"button\" class=\"primary\" ${d.approved?'':'disabled'} onclick=\"saveSupplierPrice380()\">${hubText362('Geprüfte Preisliste speichern','Save reviewed price list','Zapisz sprawdzony cennik')}</button></div>`:''}</div>`);\n const drop=document.getElementById('priceDrop380');for(const type of ['dragenter','dragover'])drop?.addEventListener(type,e=>{e.preventDefault();drop.classList.add('dragging')});drop?.addEventListener('dragleave',()=>drop.classList.remove('dragging'));drop?.addEventListener('drop',e=>{e.preventDefault();drop.classList.remove('dragging');const files=[...(e.dataTransfer?.files||[])];if(files.length===1)loadSupplierPrice380(files[0]);else priceStatus380(hubText362('Bitte eine Datei wählen.','Choose one file.','Wybierz jeden plik.'),true)});paintPriceRows380();\n}\nfunction priceUnapprove380(){if(!priceDraft380)return;priceDraft380.approved=false;const cb=document.getElementById('priceApprove380'),save=document.getElementById('priceSave380');if(cb)cb.checked=false;if(save)save.disabled=true}\nfunction priceDateChange380(key,value){priceDraft380[key]=value;if(validCatalogDate380(value)&&typeof clearFieldError370==='function')clearFieldError370(document.getElementById(key==='validFrom'?'priceFrom380':'priceUntil380'));priceUnapprove380()}\nfunction pricePage380(direction){priceDraft380.offset=Math.max(0,priceDraft380.offset+direction*50);paintPriceRows380()}\nfunction paintPriceRows380(){const d=priceDraft380,host=document.getElementById('priceRows380');if(!d||!host)return;const q=d.query.toLocaleLowerCase('de'),matches=d.items.map((r,i)=>({r,i})).filter(({r})=>(r.name+' '+r.sku).toLocaleLowerCase('de').includes(q));if(d.offset>=matches.length)d.offset=Math.max(0,Math.floor((matches.length-1)/50)*50);host.innerHTML=`<div class=\"priceTableScroll380\"><table class=\"priceTable380\"><thead><tr>${[['Übernehmen','Include','Uwzględnij'],['Bestellnr. / Artikel','Order no. / product','Numer / artykuł'],['Gebinde','Pack','Opakowanie'],['Netto €','Net €','Netto €'],['Zuordnung','Product link','Powiązanie']].map(w=>`<th>${esc(hubText362(...w))}</th>`).join('')}</tr></thead><tbody>${matches.slice(d.offset,d.offset+50).map(({r,i})=>`<tr class=\"${r.review?'priceReview380':''}\"><td><input type=\"checkbox\" aria-label=\"${esc(r.name)}\" ${r.include?'checked':''} onchange=\"priceRow380(${i},'include',this.checked)\"></td><th>${esc(r.sku)} · ${esc(r.name)}<small>${esc(r.country||'')}${r.page?' · '+hubText362('Seite ','Page ','Strona ')+r.page:''}${r.review?' · '+esc(r.review):''}</small></th><td><input aria-label=\"${esc(r.name+' '+hubText362('Gebinde','Pack','Opakowanie'))}\" value=\"${esc(r.pack)}\" onchange=\"priceRow380(${i},'pack',this.value)\"></td><td><input aria-label=\"${esc(r.name+' '+hubText362('Nettopreis','Net price','Cena netto'))}\" inputmode=\"decimal\" value=\"${esc(String(r.price).replace('.',','))}\" onchange=\"priceRow380(${i},'price',this.value)\"></td><td><button type=\"button\" class=\"soft\" onclick=\"openPriceMapping380(${i})\">${esc(state.products?.[r.productCode]?.name||hubText362('Zuordnen','Link product','Przypisz'))}</button></td></tr>`).join('')}</tbody></table></div>`;const count=document.getElementById('priceCount380');if(count)count.textContent=`${Math.min(matches.length,d.offset+1)}–${Math.min(matches.length,d.offset+50)} / ${matches.length}`}\nfunction priceRow380(i,key,value){const d=priceDraft380,r=d?.items[i];if(!r||d.busy)return;if(key==='include')r.include=!!value;if(key==='pack'){r.pack=String(value).slice(0,120);r.measure=priceEngine380?.pricePack380(r.pack)||null;r.review=r.measure?.error||''}if(key==='price'){const n=Number(String(value).replace(',','.'));if(!Number.isFinite(n)||n<=0){r.review=hubText362('Preis prüfen','Check price','Sprawdź cenę');r.include=false}else{r.price=n;r.review=r.measure?.error||''}}priceUnapprove380();paintPriceRows380()}\nasync function loadSupplierPrice380(file){const d=priceDraft380;if(!d||d.busy||!file||!priceAllowed380())return false;d.busy=true;priceStatus380(hubText362('Preisliste wird gelesen …','Reading price list …','Odczytywanie cennika …'));try{priceEngine380=await import('./supplier-price380.mjs?v=380');const result=await priceEngine380.readSupplierPriceFile380(file);if(priceDraft380!==d||!document.getElementById('priceDialog382')?.open)return false;if(!result.items.length)throw Error(result.warnings.join(' '));if(result.hash===d.hash)throw Error(hubText362('Diese Datei ist bereits geladen. Zuordnungen können direkt bearbeitet werden.','This file is already loaded. Edit product links directly.','Ten plik jest już wczytany. Możesz edytować powiązania.'));const previous=new Map(d.items.map(i=>[String(i.sku),i.productCode]));Object.assign(d,{items:result.items.map(r=>({...r,include:!r.review,productCode:previous.get(r.sku)||confirmedPriceLink380(d.supplier,r.sku)})),validFrom:result.validFrom,validUntil380:'',hash:result.hash,source:result.source,net:result.net,editing:false,query:'',offset:0,approved:false});paintPrice380();priceStatus380(`${result.items.length} ${hubText362('Artikel erkannt. Gültigkeit und Zuordnungen prüfen.','items found. Review validity and links.','artykułów. Sprawdź ważność i powiązania.')} ${result.items.filter(r=>r.review).length} ${hubText362('Gebinde brauchen Prüfung.','packs need review.','opakowań wymaga sprawdzenia.')}`);return true}catch(e){priceStatus380(e.message,true);return false}finally{d.busy=false}}\nfunction openPriceMapping380(index){const d=priceDraft380;if(!d||d.busy)return;d.mapping=index;const r=d.items[index];openPricePanel382(hubText362('Artikel zuordnen','Link product','Przypisz artykuł'),`<h3>${esc(r.name)} · ${esc(r.pack)}</h3><p class=\"tiny\">${hubText362('Nur dieselbe Lebensmittelqualität zuordnen. Mehrere Gebinde dürfen denselben Lagerartikel verwenden.','Link the same product quality only. Several packs can link to one stock product.','Przypisuj tylko tę samą jakość. Kilka opakowań może mieć jeden produkt magazynowy.')}</p><input id=\"priceProductQuery380\" type=\"search\" placeholder=\"${hubText362('Lagerartikel suchen','Search stock products','Szukaj produktu')}\" oninput=\"paintPriceProductMatches380(this.value)\"><div id=\"priceProductMatches380\"></div><div class=\"hubActions362\"><button type=\"button\" class=\"soft\" onclick=\"setPriceProduct380('')\">${hubText362('Nur im Preiskatalog behalten','Keep in catalog only','Tylko w cenniku')}</button><button type=\"button\" class=\"soft\" onclick=\"paintPrice380()\">${hubText362('Zurück','Back','Wstecz')}</button></div>`);paintPriceProductMatches380('');document.getElementById('priceProductQuery380')?.focus()}\nfunction paintPriceProductMatches380(query){const host=document.getElementById('priceProductMatches380');if(!host)return;const q=String(query).toLocaleLowerCase('de');host.innerHTML=Object.entries(state.products||{}).filter(([c,a])=>(a.name+' '+c).toLocaleLowerCase('de').includes(q)).sort((a,b)=>a[1].name.localeCompare(b[1].name,'de')).slice(0,20).map(([c,a])=>`<button type=\"button\" class=\"hubListRow362\" onclick=\"setPriceProduct380('${escJs(c)}')\"><span><b>${esc(a.name)}</b><small>${esc(c)} · ${esc(a.supplier||'')} · ${esc(a.unit)}</small></span></button>`).join('')}\nfunction setPriceProduct380(code){const d=priceDraft380,r=d?.items[d.mapping];if(!r||!priceAllowed380()||code&&!state.products[code])return false;r.productCode=code;priceUnapprove380();paintPrice380();return true}\nfunction pricePayload380(d){\n if(!priceAllowed380()||!d.approved)throw Error(hubText362('Preisliste zuerst prüfen und bestätigen.','Review and confirm the price list.','Sprawdź i potwierdź cennik.'));\n if(JSON.stringify(state.planning?.supplierPriceLists?.[d.supplier]||null)!==d.snapshot)throw Error(hubText362('Die Preisliste wurde inzwischen geändert. Bitte erneut öffnen.','The price list changed. Reopen it.','Cennik zmienił się. Otwórz ponownie.'));\n if(!state.supplierProfiles?.some(s=>s.name===d.supplier))throw Error(hubText362('Lieferant wurde geändert. Bitte erneut öffnen.','Supplier changed. Reopen it.','Dostawca się zmienił. Otwórz ponownie.'));\n if(!d.hash||!validCatalogDate380(d.validFrom)||!validCatalogDate380(d.validUntil380)||d.validUntil380<d.validFrom||!d.net)throw Error(hubText362('Datum, Gültigkeit bis und Nettopreise bestätigen.','Confirm dates, validity and net prices.','Potwierdź daty, ważność i ceny netto.'));\n const items=d.items.filter(r=>r.include);if(!items.length)throw Error(hubText362('Mindestens einen Artikel übernehmen.','Select at least one item.','Wybierz co najmniej jeden artykuł.'));\n for(const r of items)if(!(r.price>0)||!Number.isFinite(r.price)||!r.measure?.qty||!r.measure?.unit||r.review||r.productCode&&!state.products[r.productCode])throw Error(hubText362('Preis, Gebinde oder Zuordnung prüfen: ','Check price, pack or link: ','Sprawdź cenę, opakowanie lub powiązanie: ')+r.name);\n return {validFrom:d.validFrom,validUntil380:d.validUntil380,source:d.source,hash380:d.hash,net380:true,confirmed380:true,importedAt380:new Date().toISOString(),items:items.map(({include,review,...r})=>({...r,productCode:r.productCode||''}))};\n}\nfunction saveSupplierPrice380(){const d=priceDraft380;if(!d||d.busy)return false;try{pricePayload380(d)}catch(error){priceStatus380(error.message,true);for(const [id,value]of [['priceFrom380',d.validFrom],['priceUntil380',d.validUntil380]])if(!validCatalogDate380(value)||id==='priceUntil380'&&value<d.validFrom){const field=document.getElementById(id);if(typeof markFieldError370==='function')markFieldError370(field,error.message,true)}if(!d.net&&typeof markFieldError370==='function')markFieldError370(document.getElementById('priceNet380'),error.message);return false}d.busy=true;const result=operationTransaction(hubText362('Preisliste gespeichert','Price list saved','Cennik zapisany'),'suppliers',()=>{const list=pricePayload380(d);state.planning??={};if(Array.isArray(state.planning.supplierPriceLists))state.planning.supplierPriceLists={...state.planning.supplierPriceLists};state.planning.supplierPriceLists??={};state.planning.supplierPriceLists[d.supplier]=list});return afterOperation275(result,ok=>{d.busy=false;if(ok){priceDraft380=null;openSupplierPrice380(d.index);priceStatus380(hubText362('Preisliste gespeichert. Lager und bisherige Einkaufspreise bleiben erhalten.','Price list saved. Stock and historical purchase prices are preserved.','Cennik zapisany. Zapasy i wcześniejsze ceny zakupu zachowane.'))}return ok})}\nfunction catalogPriceOptions380(code,unit,supplier=''){\n const members=new Set(purchaseMembers105(code).map(([c])=>c)),options=[];\n for(const [name,list]of Object.entries(state.planning?.supplierPriceLists||{})){if(supplier&&priceSupplier380(name)!==priceSupplier380(supplier)||!priceActive380(list))continue;\n for(const item of list.items||[]){if(!members.has(item.productCode)||!(item.price>0)||!item.measure?.qty)continue;const a=state.products[item.productCode];if(priceSupplier380(a.supplier)!==priceSupplier380(name))continue;let size=convertUnit(item.measure.qty,item.measure.unit,unit);if(size===null&&normalizeUnit(a.unit)===normalizeUnit(unit)){const bridge=articlePhysicalBridge(item.productCode,item.measure.unit);if(bridge?.size>0&&!bridge.ambiguous)size=item.measure.qty/bridge.size}if(!(size>0))continue;options.push({code:item.productCode,name:item.pack,size,price:item.price,supplier:name,barcode:item.sku,catalogSku380:item.sku,divisible380:!!item.measure.divisible,validUntil380:list.validUntil380})}\n }return options;\n}\nconst purchaseOptionsBefore380=purchaseOptions105;\npurchaseOptions105=function(code,unit){const fresh=catalogPriceOptions380(code,unit),covered=new Set();for(const [name,list]of Object.entries(state.planning?.supplierPriceLists||{}))if(list.confirmed380)for(const item of list.items||[])if(item.productCode&&priceSupplier380(state.products[item.productCode]?.supplier)===priceSupplier380(name))covered.add(item.productCode);return [...purchaseOptionsBefore380(code,unit).filter(o=>!covered.has(o.code)),...fresh]};\nfunction validCatalogDate380(s){return /^\\d{4}-\\d{2}-\\d{2}$/.test(String(s))&&Number.isFinite(Date.parse(s+'T12:00:00Z'))&&new Date(s+'T12:00:00Z').toISOString().slice(0,10)===s}\nconst supplierMatchBefore380=supplierMatch;\nsupplierMatch=function(supplier,name,code=''){const list=state.planning?.supplierPriceLists?.[priceSupplier380(supplier)];if(list?.confirmed380){if(!priceActive380(list))return null;const item=list.items?.find(i=>i.productCode===code);return item?{...item,confirmed:true}:null}return supplierMatchBefore380(supplier,name,code)};\nconst orderItemsBefore380=orderItemsText;\norderItemsText=function(items,supplier){return items.map(row=>{const options=catalogPriceOptions380(row.productCode,row.unit,supplier);if(!options.length)return orderItemsBefore380([row],supplier);const mix=bestPackMix105(options,row.qty);if(!mix)return orderItemsBefore380([row],supplier);return mix.items.map(i=>`${formatQtySmart(i.count,'')} × ${i.name} ${row.name} [${i.catalogSku380}]`).join('\\n')}).join('\\n')};\n\n// A loose €/kg offer can top up whole packs instead of forcing another full bag.\nconst packMixBefore380=bestPackMix105;\nbestPackMix105=function(options,need){\n const loose=options.filter(o=>o.divisible380&&o.size>0&&o.price>0).sort((a,b)=>a.price/a.size-b.price/b.size),packs=options.filter(o=>!o.divisible380);if(!loose.length)return packMixBefore380(options,need);\n if(!(need>1e-8))return {items:[],total:0,cost:0,complete:true};\n const continuous=loose[0],rate=continuous.price/continuous.size,asLoose=quantity=>({...continuous,count:Number((quantity/continuous.size).toFixed(8))});let best={items:[asLoose(need)],total:need,cost:need*rate,complete:true};\n const full=packMixBefore380(packs,need);if(full?.cost!==null&&Number.isFinite(full?.cost)&&full.cost<best.cost-1e-8)best=full;\n const scale=1000,limit=Math.ceil(need*scale),eligible=packs.filter(o=>o.price>0&&o.size>0&&Math.abs(o.size*scale-Math.round(o.size*scale))<1e-6);\n if(limit>150000||limit*eligible.length>1500000){best.complete=options.every(o=>o.price>0&&Number.isFinite(o.price));return best;}\n const costs=new Float64Array(limit+1).fill(Infinity),prev=new Int32Array(limit+1).fill(-1);costs[0]=0;\n for(let q=1;q<=limit;q++)for(let i=0;i<eligible.length;i++){const step=Math.round(eligible[i].size*scale),p=q-step;if(p>=0&&costs[p]+eligible[i].price<costs[q]-1e-8){costs[q]=costs[p]+eligible[i].price;prev[q]=i}}\n let position=-1;for(let q=1;q<=limit;q++){const rest=Math.max(0,need-q/scale),cost=costs[q]+rest*rate;if(Number.isFinite(cost)&&cost<best.cost-1e-8){position=q;best={items:[],total:q/scale+rest,cost,complete:true,rest}}}\n if(position>=0){const counts=new Map();for(let q=position;q>0;){const i=prev[q];if(i<0)break;counts.set(i,(counts.get(i)||0)+1);q-=Math.round(eligible[i].size*scale)}best.items=[...counts].map(([i,count])=>({...eligible[i],count}));if(best.rest>1e-8)best.items.push(asLoose(best.rest));delete best.rest}best.complete=options.every(o=>o.price>0&&Number.isFinite(o.price));return best;\n};\n\nfunction openPricePanel382(title,html){let d=document.getElementById('priceDialog382');if(!d){d=document.createElement('dialog');d.id='priceDialog382';d.className='opsDialog92';document.body.appendChild(d)}d.innerHTML=`<header><h2>${esc(title)}</h2><button type=\"button\" class=\"soft\" aria-label=\"${hubText362('Schließen','Close','Zamknij')}\" onclick=\"document.getElementById('priceDialog382').close()\">×</button></header><div class=\"opsBody92\">${html}</div>`;if(!d.open)d.showModal()}\nfunction supplierPriceEdit382(index){if(!priceAllowed380())return '';const exists=!!state.planning?.supplierPriceLists?.[state.supplierProfiles?.[index]?.name]?.items?.length;return `<details class=\"supplierPriceEdit382\"><summary>${hubText362('Preisliste (optional)','Price list (optional)','Cennik (opcjonalnie)')}</summary>${index<0?`<p class=\"tiny\">${hubText362('Zuerst den Lieferanten speichern.','Save the supplier first.','Najpierw zapisz dostawcę.')}</p>`:`<button type=\"button\" class=\"soft\" onclick=\"openSupplierPrice380(${index})\">${exists?hubText362('Preisliste verwalten','Manage price list','Zarządzaj cennikiem'):hubText362('Preisliste hochladen','Upload price list','Wczytaj cennik')}</button>`}</details>`}\n"},{"src":"purchase-flow382.js?v=406","code":"'use strict';\n// Existing purchaseOrders remain the single source for pending procurement.\nlet orderDraft382=null,orderReceipt382=null,orderBusy382=false;\nconst ot382=(de,en,pl)=>hubText362(de,en,pl);\nfunction orderError382(message){const e=document.getElementById('orderError382');if(e){e.textContent=message;e.hidden=false}return false}\nfunction orderCandidates382(row,supplier){return purchaseMembers105(row.productCode).filter(([,a])=>supplierProfileFor(a.supplier)?.name===supplier||a.supplier===supplier)}\nfunction orderStockQty382(row,code){const a=state.products[code];if(!a)throw Error(ot382('Artikel nicht mehr vorhanden.','Product no longer exists.','Produkt już nie istnieje.'));let q=convertUnit(Number(row.qty),row.unit,a.unit);if(q===null){const b=articlePhysicalBridge(code,row.unit);if(b?.size>0&&!b.ambiguous)q=Number(row.qty)/b.size}if(q===null||!Number.isFinite(q)||q<=0)throw Error(ot382('Umrechnung fehlt. Packungsinhalt beim Artikel prüfen.','Conversion missing. Check product pack content.','Brak przelicznika. Sprawdź zawartość opakowania.'));return isCountLikeUnit(a.unit)?Math.ceil(q-1e-9):q}\nfunction orderNeedSignature382(group){return JSON.stringify(group.rows.map(r=>[r.key,r.productCode,r.qty,r.unit,r.signature]).sort((a,b)=>String(a[0]).localeCompare(String(b[0]))))}\nfunction openSupplierOrder382(supplier){if(!guardPermission('shopping',ot382('Bestellung prüfen','Review order','Sprawdź zamówienie'))||orderBusy382)return false;const g=shoppingChannels().orders.find(g=>g.name===supplier);if(!g?.rows.length)return openOrderTracker();orderDraft382={supplier,signature:orderNeedSignature382(g),id:'order-group-'+(globalThis.crypto?.randomUUID?.()||Date.now()+'-'+Math.random().toString(36).slice(2)),rows:g.rows.map(r=>{const candidates=orderCandidates382(r,supplier),code=candidates.length===1?candidates[0][0]:'';let qty='',error='';try{if(code)qty=orderStockQty382(r,code)}catch(e){error=e.message}return {source:clone(r),code,qty,selected:true,error}})};paintSupplierOrder382();return true}\nfunction paintSupplierOrder382(){const d=orderDraft382;if(!d)return;openOpsPanel(ot382('Bestellung prüfen','Review order','Sprawdź zamówienie')+' · '+d.supplier,`<div class=\"orderFlow382\"><p class=\"tiny\">${ot382('Erst bestätigen, nachdem du beim Lieferanten bestellt hast. Der Lagerbestand bleibt unverändert.','Confirm after placing the order with your supplier. Stock stays unchanged.','Potwierdź po złożeniu zamówienia u dostawcy. Stan magazynowy bez zmian.')}</p>${d.rows.map((r,i)=>`<div class=\"orderLine382\"><label class=\"orderPick382\"><input type=\"checkbox\" ${r.selected?'checked':''} onchange=\"orderDraft382.rows[${i}].selected=this.checked\"><b>${esc(r.source.name)}</b></label><small>${ot382('Benötigt','Needed','Potrzeba')}: ${esc(formatQtySmart(r.source.qty,r.source.unit))}</small><label>${ot382('Produktvariante','Product variant','Wariant produktu')}<select onchange=\"orderVariant382(${i},this.value)\"><option value=\"\">${ot382('Variante wählen','Choose variant','Wybierz wariant')}</option>${orderCandidates382(r.source,d.supplier).map(([code,a])=>`<option value=\"${esc(code)}\" ${code===r.code?'selected':''}>${esc(a.name)} · ${esc(a.unit)}</option>`).join('')}</select></label><label>${ot382('Tatsächlich bestellt','Actually ordered','Faktycznie zamówiono')} (${esc(state.products[r.code]?.unit||'')})<input inputmode=\"decimal\" value=\"${esc(r.qty)}\" oninput=\"orderDraft382.rows[${i}].qty=this.value\"></label>${r.error?`<p class=\"orderError382\">${esc(r.error)}</p>`:''}</div>`).join('')}<p id=\"orderError382\" class=\"orderError382\" role=\"alert\" hidden></p><label class=\"orderPick382\"><input id=\"orderConfirm382\" type=\"checkbox\" onchange=\"document.getElementById('orderSave382').disabled=!this.checked\">${ot382('Diese Produkte habe ich beim Lieferanten bestellt.','I placed this order with the supplier.','Te produkty zamówiłem u dostawcy.')}</label><div class=\"hubActions362\"><button type=\"button\" class=\"soft\" onclick=\"openOrderTracker()\">${ot382('Zurück','Back','Wstecz')}</button><button type=\"button\" id=\"orderSave382\" class=\"primary\" disabled onclick=\"saveSupplierOrder382()\">${ot382('Als bestellt erfassen','Record as ordered','Zapisz jako zamówione')}</button></div></div>`)}\nfunction orderVariant382(i,code){const r=orderDraft382?.rows[i];if(!r)return;r.code=code;r.error='';try{r.qty=code?orderStockQty382(r.source,code):''}catch(e){r.qty='';r.error=e.message}paintSupplierOrder382()}\nfunction orderPayload382(d){if(!d||!document.getElementById('orderConfirm382')?.checked)throw Error(ot382('Bestellung bitte bestätigen.','Please confirm the order.','Potwierdź zamówienie.'));const g=shoppingChannels().orders.find(g=>g.name===d.supplier);if(!g||orderNeedSignature382(g)!==d.signature)throw Error(ot382('Der Bedarf hat sich geändert. Zurückgehen und erneut prüfen.','Needs changed. Go back and review again.','Zapotrzebowanie zmieniło się. Wróć i sprawdź ponownie.'));if(state.purchaseOrders?.some(o=>o.group382===d.id))throw Error(ot382('Diese Bestellung wurde bereits erfasst.','This order is already recorded.','Zamówienie już zapisano.'));const rows=d.rows.filter(r=>r.selected).map(r=>{const a=state.products[r.code],q=costNumber(String(r.qty));if(!a||!orderCandidates382(r.source,d.supplier).some(([c])=>c===r.code)||!Number.isFinite(q)||q<=0||(isCountLikeUnit(a.unit)&&!Number.isInteger(q)))throw Error(ot382('Bei jedem ausgewählten Produkt Variante und positive Bestellmenge prüfen. Ganze Stückzahlen verwenden.','Check variant and positive order quantity for each selected product. Use whole pieces.','Sprawdź wariant i dodatnią ilość. Używaj całych sztuk.'));return {code:r.code,name:a.name,unit:a.unit,qty:q,received:0,supplier:d.supplier}});if(!rows.length)throw Error(ot382('Mindestens ein Produkt auswählen.','Select at least one product.','Wybierz co najmniej jeden produkt.'));return rows}\nfunction saveSupplierOrder382(){if(orderBusy382)return false;const d=orderDraft382;try{orderPayload382(d)}catch(e){return orderError382(e.message)}orderBusy382=true;return afterOperation275(operationTransaction(ot382('Bestellung erfasst','Order recorded','Zamówienie zapisane'),'shopping',()=>{const rows=orderPayload382(d);state.purchaseOrders??=[];rows.forEach((r,i)=>state.purchaseOrders.push({...r,id:d.id+'-'+i,group382:d.id,at:new Date().toISOString(),user:currentAccessUser().name}))}),ok=>{orderBusy382=false;if(ok){orderDraft382=null;openOrderTracker()}return ok})}\nfunction openOrderTracker(){if(!guardPermission('shopping',ot382('Bestellungen','Orders','Zamówienia'))||orderBusy382)return false;orderDraft382=null;orderReceipt382=null;const orders=(state.purchaseOrders||[]).slice().reverse(),pending=orders.filter(o=>purchaseOrderOpenQty(o)>0),finished=orders.filter(o=>purchaseOrderOpenQty(o)<=0),groups=shoppingChannels().orders;openOpsPanel(ot382('Bestellungen & Lieferungen','Orders & deliveries','Zamówienia i dostawy'),`<div class=\"orderFlow382\"><h3>${ot382('Noch zu bestellen','To order','Do zamówienia')}</h3>${groups.length?groups.map(g=>`<div class=\"orderSummary382\"><div><b>${esc(g.name)}</b><small>${g.rows.length} ${ot382('Artikel benötigt','products needed','potrzebnych produktów')}</small></div><button class=\"primary\" onclick=\"openSupplierOrder382('${escJs(g.name)}')\">${ot382('Bestellung prüfen','Review order','Sprawdź zamówienie')}</button></div>`).join(''):`<p class=\"tiny\">${ot382('Aktuell kein zusätzlicher Bestellbedarf.','No additional orders needed.','Brak dodatkowego zapotrzebowania.')}</p>`}<h3>${ot382('Offene Bestellungen','Open orders','Otwarte zamówienia')} · ${pending.length}</h3>${pending.map(orderCard382).join('')||`<p class=\"tiny\">${ot382('Noch keine Bestellung erfasst.','No orders recorded yet.','Nie zapisano jeszcze zamówień.')}</p>`}${finished.length?`<details><summary>${ot382('Erledigt / storniert','Completed / cancelled','Zakończone / anulowane')} · ${finished.length}</summary>${finished.map(orderCard382).join('')}</details>`:''}</div>`);return true}\nfunction orderCard382(o){const open=purchaseOrderOpenQty(o);return `<div class=\"orderSummary382\"><div><b>${esc(o.supplier)} · ${esc(o.name)}</b><small>${esc(purchaseOrderStatus(o))}${o.at?' · '+esc(formatDate(o.at.slice(0,10))):''}</small><small>${ot382('Bestellt','Ordered','Zamówiono')}: ${esc(formatQtySmart(o.qty,o.unit))} · ${ot382('Geliefert','Received','Dostarczono')}: ${esc(formatQtySmart(o.received||0,o.unit))} · ${ot382('Offen','Open','Pozostało')}: <b>${esc(formatQtySmart(open,o.unit))}</b></small></div>${open>0?`<button class=\"primary\" onclick=\"openOrderReceipt382('${escJs(o.id)}')\" ${hasPermission('stockAdd')?'':'disabled'}>${ot382('Lieferung erfassen','Receive delivery','Przyjmij dostawę')}</button><button class=\"soft\" onclick=\"cancelOrder382('${escJs(o.id)}')\">${ot382('Rest stornieren','Cancel remainder','Anuluj resztę')}</button>`:''}</div>`}\nconst renderOrdersBefore382=renderPurchasingOrders;\nrenderPurchasingOrders=function(){renderOrdersBefore382();const host=document.getElementById('purchasingOrders');if(!host)return;const groups=shoppingChannels().orders;host.innerHTML=`<div class=\"orderFlow382\"><div class=\"opsEntry92\"><button class=\"soft\" onclick=\"openOrderTracker()\">${ot382('Offene Bestellungen & Lieferungen','Open orders & deliveries','Otwarte zamówienia i dostawy')} · ${(state.purchaseOrders||[]).filter(o=>purchaseOrderOpenQty(o)>0).length}</button></div>${groups.map(g=>`<section class=\"orderSupplier382\"><h3>${esc(g.name)}</h3>${purchaseOverviewTable(g.rows)}<div class=\"hubActions362\">${supplierOrderActions373(g.profile,state.supplierProfiles.indexOf(g.profile))}<button class=\"soft\" onclick=\"openSupplierOrder382('${escJs(g.name)}')\">${ot382('Als bestellt erfassen','Record as ordered','Zapisz jako zamówione')}</button></div></section>`).join('')||`<p>${ot382('Aktuell kein zusätzlicher Bestellbedarf.','No additional orders needed.','Brak dodatkowego zapotrzebowania.')}</p>`}</div>`};\nfunction openOrderReceipt382(id){if(!guardPermission('stockAdd',ot382('Lieferung erfassen','Receive delivery','Przyjmij dostawę'))||orderBusy382)return false;const o=state.purchaseOrders?.find(o=>o.id===id),a=state.products[o?.code];if(!a||!(purchaseOrderOpenQty(o)>0))return openOrderTracker();orderReceipt382={id,snapshot:JSON.stringify(o),article:JSON.stringify(a),busy:false};const policy=effectiveMhd144(a);openOpsPanel(ot382('Lieferung erfassen','Receive delivery','Przyjmij dostawę')+' · '+o.supplier,`<div class=\"orderFlow382\"><h3>${esc(o.name)}</h3><p>${ot382('Noch offen','Still open','Pozostało')}: ${esc(formatQtySmart(purchaseOrderOpenQty(o),o.unit))}</p><div class=\"orderReceiptFields382\"><label>${ot382('Jetzt geliefert','Received now','Dostarczono teraz')} (${esc(o.unit)})<input id=\"receiptQty382\" inputmode=\"decimal\" value=\"${purchaseOrderOpenQty(o)}\"></label><label>${ot382('EK netto je','Net cost per','Cena netto za')} ${esc(o.unit)} (€)<input id=\"receiptPrice382\" inputmode=\"decimal\" value=\"${Number(a.price)||0}\"></label>${policy!=='none'?`<label>${policy==='required'?ot382('MHD erforderlich','Expiry required','Data wymagana'):ot382('MHD optional','Expiry optional','Data opcjonalna')}<input id=\"receiptMhd382\" type=\"date\" value=\"${esc(receiptMhd242(a)||'')}\"></label>${policy==='optional'?`<small>${ot382('Leer lassen: Ohne zugeordnetes MHD.','Leave empty: no assigned expiry date.','Puste: bez przypisanej daty.')}</small>`:''}`:''}<label class=\"orderPick382\"><input id=\"receiptCommission382\" type=\"checkbox\">${ot382('Kommissionsware','Consignment stock','Towar komisowy')}</label></div><p id=\"orderError382\" class=\"orderError382\" role=\"alert\" hidden></p><label class=\"orderPick382\"><input id=\"receiptConfirm382\" type=\"checkbox\" onchange=\"document.getElementById('receiptSave382').disabled=!this.checked\">${ot382('Lieferung geprüft, diese Menge ins Lager buchen.','Delivery checked, book this quantity into stock.','Dostawa sprawdzona, przyjmij tę ilość do magazynu.')}</label><div class=\"hubActions362\"><button class=\"soft\" onclick=\"openOrderTracker()\">${ot382('Zurück','Back','Wstecz')}</button><button id=\"receiptSave382\" class=\"primary\" disabled onclick=\"saveOrderReceipt382()\">${ot382('Lieferung einbuchen','Book delivery','Zaksięguj dostawę')}</button></div></div>`);return true}\nfunction validateOrderReceipt382(d,v){const o=state.purchaseOrders?.find(o=>o.id===d?.id),a=state.products[o?.code];if(!o||!a||JSON.stringify(o)!==d.snapshot||JSON.stringify(a)!==d.article)throw Error(ot382('Bestellung oder Artikel geändert. Lieferung erneut öffnen und prüfen.','Order or product changed. Reopen and review delivery.','Zamówienie lub produkt zmieniono. Otwórz ponownie.'));if(!v.confirmed||!Number.isFinite(v.qty)||v.qty<=0||v.qty>purchaseOrderOpenQty(o)+1e-8||(isCountLikeUnit(a.unit)&&!Number.isInteger(v.qty)))throw Error(ot382('Liefermenge prüfen und bestätigen. Teilmengen sind möglich, mehr als die offene Menge nicht.','Check and confirm delivery quantity. Partial quantities are allowed, overdelivery is not.','Sprawdź i potwierdź ilość. Częściowa dostawa dozwolona, nadmiar nie.'));if(!Number.isFinite(v.price)||v.price<0)throw Error(ot382('Nettopreis prüfen.','Check net price.','Sprawdź cenę netto.'));const policy=effectiveMhd144(a);if(policy==='review')throw Error(ot382('Zuerst die MHD-Regel beim Produkt festlegen.','Set the product expiry policy first.','Najpierw ustaw zasadę daty produktu.'));if(policy==='required'&&!v.mhd||v.mhd&&!validDate374(v.mhd))throw Error(ot382('Ein gültiges MHD angeben.','Enter a valid expiry date.','Podaj poprawną datę.'));return {o,a,policy}}\nfunction saveOrderReceipt382(){const d=orderReceipt382;if(!d||orderBusy382)return false;const get=id=>document.getElementById(id),v={qty:costNumber(get('receiptQty382').value),price:costNumber(get('receiptPrice382').value),mhd:get('receiptMhd382')?.value||'',commission:!!get('receiptCommission382')?.checked,confirmed:!!get('receiptConfirm382')?.checked};try{validateOrderReceipt382(d,v)}catch(e){return orderError382(e.message)}orderBusy382=true;return afterOperation275(operationTransaction(ot382('Lieferung eingebucht','Delivery booked','Dostawa zaksięgowana'),'stockAdd',()=>{const {o,a,policy}=validateOrderReceipt382(d,v);a.stock=(Number(a.stock)||0)+v.qty;if(v.commission)a.consignmentQty168=(Number(a.consignmentQty168)||0)+v.qty;a.price=v.price;a.supplier=o.supplier;o.received=(Number(o.received)||0)+v.qty;o.lastReceiptAt=new Date().toISOString();if(policy!=='none'&&v.mhd)addBatch(o.code,v.qty,v.mhd,{consignment168:v.commission});recordPurchasePrice(o.code,v.price,o.supplier,'Bestellung geliefert',{qty:v.qty});addMovement('in',o.code,v.qty,{supplier:o.supplier,unitCost:v.price,reason:'Bestellung '+o.id+(v.commission?' · Kommission':' · Eigener Bestand')})}),ok=>{orderBusy382=false;if(ok){orderReceipt382=null;openOrderTracker()}return ok})}\nfunction cancelOrder382(id){if(orderBusy382)return false;askConfirm(ot382('Offene Restmenge stornieren? Bereits gelieferte Mengen bleiben im Lager.','Cancel outstanding quantity? Received stock remains unchanged.','Anulować resztę? Dostarczony towar pozostanie w magazynie.'),()=>{if(orderBusy382)return;orderBusy382=true;afterOperation275(cancelPurchaseOrder(id),ok=>{orderBusy382=false;if(ok)openOrderTracker()})},ot382('Stornieren','Cancel order','Anuluj'),ot382('Restbestellung','Outstanding order','Reszta zamówienia'))}\n"},{"src":"stock-sort383.js?v=383","code":"'use strict';\nlet stockSort383='name-asc';\nfunction stockDate383(code){return (state.batches?.[code]||[]).filter(b=>Number(b.qty)>0&&validDate374(b.mhd)).map(b=>b.mhd).sort()[0]||null}\nfunction stockSortMetric383(members,name){const units=[...new Set(members.map(([,a])=>a.unit))],dates=members.map(([code])=>stockDate383(code)).filter(Boolean).sort();return {name,qty:units.length===1?members.reduce((n,[,a])=>n+(Number(a.stock)||0),0):null,date:dates[0]||null}}\nfunction compareStockMetric383(a,b){const byName=()=>a.name.localeCompare(b.name,'de',{numeric:true,sensitivity:'base'});if(stockSort383.startsWith('name'))return byName()*(stockSort383==='name-desc'?-1:1);const key=stockSort383.startsWith('stock')?'qty':'date',av=a[key],bv=b[key];if(av===null||bv===null)return av===bv?byName():av===null?1:-1;const delta=key==='qty'?av-bv:av.localeCompare(bv);return delta*(['stock-desc','expiry-desc'].includes(stockSort383)?-1:1)||byName()}\nfunction compareStockMembers383(a,b){return compareStockMetric383(stockSortMetric383([a],a[1].name),stockSortMetric383([b],b[1].name))}\nfunction compareStockPools383(a,b,storage=''){const metric=p=>{const m=stockSortMetric383(p.members.filter(([,a])=>!storage||a.storage===storage),p.summary?.name||p.members[0][1].name);if(!storage&&p.summary?.totalSafe&&Number.isFinite(p.summary.total))m.qty=p.summary.total;return m};return compareStockMetric383(metric(a),metric(b))}\nfunction compareStockSections383(a,b,storage=''){const metric=s=>stockSortMetric383(s.pools.flatMap(p=>p.members).filter(([,a])=>!storage||a.storage===storage),s.label||s.pools[0].summary?.name||s.pools[0].members[0][1].name);return compareStockMetric383(metric(a),metric(b))}\nfunction setStockSort383(value){if(!['name-asc','name-desc','stock-asc','stock-desc','expiry','expiry-desc'].includes(value))return;const open=[...document.querySelectorAll('.hubPool362[open],.brandStock371[open]')].map(n=>n.querySelector('summary b')?.textContent);stockSort383=value;render();if(typeof refreshHub362==='function')refreshHub362();document.querySelectorAll('.hubPool362,.brandStock371').forEach(n=>{if(open.includes(n.querySelector('summary b')?.textContent))n.open=true})}\nconst stockColumnsBefore383=stockColumns368;\nstockColumns368=function(){const labels={name:hubText362('Artikel','Product','Produkt'),stock:hubText362('Bestand','Stock','Stan'),expiry:hubText362('Nächstes MHD','Next best-before','Najbliższy termin')},choices={name:[['name-asc','A–Z','A–Z','A–Z'],['name-desc','Z–A','Z–A','Z–A']],stock:[['stock-desc','Viel → wenig','High → low','Malejąco'],['stock-asc','Wenig → viel','Low → high','Rosnąco']],expiry:[['expiry','Frühestes zuerst','Earliest first','Najwcześniejszy'],['expiry-desc','Spätestes zuerst','Latest first','Najpóźniejszy']]};const select=(key,mobile=false)=>`<select aria-label=\"${esc(hubText362('Sortieren nach ','Sort by ','Sortuj: ')+labels[key])}\" onchange=\"setStockSort383(this.value)\"><option value=\"\" disabled ${choices[key].some(([v])=>v===stockSort383)?'':'selected'}>${labels[key]} ▾</option>${choices[key].map(([v,...w])=>`<option value=\"${v}\" ${v===stockSort383?'selected':''}>${labels[key]} · ${hubText362(...w)}</option>`).join('')}</select>`;let columns=stockColumnsBefore383();for(const key of Object.keys(labels))columns=columns.replace(`<span>${labels[key]}</span>`,`<span class=\"stockColumnSort383\">${select(key)}</span>`);return `<div class=\"stockSortTools383\">${Object.keys(labels).map(k=>select(k,true)).join('')}</div>`+columns};\n"},{"src":"shopping-flow385.js?v=406","code":"'use strict';\nlet shoppingQuery385='',shoppingFreeDraft385=null;\nfunction shoppingExisting385(code){const ch=shoppingChannels(),members=typeof purchaseMembers105==='function'?new Set(purchaseMembers105(code).map(([c])=>c)):new Set([code]);for(const kind of ['pickup','orders'])for(const g of ch[kind]){const row=g.rows.find(r=>r.productCode===code||r.productCode&&!r.requested363&&members.has(r.productCode));if(row)return {kind,group:g,row}}return null}\nconst shoppingChooseBefore385=shoppingChooseProduct363;\nshoppingChooseProduct363=function(code){if(!guardPermission('shopping','Einkaufsliste'))return false;const found=shoppingExisting385(code);if(found)return shoppingOpenItem385(code);const r=shoppingChooseBefore385(code),body=document.querySelector('#opsDialog92 .opsBody92');body?.insertAdjacentHTML('afterbegin',`<button type=\"button\" class=\"soft\" onclick=\"shoppingAddProduct363()\">← ${hubText362('Zurück zur Suche','Back to search','Wróć do wyszukiwania')}</button>`);return r};\nshoppingAddProduct363=function(){if(!guardPermission('shopping','Einkaufsliste'))return false;shoppingFreeDraft385=null;openOpsPanel(hubText362('Artikel hinzufügen','Add item','Dodaj artykuł'),`<div class=\"shoppingAdd385\"><label>${hubText362('Was brauchst du?','What do you need?','Czego potrzebujesz?')}<input id=\"shoppingQuery385\" type=\"search\" autocomplete=\"off\" value=\"${esc(shoppingQuery385)}\" placeholder=\"${hubText362('Artikel oder Barcode eingeben','Enter item or barcode','Wpisz produkt lub kod')}\" oninput=\"shoppingQuery385=this.value;paintShoppingMatches385()\"></label><div id=\"shoppingMatches385\"></div><div id=\"shoppingFreeChoice385\"></div></div>`);paintShoppingMatches385();document.getElementById('shoppingQuery385')?.focus()};\nfunction paintShoppingMatches385(){const q=shoppingQuery385.trim(),host=document.getElementById('shoppingMatches385'),free=document.getElementById('shoppingFreeChoice385');if(host)host.innerHTML=q?(shoppingProductRows363(q)||`<p class=\"tiny\">${hubText362('Kein vorhandener Lagerartikel gefunden.','No existing stock product found.','Nie znaleziono produktu magazynowego.')}</p>`):`<p class=\"tiny\">${hubText362('Suche nach einem vorhandenen Produkt oder ergänze einen eigenen Eintrag.','Find an existing product or add your own item.','Znajdź produkt lub dodaj własny wpis.')}</p>`;if(free)free.innerHTML=q?`<button type=\"button\" class=\"soft\" onclick=\"shoppingFree363()\">+ ${hubText362('Als eigenen Listeneintrag ergänzen','Add as a custom list item','Dodaj jako własny wpis')}</button><small>${hubText362('Nur Einkaufsliste, kein neuer Lagerartikel.','Shopping list only, no new stock product.','Tylko lista zakupów, bez nowego produktu magazynowego.')}</small>`:''}\nconst shoppingManualFormBefore385=shoppingManualForm174;\nshoppingManualForm174=function(){return ''};\nshoppingFree363=function(id=''){if(!guardPermission('shopping','Einkaufsliste'))return false;const old=id?(state.shoppingChecks?._manual174||[]).find(r=>r.id===id):null;if(id&&!old)return false;shoppingManualEdit174=id;shoppingFreeDraft385={id,snapshot:JSON.stringify(old||null),busy:false};let html=shoppingManualFormBefore385().replace(/<p[^>]*>[\\s\\S]*?<\\/p>/g,'');html=html.replace(/^<details[^>]*>/,'<section class=\"shoppingFreeForm385\">').replace(/<summary>[\\s\\S]*?<\\/summary>/,'').replace(/<\\/details>$/,'</section>').replace('return shoppingManualSave174(this)','event.preventDefault();saveShoppingFree385(this)').replace('shoppingManualCancel174()','shoppingAddProduct363()');openOpsPanel(hubText362('Listeneintrag','List item','Wpis na liście'),`<div class=\"shoppingAdd385\"><button type=\"button\" class=\"soft\" onclick=\"shoppingAddProduct363()\">← ${hubText362('Zurück zur Suche','Back to search','Wróć do wyszukiwania')}</button>${html}<p id=\"shoppingError385\" role=\"alert\" hidden></p></div>`);const name=document.querySelector('#opsDialog92 [name=\"name\"]');if(name&&!old)name.value=shoppingQuery385.trim();name?.focus();return true};\nshoppingManualEditStart174=function(id){return shoppingFree363(id)};\nfunction shoppingFreePayload385(d,form){const get=n=>String(form.elements[n]?.value||'').trim(),name=get('name'),unit=get('unit'),supplier=get('supplier'),qty=Number(get('qty').replace(',','.')),area=get('area'),profile=supplierProfileFor(supplier),old=d?.id?(state.shoppingChecks?._manual174||[]).find(r=>r.id===d.id):null;if(!d||JSON.stringify(old||null)!==d.snapshot)throw Error(hubText362('Eintrag wurde geändert. Bitte erneut öffnen.','Item changed. Reopen it.','Wpis zmieniono. Otwórz ponownie.'));if(!name||name.length>160||!unit||unit.length>32||!supplier||supplier.length>100||!Number.isFinite(qty)||qty<=0||qty>100000||!shoppingAreas173.some(a=>a[0]===area))throw Error(hubText362('Name, positive Menge, Einheit und Geschäft prüfen.','Check name, positive quantity, unit and store.','Sprawdź nazwę, dodatnią ilość, jednostkę i sklep.'));if(typeof isWholeQuantityUnit==='function'&&isCountLikeUnit(unit)&&!Number.isInteger(qty))throw Error(hubText362('Für Stück und Packungen ganze Zahlen eingeben.','Use whole numbers for pieces and packs.','Użyj całych sztuk i opakowań.'));if(profile&&(profile.method!=='pickup'||profile.preorder))throw Error(hubText362('Dieser Lieferant wird separat bestellt. Wähle für diesen freien Einkaufszettel ein Geschäft zum Selbsteinkaufen.','This supplier requires a separate order. Choose a store for self shopping.','Ten dostawca wymaga zamówienia. Wybierz sklep do zakupów własnych.'));return {id:d.id||('item-'+(globalThis.crypto?.randomUUID?.()||Date.now()+'-'+Math.random().toString(36).slice(2))),name,unit,qty,supplier:profile?.name||supplier,area,private:!!form.elements.private?.checked,note:get('note').slice(0,160)}}\nfunction saveShoppingFree385(form){const d=shoppingFreeDraft385;if(!d||d.busy)return false;let next;try{next=shoppingFreePayload385(d,form)}catch(e){const node=document.getElementById('shoppingError385');if(node){node.hidden=false;node.textContent=e.message}return false}d.busy=true;return afterOperation275(operationTransaction(hubText362('Einkaufsliste ergänzt','Shopping list updated','Lista zakupów uzupełniona'),'shopping',()=>{shoppingFreePayload385(d,form);state.shoppingChecks??={};const items=state.shoppingChecks._manual174??=[];if(d.id){const i=items.findIndex(r=>r.id===d.id);if(i<0)throw Error('Eintrag nicht mehr vorhanden.');items[i]=next;delete state.shoppingChecks['manual174:'+d.id]}else items.push(next)}),ok=>{d.busy=false;if(ok){shoppingFreeDraft385=null;shoppingManualEdit174='';shoppingQuery385='';document.getElementById('opsDialog92')?.close();renderSelfShopping()}return ok})}\nconst shoppingAreaRowBefore385=shoppingAreaRow173;\nshoppingAreaRow173=function(row){return shoppingAreaRowBefore385(row).replace('<div class=\"shoppingCheckRow',`<div data-shopping-key385=\"${esc(row.key)}\" class=\"shoppingCheckRow`)};\nasync function shoppingOpenItem385(code){if(!guardPermission('shopping','Einkaufsliste'))return false;const found=shoppingExisting385(code);if(!found){shoppingChooseProduct363(code);return true}document.getElementById('opsDialog92')?.close();if(found.kind==='orders'){if(await openPurchasingOverview('orders')===false)return false;openSupplierOrder382(found.group.name);return true}if(await openPurchasingOverview('list')===false)return false;const nodes=[...document.querySelectorAll('[data-shopping-key385]')],target=nodes.find(n=>n.dataset.shoppingKey385===found.row.key&&n.closest('#selfShoppingInline,#selfShoppingBody')?.getClientRects().length);if(!target){showFeedback(hubText362('Artikel auf der Liste','Item on list','Artykuł na liście'),'info',3500,found.group.name+' · '+found.row.name);return true}for(let p=target.parentElement;p;p=p.parentElement)if(p.tagName==='DETAILS')p.open=true;document.querySelectorAll('.shoppingTarget385').forEach(n=>n.classList.remove('shoppingTarget385'));target.classList.add('shoppingTarget385');target.setAttribute('tabindex','-1');target.focus({preventScroll:true});target.scrollIntoView({block:'center',behavior:'smooth'});return true}\n"},{"src":"event-economics386.js?v=386","code":"'use strict';\n// Preserve canonical net revenue and existing cost formulas; simplify their entry.\nlet pricingDraft386={mode:'total',perPerson:''};\nconst priceWords386=(de,en,pl)=>hubText362(de,en,pl);\nfunction priceNumber386(raw){if(typeof amount231==='function')return amount231(String(raw??''));const s=String(raw??'').trim();if(!s)return null;if(!/^\\d+(?:[.,]\\d+)?$/.test(s))throw Error('Betrag prüfen.');const n=Number(s.replace(',','.'));if(!Number.isFinite(n)||n<0)throw Error('Betrag prüfen.');return n}\nfunction personTotal386(raw,guests){const price=priceNumber386(raw),n=Number(guests);if(price===null)return null;if(!Number.isInteger(n)||n<1)throw Error(priceWords386('Personenanzahl prüfen.','Check guest count.','Sprawdź liczbę osób.'));const total=Math.round((price*n+Number.EPSILON)*100)/100;if(!Number.isFinite(total))throw Error(priceWords386('Betrag ist zu groß.','Amount is too large.','Kwota jest za duża.'));return total}\nfunction pricingError386(){const field=document.getElementById('planRevenue'),box=field?.formAdapter231?.box;if(pricingDraft386.mode==='person'){try{personTotal386(pricingDraft386.perPerson,document.getElementById('planGuests')?.value)}catch(e){return ['planPerPerson386',e.message]}}if(box?.dataset.invalid231)return [field.formAdapter231.display.id,box.dataset.invalid231];try{priceNumber386(field?.value)}catch(e){return [field?.formAdapter231?.display?.id||'planRevenue',e.message]}return null}\nfunction setPricingMode386(mode){if(!['total','person'].includes(mode))return;pricingDraft386.mode=mode;invalidateApproval374();if(mode==='person')applyPersonPrice386();else{installEconomics386();updateCateringPreview()}}\nfunction applyPersonPrice386(){const field=document.getElementById('planRevenue');if(!field)return;try{const total=personTotal386(pricingDraft386.perPerson,document.getElementById('planGuests')?.value);field.value=total===null?'':total.toFixed(2).replace('.',',');if(typeof clearFieldError370==='function')clearFieldError370(document.getElementById('planPerPerson386'))}catch(e){field.value='';if(typeof markFieldError370==='function')markFieldError370(document.getElementById('planPerPerson386'),e.message)}invalidateApproval374();installEconomics386();updateCateringPreview()}\nfunction economicsModel386(c){const x=cateringCalc(c),resources=typeof resourceCostTotals==='function'?resourceCostTotals(c.resourceCosts):x.resources||{},drinks=c.drinksEnabled?c.drinks||[]:[],missingDrinks=drinks.some(d=>Number(d.qty)>0&&!(Number(state.products?.[d.productCode]?.price)>0||Number(d.price)>0)),invalidMoney=typeof document!=='undefined'&&[...(document.querySelectorAll?.('#planningGuide369 .money231')||[])].some(box=>box.dataset.invalid231),known=!invalidMoney&&Number.isFinite(x.total)&&x.costedIngredients>=x.totalIngredients&&!x.free?.length&&!x.errors?.length&&!x.drinkFree?.length&&!resources.errors?.length&&!resources.unpriced?.length&&!missingDrinks,hasRevenue=Number(c.revenue)>=0&&Number.isFinite(Number(c.revenue))&&(Number(c.revenue)>0||c.pricing386?.entered===true)&&!(planningSection==='entry'&&pricingError386()),revenue=Number(c.revenue)||0,result=revenue-x.total;return {x,known,hasRevenue,result,margin:hasRevenue&&revenue>0?result/revenue*100:null,perPerson:Number(c.guests)>0?revenue/Number(c.guests):null,status:!hasRevenue||!known?'open':result<-.005?'negative':result>.005?'positive':'even',resources}}\nfunction economicsHtml386(c){const m=economicsModel386(c),open=priceWords386('Offen','Unknown','Otwarte'),result=m.known&&m.hasRevenue?moneyFmt(m.result):open;return `<div class=\"economics386 economics-${m.status}386\"><div class=\"reviewMetrics374\"><div><small>${priceWords386('Nettoerlös gesamt','Total net revenue','Przychód netto razem')}</small><strong>${m.hasRevenue?moneyFmt(m.x.revenue):open}</strong></div><div><small>${m.known?priceWords386('Erfasste Kosten','Recorded costs','Zapisane koszty'):priceWords386('Bisher bekannte Kosten','Known costs so far','Znane dotąd koszty')}</small><strong>${moneyFmt(m.x.total)}</strong></div><div class=\"economicsResult386\"><small>${priceWords386('Nach erfassten Kosten übrig','After recorded costs','Po zapisanych kosztach')}</small><strong>${result}</strong>${m.known&&m.hasRevenue&&m.margin!==null?`<small>${m.margin.toFixed(1).replace('.',',')} % ${priceWords386('vom Nettoerlös','of net revenue','przychodu netto')}</small>`:''}</div></div>${!m.hasRevenue?`<p class=\"economicsNote386\">${priceWords386('Preis eingeben, um das Ergebnis zu berechnen.','Enter a price to calculate the result.','Wpisz cenę, aby obliczyć wynik.')}</p>`:''}${!m.known?`<p class=\"economicsWarning386\">${priceWords386('Kalkulation unvollständig: Einkaufspreise, Zutatenzuordnungen oder Kostenansätze prüfen.','Calculation incomplete: check purchase prices, ingredient links and cost rates.','Kalkulacja niepełna: sprawdź ceny, składniki i stawki kosztów.')}</p>`:''}<p class=\"economicsNote386\">${priceWords386('Planwert nach den erfassten Kosten. Weitere Fixkosten und Steuern sind noch nicht abgezogen.','Planned result after recorded costs. Further overhead and taxes are not deducted.','Planowany wynik po zapisanych kosztach. Pozostałe koszty stałe i podatki nie są odliczone.')}</p></div>`}\ncostOverview374=economicsHtml386;\nfunction installEconomics386(){const guide=document.getElementById('planningGuide369');if(!guide)return;const panel=guide.querySelector('[data-planning-step369=\"3\"]');if(!panel)return;let price=document.getElementById('planningPrice386');if(!price){price=document.createElement('section');price.id='planningPrice386';price.className='planningPrice386';price.innerHTML=`<h3>${priceWords386('Was verrechnest du?','What will you charge?','Ile policzysz?')}</h3><label class=\"priceMode386\">${priceWords386('Preis eingeben als','Enter price as','Wpisz cenę jako')}<select id=\"pricingMode386\" onchange=\"setPricingMode386(this.value)\"><option value=\"total\">${priceWords386('Gesamtbetrag','Total amount','Kwota łączna')}</option><option value=\"person\">${priceWords386('Netto pro Person','Net per person','Netto na osobę')}</option></select></label><label id=\"pricePersonField386\" hidden>${priceWords386('Netto pro Person (€)','Net per person (€)','Netto na osobę (€)')}<input id=\"planPerPerson386\" inputmode=\"decimal\" data-adapted231=\"manual-net\" placeholder=\"0,00\" oninput=\"pricingDraft386.perPerson=this.value;applyPersonPrice386()\"></label><div id=\"priceTotalSlot386\"></div><small id=\"priceBasis386\"></small>`;panel.prepend(price);const field=document.getElementById('planRevenue')?.closest('.field');if(field)price.querySelector('#priceTotalSlot386').append(field);const label=document.getElementById('planRevenueLabel');if(label)label.textContent=priceWords386('Auftragswert gesamt (€)','Total order value (€)','Wartość zamówienia razem (€)');const more=guide.querySelector('[data-planning-step369=\"0\"] .planMore353')||panel.querySelector('.planMore353');if(more?.querySelector('summary'))more.querySelector('summary').textContent=priceWords386('Zeit & weitere Angaben','Time & other details','Czas i inne dane');}\n const mode=document.getElementById('pricingMode386'),person=document.getElementById('planPerPerson386'),personField=document.getElementById('pricePersonField386'),total=document.getElementById('planRevenue');if(mode)mode.value=pricingDraft386.mode;if(person&&person.value!==pricingDraft386.perPerson)person.value=pricingDraft386.perPerson;if(personField)personField.hidden=pricingDraft386.mode!=='person';if(total){total.readOnly=pricingDraft386.mode==='person';total.formAdapter231?.sync()}\n const hint=document.getElementById('priceBasis386'),c=cateringDraftObject();if(hint)hint.textContent=pricingDraft386.mode==='person'?`${document.getElementById('planGuests')?.value||'—'} ${priceWords386('Personen × Nettopreis = Netto-Auftragswert.','people × net price = net order value.','osób × cena netto = wartość netto.')}`:Number(c.revenue)>0&&Number(c.guests)>0?`${priceWords386('Entspricht','Equals','Odpowiada')} ${moneyFmt(Number(c.revenue)/Number(c.guests))} ${priceWords386('netto pro Person','net per person','netto na osobę')}`:'';\n const fold=document.getElementById('planCostsFold');if(fold){const outer=fold.parentElement,body=fold.querySelector('.planFoldBody');if(body){const section=document.createElement('section');section.className='planningCostEntry386';const title=document.createElement('h3');title.textContent=priceWords386('Personal, Fahrt & weitere Kosten','Staff, travel & other costs','Personel, dojazd i inne koszty');section.append(title,body);outer.before(section);fold.remove();if(outer.querySelector('summary'))outer.querySelector('summary').textContent=priceWords386('Zeit & weitere Angaben','Time & other details','Czas i inne dane')}}\n const needs=document.getElementById('planningNeeds369');if(needs&&needs.parentElement?.dataset.planningStep369==='2'){const details=document.createElement('details');details.className='planningStock386';const summary=document.createElement('summary');summary.textContent=priceWords386('Bestand & Einkauf prüfen','Check stock & purchasing','Sprawdź stan i zakupy');details.append(summary,needs);panel.append(details)}\n}\nconst installGuideBefore386=installPlanningGuide369;\ninstallPlanningGuide369=function(...args){const r=installGuideBefore386.apply(this,args);installEconomics386();return r};\nconst paintGuideBefore386=paintPlanningGuide369;\npaintPlanningGuide369=function(...args){if(planningStep369===2)planningStep369=3;const r=paintGuideBefore386.apply(this,args);const label=document.getElementById('planningProgress369'),steps=[0,1,3,4],names=[priceWords386('Einsatz','Event','Wydarzenie'),priceWords386('Speisen & Mengen','Dishes & quantities','Dania i ilości'),priceWords386('Preis, Kosten & Ergebnis','Price, costs & result','Cena, koszty i wynik'),priceWords386('Prüfen & speichern','Review & save','Sprawdź i zapisz')];if(label)label.textContent=`${steps.indexOf(planningStep369)+1} / 4 · ${names[steps.indexOf(planningStep369)]}`;return r};\nconst goGuideBefore386=planningGo369;\nplanningGo369=function(step){if(step===2)step=planningStep369===3?1:3;return goGuideBefore386(step)};\nconst validateGuideBefore386=planningValidation369;\nplanningValidation369=function(step){const prior=validateGuideBefore386(step);if(prior)return prior;if(step>=3){const error=pricingError386();if(error){if(typeof markFieldError370==='function')markFieldError370(document.getElementById(error[0]),error[1]);return error}}return null};\nconst draftPricingBefore386=cateringDraftObject;\ncateringDraftObject=function(...args){const c=draftPricingBefore386.apply(this,args);let perPersonNet=null;try{perPersonNet=priceNumber386(pricingDraft386.perPerson)}catch{}return {...c,pricing386:{mode:pricingDraft386.mode,perPersonNet,entered:String(document.getElementById('planRevenue')?.value||'').trim()!==''}}};\nconst previewBefore386=updateCateringPreview;\nupdateCateringPreview=function(...args){const r=previewBefore386.apply(this,args);if(planningSection==='entry'){installEconomics386();renderFinalSummary374(cateringDraftObject())}return r};\nconst newEntryBefore386=newPlanningEntry;\nnewPlanningEntry=function(...args){pricingDraft386={mode:'total',perPerson:''};return newEntryBefore386.apply(this,args)};\nconst clearDraftBefore386=clearCateringDraft;\nclearCateringDraft=function(...args){pricingDraft386={mode:'total',perPerson:''};return clearDraftBefore386.apply(this,args)};\nconst editPricingBefore386=editCateringPlan;\neditCateringPlan=function(id,...args){const p=(state.planning?.caterings||[]).find(c=>c.id===id)?.pricing386;pricingDraft386={mode:p?.mode==='person'&&Number.isFinite(p?.perPersonNet)?'person':'total',perPerson:Number.isFinite(p?.perPersonNet)?String(p.perPersonNet).replace('.',','):''};const r=editPricingBefore386.call(this,id,...args);const saved=(state.planning?.caterings||[]).find(c=>c.id===id);if(p?.entered&&Number(saved?.revenue)===0){const field=document.getElementById('planRevenue');if(field)field.value='0';updateCateringPreview()}return r};\nconst savePricingBefore386=saveCateringPlan;\nsaveCateringPlan=function(...args){if(planningSection==='entry'&&pricingError386()){planningStep369=3;paintPlanningGuide369();const error=pricingError386(),msg=document.getElementById('planningError369');if(msg){msg.hidden=false;msg.textContent=error[1]}if(typeof markFieldError370==='function')markFieldError370(document.getElementById(error[0]),error[1]);return false}return savePricingBefore386.apply(this,args)};\nfunction refreshInvalidEconomics386(event){if(event.target?.id==='planGuests'&&pricingDraft386.mode==='person'){applyPersonPrice386();return}if(planningSection==='entry'&&event.target?.closest?.('#planningGuide369')&&event.target.closest('.money231')?.dataset.invalid231)renderFinalSummary374(cateringDraftObject())}\nif(typeof document!=='undefined'){document.addEventListener('input',refreshInvalidEconomics386);document.addEventListener('change',refreshInvalidEconomics386)}\n"},{"src":"event-shopping387.js?v=387","code":"'use strict';\nfunction eventBuyRows387(c){const result=[];for(const r of procurementFor(c).filter(r=>Number(r.qty)>0)){const buy=purchaseQuantity379(r),unit=buy.unit||r.unit;if(buy.items?.length){for(const i of buy.items){const article=state.products?.[i.code],supplier=i.supplier||article?.supplier||r.supplier;const count=i.divisible380?Math.ceil(i.count*100-1e-9)/100:Math.ceil(i.count-1e-9);result.push({...r,productCode:i.code||r.productCode,name:article?.name||r.name,supplier:typeof supplierProfileFor==='function'?(supplierProfileFor(supplier)?.name||supplier):supplier,buyLabel387:i.divisible380?formatQtySmart(count,i.name):`${count} × ${i.name}`})}}else{const q=isCountLikeUnit(unit)?Math.ceil(Number(buy.qty)-1e-9):Math.ceil(Number(buy.qty)*100-1e-9)/100;result.push({...r,buyLabel387:formatQtySmart(q,unit)})}}return result}\nfunction eventBuyGroups387(c){const groups=new Map();for(const r of eventBuyRows387(c)){const name=r.supplier||hubText362('Geschäft noch wählen','Choose store','Wybierz sklep');if(!groups.has(name))groups.set(name,[]);groups.get(name).push(r)}return [...groups].sort((a,b)=>a[0].localeCompare(b[0],'de')).map(([name,rows])=>({name,rows:rows.sort((a,b)=>a.name.localeCompare(b.name,'de'))}))}\nfunction eventBuyList387(c){const groups=eventBuyGroups387(c);return groups.length?groups.map(g=>`<section class=\"eventBuySupplier387\"><h3>${esc(g.name)}</h3><table><thead><tr><th>${hubText362('Artikel','Product','Produkt')}</th><th>${hubText362('Einkaufen','Buy','Kup')}</th></tr></thead><tbody>${g.rows.map(r=>`<tr><td>${esc(r.name)}${r.linked===false?`<small>${hubText362('Zuordnung offen','Link missing','Brak przypisania')}</small>`:''}</td><td>${esc(r.buyLabel387)}</td></tr>`).join('')}</tbody></table></section>`).join(''):`<p>${hubText362('Für diesen Einsatz ist kein zusätzlicher Einkauf nötig.','No additional shopping needed for this event.','Nie potrzeba dodatkowych zakupów.')}</p>`}\nrenderShopping=function(c){return `<div class=\"eventBuy387\"><div class=\"eventBuyActions387\"><h2>${hubText362('Einkauf für diesen Einsatz','Shopping for this event','Zakupy na wydarzenie')}</h2><button type=\"button\" class=\"soft\" onclick=\"printEventShopping387()\">${hubText362('Einkaufsliste drucken','Print shopping list','Drukuj listę zakupów')}</button></div>${eventBuyList387(c)}</div>`};\nfunction eventBuyPrint387(c){return `<!doctype html><html lang=\"de\"><head><meta charset=\"utf-8\"><title>${esc(c.event)} · ${hubText362('Einkaufsliste','Shopping list','Lista zakupów')}</title><style>body{font:14px Arial,sans-serif;color:#173C2B;margin:28px}h1{font-size:22px;margin:0 0 8px}h2,h3{font-size:17px;margin:24px 0 8px}p{font-size:12px}table{width:100%;border-collapse:collapse}th,td{padding:9px 6px;border-bottom:1px solid #DDD;text-align:left}th:last-child,td:last-child{text-align:right}td small{display:block;color:#923B28;font-size:11px}thead{display:table-header-group}tr{break-inside:avoid}h3{break-after:avoid}@page{size:A4;margin:16mm}@media print{body{margin:0}}</style></head><body><h1>${hubText362('Einkaufsliste','Shopping list','Lista zakupów')} · ${esc(c.event)}</h1><p>Zapasio${c.date?' · '+esc(formatDate(c.date)):''}</p>${eventBuyList387(c)}</body></html>`}\nfunction printEventShopping387(){if(!guardPermission('shopping','Einkaufsliste'))return false;const c=activePlanningEntry();if(!c)return false;const w=window.open('','_blank');if(!w){showFeedback(hubText362('Druckfenster blockiert','Print window blocked','Okno druku zablokowane'),'warn');return false}w.opener=null;w.document.open();w.document.write(eventBuyPrint387(c));w.document.close();w.focus();w.print();return true}\nlet costSettingsBaseline387=null,costSettingsBusy387=false;\nconst resourceEditorHtmlBefore387=resourceEditorHtml;\nresourceEditorHtml=function(context){const h=resourceEditorHtmlBefore387(context);if(context!=='settings')return h;return `<div class=\"fixedCostRoles387\"><h3>${hubText362('Feste Kostensätze','Default cost rates','Stałe stawki kosztów')}</h3><p>${hubText362('Kilometersatz und Stundensätze einmal festlegen. Neue Einsätze übernehmen die Vorgaben; bestehende behalten ihre Werte.','Set mileage and hourly rates once. New events use these defaults; existing events retain their values.','Ustaw stawkę za kilometr i godzinę. Nowe wydarzenia przejmą stawki, istniejące zachowają wartości.')}</p><div class=\"hubActions362\">${['Koch','Service','Hintergrund'].map(name=>`<button type=\"button\" class=\"soft\" onclick=\"addFixedRole387('${name}')\">+ ${esc(name)}</button>`).join('')}</div><button type=\"button\" class=\"soft\" onclick=\"reloadFixedCosts387()\">${hubText362('Gespeicherten Stand neu laden','Reload saved values','Wczytaj zapisane wartości')}</button></div>`+h};\nconst resourceRenderBefore387=renderResourceEditor;\nrenderResourceEditor=function(context){if(context==='settings'&&costSettingsBaseline387===null)costSettingsBaseline387=JSON.stringify(costCatalogue());return resourceRenderBefore387(context)};\nfunction addFixedRole387(name){if(!['Koch','Service','Hintergrund'].includes(name)||!guardPermission('settings','Kostenvorgaben'))return false;const m=costEditorModel('settings');m.rows??=[];for(const r of m.rows)delete r._editing;let row=m.rows.find(r=>r.kind==='labor'&&String(r.name).toLocaleLowerCase('de')===name.toLocaleLowerCase('de'));if(!row){row={id:'role-'+(globalThis.crypto?.randomUUID?.()||Date.now()+'-'+Math.random().toString(36).slice(2)),kind:'labor',name,scope:'Alle',rate:0,count:0,prep:0,travelHours:0,work:0,after:0,overtime:0,overtimeRate:0,qty:0};m.rows.push(row)}row._editing=true;renderResourceEditor('settings');return true}\nsaveCostCatalogue=function(){if(costSettingsBusy387||!guardPermission('settings','Kostenvorgaben'))return false;const draft=clone(costEditorModel('settings'));if(!validResourceCosts(draft))return false;const before=costSettingsBaseline387??JSON.stringify(costCatalogue());costSettingsBusy387=true;return afterOperation275(operationTransaction(hubText362('Kostenvorgaben gespeichert','Default costs saved','Stawki zapisane'),'settings',()=>{if(JSON.stringify(costCatalogue())!==before)throw Error(hubText362('Kostenvorgaben wurden zwischenzeitlich geändert. Einstellungen neu öffnen und prüfen.','Default costs changed. Reopen settings and review.','Stawki zmieniono. Otwórz ustawienia ponownie.'));if(!validResourceCosts(draft))throw Error('Kostensätze prüfen.');state.costCatalogue=clone(draft);for(const r of state.costCatalogue.rows||[])delete r._editing}),ok=>{costSettingsBusy387=false;if(ok){costSettingsDraft=null;costSettingsBaseline387=null;if(!editingCateringId){for(const[type,m]of Object.entries(draftResourceCosts))if(!(m.rows||[]).length&&!Number(m.km))delete draftResourceCosts[type];const rc=document.getElementById('planResourceEditor');if(rc)delete rc.dataset.costType}showFeedback(hubText362('Kostenvorgaben gespeichert','Default costs saved','Stawki zapisane'),'success',3500,hubText362('Neue Einsätze verwenden diese Vorgaben. Bestehende Einsätze behalten ihren Kostenstand.','New events use these defaults. Existing events keep their cost values.','Nowe wydarzenia użyją tych stawek. Istniejące zachowają swoje koszty.'));renderResourceEditor('settings')}return ok})};\n\nfunction reloadFixedCosts387(){if(costSettingsBusy387||!guardPermission('settings','Kostenvorgaben'))return false;askConfirm(hubText362('Ungespeicherte Änderungen an Kostensätzen verwerfen und gespeicherten Stand laden?','Discard unsaved cost-rate changes and reload saved values?','Odrzucić niezapisane zmiany stawek i wczytać zapisane wartości?'),()=>{costSettingsDraft=null;costSettingsBaseline387=null;renderResourceEditor('settings')},hubText362('Neu laden','Reload','Wczytaj'),'Kostenvorgaben')}\n"},{"src":"event-overview388.js?v=388","code":"\"use strict\";\nfunction shoppingButton388(){return hasPermission('shopping')?`<button type=\"button\" class=\"soft eventShoppingEntry388\" onclick=\"openEventShopping388()\">${hubText362('Einkaufsliste','Shopping list','Lista zakupów')}</button>`:''}\nfunction shoppingContext388(){return planningSection==='entry'?cateringDraftObject():activePlanningEntry()}\nlet shoppingPreview388=null;\nfunction openEventShopping388(){if(!guardPermission('shopping','Einkaufsliste'))return false;const c=shoppingContext388();if(!c){showFeedback('Bitte zuerst einen Einsatz wählen.','warn');return false}shoppingPreview388=clone(c);openOpsPanel(hubText362('Einkaufsliste','Shopping list','Lista zakupów')+' · '+(c.event||'Einsatz'),`<div class=\"eventBuy387\"><p>${esc(c.date?formatDate(c.date):'Datum noch wählen')}${planningSection==='entry'?' · '+hubText362('Vorschau des Entwurfs','Draft preview','Podgląd szkicu'):''}</p><button type=\"button\" class=\"soft\" onclick=\"printShoppingPreview388()\">${hubText362('Drucken','Print','Drukuj')}</button>${eventBuyList387(shoppingPreview388)}</div>`);return true}\nfunction printShoppingPreview388(){if(!guardPermission('shopping','Einkaufsliste')||!shoppingPreview388)return false;const w=window.open('','_blank');if(!w){showFeedback('Druckfenster blockiert. Bitte Pop-ups für Zapasio zulassen.','warn');return false}w.opener=null;w.document.open();w.document.write(eventBuyPrint387(shoppingPreview388));w.document.close();w.focus();w.print();return true}\nfunction costParts388(c,x){const a=c.costs||{},r=x.resources||{},n=v=>Number.isFinite(Number(v))?Number(v):0;const rows=[['Ware & Getränke',n(x.autoCost)+n(x.drinkCost)+n(a.extraGoods)],['Personal',n(r.personnel)+n(a.personnel)],['Fahrt',n(r.travel)+n(a.travel)],['Ausstattung',n(r.rental)+n(a.rental)],['Sonstiges & Gebühren',n(r.other)+n(a.other)+n(x.eventFeeTotal)]];const delta=n(x.total)-rows.reduce((s,r)=>s+r[1],0);if(Math.abs(delta)>.005)rows.push(['Abgleich zum gespeicherten Kostenstand',delta]);return rows}\neconomicsHtml386=function(c){const m=economicsModel386(c),x=m.x,complete=m.known&&m.hasRevenue,ratio=complete&&x.revenue>0?Math.max(0,Math.min(100,x.total/x.revenue*100)):null;return `<section class=\"economics386 economics-${m.status}386 compactEconomics388\"><div class=\"economicsMain388\"><div class=\"economicsTotals388\"><div><small>${hubText362('Nettoerlös','Net revenue','Przychód netto')}</small><strong>${m.hasRevenue?moneyFmt(x.revenue):'—'}</strong></div><div><small>${m.known?'Erfasste Kosten':'Bisher bekannte Kosten'}</small><strong>${moneyFmt(x.total)}</strong></div></div><div class=\"economicsHero388\"><small>${hubText362('Für diesen Auftrag übrig','Remaining for this order','Pozostaje dla zlecenia')}</small><strong>${complete?moneyFmt(m.result):hubText362('Kalkulation unvollständig','Calculation incomplete','Kalkulacja niepełna')}</strong>${complete&&m.margin!==null?`<span>${m.margin.toFixed(1).replace('.',',')} % vom Nettoerlös</span>`:''}${ratio!==null?`<div class=\"economicsBar388\" role=\"img\" aria-label=\"${esc('Erfasste Kosten: '+moneyFmt(x.total)+'; übrig: '+moneyFmt(m.result))}\"><i style=\"width:${ratio}%\"></i></div>`:''}</div><p class=\"economicsNote386\">Planwert nach erfassten Kosten. Weitere Fixkosten und Steuern sind noch nicht abgezogen.</p>${!m.hasRevenue?'<p class=\"economicsWarning386\">Preis eingeben, um das Ergebnis zu berechnen.</p>':''}${!m.known?'<p class=\"economicsWarning386\">Einkaufspreise, Zutatenzuordnungen und Kostenansätze prüfen.</p>':''}</div><div class=\"economicsBreakdown388\"><h4>${hubText362('Wo entstehen die Kosten?','Cost breakdown','Podział kosztów')}</h4><dl>${costParts388(c,x).map(([label,value])=>`<div><dt>${esc(label)}</dt><dd>${moneyFmt(value)}</dd></div>`).join('')}<div class=\"economicsSum388\"><dt>Gesamt</dt><dd>${moneyFmt(x.total)}</dd></div></dl>${hasPermission('planning')?`<button type=\"button\" class=\"soft\" onclick=\"editEconomics388('${escJs(c.id||'draft')}')\">${hubText362('Kosten bearbeiten','Edit costs','Edytuj koszty')}</button>`:''}</div></section>`};\ncostOverview374=economicsHtml386;\nfunction editEconomics388(id){if(!guardPermission('planning','Kosten bearbeiten'))return false;if(planningSection!=='entry'){if(!(state.planning?.caterings||[]).some(c=>c.id===id))return false;editCateringPlan(id)}planningStep369=3;paintPlanningGuide369();document.getElementById('planningPrice386')?.scrollIntoView?.({block:'start',behavior:'smooth'});return true}\nconst summaryBefore388=finalSummary374;\nfinalSummary374=function(c){return `<div class=\"eventShoppingShortcut388\">${shoppingButton388()}</div>`+summaryBefore388(c)};\nconst headerBefore388=webEventHeader360;\nwebEventHeader360=function(...args){const result=headerBefore388.apply(this,args),nav=document.querySelector('#webEventHeader360 .webEventTabs360');if(nav&&!nav.querySelector('.eventShoppingEntry388'))nav.insertAdjacentHTML('beforeend',shoppingButton388());return result};\nconst calcBefore388=calcHtml;\ncalcHtml=function(c){const html=calcBefore388(c),start=html.indexOf(\"<div class='calcKpis'>\"),end=html.indexOf(\"<div class='miniResult'>\",start);return start>=0&&end>start?html.slice(0,start)+economicsHtml386(c)+html.slice(end):economicsHtml386(c)+html};\n"},{"src":"cost-rates389.js?v=389","code":"'use strict';\n// Catalogue entries are rates. Quantities and working times belong to the event.\nfunction rateTemplate389(model){\n  const result=clone(model);\n  result.km=0;\n  for(const row of result.rows||[]){\n    for(const key of ['count','prep','travelHours','work','after','overtime','qty'])row[key]=0;\n    row.overtimeRate=row.kind==='labor'?row.rate:0;\n  }\n  return result;\n}\nconst defaultsBefore389=resourceDefaults;\nresourceDefaults=function(type){return rateTemplate389(defaultsBefore389(type))};\nconst costModelBefore389=costEditorModel;\ncostEditorModel=function(context){\n  const model=costModelBefore389(context);\n  if(context==='settings'){\n    model.km=0;\n    for(const row of model.rows||[]){\n      for(const key of ['count','prep','travelHours','work','after','overtime','qty'])row[key]=0;\n      row.overtimeRate=row.kind==='labor'?row.rate:0;\n    }\n  }\n  return model;\n};\nconst editorBefore389=resourceEditorHtml;\nresourceEditorHtml=function(context){\n  if(context!=='settings')return editorBefore389(context);\n  const model=costEditorModel(context),text=hubText362;\n  const rowHtml=(row,index)=>{\n    const change=key=>esc(`changeResourceValue('settings',${index},'${key}',this.value)`);\n    const nameLabel=row.kind==='labor'?text('Personalkategorie','Staff category','Kategoria personelu'):text('Bezeichnung','Name','Nazwa');\n    const rateLabel=row.kind==='labor'?text('Stundensatz netto (€/h)','Net hourly rate (€/h)','Stawka netto (€/h)'):row.kind==='equipment'?text('Nettokosten pro Stück / Einsatz (€)','Net cost per item / event (€)','Koszt netto za sztuke / wydarzenie (€)'):text('Nettokosten pro Einheit (€)','Net cost per unit (€)','Koszt netto za jednostke (€)');\n    return `<div class=\"rateRow389\"><div class=\"resourceGrid\"><label>${nameLabel}<input aria-label=\"${nameLabel}\" value=\"${esc(row.name||'')}\" onchange=\"${change('name')}\"></label>${resourceInput('settings',index,'rate',rateLabel,row.rate)}<label>${text('Gilt für','Applies to','Dotyczy')}<select aria-label=\"${text('Gilt für','Applies to','Dotyczy')}\" onchange=\"${change('scope')}\">${['Alle','Catering','Veranstaltung','Tagesgeschäft'].map(scope=>`<option value=\"${esc(scope)}\" ${(row.scope||'Alle')===scope?'selected':''}>${esc(scope)}</option>`).join('')}</select></label></div><button type=\"button\" class=\"soft\" onclick=\"removeResourceRow('settings',${index})\">${text('Entfernen','Remove','Usun')}</button></div>`;\n  };\n  const presets=['Koch','Service','Hintergrund'].filter(name=>!(model.rows||[]).some(row=>row.kind==='labor'&&String(row.name).toLocaleLowerCase('de')===name.toLocaleLowerCase('de')));\n  return `<div class=\"costRates389\"><h3>${text('Kostensätze','Cost rates','Stawki kosztów')}</h3><p>${text('Hier legst du nur die Nettokostensätze fest. Kilometer, Personen und Arbeitszeiten gibst du beim Einsatz ein. Neue Einsätze übernehmen die Sätze; bestehende behalten ihre Werte.','Set net cost rates here. Enter distances, people and working hours in each event. New events use these rates; existing events retain their values.','Tutaj ustawiasz stawki netto. Kilometry, osoby i godziny wpisujesz w wydarzeniu. Nowe wydarzenia przejma stawki; istniejace zachowaja swoje wartosci.')}</p><section class=\"resourceGroup\"><h4>${text('Fahrzeug','Vehicle','Pojazd')}</h4>${resourceInput('settings',-1,'kmRate',text('Kilometersatz netto (€/km)','Net mileage rate (€/km)','Stawka netto (€/km)'),model.kmRate)}</section><section class=\"resourceGroup\"><h4>${text('Personal','Staff','Personel')}</h4><p>${text('Bezeichnung und festen Stundensatz direkt bearbeiten.','Edit the category and fixed hourly rate directly.','Edytuj nazwe i stala stawke godzinowa.')}</p>${(model.rows||[]).map((row,index)=>row.kind==='labor'?rowHtml(row,index):'').join('')}<div class=\"hubActions362\">${presets.map(name=>`<button type=\"button\" class=\"soft\" onclick=\"addFixedRole387('${name}')\">+ ${esc(name)}</button>`).join('')}<button type=\"button\" class=\"soft\" onclick=\"addResourceRow('settings','labor')\">${text('+ Neue Personalkategorie','+ New staff category','+ Nowa kategoria personelu')}</button></div></section>${['equipment','other'].map(kind=>`<section class=\"resourceGroup\"><h4>${kind==='equipment'?text('Ausstattung','Equipment','Wyposazenie'):text('Sonstige Kosten','Other costs','Inne koszty')}</h4>${(model.rows||[]).map((row,index)=>row.kind===kind?rowHtml(row,index):'').join('')}<button type=\"button\" class=\"soft\" onclick=\"addResourceRow('settings','${kind}')\">${kind==='equipment'?text('+ Ausstattung','+ Equipment','+ Wyposazenie'):text('+ Kostenposition','+ Cost item','+ Pozycja kosztowa')}</button></section>`).join('')}<div class=\"hubActions362\"><button type=\"button\" class=\"primary\" onclick=\"saveCostCatalogue()\">${text('Kostensätze speichern','Save cost rates','Zapisz stawki')}</button><button type=\"button\" class=\"soft\" onclick=\"reloadFixedCosts387()\">${text('Gespeicherten Stand neu laden','Reload saved values','Wczytaj zapisane wartosci')}</button></div></div>`;\n};\n// Adding a catalogue item to an existing event must not import historical usage.\naddCatalogueResource=function(context,index){\n  if(index===''||!guardPermission(context==='plan'?'planning':'calc','Kalkulation'))return;\n  const row=costCatalogue().rows?.[Number(index)];\n  if(!row)return;\n  const model=costEditorModel(context);\n  if(model.rows.some(item=>item.id===row.id)){showFeedback('Position ist bereits enthalten','info');return}\n  model.rows.push(rateTemplate389({rows:[row]}).rows[0]);\n  renderResourceEditor(context);refreshResourceTotals(context);\n};\n"},{"src":"day-model390.js?v=406","code":"'use strict';\nfunction dayNumber390(value){if(value===null||value===undefined||String(value).trim()==='')return null;const s=String(value).trim().replace(',','.');if(!/^\\d+(?:\\.\\d+)?$/.test(s))return null;const n=Number(s);return Number.isFinite(n)&&n>=0?n:null}\nfunction dayId390(){return globalThis.crypto?.randomUUID?.()||Date.now()+'-'+Math.random().toString(36).slice(2)}\nfunction dayRecipes390(){return (state.recipeGroups||[]).flatMap(g=>(g.recipes||[]).map(r=>({recipe:r,group:g.name})))}\nfunction dayBasis390(r){return JSON.stringify({unit:recipePlanningUnit108(r),yield:r.yieldQty,yieldUnit:r.yieldUnit,portion:r.portioning||null})}\nfunction dayLive390(id){const r=findRecipeById(id)?.recipe;if(!r)return {complete:false,name:'Rezept fehlt',unit:'Portionen',unitCost:null,priceNet:null,warning:'Verknüpftes Rezept fehlt.'};const e=recipeEconomicMeta(r),p=r.dayPrice390,unit=recipePlanningUnit108(r),complete=e.complete===true&&Number.isFinite(e.unitCost)&&e.unitCost>=0&&['Portionen','Stück'].includes(unit),changed=!!p&&(!complete||Math.abs(e.unitCost-p.unitCost)>.00005||p.basis!==dayBasis390(r));return {recipeId:id,name:r.name,unit,unitCost:complete?e.unitCost:null,complete,priceNet:p?.priceNet??null,priceVersion:p?.id||'',basis:dayBasis390(r),pack:clone(r.dayPack390||null),previousCost:p?.unitCost??null,changed,warning:!p?'Tagespreis noch nicht bestätigt.':!complete?'Rezeptkosten unvollständig.':changed?'Rezeptkosten oder Portionsbasis geändert. Tagespreis bleibt unverändert.':''}}\nfunction dayRow390(id){return {id:dayId390(),recipeId:id,start:'',unit:recipePlanningUnit108(findRecipeById(id)?.recipe),planned:'',sold:'',revenueNet:'',leftover:'',waste:'',own:'',additions:[],snapshot:clone(dayLive390(id))}}\nfunction dayFresh390(){return {id:'day-'+dayId390(),version:1,event:'',date:localDateKey(),planning:false,phase:0,status:'draft',rows:[],resourceCosts:resourceDefaults('Tagesgeschäft'),extras:{standFee:0,sharePct:0,other:0},note:''}}\nfunction dayRowEconomics390(row){return row.snapshot||dayLive390(row.recipeId)}\nfunction dayPhysical395(meta){try{const b=JSON.parse(meta.basis);if(!['Portionen','Stück'].includes(meta.unit))return null;const r={yieldQty:b.yield,yieldUnit:b.yieldUnit,portioning:b.portion},qty=recipeOutputQty108(r,1,meta.unit);return qty>0?{qty,unit:r.yieldUnit}:null}catch{return null}}\nfunction dayPortions390(row,quantity){const n=dayNumber390(quantity);if(n===null)return null;const meta=dayRowEconomics390(row);if(row.unit===meta.unit||(['Portion','Portionen'].includes(row.unit)&&meta.unit==='Portionen'))return n;let unit=row.unit,q=n;if(unit==='Kübel'){const pack=meta.pack;if(!(Number(pack?.qty)>0))return null;q*=pack.qty;unit=pack.unit}if(unit===meta.unit)return q;const basis=dayPhysical395(meta);if(!basis)return null;const physical=convertUnit(q,unit,basis.unit);return physical!==null?physical/basis.qty:null}\nfunction daySupply395(row,target){const parts=[dayPortions390(row,row.start),...(row.additions||[]).map(a=>dayPortions390(row,a.qty))],per=dayPortions390(row,1),available=parts.every(n=>n!==null)?parts.reduce((a,b)=>a+b,0):null,n=dayNumber390(target),missing=available!==null&&n!==null?Math.max(0,n-available):null;return {available,missing,extra:missing!==null&&per>0?missing/per:null,per}}\nfunction daySupplyText395(row,target,planned=true){const m=dayRowEconomics390(row),s=daySupply395(row,target),fmt=n=>new Intl.NumberFormat('de-DE',{maximumFractionDigits:2}).format(n),basis=dayPhysical395(m);if(s.available===null)return row.unit==='Kübel'&&!(m.pack?.qty>0)?'Kübelinhalt fehlt. Bitte bei den Tagespreisen hinterlegen und anschließend die aktuellen Werte für diesen Einsatz übernehmen.':'Portionsumrechnung fehlt. Bitte Portionsgröße und passende Gewicht-/Volumeneinheit im Rezept prüfen.';const total=(dayNumber390(row.start)??0)+(row.additions||[]).reduce((n,a)=>n+(dayNumber390(a.qty)??0),0),physical=row.unit==='Kübel'&&m.pack?.qty>0?' = '+fmt(total*m.pack.qty)+' '+m.pack.unit:'',portion=basis&&m.unit==='Portionen'?' bei '+fmt(convertUnit(basis.qty,basis.unit,'g')??basis.qty)+' '+(convertUnit(basis.qty,basis.unit,'g')!==null?'g':basis.unit)+' je Portion':'';let text=row.unit===m.unit&&m.unit==='Stück'?fmt(total)+' Stück bereitgestellt.':fmt(total)+' '+row.unit+physical+' ergeben rechnerisch ca. '+fmt(s.available)+' '+m.unit+portion+'.';if(s.missing>.01){text+=planned?' Für '+fmt(dayNumber390(target))+' geplante Verkäufe fehlen ca. '+fmt(s.missing)+' '+m.unit+(s.extra!==null&&row.unit!==m.unit?' ('+fmt(s.extra)+' '+row.unit+')':'')+'. Nachproduktion einplanen.':' '+fmt(dayNumber390(target))+' Verkäufe liegen darüber. Nachproduktion oder Portionsgröße prüfen.';}return text}\nfunction dayTotals390(d,planned=false){let revenue=0,food=0,waste=0,own=0,known=true;const errors=[],warnings=[];\n for(const row of d.rows||[]){const m=dayRowEconomics390(row),raw=planned?row.planned:row.sold,n=dayNumber390(raw);if(n===null||!Number.isInteger(n)){known=false;errors.push(m.name+': ganze '+(planned?'geplante':'verkaufte')+' Portionen / Stück eintragen.');continue}if(!m.complete||m.unitCost===null){known=false;errors.push(m.name+': Rezeptkosten fehlen.')}if(m.priceNet===null){known=false;errors.push(m.name+': bestätigter Tagespreis fehlt.')}const actual=!planned&&String(row.revenueNet??'').trim()!==''?dayNumber390(row.revenueNet):n*(m.priceNet??0);if(actual===null){known=false;errors.push(m.name+': Nettoerlös prüfen.')}revenue+=actual??0;food+=n*(m.unitCost??0);\n if(planned&&daySupply395(row,n).missing>.01)warnings.push(m.name+': '+daySupplyText395(row,n,true));\n if(!planned){for(const key of ['waste','own']){const val=String(row[key]??'').trim()===''?0:dayNumber390(row[key]);if(val===null){known=false;errors.push(m.name+': Ausschuss / Eigenverbrauch prüfen.')}else if(key==='waste')waste+=val*(m.unitCost??0);else own+=val*(m.unitCost??0)}const supply=daySupply395(row,n);if(supply.missing>.01)warnings.push(m.name+': '+daySupplyText395(row,n,false));}\n }\n const resources=resourceCostTotals(d.resourceCosts);if(resources.errors.length||resources.unpriced.length){known=false;errors.push(...resources.errors,...resources.unpriced.map(x=>x+': Kostenansatz fehlt.'))}const extra=d.extras||{},vals=['standFee','sharePct','other'].map(k=>dayNumber390(extra[k]??0));if(vals.some(v=>v===null)||vals[1]>100){known=false;errors.push('Standgebühr, Umsatzbeteiligung oder weitere Kosten prüfen.')}const fees=(vals[0]??0)+revenue*(vals[1]??0)/100,other=vals[2]??0,total=food+waste+own+resources.total+fees+other;return {revenue,food,waste,own,resources,fees,other,total,known:known&&(d.rows||[]).length>0,result:known&&d.rows.length?revenue-total:null,errors:[...new Set(errors)],warnings};\n}\nfunction dayDemand390(d){if(d.status==='closed')return [];return (d.rows||[]).map(row=>{const m=dayRowEconomics390(row),provided=dayPortions390(row,row.start),added=(row.additions||[]).map(a=>dayPortions390(row,a.qty));const target=d.planning?dayNumber390(row.planned):null;if(target===null)return null;const already=provided===null?0:provided;return {recipeId:row.recipeId,label:m.name,qty:Math.max(0,target-already-added.reduce((s,x)=>s+(x??0),0)),unit:m.unit}}).filter(r=>r&&r.qty>0)}\nfunction dayEntry390(d,previous={}){return {...clone(previous),id:d.id,event:d.event,date:d.date,type:'Tagesgeschäft',guests:0,lines:[],drinks:[],drinksEnabled:false,dayItems:dayDemand390(d),resourceCosts:clone(d.resourceCosts),revenue:dayTotals390(d,d.status!=='closed'&&(!d.closing405||d.phase<2)).revenue,costs:{},day390:clone(d),workflow:{...(previous.workflow||{}),closed:d.status==='closed'}}}\nfunction dayValidate390(d,close=false){if(!String(d.event||'').trim())return 'Bitte Standort / Bezeichnung eingeben.';if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(d.date||'')||!parseDateISO(d.date))return 'Bitte gültiges Datum eingeben.';if(!(d.rows||[]).length)return 'Mindestens ein Rezept auswählen.';if(new Set(d.rows.map(r=>r.recipeId)).size!==d.rows.length)return 'Jedes Rezept nur einmal auswählen.';for(const row of d.rows){if(!findRecipeById(row.recipeId))return 'Ein verknüpftes Rezept fehlt.';for(const key of ['start','leftover'])if(String(row[key]??'').trim()!==''&&dayNumber390(row[key])===null)return 'Bereitgestellte Menge und Restmenge prüfen.';if((row.additions||[]).some(a=>!(dayNumber390(a.qty)>0)))return 'Nachproduktion prüfen.';if(d.planning&&(dayNumber390(row.planned)===null||!Number.isInteger(dayNumber390(row.planned))))return 'Ganze geplante Portionen / Stück eingeben.';}const rc=resourceCostTotals(d.resourceCosts);if(rc.errors.length)return rc.errors[0];if(close){const x=dayTotals390(d);if(!x.known)return x.errors[0]||'Kalkulation unvollständig.';}return ''}\nfunction dayClosing390(d){const x=dayTotals390(d);return {id:'close-'+d.id,planId390:d.id,date:d.date,location:d.event,savedAt:new Date().toISOString(),items:d.rows.map(row=>{const m=dayRowEconomics390(row),sold=dayNumber390(row.sold)||0;return {recipeId:row.recipeId,sold,prepared:row.unit===m.unit?dayNumber390(row.start):null,leftover:row.unit===m.unit?dayNumber390(row.leftover):null,sellPriceNet:m.priceNet,waste406:{rowId:row.id,recipeId:row.recipeId,name:m.name,qty:dayNumber390(row.waste)||0,unit:m.unit,unitCost:m.unitCost,known:m.complete},economics255:{revenue:String(row.revenueNet??'').trim()===''?sold*m.priceNet:dayNumber390(row.revenueNet),cost:sold*m.unitCost,complete:m.complete}}}),totals390:clone(x)}}\n"},{"src":"day-work390.js?v=406","code":"'use strict';\nlet dayDraft390=null,dayBase390=null,dayBusy390=false,dayCheckpointTimer390=null;\nfunction dayDraftKey390(){return 'zapasio-day390:'+String(typeof cloudConfig==='function'?cloudConfig()?.workspace||'local':'local')+':'+currentAccessUser().id}\nfunction dayCheckpoint390(){clearTimeout(dayCheckpointTimer390);dayCheckpointTimer390=setTimeout(async()=>{if(!dayDraft390)return;try{localStorage.setItem(dayDraftKey390(),JSON.stringify({draft:dayDraft390,base:dayBase390}));await window.deviceStore257?.flush();}catch{showFeedback('Entwurf noch nicht gesichert','warn',5000,'Bitte den Verkaufstag ausdrücklich speichern.')}},350)}\nfunction dayClearCheckpoint390(){clearTimeout(dayCheckpointTimer390);try{localStorage.removeItem(dayDraftKey390())}catch{}}\nfunction dayInput390(label,key,value,index=-1,type='text'){return `<label>${esc(label)}<input data-adapted231=\"day-net\" type=\"${type}\" ${type==='text'&&!['event','note'].includes(key)?'inputmode=\"decimal\"':''} value=\"${esc(value??'')}\" oninput=\"dayField390(${index},'${key}',this.value)\"></label>`}\nfunction dayField390(index,key,value){if(!dayDraft390||dayBusy390||!guardPermission('planning','Tagesgeschäft bearbeiten'))return false;const d=dayDraft390,allowed=index<0?['event','date','note']:['start','planned','sold','revenueNet','leftover','waste','own','unit'];if(!allowed.includes(key)||index>=d.rows.length)return false;const target=index<0?d:d.rows[index];target[key]=value;dayCheckpoint390();if(key==='unit')paintDay390();else{dayUpdateSupply395();if(d.closing405&&typeof dayUpdateExpected405==='function')dayUpdateExpected405();if(d.phase===3||d.phase===0&&d.planning)dayUpdateResult390();}return true}\nfunction daySetPlanning390(on){if(!dayDraft390||dayBusy390||!guardPermission('planning','Verkaufsplanung'))return;dayDraft390.planning=on;dayCheckpoint390();paintDay390()}\nfunction dayScrollTop392(){const dialog=document.getElementById('opsDialog92');if(dialog)dialog.scrollTop=0}\nfunction dayNavigate390(step){if(!dayDraft390||dayBusy390||!guardPermission('planning','Tagesgeschäft'))return;dayDraft390.phase=Math.max(0,Math.min(3,Number(step)||0));dayCheckpoint390();paintDay390();dayScrollTop392()}\nfunction dayAddRecipe390(){if(!dayDraft390||dayBusy390||!guardPermission('planning','Rezept auswählen'))return;const id=document.getElementById('dayRecipeSelect390')?.value;if(!id||!findRecipeById(id)){showFeedback('Bitte Rezept auswählen.','warn');return}if(dayDraft390.rows.some(r=>r.recipeId===id)){showFeedback('Rezept ist bereits enthalten.','info');return}dayDraft390.rows.push(dayRow390(id));dayCheckpoint390();paintDay390()}\nfunction dayRemoveRow390(index){if(!guardPermission('planning','Speise entfernen')||dayBusy390)return;askDeleteConfirm('Speise aus diesem Entwurf entfernen?',()=>{dayDraft390.rows.splice(index,1);dayCheckpoint390();paintDay390()},'Speise entfernen')}\nfunction dayRefreshRow390(index){if(!guardPermission('planning','Aktuelle Rezeptwerte übernehmen')||dayBusy390)return;askConfirm('Aktuelle Rezeptkosten und bestätigten Tagespreis für diese Speise übernehmen? Bisherige Verkäufe und Mengen bleiben erhalten.',()=>{if(dayDraft390?.rows[index]){dayDraft390.rows[index].snapshot=clone(dayLive390(dayDraft390.rows[index].recipeId));dayCheckpoint390();paintDay390()}},'Werte übernehmen','Rezeptwerte aktualisieren')}\nfunction dayProduce390(index){if(!dayDraft390||dayBusy390||!guardPermission('planning','Nachproduktion'))return;const row=dayDraft390.rows[index],n=dayNumber390(document.getElementById('dayAdd390_'+index)?.value);if(!row||!(n>0)){showFeedback('Bitte gültige zusätzliche Menge eingeben.','warn');return}row.additions.push({id:dayId390(),qty:n,at:new Date().toISOString()});dayCheckpoint390();paintDay390()}\nfunction dayUndoProduction390(index,id){if(!guardPermission('planning','Nachproduktion korrigieren')||dayBusy390)return;askConfirm('Diesen zusätzlichen Mengen-Eintrag aus dem Entwurf entfernen? Bestehende Lagerbuchungen werden nicht verändert.',()=>{dayDraft390.rows[index].additions=dayDraft390.rows[index].additions.filter(x=>x.id!==id);dayCheckpoint390();paintDay390()},'Eintrag entfernen','Nachproduktion korrigieren')}\nfunction dayCostField390(index,key,value){if(!dayDraft390||dayBusy390||!guardPermission('planning','Tageskosten'))return;const d=dayDraft390;if(index<0){if(['standFee','sharePct','other'].includes(key))d.extras[key]=value;else if(key==='km')d.resourceCosts.km=value;else return}else{const r=d.resourceCosts.rows[index];if(!r)return;if(key==='hours'&&r.kind==='labor'){r.count=1;r.work=value;for(const k of ['prep','travelHours','after','overtime'])r[k]=0;}else if(key==='qty'&&r.kind!=='labor')r.qty=value;else return}dayCheckpoint390();dayUpdateResult390()}\nfunction dayCostsHtml390(){const d=dayDraft390;return `<details class=\"dayCard390\"><summary>${dayWords390('Personal, Fahrt und weitere Tageskosten','Staff, travel and other daily costs','Personel, dojazd i inne koszty dnia')}</summary><div class=\"dayGrid390\">${d.resourceCosts.rows.map((r,i)=>{const hours=(Number(r.count)||0)*(['prep','work','travelHours','after','overtime'].reduce((s,k)=>s+(Number(r[k])||0),0));return `<label>${esc(r.name)} · ${r.kind==='labor'?'gesamte Arbeitsstunden (h)':'Menge'}<input data-adapted231=\"day-net\" inputmode=\"decimal\" value=\"${esc(r.kind==='labor'?hours:r.qty??0)}\" oninput=\"dayCostField390(${i},'${r.kind==='labor'?'hours':'qty'}',this.value)\"><small>${moneyFmt(Number(String(r.rate).replace(',','.'))||0)} ${r.kind==='labor'?'netto / h':'netto / Einheit'}</small></label>`}).join('')}<label>Gesamtkilometer hin und zurück (km)<input data-adapted231=\"day-net\" inputmode=\"decimal\" value=\"${esc(d.resourceCosts.km??0)}\" oninput=\"dayCostField390(-1,'km',this.value)\"><small>${moneyFmt(costNumber(d.resourceCosts.kmRate)||0)} netto / km</small></label>${['standFee','sharePct','other'].map((k,i)=>`<label>${['Standgebühr netto (€)','Umsatzbeteiligung vom Nettoerlös (%)','Weitere Kosten netto (€)'][i]}<input data-adapted231=\"day-net\" inputmode=\"decimal\" value=\"${esc(d.extras[k]??0)}\" oninput=\"dayCostField390(-1,'${k}',this.value)\"></label>`).join('')}</div><p class=\"muted\">Gesamte Arbeitsstunden aller eingesetzten Personen einschließlich Vorbereitung, Fahrt und Nachbereitung. Sätze stammen aus Einstellungen → Kostensätze.</p></details>`}\nfunction dayRowsHtml390(){const d=dayDraft390;return d.rows.map((row,i)=>{const m=dayRowEconomics390(row),live=dayLive390(row.recipeId),total=(dayNumber390(row.start)??0)+row.additions.reduce((s,x)=>s+x.qty,0);let fields='';if(d.phase===0)fields=`<div class=\"dayGrid390\">${dayInput390('Bereitgestellte Menge','start',row.start,i)}<label>Einheit<select onchange=\"dayField390(${i},'unit',this.value)\">${[...new Set([m.unit,'Kübel','kg','Liter'])].map(u=>`<option ${row.unit===u?'selected':''}>${esc(u)}</option>`).join('')}</select></label>${d.planning?dayInput390('Geplante Verkäufe · Portionen / Stück','planned',row.planned,i):''}</div>`;\n if(d.phase===1)fields=`<p><b>${esc(formatQtySmart(total,row.unit))}</b> bereitgestellt / nachgelegt</p><div class=\"dayActions390\"><label>Weitere ${esc(row.unit)}<input id=\"dayAdd390_${i}\" inputmode=\"decimal\" placeholder=\"Menge\"></label><button type=\"button\" class=\"soft\" onclick=\"dayProduce390(${i})\">+ Menge ergänzen</button></div>${row.additions.map(a=>`<div class=\"dayLine390\"><span>${esc(formatQtySmart(a.qty,row.unit))} nachgelegt</span><button class=\"soft\" type=\"button\" onclick=\"dayUndoProduction390(${i},'${escJs(a.id)}')\">Korrigieren</button></div>`).join('')}`;\n if(d.phase===2)fields=`<div class=\"dayGrid390\">${dayInput390('Verkauft · Portionen / Stück','sold',row.sold,i)}${!d.closing405?dayInput390('Abweichender Nettoerlös gesamt (€) · optional','revenueNet',row.revenueNet,i):''}</div>${!d.closing405?'<p class=\"muted\">Ohne abweichenden Erlös: verkaufte Menge × bestätigter Tagespreis. Bei Rabatten oder unterschiedlichen Preisen den tatsächlichen Nettoerlös eintragen.</p>':''}<details><summary>Reste, Ausschuss und Eigenverbrauch · optional</summary><div class=\"dayGrid390\">${dayInput390('Weiterverwendbarer Rest ('+row.unit+')','leftover',row.leftover,i)}${dayInput390('Ausschuss · übliche Portionen / Stück','waste',row.waste,i)}${dayInput390('Eigenverbrauch · übliche Portionen / Stück','own',row.own,i)}</div><p class=\"muted\">Weiterverwendbare Reste werden nicht als Tagesverbrauch gerechnet.</p></details>`;\n return `<section class=\"dayCard390\"><div class=\"dayHeading390\"><h3>${esc(m.name)}</h3>${d.phase===0?`<button class=\"soft\" type=\"button\" onclick=\"dayRemoveRow390(${i})\">Entfernen</button>`:''}</div><p class=\"dayReadOnly390\">Tagespreis netto: <b>${m.priceNet!==null?moneyFmt(m.priceNet):'Noch offen'}</b> · Rezeptkosten / ${esc(m.unit)}: <b>${m.complete?moneyFmt(m.unitCost):'Unvollständig'}</b></p>${live.warning?dayPriceWarning390(row.recipeId):''}${row.snapshot&&((live.priceVersion!==m.priceVersion)||live.basis!==m.basis||Math.abs((live.unitCost??0)-(m.unitCost??0))>.00005||JSON.stringify(live.pack)!==JSON.stringify(m.pack)||JSON.stringify(live.container403)!==JSON.stringify(m.container403))?`<p class=\"dayWarning390\">Dieser Einsatz behält seinen gespeicherten Preis- und Kostenstand. <button type=\"button\" class=\"soft\" onclick=\"dayRefreshRow390(${i})\">Aktuelle Werte übernehmen</button></p>`:''}${fields}<div id=\"daySupply395_${i}\" aria-live=\"polite\">${daySupplyHtml395(row,i)}</div></section>`}).join('')}\nfunction dayResultHtml390(d,planned=false){const x=dayTotals390(d,planned),rows=[['Speisen · Rezeptansatz',x.food],...(x.additionalFood!==undefined&&(x.additionalFood>0||d.extraSales405?.length)?[['Weitere Verkäufe · Warenkosten',x.additionalFood]]:[]),['Ausschuss · Rezeptansatz',x.waste],['Eigenverbrauch · Rezeptansatz',x.own],['Personal',x.resources.personnel],['Fahrtkosten',x.resources.travel],['Ausstattung',x.resources.rental],['Sonstige Kostenvorgaben',x.resources.other],['Standgebühr & Beteiligung',x.fees],['Weitere Kosten',x.other]];return `<section class=\"dayResult390\"><div><div class=\"dayMetrics390\"><div><small>${planned?'Geplanter':'Tatsächlicher'} Nettoerlös</small><b>${moneyFmt(x.revenue)}</b></div><div><small>Erfasste / kalkulierte Kosten</small><b>${moneyFmt(x.total)}</b></div></div><div class=\"dayHero390 ${!x.known?'open':x.result<0?'negative':''}\"><span>${planned?'Geplantes Ergebnis':'Für diesen Tag übrig'}</span><strong>${x.known?moneyFmt(x.result):'Kalkulation unvollständig'}</strong>${x.known&&x.revenue>0?`<small>${(x.result/x.revenue*100).toFixed(1).replace('.',',')} % vom Nettoerlös</small>`:''}</div><p class=\"muted\">Kalkuliert mit üblichen Portionskosten. Weitere nicht erfasste Fixkosten und Steuern sind nicht abgezogen.</p>${x.errors.length?`<ul class=\"dayWarning390\">${x.errors.map(e=>`<li>${esc(e)}</li>`).join('')}</ul>`:''}${x.warnings.length?`<ul>${x.warnings.map(e=>`<li>${esc(e)}</li>`).join('')}</ul>`:''}</div><div><h3>Wo entstehen die Kosten?</h3><dl>${rows.filter(([,n],i)=>i===0||n!==0).map(([name,n])=>`<div><dt>${name}</dt><dd>${moneyFmt(n)}</dd></div>`).join('')}<div><dt><b>Gesamtkosten</b></dt><dd>${moneyFmt(x.total)}</dd></div></dl></div></section>`}\nfunction daySupplyHtml395(row,index){const d=dayDraft390;if(String(row.start??'').trim()==='')return '';const target=d.phase<2&&d.planning?row.planned:d.phase>=2?row.sold:null,s=daySupply395(row,target),unknown=s.available===null,text=daySupplyText395(row,target,d.phase<2);return '<p class=\"'+(unknown||s.missing>.01?'dayWarning390':'dayReadOnly390')+'\">'+esc(text)+(unknown&&hasPermission('settings')?' <button type=\"button\" class=\"soft\" onclick=\"openDayPriceCalc390(\\''+escJs(row.recipeId)+'\\')\">Rezept / Kübelinhalt prüfen</button>':'')+'</p>'+(row.unit==='Stück'&&dayRowEconomics390(row).unit==='Stück'?'':'<small>Richtwert aus dem Rezept. Tatsächliche Portionen dürfen schwanken.</small>')}\nfunction dayUpdateSupply395(){for(const [i,row] of (dayDraft390?.rows||[]).entries()){const el=document.getElementById('daySupply395_'+i);if(el)el.innerHTML=daySupplyHtml395(row,i)}}\nfunction dayUpdateResult390(){const box=document.getElementById('dayResult390');if(box&&dayDraft390)box.innerHTML=dayResultHtml390(dayDraft390,dayDraft390.status!=='closed'&&dayDraft390.phase<2)}\nfunction paintDay390(){if(!dayDraft390||!guardPermission('planning','Tagesgeschäft ansehen'))return;const d=dayDraft390,names=['Vorbereiten','Verkauf läuft','Tagesabschluss','Ergebnis'];const add=`<div class=\"dayActions390\"><label>Gericht aus Rezepten<select id=\"dayRecipeSelect390\">${recipeChoices104()}</select></label><button type=\"button\" class=\"soft\" onclick=\"dayAddRecipe390()\">+ Gericht hinzufügen</button></div>`;openOpsPanel(dayWords390('Tagesgeschäft','Daily business','Sprzedaż dzienna'),`<div class=\"day390\"><nav class=\"daySteps390\" aria-label=\"Tagesablauf\">${names.map((n,i)=>`<button type=\"button\" class=\"${d.phase===i?'primary':'soft'}\" ${d.phase===i?'aria-current=\"step\"':''} onclick=\"dayNavigate390(${i})\">${i+1}. ${n}</button>`).join('')}</nav><p class=\"dayDraftStatus390\" role=\"status\">${d.status==='closed'?'Gespeicherter Tagesabschluss · Änderungen erst mit erneutem Speichern wirksam':'Einsatzentwurf · Änderungen werden auf diesem Gerät zwischengesichert'}</p>${d.phase===0?`<h2>Was nimmst du mit?</h2><div class=\"dayGrid390\">${dayInput390('Standort / Bezeichnung','event',d.event)}${dayInput390('Datum','date',d.date,-1,'date')}</div><label class=\"dayCheck390\"><input type=\"checkbox\" ${d.planning?'checked':''} onchange=\"daySetPlanning390(this.checked)\"><span><b>Verkäufe vorher schätzen</b><small>Optional: geplante Portionen für eine Vorschau eingeben. Die tatsächlichen Verkäufe erfasst du am Tagesende.</small></span></label><p>Rezeptwerte und bestätigte Tagespreise werden übernommen.</p>`:d.phase===1?'<h2>Nur Nachproduktion ergänzen</h2><p>Die Verkäufe werden gesammelt am Tagesende erfasst.</p>':d.phase===2?'<h2>Was wurde verkauft?</h2>':'<h2>Dein Tagesergebnis</h2>'}${d.phase===2&&d.closing405?dayCashHtml405(d):''}${d.phase<3?dayRowsHtml390():`<div id=\"dayResult390\" aria-live=\"polite\">${dayResultHtml390(d)}</div>${d.planning?`<details class=\"dayCard390\"><summary>Vergleich mit der Verkaufsplanung</summary>${dayResultHtml390(d,true)}</details>`:''}`}${d.phase===0?add:''}${d.phase===2&&d.closing405?dayExtraHtml405(d):''}${[0,2].includes(d.phase)?dayCostsHtml390():''}${d.phase===0&&d.planning?`<details class=\"dayCard390\"><summary>Geplantes Ergebnis ansehen</summary><div id=\"dayResult390\">${dayResultHtml390(d,true)}</div></details>`:''}${d.phase===3?'<p class=\"dayInfo390\">Der Abschluss erfasst Verkäufe und kalkulierte Kosten. Er löst keine Lagerbuchung aus. Bestehende Produktions- und Warenbuchungen bleiben getrennt nachvollziehbar.</p>':''}<div class=\"dayFooter390\"><button type=\"button\" class=\"soft\" onclick=\"saveDay390(false)\">Zwischenstand speichern</button>${d.phase<3?`<button type=\"button\" class=\"primary\" onclick=\"dayNavigate390(${d.phase+1})\">${['Zum Verkauf','Zum Tagesabschluss','Ergebnis prüfen'][d.phase]}</button>`:`<button type=\"button\" class=\"primary\" onclick=\"saveDay390(true)\">Tagesabschluss bestätigen</button>`}</div><div class=\"dayActions390\"><button class=\"soft\" type=\"button\" onclick=\"dayShopping390()\">Einkaufsliste</button><button class=\"soft\" type=\"button\" onclick=\"openDayPriceSettings390()\">Tagespreise ansehen</button></div></div>`);dayScrollTop392()}\nfunction openDay390(id=''){if(state.subscriptionPlan!=='complete'){showFeedback('Im Paket Zapasio Komplett verfügbar','warn');return false}if(dayBusy390||!guardPermission('planning','Tagesgeschäft'))return false;const c=id?(state.planning?.caterings||[]).find(c=>c.id===id):null;if(id&&!c){showFeedback('Einsatz nicht gefunden.','warn');return false}if(c&&!c.day390){showFeedback('Bestehender Tages-Einsatz bleibt im bisherigen Ablauf.','info');return editBeforeDay390(id)}const checkpoint=safeReadStore(dayDraftKey390());dayBase390=c?JSON.stringify(c):null;if(checkpoint?.draft&&(id?checkpoint.draft.id===id:!checkpoint.base)&&checkpoint.base===dayBase390){dayDraft390=clone(checkpoint.draft)}else dayDraft390=c?clone(c.day390):dayFresh390();if(c?.status390==='cancelled'){showFeedback('Dieser Einsatz wurde abgesagt. Zuerst in der Historie wieder aktivieren.','warn');return false}paintDay390();return true}\nfunction saveDay390(close=false){if(state.subscriptionPlan!=='complete')return false;if(dayDraft390?.status==='closed')close=true;if(dayBusy390||!dayDraft390||!guardPermission('planning','Tagesgeschäft speichern')||(close&&!guardPermission('calc','Tagesabschluss')))return false;const d=clone(dayDraft390),error=dayValidate390(d,close);if(error){showFeedback(error,'warn',5000);return false}for(const row of d.rows)if(!row.snapshot)row.snapshot=clone(dayLive390(row.recipeId));d.status=close?'closed':d.status==='closed'?'closed':d.phase>0?'running':'draft';if(!close&&d.status==='closed')d.status='draft';const baseline=dayBase390;dayBusy390=true;return afterOperation275(operationTransaction(close?'Tagesabschluss gespeichert':'Tagesgeschäft gespeichert','planning',()=>{const old=(state.planning?.caterings||[]).find(c=>c.id===d.id);if((old?JSON.stringify(old):null)!==baseline)throw Error('Der Einsatz wurde inzwischen geändert. Gespeicherten Stand neu öffnen; dein Entwurf bleibt erhalten.');const e=dayValidate390(d,close);if(e)throw Error(e);state.planning??={caterings:[]};state.planning.caterings??=[];const entry=dayEntry390(d,old);entry.updatedAt390=new Date().toISOString();state.planning.caterings=state.planning.caterings.filter(c=>c.id!==d.id);state.planning.caterings.push(entry);if(close){state.dailyClosings=(state.dailyClosings||[]).filter(c=>c.planId390!==d.id);state.dailyClosings.push(dayClosing390(d));}}),ok=>{dayBusy390=false;if(ok){const saved=state.planning.caterings.find(c=>c.id===d.id);dayDraft390=clone(saved.day390);dayBase390=JSON.stringify(saved);dayClearCheckpoint390();paintDay390();showFeedback(close?'Tagesabschluss gespeichert':'Zwischenstand gespeichert','success')}return ok})}\nfunction dayShopping390(){if(!dayDraft390||!guardPermission('shopping','Einkaufsliste'))return false;const d=dayDraft390;shoppingPreview388=dayEntry390(d);openOpsPanel('Einkauf für das Tagesgeschäft',`<div class=\"day390\"><p>Zusätzliche Vorbereitung aus der freiwilligen Verkaufsplanung, abzüglich erfasster bereitgestellter Mengen. Lagerbestand wird von der bestehenden Einkaufsermittlung berücksichtigt. Ohne Planmengen wird kein Bedarf erfunden.</p>${d.rows.some(r=>r.unit==='Kübel'&&!dayRowEconomics390(r).pack)?'<p class=\"dayWarning390\">Kübelinhalt fehlt: Diese bereitgestellten Mengen konnten nicht vom geplanten Portionsbedarf abgezogen werden. Bedarf vor dem Einkauf prüfen.</p>':''}${eventBuyList387(shoppingPreview388)}<button type=\"button\" class=\"soft\" onclick=\"printShoppingPreview388()\">Einkaufsliste drucken</button><button type=\"button\" class=\"primary\" onclick=\"paintDay390()\">Zurück zum Tag</button></div>`);return true}\nconst newBeforeDay390=newPlanningEntry;\nnewPlanningEntry=function(type){if(!guardPermission('planning','Einsatz anlegen'))return false;if(type==='Catering'){document.getElementById('opsDialog92')?.close();return newBeforeDay390()}if(type==='Tagesgeschäft')return openDay390();openOpsPanel('Was planst du?',`<div class=\"day390 dayChoice294\"><p class=\"dayChoiceIntro294\">Wähle den passenden Ablauf.</p><div class=\"dayChoiceGrid294\"><button type=\"button\" class=\"soft dayChoiceCard294\" onclick=\"newPlanningEntry('Tagesgeschäft')\"><b>Tagesgeschäft / Verkaufstag</b><span>Portionen verkaufen und den Tag abrechnen.</span></button><button type=\"button\" class=\"soft dayChoiceCard294\" onclick=\"newPlanningEntry('Catering')\"><b>Catering / Veranstaltung</b><span>Für Gäste planen und einen Auftrag kalkulieren.</span></button></div><div class=\"dayChoiceSecondary294\">${safeReadStore(dayDraftKey390())?.draft?'<button type=\"button\" class=\"soft\" onclick=\"resumeDay390()\">Gesicherten Tagesentwurf fortsetzen</button>':''}<button type=\"button\" class=\"soft\" onclick=\"showCancelled390()\">Abgesagte Einsätze ansehen</button></div></div>`);return true};\nfunction resumeDay390(){const saved=safeReadStore(dayDraftKey390());if(!saved?.draft)return;return openDay390(saved.base?saved.draft.id:'')}\nconst editBeforeDay390=editCateringPlan;\neditCateringPlan=function(id,...args){return state.planning?.caterings?.find(c=>c.id===id)?.day390?openDay390(id):editBeforeDay390.call(this,id,...args)};\nconst chooseBeforeDay390=selectPlanningEntry;\nselectPlanningEntry=function(id,section='overview'){const c=state.planning?.caterings?.find(c=>c.id===id);if(c?.day390){activeCateringId=id;chooseBeforeDay390.call(this,id,section);return openDay390(id)}return chooseBeforeDay390(id,section)};\nconst closeBeforeDay390=openDailyClose;\nopenDailyClose=function(date=localDateKey(),...args){const active=activePlanningEntry();if(active?.day390&&active.date===date){if(!openDay390(active.id))return false;dayNavigate390(2);return true}return closeBeforeDay390(date,...args)};\nconst calcBeforeDay390=cateringCalc;\ncateringCalc=function(c){if(!c?.day390)return calcBeforeDay390(c);const live=cateringCalcLive({...c,day390:undefined}),x=dayTotals390(c.day390,c.day390.status!=='closed'&&(!c.day390.closing405||c.day390.phase<2));return {...live,autoCost:x.food,drinkCost:0,total:x.total,revenue:x.revenue,contribution:x.result??0,margin:x.known&&x.revenue>0?x.result/x.revenue*100:0,resources:x.resources,dayIncomplete390:!x.known}}\nconst calcHtmlBeforeDay390=calcHtml;\ncalcHtml=function(c){return c?.day390?dayResultHtml390(c.day390,c.day390.status!=='closed'&&(!c.day390.closing405||c.day390.phase<2)):calcHtmlBeforeDay390(c)};\n"},{"src":"day-prices390.js?v=394","code":"'use strict';\nlet dayPriceBusy390=false,dayPriceReview390=null;\nfunction dayWords390(de,en,pl){return hubText362(de,en,pl)}\nfunction dayPriceAction390(id){return `openDayPriceCalc390('${escJs(id)}')`}\nfunction dayPriceWarning390(id){const m=dayLive390(id);if(!m.warning)return '';return `<div class=\"dayWarning390\" role=\"status\"><b>${esc(m.name)}: ${esc(m.warning)}</b>${m.changed&&m.complete?`<span>${dayWords390('Portionskosten bisher','Previous portion cost','Poprzedni koszt porcji')} ${moneyFmt(m.previousCost)} → ${moneyFmt(m.unitCost)}. ${dayWords390('Deckungsbeitrag vor weiteren Kosten','Contribution before other costs','Marża przed innymi kosztami')}: ${moneyFmt(m.priceNet-m.previousCost)} → ${moneyFmt(m.priceNet-m.unitCost)}.</span>`:''}${hasPermission('settings')?`<button type=\"button\" class=\"soft\" onclick=\"${esc(dayPriceAction390(id))}\">${dayWords390('Kalkulation prüfen','Review costing','Sprawdź kalkulację')}</button>`:''}</div>`}\nfunction openDayPriceSettings390(){if(!guardPermission('settings','Tagespreise'))return false;document.getElementById('opsDialog92')?.close();showTab('settings');if(typeof activateSettingsGroup130==='function')activateSettingsGroup130('planning',false);mountDaySettings390();const el=document.getElementById('dayPriceSettings390');if(el){document.getElementById('dayPricesBody390').innerHTML=dayPricesHtml390();el.open=true;el.scrollIntoView({block:'start'})}return true}\nfunction dayPriceInput393(value){const language=typeof business130==='function'?business130().language:'de';return typeof value==='number'?new Intl.NumberFormat(language==='en'?'en-GB':language==='pl'?'pl-PL':'de-DE',{useGrouping:false,minimumFractionDigits:2,maximumFractionDigits:2}).format(value):value}\nfunction dayPriceForm390(id,proposal){const r=findRecipeById(id)?.recipe;if(!r)return '<p>Rezept nicht gefunden.</p>';const e=recipeEconomicMeta(r),m=dayLive390(id),suggestion=proposal!==undefined?proposal:r.dayPrice390?.priceNet??(e.recommended!==null?e.recommended:(Number(r.sellPriceNet)>0?r.sellPriceNet:''));return `<div class=\"day390\"><p>${dayWords390('Rezept → Warenkalkulation → bestätigen → Tagespreis → Planung','Recipe → costing → approval → daily price → planning','Przepis → kalkulacja → zatwierdzenie → cena → plan')}</p>${stripBefore390(r)}${dayPriceWarning390(id)}<section class=\"dayCard390\"><h3>${dayWords390('Verbindlichen Tagespreis festlegen','Approve daily selling price','Ustal zatwierdzoną cenę')}</h3><p>${dayWords390('Dieser Nettopreis wird erst nach deiner Bestätigung bei neuen Verkaufstagen verwendet.','New sales days use this net price only after your approval.','Nowe dni sprzedaży użyją ceny netto po zatwierdzeniu.')}</p><label>${dayWords390('Verkaufspreis netto je Portion / Stück (€)','Net selling price per portion / item (€)','Cena netto za porcję / sztukę (€)')}<input data-adapted231=\"day-net\" id=\"dayPriceValue390\" inputmode=\"decimal\" value=\"${esc(dayPriceInput393(suggestion))}\"></label><details><summary>${dayWords390('Kübelinhalt einmal hinterlegen · optional','Set bucket contents once · optional','Ustaw zawartość pojemnika · opcjonalnie')}</summary><div class=\"dayGrid390\"><label>${dayWords390('Ein Kübel enthält','One bucket contains','Jeden pojemnik zawiera')}<input data-adapted231=\"day-net\" id=\"dayPackQty390\" inputmode=\"decimal\" value=\"${esc(r.dayPack390?.qty??'')}\"></label><label>${dayWords390('Einheit','Unit','Jednostka')}<select id=\"dayPackUnit390\">${['kg','Liter','Portionen','Stück'].map(u=>`<option ${r.dayPack390?.unit===u?'selected':''}>${u}</option>`).join('')}</select></label></div><p>${dayWords390('Nur mit bestätigtem Inhalt wird ein Kübel in Portionen umgerechnet. Die Portionsgröße kommt aus dem Rezept.','Bucket-to-portion conversion requires confirmed contents. Portion size comes from the recipe.','Przeliczenie pojemnika na porcje wymaga podanej zawartości i wielkości porcji z przepisu.')}</p></details><button type=\"button\" class=\"primary\" onclick=\"confirmDayPrice390('${escJs(id)}')\">${dayWords390('Als Tagespreis übernehmen','Approve as daily price','Zatwierdź cenę')}</button>${!m.complete?`<p class=\"dayWarning390\">${dayWords390('Zuerst Rezeptkosten und Portionsbasis vervollständigen.','Complete recipe costs and portion basis first.','Najpierw uzupełnij koszty i wielkość porcji.')}</p>`:''}</section>${dayDraft390?`<button class=\"soft\" type=\"button\" onclick=\"paintDay390()\">${dayWords390('Zurück zum Tagesgeschäft','Back to daily business','Wróć do dnia sprzedaży')}</button>`:''}</div>`}\nfunction openDayPriceCalc390(id,proposal){if(!guardPermission('settings','Tagespreis kalkulieren'))return false;const reviewed=dayLive390(id);dayPriceReview390={id,basis:reviewed.basis,unitCost:reviewed.unitCost,priceVersion:reviewed.priceVersion};openOpsPanel(dayWords390('Tagespreis kalkulieren','Daily price calculation','Kalkulacja ceny'),dayPriceForm390(id,proposal));return true}\nfunction confirmDayPrice390(id){if(dayPriceBusy390||!guardPermission('settings','Tagespreis bestätigen'))return false;const price=dayNumber390(document.getElementById('dayPriceValue390')?.value),raw=document.getElementById('dayPackQty390')?.value||'',packQty=dayNumber390(raw),unit=document.getElementById('dayPackUnit390')?.value||'kg';if(price===null){showFeedback('Bitte gültigen Nettopreis eingeben.','warn');return false}if(raw&&!(packQty>0)){showFeedback('Kübelinhalt prüfen.','warn');return false}dayPriceBusy390=true;return afterOperation275(operationTransaction(dayWords390('Tagespreis bestätigt','Daily price approved','Cena zatwierdzona'),'settings',()=>{const r=findRecipeById(id)?.recipe,m=dayLive390(id);if(!r||!m.complete)throw Error('Rezeptkosten oder Portionsbasis noch unvollständig.');if(!dayPriceReview390||dayPriceReview390.id!==id||dayPriceReview390.basis!==m.basis||dayPriceReview390.unitCost!==m.unitCost||dayPriceReview390.priceVersion!==m.priceVersion)throw Error('Rezeptkosten oder Tagespreis haben sich inzwischen geändert. Kalkulation erneut öffnen und prüfen.');const before=r.dayPrice390;if(before){r.dayPriceHistory390??=[];r.dayPriceHistory390.push(clone(before))}r.dayPrice390={id:dayId390(),priceNet:price,unitCost:m.unitCost,basis:dayBasis390(r),approvedAt:new Date().toISOString(),approvedBy:currentAccessUser().id};r.sellPriceNet=price;if(packQty>0)r.dayPack390={qty:packQty,unit};else delete r.dayPack390;}),ok=>{dayPriceBusy390=false;if(ok){document.getElementById('opsDialog92')?.close();openDayPriceSettings390();showFeedback('Tagespreis bestätigt','success',4000,'Neue Verkaufstage verwenden diesen Preis. Bestehende Einsätze behalten ihre Werte.')}return ok})}\nfunction dayPricesHtml390(){return `<p>${dayWords390('Rezepte und Portionskosten werden automatisch verknüpft. Verkaufspreise gelten nach ausdrücklicher Bestätigung; Änderungen wirken nicht rückwirkend.','Recipes and portion costs are linked. Prices require approval and do not change past events.','Przepisy i koszty porcji są połączone. Ceny wymagają zatwierdzenia i nie zmieniają historii.')}</p>${dayRecipes390().map(({recipe:r})=>{const m=dayLive390(r.id);return `<section class=\"dayPriceRow390\"><div><b>${esc(r.name)}</b><small>${dayWords390('Bestätigter Tagespreis netto','Approved daily net price','Zatwierdzona cena netto')}: ${m.priceNet!==null?moneyFmt(m.priceNet):dayWords390('Noch offen','Not set','Brak ceny')} · ${dayWords390('Aktuelle Portionskosten','Current portion cost','Aktualny koszt porcji')}: ${m.complete?moneyFmt(m.unitCost):dayWords390('Unvollständig','Incomplete','Niepełne')}</small></div><button type=\"button\" class=\"soft\" onclick=\"${esc(dayPriceAction390(r.id))}\">${dayWords390('Kalkulieren / bestätigen','Calculate / approve','Oblicz / zatwierdź')}</button>${dayPriceWarning390(r.id)}</section>`}).join('')||'<p>Bitte zuerst ein Rezept anlegen.</p>'}`}\nfunction mountDaySettings390(){const root=document.getElementById('tabSettings');if(!root)return;const host=document.getElementById('settingsPanel130_planning')||root;let prices=document.getElementById('dayPriceSettings390');if(!prices){prices=document.createElement('details');prices.id='dayPriceSettings390';prices.className='card settingsFold';prices.innerHTML=`<summary>${dayWords390('Tagespreise','Daily selling prices','Ceny dzienne')}</summary><div id=\"dayPricesBody390\"></div>`;host.appendChild(prices)}if(prices.parentElement!==host)host.appendChild(prices);prices.ontoggle=()=>{if(prices.open)document.getElementById('dayPricesBody390').innerHTML=dayPricesHtml390()};if((host.classList.contains('active')||prices.open)&&!document.activeElement?.closest?.('#dayPriceSettings390'))document.getElementById('dayPricesBody390').innerHTML=dayPricesHtml390();let display=document.getElementById('quantitySettings390');if(!display){display=document.createElement('details');display.id='quantitySettings390';display.className='card settingsFold';host.appendChild(display)}if(display.parentElement!==host)host.appendChild(display);if(document.activeElement?.closest?.('#quantitySettings390'))return;display.innerHTML=`<summary>${dayWords390('Mengenanzeige','Quantity display','Wyświetlanie ilości')}</summary><div class=\"day390\"><p>${dayWords390('Einheitliche Anzeige auf Arbeitslisten. Eingaben, Bedarf, Preise und Lagerbuchungen bleiben exakt.','Consistent quantity display on work lists. Inputs, demand, costing and stock bookings remain exact.','Jednolity widok ilości na listach. Dane, zapotrzebowanie, koszty i ruchy magazynowe pozostają dokładne.')}</p><label>${dayWords390('Mengen anzeigen','Display quantities','Wyświetlanie ilości')}<select onchange=\"setQuantityMode390(this.value)\"><option value=\"exact\" ${state.businessSettings?.quantityMode390!=='rounded'?'selected':''}>${dayWords390('Genau · z. B. 1,5 Flaschen','Exact · e.g. 1.5 bottles','Dokładnie · np. 1,5 butelki')}</option><option value=\"rounded\" ${state.businessSettings?.quantityMode390==='rounded'?'selected':''}>${dayWords390('Aufgerundet · z. B. 2 Flaschen','Rounded up · e.g. 2 bottles','W górę · np. 2 butelki')}</option></select></label><p>${dayWords390('Auf ganze angezeigte Einheiten. Standard: genaue Mengen.','Round up to whole displayed units. Default: exact quantities.','Zaokrąglanie do pełnych wyświetlanych jednostek. Domyślnie: dokładne ilości.')}</p></div>`}\nfunction setQuantityMode390(mode){if(!['exact','rounded'].includes(mode)||!guardPermission('settings','Mengenanzeige'))return false;return afterOperation275(operationTransaction('Mengenanzeige gespeichert','settings',()=>{state.businessSettings??={};state.businessSettings.quantityMode390=mode;}),ok=>{mountDaySettings390();return ok})}\n// Quantity display only: callers' numbers and procurement recommendations remain unchanged.\nconst qtyBefore390=formatQtySmart;\nformatQtySmart=function(q,u,options){if(options?.exact||state.businessSettings?.quantityMode390!=='rounded')return qtyBefore390(q,u);let n=Number(q)||0,unit=String(u||''),nu=normalizeUnit(unit);if(nu==='g'&&Math.abs(n)>=1000){n/=1000;unit='kg'}else if(nu==='ml'&&Math.abs(n)>=1000){n/=1000;unit='Liter'}return qtyBefore390(Math.ceil(n-1e-9),unit)};\nconst eventBuyBefore390=eventBuyRows387;\neventBuyRows387=function(c){if(state.businessSettings?.quantityMode390==='rounded')return eventBuyBefore390(c);return procurementFor(c).filter(r=>Number(r.qty)>0).map(r=>({...r,buyLabel387:qtyBefore390(r.qty,r.unit)}))};\nconst stripBefore390=recipeCostStripHtml;\nrecipeCostStripHtml=function(r){return stripBefore390(r)+`<div class=\"dayRecipePrice390\">${dayPriceWarning390(r.id)}${hasPermission('settings')?`<button class=\"soft\" type=\"button\" onclick=\"${esc(dayPriceAction390(r.id))}\">${dayWords390('Tagespreis kalkulieren / bestätigen','Calculate / approve daily price','Oblicz / zatwierdź cenę')}</button>`:''}</div>`};\nconst foodBefore390=updateFoodCalc143;\nfunction reviewFoodDayPrice393(){if(typeof drinkMode189!=='undefined'&&drinkMode189==='drink')return false;if(!guardPermission('settings','Tagespreis übernehmen'))return false;const id=document.getElementById('foodRecipe145')?.value||document.getElementById('foodRecipe143')?.value,input=document.getElementById('foodNetPer145');if(!id)return false;const proposal=input?dayNumber390(input.value):undefined;if(input&&(proposal===null||input.formAdapter231?.box?.dataset?.invalid231)){showFeedback('Bitte einen gültigen Netto-Verkaufspreis je Portion eingeben.','warn');input.focus?.();return false}return openDayPriceCalc390(id,proposal)}\nupdateFoodCalc143=function(...args){const result=foodBefore390.apply(this,args),id=document.getElementById('foodRecipe145')?.value||document.getElementById('foodRecipe143')?.value,box=document.getElementById('foodMetrics145')||document.getElementById('foodResult143');if(id&&box&&hasPermission('settings')&&!(typeof drinkMode189!=='undefined'&&drinkMode189==='drink')){document.getElementById('takeDayPrice393')?.remove();box.insertAdjacentHTML('afterend',`<div id=\"takeDayPrice393\" class=\"dayPriceTake393\"><button type=\"button\" class=\"primary\" onclick=\"reviewFoodDayPrice393()\">${dayWords390('Als Tagespreis übernehmen','Approve as daily price','Zatwierdź cenę dzienną')}</button><small>${dayWords390('Den eingetragenen Netto-Verkaufspreis prüfen und anschließend bestätigen.','Review the entered net selling price, then confirm it.','Sprawdź wpisaną cenę netto, a następnie ją zatwierdź.')}</small></div>`)}else document.getElementById('takeDayPrice393')?.remove();return result};\nconst renderBeforeDay390=render;\nrender=function(...args){const result=renderBeforeDay390.apply(this,args);mountDaySettings390();return result};\nconst showBeforeDay390=showTab;\nshowTab=function(which,...args){const result=showBeforeDay390.call(this,which,...args);if(which==='settings')mountDaySettings390();return result};\n\nif(typeof settingsGroupFor130==='function'){const settingsGroupBeforeDay393=settingsGroupFor130;settingsGroupFor130=function(detail){return ['dayPriceSettings390','quantitySettings390'].includes(detail?.id)?'planning':settingsGroupBeforeDay393(detail)}}\nif(typeof activateSettingsGroup130==='function'){const activateSettingsBeforeDay393=activateSettingsGroup130;activateSettingsGroup130=function(...args){const result=activateSettingsBeforeDay393.apply(this,args);mountDaySettings390();return result}}\n"},{"src":"event-management390.js?v=405","code":"'use strict';\nfunction activeEvent390(c){return !['cancelled','archived'].includes(c?.status390)}\nfunction setEventStatus390(id,status){if(!guardPermission('planning','Einsatz verwalten')||!['cancelled','archived','active'].includes(status))return false;const old=state.planning?.caterings?.find(c=>c.id===id);if(!old)return false;const title=status==='active'?'Einsatz wieder aktivieren':status==='archived'?'Einsatz archivieren':'Einsatz absagen';askConfirm(`${title}: „${old.event}“? Vorhandene Buchungen und Abschlüsse bleiben erhalten.`,()=>{const oldActive=activeCateringId;return afterOperation275(operationTransaction(title,'planning',()=>{const c=state.planning.caterings.find(c=>c.id===id);if(!c)throw Error('Einsatz nicht mehr vorhanden.');c.status390=status;c.statusChangedAt390=new Date().toISOString();if(status!=='active'){const next=state.planning.caterings.find(x=>x.id!==id&&activeEvent390(x))?.id||'';if(state.mobileSelectedPlanId===id)state.mobileSelectedPlanId=next;if(activeCateringId===id)activeCateringId=next}}),ok=>{if(!ok)activeCateringId=oldActive;else{showFeedback(title,'success');showCancelled390()}return ok})},title,'Einsatzverwaltung');return true}\nfunction showCancelled390(){if(!guardPermission('planning','Einsatzhistorie'))return false;const rows=(state.planning?.caterings||[]).filter(c=>!activeEvent390(c));openOpsPanel('Abgesagte und archivierte Einsätze',`<div class=\"day390\">${rows.map(c=>`<section class=\"dayPriceRow390\"><div><b>${esc(c.event)}</b><small>${esc(c.date?formatDate(c.date):'Datum offen')} · ${c.status390==='archived'?'Archiviert':'Abgesagt'}</small></div><button class=\"soft\" type=\"button\" onclick=\"viewEventHistory390('${escJs(c.id)}')\">Ansehen</button><button class=\"soft\" type=\"button\" onclick=\"setEventStatus390('${escJs(c.id)}','active')\">Wieder aktivieren</button></section>`).join('')||'<p>Keine abgesagten oder archivierten Einsätze.</p>'}</div>`);return true}\nconst statusBefore390=planningStatusHtml;\nplanningStatusHtml=function(c){const h=statusBefore390(c);if(!c||!hasPermission('planning'))return h;return `<div class=\"dayActions390\"><button type=\"button\" class=\"soft\" onclick=\"setEventStatus390('${escJs(c.id)}','${c.workflow?.closed?'archived':'cancelled'}')\">${c.workflow?.closed?'Archivieren':'Absagen'}</button><button type=\"button\" class=\"soft\" onclick=\"showCancelled390()\">Historie</button>${c.day390?`<button type=\"button\" class=\"primary\" onclick=\"openDay390('${escJs(c.id)}')\">Tagesgeschäft öffnen</button>`:''}</div>`+h};\nconst deleteBefore390=removeCateringPlan;\nremoveCateringPlan=function(id){const c=state.planning?.caterings?.find(c=>c.id===id);if((state.dailyClosings||[]).some(x=>x.planId390===id)||c?.workflow?.closed&&['cargo','productions','settlements'].some(k=>c.stockFlow?.[k]?.length))return setEventStatus390(id,'archived');return deleteBefore390(id)};\nconst refsBefore390=recipeReferenceSummary;\nrecipeReferenceSummary=function(id){const result=refsBefore390(id);for(const c of state.planning?.caterings||[])if(c.day390?.rows?.some(r=>r.recipeId===id)&&!result.caterings.includes(c.event))result.caterings.push(c.event||'Tagesgeschäft');for(const c of state.dailyClosings||[])if(c.items?.some(r=>r.recipeId===id)&&!result.caterings.includes(c.location))result.caterings.push(c.location||'Tagesabschluss');return result};\nconst headerDayBefore390=webEventHeader360;\nwebEventHeader360=function(...args){const result=headerDayBefore390.apply(this,args),menu=document.querySelector('#webEventHeader360 .webEventMenu360>div'),c=activePlanningEntry();if(menu&&hasPermission('planning')&&!menu.querySelector('[data-status390]'))menu.insertAdjacentHTML('beforeend',`${c?`<button data-status390 type=\"button\" class=\"soft\" onclick=\"setEventStatus390('${escJs(c.id)}','${c.workflow?.closed?'archived':'cancelled'}')\">${c.workflow?.closed?'Archivieren':'Absagen'}</button>`:''}<button type=\"button\" class=\"soft\" onclick=\"showCancelled390()\">Historie</button>`);return result};\n// Open the exact closing, rather than the first record with the same date.\nfunction openDailyClosing390(id){if(!guardPermission('calc','Tagesabschluss ansehen'))return false;const closing=(state.dailyClosings||[]).find(c=>c.id===id);if(!closing)return false;if(closing.planId390){const entry=state.planning?.caterings?.find(c=>c.id===closing.planId390);if(!entry?.day390)return false;if(!activeEvent390(entry)){openOpsPanel('Gespeicherter Tagesabschluss',`<div class=\"day390\"><h3>${esc(entry.event)}</h3>${dayResultHtml390(entry.day390)}<p>Archivierter Einsatz. Preise, Kosten und Buchungen bleiben erhalten.</p></div>`);return true}if(!openDay390(entry.id))return false;dayNavigate390(3);return true}const location=document.getElementById('dailyCloseLocation');if(location)location.value=closing.location||'';return closeBeforeDay390(closing.date)}\nconst dailyListBefore390=renderDaily143;\nrenderDaily143=function(){const el=document.getElementById('calc143_daily');if(!el)return dailyListBefore390();const rows=(state.dailyClosings||[]).slice().sort((a,b)=>String(b.date).localeCompare(String(a.date)));el.innerHTML=`<article class=\"calcCard143\"><div class=\"dayHeading390\"><h2>Tagesabschlüsse</h2><button type=\"button\" class=\"primary\" onclick=\"newPlanningEntry('Tagesgeschäft')\">+ Verkaufstag</button></div>${rows.map(c=>`<button type=\"button\" class=\"closeRow143\" onclick=\"openDailyClosing390('${escJs(c.id)}')\"><b>${esc(formatDate(c.date))}</b><span><b>${esc(c.location||'Ohne Standort')}</b><small>${(c.items||[]).reduce((s,r)=>s+(Number(r.sold)||0),0)} Portionen / Stück${c.extraSales405?.length?' aus Rezepten · weitere Verkäufe erfasst':' verkauft'}</small></span>${c.totals390?`<span><small>Nettoerlös</small>${moneyFmt(c.totals390.revenue)}</span><span><small>Kalkuliertes Ergebnis</small>${c.totals390.known?moneyFmt(c.totals390.result):'Unvollständig'}</span>`:''}</button>`).join('')||'<p>Noch keine Tagesabschlüsse gespeichert.</p>'}</article>`};\n\nfunction viewEventHistory390(id){if(!guardPermission('calc','Einsatzhistorie ansehen'))return false;const c=state.planning?.caterings?.find(c=>c.id===id);if(!c)return false;openOpsPanel('Gespeicherter Einsatz',`<div class=\"day390\"><h3>${esc(c.event)}</h3>${c.day390?dayResultHtml390(c.day390):calcHtml(c)}<button type=\"button\" class=\"soft\" onclick=\"showCancelled390()\">Zurück zur Historie</button></div>`);return true}\nconst saveLegacyBefore390=saveDailyClose;\nsaveDailyClose=function(...args){const date=document.getElementById('dailyCloseDate')?.value,location=String(document.getElementById('dailyCloseLocation')?.value||'').trim(),linked=(state.dailyClosings||[]).find(c=>c.planId390&&c.date===date&&String(c.location||'').trim()===location);if(linked){showFeedback('Dieser Abschluss gehört zum Tagesgeschäft. Bitte dort bearbeiten.','warn');return openDailyClosing390(linked.id)}return saveLegacyBefore390.apply(this,args)};\n"},{"src":"live-scanner391.js?v=391","code":"'use strict';\n// One camera session; results enter the existing barcode routing, never stock writes.\nlet liveScan391=null, frameId391=0;\nconst framePending391=new Map();\nwindow.zapasioBarcodeFrame391=(id,items)=>{const done=framePending391.get(Number(id));if(done){framePending391.delete(Number(id));done(Array.isArray(items)?items:[])}};\nfunction scanAlive391(s){return liveScan391===s&&!s.closed}\nfunction acceptManualScan433(s,value){\n if(!scanAlive391(s)||s.accepted)return false;const code=normalizeBarcode306(value);\n if(!validGtin179(code)&&!resolveBarcode306(code)){s.status.textContent='Nummer und Prüfziffer prüfen. Die Eingabe bleibt erhalten.';return false}\n s.accepted=true;stopLiveScan391();Promise.resolve().then(()=>s.accept(code)).catch(()=>showFeedback('Barcode konnte nicht geöffnet werden','warn',4500,'Bitte die Eingabe erneut prüfen.'));return true;\n}\nfunction stopLiveScan391(){const s=liveScan391;if(!s)return;s.closed=true;liveScan391=null;clearTimeout(s.timer);s.stream?.getTracks().forEach(t=>t.stop());s.box?.remove();s.focus?.focus?.()}\nfunction scanCandidate391(items){\n const usable=items.filter(x=>x&&x.boundingBox&&Number(x.boundingBox.width)>0&&Number(x.boundingBox.height)>0);\n return usable.length===1?usable[0]:null;\n}\nasync function scanDetector430(){\n if(typeof BarcodeDetector==='undefined')return null;\n try{\n  const wanted=['ean_13','ean_8','upc_a','upc_e','code_128','code_39','itf','qr_code'];\n  const supported=typeof BarcodeDetector.getSupportedFormats==='function'?await BarcodeDetector.getSupportedFormats():wanted;\n  const formats=wanted.filter(x=>supported.includes(x));\n  return formats.length?new BarcodeDetector({formats}):null;\n }catch{return null}\n}\nasync function scanNativeFrame430(s){\n const c=s.canvas,scale=Math.min(1,1800/Math.max(s.video.videoWidth,s.video.videoHeight));c.width=Math.round(s.video.videoWidth*scale);c.height=Math.round(s.video.videoHeight*scale);c.getContext('2d').drawImage(s.video,0,0,c.width,c.height);\n const data=c.toDataURL('image/jpeg',.9);\n let items;\n if(typeof window.ZapasioBarcode?.decodeBarcodeFrame==='function')items=await new Promise(resolve=>{const id=++frameId391;const timeout=setTimeout(()=>{framePending391.delete(id);resolve([])},9000);framePending391.set(id,items=>{clearTimeout(timeout);resolve(items)});try{window.ZapasioBarcode.decodeBarcodeFrame(data,id)}catch{framePending391.delete(id);clearTimeout(timeout);resolve([])}});\n else{const value=await zNativeBarcode166(data);items=value?[{rawValue:value}]:[]}\n // Both decoders report positions in video coordinates, including scaled native frames.\n return items.map(hit=>{const b=hit.boundingBox;return b?{...hit,boundingBox:{x:b.x/scale,y:b.y/scale,width:b.width/scale,height:b.height/scale}}:hit});\n}\nasync function scanFrame391(s){\n let found=[],error;\n if(s.detector)try{found=await s.detector.detect(s.video);if(found.some(x=>validGtin179(normalizeBarcode306(x.rawValue))||resolveBarcode306(x.rawValue)))return found}catch(e){error=e}\n const native=typeof window.ZapasioBarcode?.decodeBarcodeFrame==='function'||typeof window.ZapasioBarcode?.decodeBarcode==='function';\n if(native&&scanAlive391(s)){const extra=await scanNativeFrame430(s);if(extra.some(x=>validGtin179(normalizeBarcode306(x.rawValue))||resolveBarcode306(x.rawValue)))return extra;found=extra.length?extra:found}\n if(error&&!found.length)throw error;\n return found;\n}\nfunction paintScan391(s,hit){\n const b=hit?.boundingBox,overlay=s.overlay,v=s.video;\n if(!b){overlay.hidden=true;return}\n const w=v.videoWidth,h=v.videoHeight;\n const vw=v.clientWidth||w,vh=v.clientHeight||h,scale=Math.min(vw/w,vh/h),ox=(vw-w*scale)/2,oy=(vh-h*scale)/2;\n overlay.hidden=false;overlay.style.left=(100*(ox+b.x*scale)/vw)+'%';overlay.style.top=(100*(oy+b.y*scale)/vh)+'%';overlay.style.width=(100*b.width*scale/vw)+'%';overlay.style.height=(100*b.height*scale/vh)+'%';\n}\nasync function scanZoom391(s,value){\n if(!scanAlive391(s)||!s.zoom||s.adjusting)return;\n s.adjusting=true;\n try{await s.track.applyConstraints({advanced:[{zoom:Math.max(s.zoom.min,Math.min(s.zoom.max,Number(value)))}]});if(scanAlive391(s))s.range.value=s.track.getSettings().zoom||value}catch{if(scanAlive391(s))s.status.textContent='Zoom nicht verfügbar. Bitte näher an den Code gehen.'}finally{s.adjusting=false}\n}\nasync function scanTick391(s){\n if(!scanAlive391(s)||s.accepted||s.manual)return;\n try{\n  if(s.video.readyState>=2&&s.video.videoWidth){\n   const items=await scanFrame391(s);if(!scanAlive391(s)||s.manual)return;\n   const decoded=[...new Set(items.map(x=>String(x.rawValue||'').trim()).filter(Boolean))];\n   if(decoded.length===1){\n    const raw=decoded[0],code=normalizeBarcode306(raw);\n    // Product scanning accepts GTINs or exact local article codes, not arbitrary URLs/receipt QR data.\n    if(validGtin179(code)||resolveBarcode306(code)){\n     if(s.previous===raw)s.reads++;else{s.previous=raw;s.reads=1}\n     paintScan391(s,items.find(x=>x.rawValue===raw));s.status.textContent='Code erkannt …';\n     if(s.reads>=2){s.accepted=true;s.box.classList.add('read391');s.status.textContent='Barcode erkannt';navigator.vibrate?.(70);s.timer=setTimeout(()=>{if(!scanAlive391(s))return;stopLiveScan391();Promise.resolve().then(()=>s.accept(code)).catch(()=>showFeedback('Barcode konnte nicht geöffnet werden','warn',4500,'Bitte erneut scannen.'))},180);return}\n    }else{s.previous='';s.reads=0;paintScan391(s,null);s.status.textContent='Gelesener Code: '+raw.slice(0,48)+' · Keine gültige Artikelnummer. Foto verwenden oder Nummer eingeben.'}\n   }else{\n    s.previous='';s.reads=0;const candidate=scanCandidate391(items);paintScan391(s,candidate);\n    s.status.textContent=decoded.length>1?'Mehrere Codes sichtbar. Bitte einen Code anvisieren.':'Barcode in die Kamera halten';\n    if(decoded.length===0&&items.length===1&&candidate&&s.zoom&&Date.now()-s.lastZoom>1400){const fraction=candidate.boundingBox.width/s.video.videoWidth;if(fraction<.28){s.lastZoom=Date.now();await scanZoom391(s,Number(s.range.value)*1.25)}}\n   }\n  }\n }catch{if(scanAlive391(s))s.status.textContent='Kamera ruhig halten. Alternativ ein Foto verwenden.'}\n if(scanAlive391(s))s.timer=setTimeout(()=>scanTick391(s),160);\n}\nasync function startLiveScan391(accept,fallback){\n if(liveScan391)return;\n const s={accept,fallback,focus:document.activeElement,closed:false,reads:0,lastZoom:0,canvas:document.createElement('canvas')};liveScan391=s;\n const box=document.createElement('div');s.box=box;box.className='liveScanner391';box.setAttribute('role','dialog');box.setAttribute('aria-modal','true');box.setAttribute('aria-label','Barcode scannen');\n box.innerHTML='<div class=\"scanHead391\"><strong>Barcode scannen</strong><button type=\"button\" data-close>Schließen</button></div><div class=\"scanView391\"><video autoplay playsinline muted></video><div class=\"scanBounds391\" hidden></div></div><p role=\"status\">Kamera wird geöffnet …</p><div class=\"scanTools391\"><label hidden>Zoom <input type=\"range\" min=\"1\" max=\"1\" step=\"0.1\" aria-label=\"Kamera-Zoom\"></label><button type=\"button\" data-torch hidden aria-pressed=\"false\">Licht</button><button type=\"button\" data-photo>Foto verwenden</button><button type=\"button\" data-manual>Nummer eingeben</button></div><form class=\"scanManual433\" hidden><label>Nummer unter dem Barcode<input inputmode=\"numeric\" autocomplete=\"off\" aria-label=\"Barcodenummer\"></label><button type=\"submit\">Barcode übernehmen</button></form>';\n document.body.append(box);s.video=box.querySelector('video');s.overlay=box.querySelector('.scanBounds391');s.status=box.querySelector('[role=status]');s.range=box.querySelector('input');\n box.querySelector('[data-close]').onclick=stopLiveScan391;box.querySelector('[data-close]').focus();\n box.querySelector('[data-photo]').onclick=()=>{stopLiveScan391();fallback()};\n const manual=box.querySelector('.scanManual433');box.querySelector('[data-manual]').onclick=()=>{s.manual=true;manual.hidden=false;box.classList.add('manualScan433');manual.querySelector('input').focus()};manual.onsubmit=event=>{event.preventDefault();acceptManualScan433(s,manual.querySelector('input').value)};\n box.onkeydown=e=>{if(e.key==='Escape'){e.preventDefault();e.stopPropagation();stopLiveScan391()}if(e.key==='Tab'){const all=[...box.querySelectorAll('button,input')].filter(x=>!x.hidden&&!x.closest('[hidden]'));const first=all[0],last=all.at(-1);if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus()}else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus()}}};\n try{\n  s.detector=await scanDetector430();if(!scanAlive391(s))return;\n  if(!s.detector&&typeof window.ZapasioBarcode?.decodeBarcode!=='function'&&typeof window.ZapasioBarcode?.decodeBarcodeFrame!=='function')throw Error('decoder');\n  const stream=await navigator.mediaDevices.getUserMedia({video:{facingMode:{ideal:'environment'},width:{ideal:1920},height:{ideal:1080}},audio:false});\n  if(!scanAlive391(s)){stream.getTracks().forEach(t=>t.stop());return}s.stream=stream;s.track=stream.getVideoTracks()[0];s.video.srcObject=stream;await s.video.play();if(!scanAlive391(s))return;\n  const caps=s.track.getCapabilities?.()||{};\n  if(caps.focusMode?.includes('continuous'))try{await s.track.applyConstraints({advanced:[{focusMode:'continuous'}]})}catch{}\n  if(!scanAlive391(s))return;\n  if(caps.zoom&&caps.zoom.max>caps.zoom.min){s.zoom=caps.zoom;Object.assign(s.range,{min:caps.zoom.min,max:caps.zoom.max,step:caps.zoom.step||.1,value:s.track.getSettings().zoom||caps.zoom.min});s.range.parentElement.hidden=false;s.range.oninput=()=>scanZoom391(s,s.range.value)}\n  const torch=box.querySelector('[data-torch]');if(caps.torch){torch.hidden=false;torch.onclick=async()=>{const next=torch.getAttribute('aria-pressed')!=='true';try{await s.track.applyConstraints({advanced:[{torch:next}]});if(scanAlive391(s))torch.setAttribute('aria-pressed',String(next))}catch{if(scanAlive391(s))s.status.textContent='Licht konnte nicht eingeschaltet werden.'}}}\n  void scanTick391(s);\n }catch{if(scanAlive391(s))s.status.textContent='Live-Erkennung nicht verfügbar. Bitte Kamera erlauben oder „Foto verwenden“ wählen.'}\n}\nconst scanPhotoBefore391=scanBarcode215;\nscanBarcode215=function(){if(!guardPermission('scan','Barcode scannen'))return;void startLiveScan391(routeBarcode306,()=>scanPhotoBefore391())};\nconst guideCameraBefore391=guideCamera194;\nguideCamera194=function(){if(productGuide194?.step!==0)return guideCameraBefore391();const d=productGuide194;if(d.busy)return;void startLiveScan391(async code=>{if(!guideAlive194(d)||d.step!==0)return;d.barcode=code;d.lookup=null;d.existing=null;renderGuide194();await lookupGuideBarcode194()},()=>{if(guideAlive194(d))guideCameraBefore391()})};\n// Camera permission requests and decoded frames may finish after close/background.\ndocument.addEventListener('visibilitychange',()=>{if(document.hidden)stopLiveScan391()});\nwindow.addEventListener('pagehide',stopLiveScan391);\n"},{"src":"day-fixes397.js?v=397","code":"'use strict';\n// Historical records remain unchanged. Operational needs use the current day state.\nfunction dayOperational397(c){return c?.day390?{...c,lines:[],dayItems:dayDemand390(c.day390)}:c}\nconst needBefore397=cateringNeedDetails;\ncateringNeedDetails=function(c){return needBefore397(dayOperational397(c))};\nconst loadTasksBefore397=loadListAutoTasks;\nloadListAutoTasks=function(c){return loadTasksBefore397(dayOperational397(c))};\nconst mobileFoodsBefore397=mobilePlanFoods;\nmobilePlanFoods=function(c){return c?.day390?eventSummaryRows397(c).map(r=>({name:r.label,qty:r.qty})):mobileFoodsBefore397(c)};\n"},{"src":"day-repair398.js?v=398","code":"'use strict';\nlet dayArticleReturn398=null;\nlet dayArticleObserver398=null;\nfunction dayPriceFields398(){const qty=document.getElementById('dayPackQty390');return {price:document.getElementById('dayPriceValue390')?.value??'',packQty:qty?.value??'',packUnit:document.getElementById('dayPackUnit390')?.value??'kg',packOpen:!!qty?.closest('details')?.open}}\nfunction restoreDayArticle398(ctx){if(!openDayPriceCalc390(ctx.recipeId,ctx.price))return false;const qty=document.getElementById('dayPackQty390'),unit=document.getElementById('dayPackUnit390');if(qty){qty.value=ctx.packQty;const details=qty.closest('details');if(details)details.open=ctx.packOpen}if(unit)unit.value=ctx.packUnit;return true}\nconst repairBefore398=repairRecipeIssue;\nrepairRecipeIssue=function(recipeId,key){const dialog=document.getElementById('opsDialog92'),r=findRecipeById(recipeId)?.recipe,issue=r&&recipeCostIssues(r).find(x=>x.key===key),fromDayPrice=dialog?.open&&document.getElementById('dayPriceValue390')&&['create','price','content'].includes(issue?.kind),ctx=fromDayPrice?{recipeId,...dayPriceFields398()}:null;const result=repairBefore398.apply(this,arguments);if(ctx&&document.getElementById('articleModal')?.style.display==='flex'){dayArticleReturn398=ctx;dialog.close();const field=document.getElementById(issue.kind==='price'?'aePrice':issue.kind==='content'?'aeContentQty':'aeName');field?.focus()}return result};\nconst closeArticleBefore398=closeArticleEditor;\nfunction finishDayArticle398(){const modal=document.getElementById('articleModal');if(!dayArticleReturn398||modal?.style.display==='flex'||typeof articleSaving257!=='undefined'&&articleSaving257)return false;const ctx=dayArticleReturn398;dayArticleReturn398=null;dayArticleObserver398?.disconnect();dayArticleObserver398=null;return restoreDayArticle398(ctx)}\ncloseArticleEditor=function(...args){const wasOpen=document.getElementById('articleModal')?.style.display==='flex',ctx=wasOpen?dayArticleReturn398:null;const result=closeArticleBefore398.apply(this,args);if(ctx&&document.getElementById('articleModal')?.style.display!=='flex')queueMicrotask(()=>{const modal=document.getElementById('articleModal');if(typeof articleSaving257!=='undefined'&&articleSaving257&&!dayArticleObserver398){dayArticleObserver398=new MutationObserver(()=>finishDayArticle398());dayArticleObserver398.observe(modal,{attributes:true,childList:true})}finishDayArticle398()});return result};\n"},{"src":"supplier-identity401.js?v=401","code":"'use strict';\n// Active references use the supplier profile; document and price history retain their original labels.\nconst persistSupplierBefore401=persistState;\npersistState=function(...args){normalizeSupplierRefs401(state);return persistSupplierBefore401.apply(this,args)};\n"},{"src":"event-entry402.js?v=402","code":"'use strict';\nlet eventEntryBusy402=false;\nconst newEventBefore402=newPlanningEntry;\nnewPlanningEntry=async function(type){\n if(eventEntryBusy402||!guardPermission('planning','Einsatz anlegen'))return false;\n if(type==='Tagesgeschäft')return newEventBefore402.apply(this,arguments);\n eventEntryBusy402=true;\n try{\n  if(type==='Catering'){\n   const opened=await hubRoute362(()=>hubOpenPlanning366('overview'));\n   if(opened===false||!hasPermission('planning'))return false;\n  }else{\n   const opened=await openHub362('events');\n   if(opened===false||!hasPermission('planning'))return false;\n  }\n  return newEventBefore402.apply(this,arguments);\n }finally{eventEntryBusy402=false}\n};\nconst eventActionBefore402=hubAction362;\nhubAction362=function(action){if(action==='newEvent')return newPlanningEntry();return eventActionBefore402.apply(this,arguments)};\n"},{"src":"day-container403.js?v=404","code":"'use strict';\n// Only an unambiguous, directly linked bucket ingredient can supply the basis.\n// Side dishes and packaging never contribute to its portion weight.\nfunction dayContainer403(r){\n const candidates=[];let buckets=0;\n for(const ing of r.ingredients||[]){\n  if(ing.kind==='packaging'||ing.recipeId||ing.valuation==='average'||!(Number(ing.qty)>0))continue;\n  let code=(ing.productId&&productCodeByInternalId(ing.productId))||ing.productCode||(typeof autoIngredientProductCode==='function'?autoIngredientProductCode(ing):'');\n  if(ing.mainArticleId){const members=Object.entries(state.products||{}).filter(([,p])=>p.mainArticleId===ing.mainArticleId);if(members.length!==1)return null;code=members[0][0];}\n  const a=state.products?.[code],c=a?.stockContent;\n  if(!a||!/^kübel$/i.test(a.unit))continue;\n  buckets++;\n  if(c?.stockUnit!==a.unit||!(Number(c.qty)>0)||Number(a.prepLossPct)>0||!['kg','g','Liter','l','ml'].includes(c.unit))continue;\n  // A group may select another variant; its contents are not a reliable source.\n  if(a.mainArticleId&&!ing.pinVariant&&Object.values(state.products||{}).filter(p=>p.mainArticleId===a.mainArticleId).length>1)continue;\n  const output=recipeOutputQty108(r,1,recipePlanningUnit108(r));\n  const amount=convertUnit(Number(ing.qty),ing.unit,c.unit)??(ing.unit===a.unit?Number(ing.qty)*Number(c.qty):null);\n  const qty=amount===null?null:amount*output/Number(r.yieldQty);\n  if(!(output>0&&qty>0))continue;\n  candidates.push({pack:{qty:Number(c.qty),unit:c.unit},physical:{qty,unit:c.unit},productCode:code,name:a.name});\n }\n // Even two bucket ingredients with the same size must not be guessed apart.\n return buckets===1&&candidates.length===1?candidates[0]:null;\n}\nconst dayLiveBefore403=dayLive390;\ndayLive390=function(id){const m=dayLiveBefore403(id),r=findRecipeById(id)?.recipe,c=r&&dayContainer403(r);if(c){m.pack=c.pack;m.container403=c;}return m;};\nconst dayPhysicalBefore403=dayPhysical395;\ndayPhysical395=function(meta){return meta.container403?.physical||dayPhysicalBefore403(meta);};\nconst daySupplyBefore403=daySupplyText395;\ndaySupplyText395=function(row,target,planned=true){let text=daySupplyBefore403(row,target,planned);const m=dayRowEconomics390(row),s=daySupply395(row,target),b=dayPhysical395(m);if(s.missing>.01&&b){const kg=convertUnit(s.missing*b.qty,b.unit,'kg');if(kg!==null)text+=' Fehlende Menge: '+new Intl.NumberFormat('de-DE',{maximumFractionDigits:2}).format(kg)+' kg.';}return text;};\nconst daySupplyBefore404=daySupplyText395;\ndaySupplyText395=function(row,target,planned=true){const text=daySupplyBefore404(row,target,planned),start=dayNumber390(row.start),added=(row.additions||[]).reduce((sum,a)=>sum+(dayNumber390(a.qty)??0),0),fmt=n=>new Intl.NumberFormat('de-DE',{maximumFractionDigits:2}).format(n);return start!==null&&added>0?fmt(start)+' '+row.unit+' mitgenommen + '+fmt(added)+' '+row.unit+' nachgelegt: insgesamt '+text:text;};\nfunction dayContainerNote403(m){const c=m.container403;if(!c)return '';const fmt=n=>new Intl.NumberFormat('de-DE',{maximumFractionDigits:2}).format(n);return `<p class=\"dayReadOnly390\">Kübelinhalt vom Produkt: <b>${esc(c.name)}</b> · ${fmt(c.pack.qty)} ${esc(c.pack.unit)} je Kübel. Die Portionsmenge stammt aus der Rezeptzutat; Beilagen und Verpackung zählen separat zu den Kosten.</p>`;}\nconst dayRowsBefore403=dayRowsHtml390;\ndayRowsHtml390=function(){let html=dayRowsBefore403();for(const row of dayDraft390?.rows||[]){const note=dayContainerNote403(dayRowEconomics390(row));if(note)html=html.replace(`<div id=\"daySupply395_${dayDraft390.rows.indexOf(row)}\"`,note+`<div id=\"daySupply395_${dayDraft390.rows.indexOf(row)}\"`);}return html;};\nconst dayPriceFormBefore403=dayPriceForm390;\ndayPriceForm390=function(id,proposal){const m=dayLive390(id),html=dayPriceFormBefore403(id,proposal);if(!m.container403)return html;return html.replace(/<details><summary>(?:Kübelinhalt einmal hinterlegen|Set bucket contents once|Ustaw zawartosc pojemnika)[\\s\\S]*?<\\/details>/,dayContainerNote403(m));};\n"},{"src":"day-cash405.js?v=406","code":"'use strict';\n// One actual cash total per day. Extra sales supply costs, never more revenue.\nfunction dayEnableCash405(d){if(d&&d.status!=='closed'&&!d.closing405){d.closing405=true;d.cashNet405='';d.extraSales405=[];}return d;}\nfunction dayMoney405(value){const s=String(value??'').trim();return /^\\d+(?:[.,]\\d{1,2})?$/.test(s)?dayNumber390(s):null;}\nfunction dayExtraCost405(row){\n const qty=dayNumber390(row.qty),manual=dayMoney405(row.costNet),override=String(row.costNet??'').trim()!=='';\n if(row.kind==='manual')return {known:!!String(row.name||'').trim()&&manual!==null,cost:manual??0};\n const snap=row.snapshot||{},unitCost=snap.unitCost,validQty=qty!==null&&qty>0;\n return {known:validQty&&!!snap.name&&(override?manual!==null:Number.isFinite(unitCost)&&unitCost>0),cost:validQty?(override?manual??0:qty*(unitCost??0)):0};\n}\nfunction dayExpected405(d){let total=0,known=true;for(const row of d.rows||[]){const m=dayRowEconomics390(row),n=dayNumber390(row.sold);if(n===null||!Number.isInteger(n)||n>0&&!Number.isFinite(m.priceNet))known=false;else total+=n*(m.priceNet??0);}return {total,known};}\nconst dayTotalsBefore405=dayTotals390;\ndayTotals390=function(d,planned=false){\n if(!d.closing405||planned)return dayTotalsBefore405(d,planned);\n // Missing selling prices do not invalidate actual cash revenue. Recipe costs do.\n const source={...d,rows:(d.rows||[]).map(row=>({...row,revenueNet:'',snapshot:{...dayRowEconomics390(row),priceNet:dayRowEconomics390(row).priceNet??0}}))},base=dayTotalsBefore405(source,false),cash=dayMoney405(d.cashNet405),sales=d.extraSales405||[];\n let additionalFood=0;const errors=[...base.errors];let extraKnown=true;\n for(const row of sales){const c=dayExtraCost405(row);additionalFood+=c.cost;if(!c.known){extraKnown=false;errors.push((row.snapshot?.name||row.name||'Weiterer Verkauf')+': Menge und Warenkosten ergänzen.');}}\n if(cash===null)errors.unshift('Bitte den tatsächlichen Tageserlös netto aus der Kasse eingeben.');\n const revenue=cash??0,fees=(dayNumber390(d.extras?.standFee??0)??0)+revenue*(dayNumber390(d.extras?.sharePct??0)??0)/100,total=base.total-base.fees+fees+additionalFood,known=base.known&&cash!==null&&extraKnown;\n return {...base,revenue,fees,total,additionalFood,cashKnown405:cash!==null,expected405:dayExpected405(d),known,result:known?revenue-total:null,errors:[...new Set(errors)]};\n};\nconst dayValidateBefore405=dayValidate390;\ndayValidate390=function(d,close=false){if(d.closing405&&close&&dayMoney405(d.cashNet405)===null)return 'Bitte den tatsächlichen Tageserlös netto aus der Kasse eingeben.';return dayValidateBefore405(d,close);};\nconst dayClosingBefore405=dayClosing390;\ndayClosing390=function(d){\n if(!d.closing405)return dayClosingBefore405(d);\n const record=dayClosingBefore405(d),x=dayTotals390(d);record.cashNet405=x.revenue;record.extraSales405=clone(d.extraSales405||[]);\n for(const [i,row] of record.items.entries()){row.expectedRevenue405=dayNumber390(d.rows[i].sold)*(dayRowEconomics390(d.rows[i]).priceNet??0);row.economics255.revenue=0;row.economics255.revenueNotAllocated405=true;}\n // Legacy whole-day summaries can read actual cash without allocating it to a dish.\n if(record.items[0])record.items[0].dayCashSummary405={revenue:x.revenue,cost:x.food+x.additionalFood+x.waste+x.own,complete:x.known};\n return record;\n};\nif(typeof dailyEconomics255==='function'){const before=dailyEconomics255;dailyEconomics255=function(items){const cash=items.find(r=>r.dayCashSummary405)?.dayCashSummary405;return cash?{...cash,contribution:cash.complete?cash.revenue-cash.cost:null}:before(items);};}\nfunction dayCashField405(value){if(!dayDraft390||dayBusy390||!guardPermission('planning','Tageserlös erfassen'))return false;dayDraft390.cashNet405=value;dayCheckpoint390();return true;}\nfunction dayExtraField405(index,key,value){if(!dayDraft390||dayBusy390||!guardPermission('planning','Weitere Verkäufe erfassen'))return false;const r=dayDraft390.extraSales405?.[index];if(!r||!['name','qty','costNet'].includes(key)||key==='name'&&r.kind!=='manual')return false;r[key]=value;dayCheckpoint390();return true;}\nfunction dayExtraAdd405(kind='product'){\n if(!dayDraft390||dayBusy390||!guardPermission('planning','Weitere Verkäufe erfassen'))return false;\n let row={id:dayId390(),kind,name:'',costNet:''};\n if(kind==='product'){const code=document.getElementById('dayExtraProduct405')?.value,a=state.products?.[code];if(!a){showFeedback('Bitte einen Artikel auswählen.','warn');return false;}if(dayDraft390.extraSales405?.some(r=>r.productCode===code)){showFeedback('Dieser Artikel ist bereits enthalten. Bitte dort die Menge ergänzen.','info');return false;}const price=costNumber(a.price);row={...row,productCode:code,qty:'',snapshot:{name:a.name,unit:a.unit||'Stück',unitCost:Number.isFinite(price)&&price>0?price:null}};}\n else if(kind!=='manual')return false;\n (dayDraft390.extraSales405??=[]).push(row);dayCheckpoint390();paintDay390();return true;\n}\nfunction dayExtraRemove405(index){if(!dayDraft390||dayBusy390||!guardPermission('planning','Weiteren Verkauf entfernen'))return false;askDeleteConfirm('Diesen zusätzlichen Verkauf aus dem Entwurf entfernen?',()=>{dayDraft390.extraSales405.splice(index,1);dayCheckpoint390();paintDay390();},'Verkauf entfernen');return true;}\nfunction dayCashHtml405(d){const expected=dayExpected405(d);return `<section class=\"dayCard390 dayCash405\"><h3>Einnahmen aus der Kasse</h3><label>Tatsächlicher Tageserlös netto gesamt (€)<input data-adapted231=\"day-net\" id=\"dayCashNet405\" inputmode=\"decimal\" required aria-required=\"true\" value=\"${esc(d.cashNet405??'')}\" oninput=\"dayCashField405(this.value)\"></label><p>Hier musst du den gesamten Nettoerlös des Tages eingeben, einschließlich Getränke, Saucen und weiterer Verkäufe. Den Nettobetrag findest du im Kassenabschluss.</p><small id=\"dayCashExpected405\">${expected.known?'Zum Vergleich: Die erfassten Gerichte zum Tagespreis ergeben '+moneyFmt(expected.total)+'. Weitere Artikel sind in diesem Vergleich nicht enthalten.':'Der Vergleich mit den Tagespreisen ist noch unvollständig.'}</small></section>`;}\nfunction dayExtraHtml405(d){const rows=d.extraSales405||[];return `<section class=\"dayCard390\"><h3>Weitere Verkäufe</h3><p>Auch Getränke, Saucen oder andere Artikel verkauft? Ergänze sie hier für die Warenkosten, sofern sie noch nicht im Gericht enthalten sind. Ihre Einnahmen sind bereits im Tageserlös oben enthalten.</p>${rows.map((r,i)=>`<div class=\"dayExtraRow405\"><div class=\"dayHeading390\"><b>${esc(r.kind==='product'?r.snapshot?.name:'Ohne hinterlegten Artikel')}</b><button type=\"button\" class=\"soft\" onclick=\"dayExtraRemove405(${i})\">Entfernen</button></div>${r.kind==='manual'?`<label>Was wurde verkauft?<input value=\"${esc(r.name||'')}\" oninput=\"dayExtraField405(${i},'name',this.value)\"></label>`:`<label>Verkaufte Menge (${esc(r.snapshot?.unit||'Stück')})<input data-adapted231=\"day-net\" inputmode=\"decimal\" value=\"${esc(r.qty??'')}\" oninput=\"dayExtraField405(${i},'qty',this.value)\"></label><small>${Number.isFinite(r.snapshot?.unitCost)?'Warenkosten: '+moneyFmt(r.snapshot.unitCost)+' netto je '+esc(r.snapshot.unit)+'.':'Beim Artikel fehlt der Einkaufspreis. Bitte die gesamten Warenkosten unten eingeben.'}</small>`}${r.kind==='manual'||!Number.isFinite(r.snapshot?.unitCost)?`<label>Warenkosten dieser Verkäufe netto gesamt (€)<input data-adapted231=\"day-net\" inputmode=\"decimal\" value=\"${esc(r.costNet??'')}\" oninput=\"dayExtraField405(${i},'costNet',this.value)\"></label><small>Gesamte Einkaufskosten, nicht der Verkaufspreis.</small>`:''}</div>`).join('')}<details class=\"dayAddExtra405\" ${rows.length?'':'open'}><summary>Weitere Verkäufe hinzufügen</summary><label>Artikel aus dem Lager<select id=\"dayExtraProduct405\"><option value=\"\">Artikel auswählen</option>${Object.entries(state.products||{}).sort((a,b)=>String(a[1].name).localeCompare(String(b[1].name),'de')).map(([code,a])=>`<option value=\"${esc(code)}\">${esc(a.name)} · ${esc(a.unit||'Stück')}</option>`).join('')}</select></label><div class=\"dayActions390\"><button type=\"button\" class=\"soft\" onclick=\"dayExtraAdd405('product')\">Artikel hinzufügen</button><button type=\"button\" class=\"soft\" onclick=\"dayExtraAdd405('manual')\">Ohne Artikel erfassen</button></div></details></section>`;}\nconst dayPaintBefore405=paintDay390;\npaintDay390=function(){if(dayDraft390&&guardPermission('planning','Tagesgeschäft ansehen')){const before=dayDraft390.closing405;dayEnableCash405(dayDraft390);if(!before&&dayDraft390.closing405)dayCheckpoint390();}return dayPaintBefore405();};\nconst dayResultBefore405=dayResultHtml390;\ndayResultHtml390=function(d,planned=false){let h=dayResultBefore405(d,planned);if(!d.closing405||planned)return h;const x=dayTotals390(d);h=h.replace('Tatsächlicher Nettoerlös','Tageserlös netto aus der Kasse');if(!x.cashKnown405)h=h.replace('<b>'+moneyFmt(0)+'</b>','<b>Noch eingeben</b>');return h;};\n\nfunction dayUpdateExpected405(){const el=document.getElementById('dayCashExpected405');if(!el||!dayDraft390)return;const x=dayExpected405(dayDraft390);el.textContent=x.known?'Zum Vergleich: Die erfassten Gerichte zum Tagespreis ergeben '+moneyFmt(x.total)+'. Weitere Artikel sind in diesem Vergleich nicht enthalten.':'Der Vergleich mit den Tagespreisen ist noch unvollständig.';}\n"},{"src":"settings-tax408.js?v=409","code":"'use strict';\n// Country suggestions, not automatic tax classification of goods or services.\nconst TAX_PRESETS408={AT:[10,20,13,4.9,0],DE:[7,19,0],PL:[8,23,5,0]};\nconst TAX_GUIDE408={\n AT:{source:'https://www.usp.gv.at/themen/steuern-finanzen/umsatzsteuer-ueberblick/steuersaetze-und-steuerbefreiungen-der-umsatzsteuer.html',rows:[\n  ['Speisen in der Gastronomie',10,'Grundsätzlich 10 %. Getränke und besondere Leistungen getrennt prüfen.'],\n  ['Getränke',20,'Grundsätzlich 20 %. Gesetzliche Ausnahmen, etwa bestimmte Milch- oder Wasserumsätze, gesondert prüfen.'],\n  ['Ausgewählte Nahrungsmittellieferungen',4.9,'Seit 01.07.2026, z. B. bestimmte Milchprodukte, Gemüse, Reis und Brot nach gesetzlicher Warenliste. Keine pauschale Anwendung auf Restaurationsumsätze.'],\n  ['Andere begünstigte Lebensmittel',10,'Soweit sie unter die gesetzliche Begünstigung fallen und kein anderer Satz gilt.'],\n  ['Weitere ermäßigte Umsätze',13,'Nur gesetzlich zugeordnete Waren und Leistungen, z. B. bestimmte Kulturumsätze.'],\n  ['Allgemeiner Steuersatz',20,'Für steuerpflichtige Umsätze ohne Ermäßigung.']\n ]},\n DE:{source:'https://www.bundesfinanzministerium.de/Content/DE/Standardartikel/Themen/Steuern/das-aendert-sich-2026.html',rows:[\n  ['Speisen in Restaurant und Catering',7,'Seit 01.01.2026 grundsätzlich 7 % auf Restaurant- und Verpflegungsdienstleistungen, ausgenommen Getränke.'],\n  ['Getränke',19,'Grundsätzlich 19 %. Begünstigte Warenlieferungen, etwa bestimmte Milchprodukte, gesondert prüfen.'],\n  ['Begünstigte Lebensmittel als Warenlieferung',7,'Gesetzlich aufgeführte Lebensmittel; nicht jedes Produkt oder Getränk ist begünstigt.'],\n  ['Allgemeiner Steuersatz',19,'Für steuerpflichtige Umsätze ohne Ermäßigung.']\n ]},\n PL:{source:'https://www.podatki.gov.pl/podatki-firmowe/vat/stawki-i-limity',rows:[\n  ['Begünstigte Gastronomie- und Cateringumsätze',8,'Grundsätzlich für begünstigte Verpflegungsumsätze nach PKWiU 56. Gesetzliche Ausnahmen getrennt prüfen.'],\n  ['Getränke zum Regelsteuersatz',23,'Viele Getränke, z. B. Kaffee, Tee oder Alkohol. Zusammensetzung und Leistungsart können die Zuordnung ändern.'],\n  ['Begünstigte Lebensmittel als Warenlieferung',5,'Lebensmittel nach gesetzlicher Warenliste. Keine pauschalen 5 % auf jedes Essen zum Mitnehmen.'],\n  ['Weitere begünstigte Waren und Leistungen',8,'Nach gesetzlicher Liste und zutreffender Klassifikation.'],\n  ['Allgemeiner Steuersatz',23,'Für steuerpflichtige Umsätze ohne Ermäßigung.']\n ]}\n};\nfunction taxGuideHtml408(country){const guide=TAX_GUIDE408[country];if(!guide)return '';return `<h3>Typische Anwendungen in ${esc(VAT130[country].label)}</h3><div class=\"taxGuide408\">${guide.rows.map(([name,rate,note])=>`<article><div><strong>${esc(name)}</strong><b>${Number(rate).toLocaleString('de-AT')} %</b></div><p>${esc(note)}</p></article>`).join('')}<article><div><strong>0 % / besondere Fälle</strong><b>0 %</b></div><p>Nur bei erfüllten gesetzlichen Voraussetzungen. Steuerbefreiung ist eine eigene steuerliche Behandlung und wird nicht automatisch als 0 % zugeordnet.</p></article></div><details><summary>Quellen und Gültigkeit</summary><p>Stand der Vorauswahl: 18.10.2026. Leistungsdatum und konkrete Ware oder Leistung sind entscheidend.</p><a href=\"${guide.source}\" target=\"_blank\" rel=\"noopener noreferrer\">Offizielle Steuerinformationen für ${esc(VAT130[country].label)}</a>${country==='AT'?'<p><a href=\"https://www.oesterreich.gv.at/de/Gesetzliche-Neuerungen/Bundesgesetzblatt/umsatzsteuergesetz\" target=\"_blank\" rel=\"noopener noreferrer\">4,9 % ab 01.07.2026: Anwendungsbereich</a></p>':''}</details>`}\nfunction taxCountryPanelHtml409(country=business130().country){return `<h2>Steuersätze für ${esc(VAT130[country].label)}</h2><p>Die Angaben folgen deinem eingestellten Betriebsland.</p>${taxGuideHtml408(country)}<p class=\"tiny\">Kombinationen aus Speisen und Getränken können mehrere Steuersätze benötigen. Die Liste ist eine Vorauswahl; bestehende Artikel werden dadurch nicht automatisch steuerlich neu zugeordnet.</p>`}\nfunction taxRate408(value){const text=String(value??'').trim();if(!/^\\d+(?:[.,]\\d{1,4})?$/.test(text))throw Error('MwSt.-Satz zwischen 0 und 100 eingeben.');const rate=Number(text.replace(',','.'));if(!Number.isFinite(rate)||rate<0||rate>100)throw Error('MwSt.-Satz zwischen 0 und 100 eingeben.');return rate}\nfunction taxRates408(country=business130().country){\n const b=business130(),rates=[...(TAX_PRESETS408[country]||[]),...(b.vatCustom408?.[country]||[])];\n if(country===b.country&&b.vat!==null&&b.vat!==undefined&&b.vat!=='')rates.push(b.vat);\n return [...new Set(rates.flatMap(value=>{try{return [taxRate408(value)]}catch{return []}}))];\n}\nfunction syncTaxCatalog408(){for(const country of Object.keys(TAX_PRESETS408))if(VAT130[country])VAT130[country].rates=taxRates408(country)}\nfunction setTaxDefault408(value){let rate;try{rate=taxRate408(value)}catch(error){showFeedback(error.message,'warn');return false}\n const result=operationTransaction('MwSt.-Vorgabe gespeichert','settings',()=>{const b=business130();b.vat=rate;b.vatDefaults408??={};b.vatDefaults408[b.country]=rate;syncTaxCatalog408()});\n return afterOperation275(result,ok=>{syncTaxCatalog408();refreshTaxSettings408();return ok});\n}\nfunction addTaxRate408(value){let rate;try{rate=taxRate408(value)}catch(error){showFeedback(error.message,'warn');return false}\n const result=operationTransaction('MwSt.-Satz ergänzt','settings',()=>{const b=business130();b.vatCustom408??={};const rates=b.vatCustom408[b.country]||[];b.vatCustom408[b.country]=[...new Set([...rates,rate])];syncTaxCatalog408()});\n return afterOperation275(result,ok=>{syncTaxCatalog408();refreshTaxSettings408();return ok});\n}\nfunction removeTaxRate408(value){const rate=taxRate408(value);const result=operationTransaction('Eigenen MwSt.-Satz entfernt','settings',()=>{const b=business130();if(Number(b.vat)===rate)throw Error('Zuerst eine andere MwSt.-Vorgabe wählen.');b.vatCustom408??={};b.vatCustom408[b.country]=(b.vatCustom408[b.country]||[]).filter(x=>Number(x)!==rate);syncTaxCatalog408()});return afterOperation275(result,ok=>{syncTaxCatalog408();refreshTaxSettings408();return ok})}\nconst setBusinessBefore408=setBusiness130;\nsetBusiness130=function(key,value){\n if(key==='vat')return setTaxDefault408(value);\n if(key!=='country')return setBusinessBefore408(key,value);\n if(!TAX_PRESETS408[value]){showFeedback('Land nicht unterstützt','warn');return false}\n const result=operationTransaction('Land und MwSt.-Vorauswahl gespeichert','settings',()=>{const b=business130();b.vatDefaults408??={};b.vatDefaults408[b.country]=taxRate408(b.vat);b.country=value;b.vat=Object.hasOwn(b.vatDefaults408,value)?taxRate408(b.vatDefaults408[value]):TAX_PRESETS408[value][0];syncTaxCatalog408()});\n return afterOperation275(result,ok=>{syncTaxCatalog408();refreshTaxSettings408();return ok});\n};\nfunction refreshTaxSettings408(){\n const panel=document.getElementById('settingsPanel130_business');if(!panel)return;\n let card=document.getElementById('taxSettings408');if(!card){card=document.createElement('section');card.id='taxSettings408';card.className='card';panel.appendChild(card)}\n const b=business130(),country=b.country,rateText=n=>Number(n).toLocaleString('de-AT',{maximumFractionDigits:4})+' %';\n const custom=(b.vatCustom408?.[country]||[]).filter(x=>!TAX_PRESETS408[country].includes(Number(x)));\n const html=`<h2>Mehrwertsteuer</h2><p>Vorauswahl für ${esc(VAT130[country].label)}. Das Land wählst du unter „Betriebssitz & Sprache“.</p><p>Vorgabe für neue Kalkulationen: <b>${esc(rateText(b.vat))}</b></p><div class=\"taxRateChoices408\" role=\"group\" aria-label=\"MwSt.-Vorgabe\">${taxRates408(country).map(rate=>`<button type=\"button\" aria-pressed=\"${Number(b.vat)===rate}\" onclick=\"setTaxDefault408(${rate})\">${esc(rateText(rate))}</button>`).join('')}</div><label class=\"taxCustom408\">Eigener MwSt.-Satz (%)<input id=\"taxCustomRate408\" data-adapted231=\"tax-rate\" inputmode=\"decimal\" placeholder=\"z. B. 4,9\"></label><button type=\"button\" class=\"soft\" onclick=\"addTaxRate408(document.getElementById('taxCustomRate408').value)\">Satz hinzufügen</button>${custom.map(rate=>`<button type=\"button\" class=\"soft\" onclick=\"removeTaxRate408(${Number(rate)})\">Eigenen Satz ${esc(rateText(rate))} entfernen</button>`).join('')}<p class=\"tiny\">Vorschläge geprüft am 18.10.2026. Den zutreffenden Satz je Ware oder Leistung prüfen. 0 % ist nicht dasselbe wie steuerbefreit. Bestehende Preise und Abschlüsse bleiben erhalten.</p>${country==='AT'?'<p class=\"tiny\">4,9 % gelten seit 01.07.2026 nur für bestimmte Nahrungsmittellieferungen, nicht pauschal für Restaurationsumsätze.</p>':''}`;\n if(card._html408!==html){card.innerHTML=html;card._html408=html}\n let guide=document.getElementById('taxCountryGuide408');if(!guide){guide=document.createElement('section');guide.id='taxCountryGuide408';guide.className='card';panel.appendChild(guide)}\n const guideHtml=taxCountryPanelHtml409(country);\n if(guide._html408!==guideHtml){guide.innerHTML=guideHtml;guide._html408=guideHtml}\n const countryInput=document.querySelector('#businessCountry130 select[onchange*=\"country\"]');if(countryInput&&countryInput.value!==country)countryInput.value=country;\n let list=document.getElementById('taxSuggestions408');if(!list){list=document.createElement('datalist');list.id='taxSuggestions408';document.body.appendChild(list)}\n const options=taxRates408().map(rate=>`<option value=\"${rate}\">${esc(rateText(rate))}</option>`).join('');if(list.innerHTML!==options)list.innerHTML=options;\n}\n// Keep original controls and their conversion/validation handlers as the source\n// of truth; changing presentation must not change the stored price basis.\nfunction installPriceSwitch408(select){\n if(select.priceSwitch408){select.priceSwitch408.sync();return}\n if(select.options.length!==2||![...select.options].every(x=>['net','gross'].includes(x.value)))return;\n const button=document.createElement('button');button.type='button';button.className='priceSwitch408';button.setAttribute('role','switch');button.setAttribute('aria-label','Bruttobetrag');\n button.innerHTML='<span data-basis408=\"net\">Netto</span><span class=\"priceSwitchTrack408\" aria-hidden=\"true\"><span></span></span><span data-basis408=\"gross\">Brutto</span>';\n const sync=()=>{const gross=select.value==='gross';button.setAttribute('aria-checked',String(gross));button.disabled=select.disabled;button.querySelector('[data-basis408=\"net\"]').classList.toggle('selected',!gross);button.querySelector('[data-basis408=\"gross\"]').classList.toggle('selected',gross)};\n const descriptor=Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype,'value');\n if(descriptor?.get&&descriptor?.set)Object.defineProperty(select,'value',{configurable:true,get(){return descriptor.get.call(this)},set(value){descriptor.set.call(this,value);sync()}});\n button.addEventListener('click',()=>{if(select.disabled)return;select.value=select.value==='gross'?'net':'gross';select.dispatchEvent(new Event('change',{bubbles:true}));sync()});\n select.addEventListener('change',sync);select.hidden=true;select.after(button);select.priceSwitch408={button,sync};sync();\n}\nfunction installTaxPresentation408(root=document){\n for(const select of [...(root.matches?.('select')?[root]:[]),...root.querySelectorAll('select')])installPriceSwitch408(select);\n for(const input of root.querySelectorAll('.moneyVat231,.priceBasis228 input'))input.setAttribute('list','taxSuggestions408');\n}\nconst renderBeforeTax408=render;\nrender=function(...args){syncTaxCatalog408();const result=renderBeforeTax408(...args);refreshTaxSettings408();installTaxPresentation408();return result};\nlet taxScheduled408=false;\nnew MutationObserver(records=>{if(!records.some(r=>r.type==='attributes'||[...r.addedNodes].some(n=>n.nodeType===1)))return;if(taxScheduled408)return;taxScheduled408=true;requestAnimationFrame(()=>{taxScheduled408=false;installTaxPresentation408()})}).observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['disabled']});\nsyncTaxCatalog408();refreshTaxSettings408();installTaxPresentation408();\n"},{"src":"help-start409.js?v=409","code":"'use strict';\n// A read-only introduction; it does not launch a setup or change business data.\nfunction installStartHelp409(){const host=document.querySelector('#tabHelp .helpPanel');if(!host||document.getElementById('startHelp409'))return;const card=document.createElement('section');card.id='startHelp409';card.className='dashboardCard';card.innerHTML='<h2>Neu bei Zapasio?</h2><p>Richte dein Betriebsland ein und erfasse deinen ersten Artikel mit geprüftem Bestand. Die Anleitung erklärt Mengen, Gebinde, MHD und die Bestätigung einer Buchung.</p><p><a class=\"soft\" href=\"hilfe-erste-schritte.html\" target=\"_blank\" rel=\"noopener\">Erste Schritte: Anleitung öffnen</a></p><p><a href=\"hilfe-erste-schritte.html\" download=\"Zapasio-Erste-Schritte.html\">Hilfsdatei herunterladen</a></p>';host.prepend(card)}\ninstallStartHelp409();\n"},{"src":"navigation-stability411.js?v=411","code":"'use strict';\n// Install after all navigation extensions so mobile and guided Back obey the\n// same durable-save boundary as desktop navigation.\nfunction navigationBusy411(){\n if(!busyOperation275())return false;\n showFeedback('Speicherung läuft','info',2500,'Bitte kurz warten.');return true;\n}\nfunction openSelectedPlanning411(id,section='overview',mobile=false){\n return afterOperation275(openPlanningFromNav(section),ok=>{\n  if(ok===false)return false;\n  selectPlanningEntry(id,section);state.mobileSelectedPlanId=id;\n  if(mobile)document.body.classList.add('mobile-detail-mode');return true;\n });\n}\nconst tabBefore411=showTab;\nshowTab=function(...args){if(navigationBusy411())return false;return tabBefore411.apply(this,args)};\nconst mobileBefore411=mobileShow;\nmobileShow=function(...args){\n if(navigationBusy411())return false;\n const pending=pendingRecipeAutosave347;\n if(!pending)return mobileBefore411.apply(this,args);\n if(!document.getElementById(`ingredientRows_${pending.gi}_${pending.ri}`))return false;\n return afterOperation275(saveRecipeDetails(pending.gi,pending.ri,true),ok=>ok?mobileBefore411.apply(this,args):false);\n};\nconst backBefore411=window.zapasioHandleBack;\nwindow.zapasioHandleBack=function(...args){if(busyOperation275())return true;return backBefore411?.apply(this,args)||false};\n"},{"src":"fixed-costs412.js?v=418","code":"'use strict';\nconst FIXED_STEPS412=['Miete & Räume','Energie','Weitere Kosten','Arbeitszeit & Auszahlung','Prüfen & speichern'];\nconst FIXED_ROWS412=[['kitchen','rooms','Küchenmiete'],['premises','rooms','Lokal- oder Büromiete'],['storage','rooms','Lagermiete'],['utilities','rooms','Betriebskosten für Räume'],['electricity','energy','Strom'],['gas','energy','Gas / Heizung'],['water','energy','Wasser'],['insurance','other','Versicherungen'],['leasing','other','Leasing / laufende Fahrzeugkosten'],['software','other','Software & Abos']];\nlet fixedDraft412=null,fixedStep412=0,fixedSession412=0,fixedAutoShown412=false,fixedBusy412=false,fixedBase412=null,fixedScopeBase412='',fixedClickLast412=null;\nfunction fixedScope412(){return typeof cloudConfig==='function'?String(cloudConfig()?.workspace||'local'):'local'}\nfunction fixedAdmin412(){return currentAccessUser()?.role==='admin'}\nfunction fixedNew412(){return {schema:1,status:'draft',currency:business130().currency||'EUR',rows:FIXED_ROWS412.map(([id,category,label])=>({id,category,label,amount:'',period:'month',basis:'net',vat:'',excluded:false})),ownerEnabled:false,ownerHourly:'',payoutEnabled:false,payouts:[],updatedAt:''}}\nfunction fixedNumber412(raw){const s=String(raw??'').trim();if(!s)return null;if(!/^\\d+(?:[.,]\\d{1,4})?$/.test(s))throw Error('Bitte eine Zahl ab 0 eingeben, zum Beispiel 120,50.');const n=Number(s.replace(',','.'));if(!Number.isFinite(n)||n>1e10)throw Error('Betrag ist zu groß.');return n}\nfunction fixedMonthlyRow412(row){\n if(row.excluded)return {monthly:0,known:true};\n if(!['month','year'].includes(row.period)||!['net','gross'].includes(row.basis))throw Error('Zeitraum oder Brutto/Netto prüfen.');\n const amount=fixedNumber412(row.amount);if(amount===null)return {monthly:0,known:false,reason:'amount'};\n let net=amount;if(row.basis==='gross'){const vat=fixedNumber412(row.vat);if(vat===null)return {monthly:0,known:false,reason:'vat'};if(vat>100)throw Error('MwSt. zwischen 0 und 100 eingeben.');net=amount/(1+vat/100)}\n return {monthly:net/(row.period==='year'?12:1),known:true};\n}\nfunction fixedSummary412(model=state.fixedCosts412){\n const result={monthly:0,annual:0,unresolved:[],invalid:[],complete:false,currency:model?.currency||business130().currency||'EUR',ownerHourly:null,payoutMonthly:0,payoutAnnual:0,planningMonthly:0,planningAnnual:0};\n if(!model){result.unresolved=['Erstanlage fehlt'];return result}\n for(const row of model.rows||[]){try{const v=fixedMonthlyRow412(row);result.monthly+=v.monthly;if(!v.known)result.unresolved.push(row.label)}catch(error){result.invalid.push(row.label+': '+error.message)}}\n if(model.ownerEnabled){try{result.ownerHourly=fixedNumber412(model.ownerHourly);if(result.ownerHourly===null)result.unresolved.push('Eigene Arbeitszeit')}catch(error){result.invalid.push('Eigene Arbeitszeit: '+error.message)}}\n if(model.payoutEnabled){for(const person of model.payouts||[]){try{if(!String(person.name||'').trim())result.invalid.push('Auszahlungsziel: Bitte die Person benennen.');const amount=fixedPayoutMonthly415(person);if(amount===null)result.unresolved.push('Auszahlungsziel '+(person.name||'Person'));else result.payoutMonthly+=amount}catch(error){result.invalid.push('Auszahlungsziel '+(person.name||'Person')+': '+error.message)}}if(!model.payouts?.length)result.unresolved.push('Auszahlungsziel: Person fehlt')}\n result.payoutAnnual=result.payoutMonthly*12;result.planningMonthly=result.monthly+result.payoutMonthly;result.planningAnnual=result.planningMonthly*12;\n result.annual=result.monthly*12;result.complete=model.status==='saved'&&result.unresolved.length===0&&result.invalid.length===0&&model.rows?.length>0;return result;\n}\nfunction fixedMoney412(value,currency=fixedDraft412?.currency||state.fixedCosts412?.currency||'EUR'){return new Intl.NumberFormat('de-AT',{style:'currency',currency:['EUR','PLN'].includes(currency)?currency:'EUR'}).format(value)}\nfunction fixedError412(message){const box=document.getElementById('fixedError412');if(box){box.textContent=message;box.hidden=!message}}\nfunction fixedInput412(id,key,value){if(!fixedAdmin412()||!fixedDraft412||fixedBusy412)return;const row=fixedDraft412.rows.find(r=>r.id===id);if(row&&['amount','vat','label'].includes(key))row[key]=String(value).slice(0,120)}\nfunction fixedToggle412(id,key){if(!fixedAdmin412()||!fixedDraft412||fixedBusy412)return;const row=fixedDraft412.rows.find(r=>r.id===id);if(!row)return;if(key==='excluded')row.excluded=!row.excluded;else if(key==='period')row.period=row.period==='year'?'month':'year';else if(key==='basis')row.basis=row.basis==='gross'?'net':'gross';fixedRender412()}\nfunction fixedSwitch412(label,checked,action,text){return `<button type=\"button\" class=\"fixedSwitch412\" role=\"switch\" aria-label=\"${esc(label)}\" aria-checked=\"${!!checked}\" onclick=\"${action}\"><span class=\"fixedTrack412\" aria-hidden=\"true\"><span></span></span>${esc(text)}</button>`}\nfunction fixedRowHtml412(row){const id=escJs(row.id),custom=row.id.startsWith('custom-');return `<section class=\"fixedRow412\"><div class=\"fixedRowHead412\">${custom?`<label>Bezeichnung<input data-adapted231=\"fixed412\" id=\"fcName412_${esc(row.id)}\" aria-label=\"Bezeichnung der eigenen Kostenposition\" value=\"${esc(row.label)}\" maxlength=\"120\" oninput=\"fixedInput412('${id}','label',this.value)\"></label>`:`<h3>${esc(row.label)}</h3>`}${fixedSwitch412(row.label+': trifft nicht zu',row.excluded,`fixedToggle412('${id}','excluded')`,row.excluded?'Trifft nicht zu':'Trifft zu / noch prüfen')}</div>${row.excluded?'':`<label>Betrag (${esc(fixedDraft412.currency)})<input data-adapted231=\"fixed412\" id=\"fcAmount412_${esc(row.id)}\" aria-label=\"${esc(row.label)} Betrag\" inputmode=\"decimal\" placeholder=\"Noch offen\" value=\"${esc(row.amount)}\" oninput=\"fixedInput412('${id}','amount',this.value)\"></label><div class=\"fixedChoices412\">${fixedSwitch412(row.label+': jährlich',row.period==='year',`fixedToggle412('${id}','period')`,row.period==='year'?'Jährlich':'Monatlich')}${fixedSwitch412(row.label+': Brutto',row.basis==='gross',`fixedToggle412('${id}','basis')`,row.basis==='gross'?'Brutto':'Netto')}</div>${row.basis==='gross'?`<label>MwSt. laut Rechnung (%)<input data-adapted231=\"fixed412\" id=\"fcVat412_${esc(row.id)}\" aria-label=\"${esc(row.label)} MwSt.\" inputmode=\"decimal\" placeholder=\"Satz angeben, auch bei 0 %\" value=\"${esc(row.vat)}\" oninput=\"fixedInput412('${id}','vat',this.value)\"></label><p class=\"tiny\">Für die Kostenbasis wird die enthaltene MwSt. herausgerechnet. Nicht abziehbare Steuer bitte im kostenwirksamen Betrag berücksichtigen und als Netto erfassen.</p>`:''}`}</section>`}\nfunction fixedRowDisplay414(row,currency=fixedDraft412?.currency||state.fixedCosts412?.currency||'EUR'){\n try{const value=fixedMonthlyRow412(row);\n  if(row.excluded)return {value:'Trifft nicht zu',note:'',pending:false};\n  if(value.known)return {value:fixedMoney412(value.monthly,currency)+' / Monat',note:row.basis==='gross'?'Netto umgerechnet aus '+fixedMoney412(fixedNumber412(row.amount),currency)+' brutto / '+(row.period==='year'?'Jahr':'Monat'):'',pending:false};\n  if(value.reason==='vat')return {value:fixedMoney412(fixedNumber412(row.amount),currency)+' brutto / '+(row.period==='year'?'Jahr':'Monat'),note:'MwSt.-Satz fehlt. Noch nicht in der Summe berücksichtigt.',pending:true};\n  return {value:'Betrag fehlt',note:'Noch nicht in der Summe berücksichtigt.',pending:true};\n }catch(error){return {value:String(row.amount||'Eingabe prüfen'),note:error.message+' Noch nicht in der Summe berücksichtigt.',pending:true}}\n}\nfunction fixedPayoutMonthly415(person){if(!['month','year'].includes(person.period))throw Error('Zeitraum prüfen.');const amount=fixedNumber412(person.amount);return amount===null?null:amount/(person.period==='year'?12:1)}\nfunction fixedPayoutInput415(id,key,value){if(!fixedAdmin412()||!fixedDraft412||fixedBusy412)return;const person=fixedDraft412.payouts?.find(p=>p.id===id);if(person&&['name','amount'].includes(key))person[key]=String(value).slice(0,120)}\nfunction fixedPayoutAction415(action,id){if(!fixedAdmin412()||!fixedDraft412||fixedBusy412)return;fixedDraft412.payouts??=[];if(action==='toggle')fixedDraft412.payoutEnabled=!fixedDraft412.payoutEnabled;if(action==='add'||action==='toggle'&&fixedDraft412.payoutEnabled&&!fixedDraft412.payouts.length)fixedDraft412.payouts.push({id:'person-'+Date.now()+'-'+Math.random().toString(36).slice(2,8),name:'',amount:'',period:'month'});if(action==='remove')fixedDraft412.payouts=fixedDraft412.payouts.filter(p=>p.id!==id);if(action==='period'){const p=fixedDraft412.payouts.find(p=>p.id===id);if(p)p.period=p.period==='year'?'month':'year'}fixedRender412()}\nfunction fixedPayoutForm415(){return `<section><h3>Unternehmergehalt / gewünschte Auszahlung</h3><p>Nur für Selbstständige: gewünschte Auszahlung je Person. Mitarbeitergehälter hier nicht eintragen.</p>${fixedSwitch412('Auszahlungsziele für Selbstständige berücksichtigen',fixedDraft412.payoutEnabled,\"fixedPayoutAction415('toggle')\",'Auszahlungsziele berücksichtigen')}${fixedDraft412.payoutEnabled?`${(fixedDraft412.payouts||[]).map((p,i)=>`<section class=\"fixedRow412\"><h4>Selbstständige Person ${i+1}</h4><label>Name / Bezeichnung<input data-adapted231=\"fixed412\" id=\"fcPayoutName415_${esc(p.id)}\" aria-label=\"Name selbstständige Person ${i+1}\" maxlength=\"120\" value=\"${esc(p.name)}\" oninput=\"fixedPayoutInput415('${escJs(p.id)}','name',this.value)\"></label><label>Gewünschte Auszahlung (${esc(fixedDraft412.currency)})<input data-adapted231=\"fixed412\" id=\"fcPayoutAmount415_${esc(p.id)}\" aria-label=\"Auszahlungsziel Person ${i+1}\" inputmode=\"decimal\" placeholder=\"Noch offen\" value=\"${esc(p.amount)}\" oninput=\"fixedPayoutInput415('${escJs(p.id)}','amount',this.value)\"></label>${fixedSwitch412('Auszahlungsziel Person '+(i+1)+': jährlich',p.period==='year',\"fixedPayoutAction415('period','\"+escJs(p.id)+\"')\",p.period==='year'?'Jährlich':'Monatlich')}<button type=\"button\" class=\"soft\" onclick=\"fixedPayoutAction415('remove','${escJs(p.id)}')\">Person entfernen</button></section>`).join('')}<button type=\"button\" class=\"soft\" onclick=\"fixedPayoutAction415('add')\">Weitere selbstständige Person hinzufügen</button><p>Persönliches Ziel ohne MwSt.-Umrechnung. Eigene Arbeitszeit für denselben Zweck nicht zusätzlich ansetzen.</p>`:''}</section>`}\nfunction fixedPayoutReview415(s,model=fixedDraft412){if(!model?.payoutEnabled)return '';return `<section class=\"fixedPayout415\"><h3>Gewünschte Auszahlungen für Selbstständige</h3><div class=\"fixedReviewRows412\">${(model.payouts||[]).map(p=>{let value='Betrag fehlt';try{const amount=fixedPayoutMonthly415(p);if(amount!==null)value=fixedMoney412(amount,s.currency)+' / Monat'}catch{value='Eingabe prüfen'}return `<div><span>${esc(p.name||'Person noch benennen')}</span><b>${esc(value)}</b></div>`}).join('')}</div><p>Auszahlungsziele gesamt: <b>${fixedMoney412(s.payoutMonthly,s.currency)} / Monat</b></p><div class=\"fixedTotal412\"><span>${s.complete?'Planungsbedarf':'Bekannter Planungsbedarf · vorläufig'} · Betriebskosten + Auszahlungsziele</span><strong>${fixedMoney412(s.planningMonthly,s.currency)} / Monat</strong><span>${fixedMoney412(s.planningAnnual,s.currency)} / Jahr</span></div><p>Die Auszahlungsziele sind separat geplant. Sie verändern den ausgewiesenen Betriebskostenbetrag nicht. Der eigene Stundensatz wird hier nicht zusätzlich addiert.</p></section>`}\nfunction fixedReview412(){const s=fixedSummary412({...fixedDraft412,status:'saved'});return `<div class=\"fixedTotal412\"><span>${s.complete?'Laufende Betriebskosten':'Bekannte Betriebskosten · vorläufig'}</span><strong>${fixedMoney412(s.monthly)} / Monat</strong><span>${fixedMoney412(s.annual)} / Jahr</span></div>${s.unresolved.length?`<p class=\"fixedNote412\">Noch offen: ${s.unresolved.map(label=>{const row=fixedDraft412.rows.find(r=>r.label===label);return esc(label+(row&&fixedRowDisplay414(row).note.startsWith('MwSt.-Satz fehlt')?' (MwSt.-Satz fehlt)':''))}).join(', ')}. Fehlende Angaben zählen nicht als bestätigte 0.</p>`:''}${s.invalid.length?`<p role=\"alert\">${s.invalid.map(esc).join(' ')}</p>`:''}<div class=\"fixedReviewRows412\">${fixedDraft412.rows.map(r=>{const display=fixedRowDisplay414(r);return `<div class=\"${display.pending?'fixedPending414':''}\"><span>${esc(r.label)}${display.note?`<small>${esc(display.note)}</small>`:''}</span><b>${esc(display.value)}</b></div>`}).join('')}</div>${fixedPayoutReview415(s)}<p>Eigene Arbeitszeit: ${fixedDraft412.ownerEnabled?(s.ownerHourly===null?'noch offen':fixedMoney412(s.ownerHourly)+' / Stunde'):'kein kalkulatorischer Stundensatz hinterlegt'}. Dieser Stundensatz ist kein monatlicher Fixkostenbetrag.</p><p>Diese Kosten gelten auch bei Einsätzen außerhalb deiner Küche. Zusätzliche Standgebühren und andere Einsatzkosten werden dort getrennt erfasst.</p><p class=\"fixedNote412\">Im DB-Assistenten kannst du mit dem Monatsstundenbudget einen Betriebskostenanteil berechnen. Gespeicherte Einsätze und Abschlüsse werden dadurch nicht automatisch geändert.</p>`}\nfunction fixedViewPosition413(dialog){\n const active=document.activeElement,inside=active&&dialog.contains(active);\n return {top:dialog.scrollTop,left:dialog.scrollLeft,focus:inside?{id:active.id,action:active.getAttribute('onclick'),start:active.selectionStart,end:active.selectionEnd}:null};\n}\nfunction fixedRestoreView413(dialog,position,reset){\n if(reset){dialog.querySelector('h2')?.focus({preventScroll:true});dialog.scrollTop=0;dialog.scrollLeft=0;return}\n const key=position.focus;\n const target=key?.id?[...dialog.querySelectorAll('[id]')].find(el=>el.id===key.id):key?.action?[...dialog.querySelectorAll('[onclick]')].find(el=>el.getAttribute('onclick')===key.action):null;\n target?.focus({preventScroll:true});\n if(target&&Number.isInteger(key.start)&&typeof target.setSelectionRange==='function')try{target.setSelectionRange(key.start,key.end)}catch{}\n dialog.scrollTop=position.top;dialog.scrollLeft=position.left;\n}\nfunction fixedRender412(reset=false){const d=document.getElementById('fixedCostsDialog412');if(!d||!fixedDraft412)return;const position=fixedViewPosition413(d);const group=['rooms','energy','other'][fixedStep412];let body='';\n if(group){const notes={rooms:'Miete und Betriebskosten nur einmal erfassen. Sind Betriebskosten bereits in der Miete enthalten, markiere die separate Position als „Trifft nicht zu“.',energy:'Regelmäßige Energiekosten eintragen. Bereits enthaltene Kosten nicht doppelt erfassen.',other:'Nur laufende Kosten. Zusätzliche Standgebühren oder Personal gehören zum Einsatz. Leasing und Kredit dienen hier der Planung.'};body=`<p>${notes[group]}</p>${fixedDraft412.rows.filter(r=>r.category===group).map(fixedRowHtml412).join('')}<button type=\"button\" class=\"soft\" onclick=\"fixedAdd412('${group}')\">Eigene Kostenposition ergänzen</button>`}\n else if(fixedStep412===3)body=`<p>Optional: Was ist eine Stunde deiner eigenen Arbeit wert? Der DB-Assistent rechnet diesen Satz nicht automatisch zusätzlich zum Auszahlungsziel ein.</p>${fixedSwitch412('Eigene Arbeitszeit berücksichtigen',fixedDraft412.ownerEnabled,'fixedOwnerToggle412()',fixedDraft412.ownerEnabled?'Eigene Arbeitszeit berücksichtigen':'Optional: eigene Arbeitszeit berücksichtigen')}${fixedDraft412.ownerEnabled?`<label>Kalkulatorischer Stundensatz (${esc(fixedDraft412.currency)})<input data-adapted231=\"fixed412\" id=\"fcOwner412\" aria-label=\"Eigene Arbeitszeit Stundensatz\" inputmode=\"decimal\" placeholder=\"Noch offen\" value=\"${esc(fixedDraft412.ownerHourly)}\" oninput=\"fixedOwnerInput412(this.value)\"></label><p>Dieser Wert ist eine interne Planungsgröße. Bereits als Personalkosten erfasste Arbeit nicht nochmals berechnen.</p>`:''}`;\n if(fixedStep412===3)body+=fixedPayoutForm415();\n else if(fixedStep412===4)body=fixedReview412();\n d.innerHTML=`<header><button type=\"button\" class=\"soft fixedClose417\" aria-label=\"Zwischenstand speichern und Fenster schließen\" title=\"Auch offene Angaben als Zwischenstand speichern und schließen\" onclick=\"fixedSave412('draft')\">×</button><p class=\"fixedEyebrow412\">Betrieb einrichten · nur Admin</p><h2 id=\"fixedTitle412\">${FIXED_STEPS412[fixedStep412]}</h2><p>Schritt ${fixedStep412+1} von ${FIXED_STEPS412.length}</p><progress value=\"${fixedStep412+1}\" max=\"5\" aria-label=\"Fortschritt\"></progress></header><div id=\"fixedBody412\">${body}</div><p id=\"fixedError412\" role=\"alert\" hidden></p><footer>${fixedStep412?'<button type=\"button\" class=\"soft\" onclick=\"if(!(event.detail>1))fixedMove412(-1)\">Zurück</button>':''}<button type=\"button\" class=\"soft\" onclick=\"fixedSave412('draft')\" title=\"Auch unvollständige Angaben als Zwischenstand speichern und schließen\">Speichern & schließen</button>${fixedStep412<4?'<button type=\"button\" class=\"primary\" onclick=\"if(!(event.detail>1))fixedMove412(1)\">Weiter</button>':'<button type=\"button\" class=\"primary\" onclick=\"fixedSave412(\\'saved\\')\">Angaben speichern</button>'}</footer>`;\n d.querySelector('h2')?.setAttribute('tabindex','-1');fixedRestoreView413(d,position,reset);\n}\nfunction fixedOwnerToggle412(){if(!fixedAdmin412()||!fixedDraft412||fixedBusy412)return;fixedDraft412.ownerEnabled=!fixedDraft412.ownerEnabled;fixedRender412()}\nfunction fixedOwnerInput412(value){if(fixedAdmin412()&&fixedDraft412&&!fixedBusy412)fixedDraft412.ownerHourly=String(value).slice(0,120)}\nfunction fixedAdd412(category){if(!fixedAdmin412()||!fixedDraft412||fixedBusy412||!['rooms','energy','other'].includes(category))return;fixedDraft412.rows.push({id:'custom-'+Date.now()+'-'+Math.random().toString(36).slice(2,8),category,label:'Eigene Kosten',amount:'',period:'month',basis:'net',vat:'',excluded:false});fixedRender412();document.getElementById('fixedBody412')?.lastElementChild?.previousElementSibling?.scrollIntoView({block:'nearest'})}\nfunction fixedMove412(direction){if(!fixedAdmin412()||!fixedDraft412||fixedBusy412)return false;if(direction>0){const group=['rooms','energy','other'][fixedStep412];try{for(const row of fixedDraft412.rows.filter(r=>r.category===group))fixedMonthlyRow412(row);if(fixedStep412===3&&fixedDraft412.ownerEnabled)fixedNumber412(fixedDraft412.ownerHourly);if(fixedStep412===3&&fixedDraft412.payoutEnabled)for(const person of fixedDraft412.payouts||[])fixedPayoutMonthly415(person)}catch(error){fixedError412(error.message);return false}}fixedStep412=Math.max(0,Math.min(4,fixedStep412+direction));fixedRender412(true);return true}\nfunction fixedClickGuard412(event){\n if(!event.target?.closest?.('button')||!event.detail)return;\n const previous=fixedClickLast412;fixedClickLast412={x:event.clientX,y:event.clientY,at:event.timeStamp};\n // One double-click gesture must not activate a newly exposed button after a\n // step or row changes. Different positions and keyboard actions remain usable.\n if(previous&&event.timeStamp-previous.at<500&&Math.abs(event.clientX-previous.x)<5&&Math.abs(event.clientY-previous.y)<5){event.preventDefault();event.stopImmediatePropagation();return false}\n}\nfunction fixedOpen412(){if(!fixedAdmin412()){showFeedback('Nur für Admins','warn',3000,'Laufende Betriebskosten kann nur ein Admin bearbeiten.');return false}if(busyOperation275()||fixedBusy412)return false;const existing=document.getElementById('fixedCostsDialog412');if(existing?.open)return true;fixedBase412=JSON.stringify(state.fixedCosts412||null);fixedScopeBase412=fixedScope412();fixedDraft412=clone(state.fixedCosts412||fixedNew412());fixedStep412=0;fixedClickLast412=null;fixedSession412++;let d=existing;if(!d){d=document.createElement('dialog');d.id='fixedCostsDialog412';d.setAttribute('aria-labelledby','fixedTitle412');d.addEventListener('click',fixedClickGuard412,true);d.addEventListener('cancel',event=>{event.preventDefault();fixedSave412('draft')});document.body.appendChild(d)}fixedRender412(true);d.showModal();return true}\nfunction fixedSave412(status='saved'){\n if(!fixedAdmin412()||!fixedDraft412||fixedBusy412)return false;\n if(status==='draft'&&JSON.stringify(fixedDraft412)===fixedBase412){document.getElementById('fixedCostsDialog412')?.close();fixedDraft412=null;return true}\n const candidate=clone(fixedDraft412),session=fixedSession412;candidate.status=status==='saved'?'saved':'draft';candidate.updatedAt=new Date().toISOString();\n if(candidate.status==='saved'){const s=fixedSummary412(candidate);if(s.invalid.length){fixedError412(s.invalid.join(' '));return false}if(candidate.rows.some(r=>!String(r.label||'').trim())){fixedError412('Bitte alle Kostenpositionen benennen.');return false}}\n fixedBusy412=true;\n const result=operationTransaction(candidate.status==='saved'?'Betriebskosten gespeichert':'Einrichtung zum Fortsetzen gespeichert','settings',()=>{if(!fixedAdmin412())throw Error('Nur ein Admin kann Betriebskosten ändern.');if(fixedScope412()!==fixedScopeBase412||JSON.stringify(state.fixedCosts412||null)!==fixedBase412)throw Error('Der Betriebsstand wurde inzwischen geändert. Bitte die Angaben erneut öffnen und prüfen.');state.fixedCosts412=candidate});\n return afterOperation275(result,ok=>{fixedBusy412=false;if(session!==fixedSession412)return ok;if(ok){document.getElementById('fixedCostsDialog412')?.close();fixedDraft412=null;fixedCard412()}else fixedError412('Nicht gespeichert. Deine Eingaben bleiben geöffnet. Bitte erneut versuchen.');return ok});\n}\nfunction fixedHelp412(){const host=document.querySelector('#tabHelp .helpPanel');if(!host||document.getElementById('fixedHelp412'))return;const card=document.createElement('section');card.id='fixedHelp412';card.className='dashboardCard';card.innerHTML='<h2>Laufende Betriebskosten</h2><p>Erstanlage, monatliche und jährliche Beträge, offene Angaben und zusätzliche Einsatzkosten.</p><a href=\"hilfe-betriebskosten412.html\" target=\"_blank\" rel=\"noopener\">Anleitung öffnen</a>';host.appendChild(card)}\nfunction fixedCard412(){fixedHelp412();const panel=document.getElementById('settingsPanel130_business');if(!panel)return;let card=document.getElementById('fixedCostsCard412');if(!card){card=document.createElement('section');card.id='fixedCostsCard412';card.className='card';panel.prepend(card)}card.hidden=!fixedAdmin412();if(card.hidden)return;const s=fixedSummary412(),m=state.fixedCosts412;card.innerHTML=`<h2>Laufende Betriebskosten</h2><p>Deine laufenden Kosten einmal hinterlegen.</p><div class=\"fixedTotal412\"><span>${s.complete?'Angaben vollständig':m?.status==='draft'?'Einrichtung noch offen':'Betriebskosten noch unvollständig'}</span><strong>${fixedMoney412(s.monthly,s.currency)} / Monat</strong><span>${fixedMoney412(s.annual,s.currency)} / Jahr${s.complete?'':' · bekannter Teilbetrag'}</span></div>${m?.currency&&m.currency!==(business130().currency||'EUR')?'<p class=\"fixedNote412\">Die Betriebswährung wurde geändert. Diese Kosten bleiben in ihrer gespeicherten Währung; sie wurden nicht umgerechnet.</p>':''}${fixedPayoutReview415(s,m)}<p>${s.unresolved.length?`${s.unresolved.length} Angabe(n) noch offen. `:''}Anteile berechnest du im DB-Assistenten.</p><button type=\"button\" class=\"primary\" onclick=\"fixedOpen412()\">${m?'Betriebskosten prüfen / bearbeiten':'Geführte Erstanlage starten'}</button><p><a href=\"hilfe-betriebskosten412.html\" target=\"_blank\" rel=\"noopener\">Hilfe zu laufenden Kosten</a></p>`}\nfunction fixedAuto412(){if(fixedAutoShown412||globalThis.gsBootBlocked||state.fixedCosts412||!fixedAdmin412()||document.getElementById('deviceBoot257')||busyOperation275()||document.querySelector('dialog[open],.modal[style*=\"flex\"],.appConfirm.show,.appPrompt.show'))return;fixedAutoShown412=true;fixedOpen412()}\nconst fixedRenderBefore412=render;\nrender=function(...args){const result=fixedRenderBefore412(...args);fixedCard412();setTimeout(fixedAuto412,0);if(!fixedAdmin412()&&document.getElementById('fixedCostsDialog412')?.open){document.getElementById('fixedCostsDialog412').close();fixedDraft412=null}return result};\nconst fixedBackBefore412=window.zapasioHandleBack;\nwindow.zapasioHandleBack=function(...args){if(document.getElementById('fixedCostsDialog412')?.open){if(!busyOperation275()&&!fixedBusy412)fixedSave412('draft');return true}return fixedBackBefore412?.apply(this,args)||false};\nwindow.addEventListener('beforeunload',event=>{if(fixedDraft412){event.preventDefault();event.returnValue=''}});\nnew MutationObserver(()=>{if(!document.getElementById('deviceBoot257'))fixedAuto412()}).observe(document.body,{childList:true});\nfixedCard412();fixedAuto412();\n"},{"src":"db-assistant416.js?v=418","code":"'use strict';\n// One calculation engine for guided and compact views. No event/closing mutation.\nconst DB_STEPS416=['Nettoerlös','Warenkosten','Verkaufsabhängige Kosten','Weitere Einsatzkosten','Betriebskosten verteilen','Ergebnis verstehen'];\nlet dbDraft416=null,dbBase416=null,dbScope416='',dbBusy416=false;\nfunction dbBasis416(){const m=state.fixedCosts412,s=fixedSummary412(m);return {currency:s.currency,monthly:s.monthly,payoutMonthly:s.payoutMonthly||0,complete:s.complete,payoutEnabled:!!m?.payoutEnabled,monthlyHours:String(m?.allocation416?.monthlyHours||''),updatedAt:m?.updatedAt||'',issues:[...s.unresolved,...s.invalid]}}\nfunction dbNew416(){return {schema:1,mode:'amateur',step:0,label:'',revenue:'',goods:'',variable:'',direct:'',allocation:false,payout:false,hours:'',basis:dbBasis416()}}\nfunction dbNumber416(value,label){let n;try{n=fixedNumber412(value)}catch{throw Error(label+': Bitte eine gültige Zahl ab 0 eingeben.')}if(n===null)throw Error(label+': Betrag fehlt. Wenn nichts anfällt, bitte 0 eintragen.');return n}\nfunction dbCalculate416(m){const errors=[],v={};for(const key of ['revenue','goods','variable','direct'])try{v[key]=dbNumber416(m[key],{revenue:'Nettoerlös',goods:'Warenkosten',variable:'Verkaufsabhängige Kosten',direct:'Weitere Einsatzkosten'}[key])}catch(e){errors.push(e.message)}\n let fixed=0,payout=0,share=0;\n if(m.allocation){try{if(!m.basis?.complete)throw Error('Die gespeicherte Betriebskostenbasis ist noch unvollständig. Bitte zuerst die Erstanlage abschließen.');const capacity=dbNumber416(m.basis.monthlyHours,'Geplante Einsatzstunden pro Monat'),hours=dbNumber416(m.hours,'Stunden dieser Berechnung');if(capacity<=0)throw Error('Geplante Einsatzstunden pro Monat müssen größer als 0 sein.');if(hours>capacity)throw Error('Die Einsatzstunden überschreiten die Monatsbasis. Bitte die Stundenverteilung prüfen.');if(!Number.isFinite(m.basis.monthly)||m.basis.monthly<0||!Number.isFinite(m.basis.payoutMonthly)||m.basis.payoutMonthly<0)throw Error('Gespeicherte Kostenbasis prüfen.');share=hours/capacity;fixed=m.basis.monthly*share;if(m.payout)payout=m.basis.payoutMonthly*share}catch(e){errors.push(e.message)}}\n if(errors.length)return {errors};const goodsContribution=v.revenue-v.goods,contribution=goodsContribution-v.variable,afterDirect=contribution-v.direct,afterFixed=afterDirect-fixed;return {errors:[],...v,goodsContribution,contribution,afterDirect,fixed,payout,share,afterFixed,afterTarget:afterFixed-payout,quote:v.revenue>0?contribution/v.revenue*100:null,allocated:!!m.allocation};\n}\nfunction dbEnsure416(){const scope=fixedScope412();if(!dbDraft416||dbScope416!==scope){dbDraft416=clone(state.dbAssistant416||dbNew416());dbBase416=JSON.stringify(state.dbAssistant416||null);dbScope416=scope}return dbDraft416}\nfunction dbInput416(key,value){if(!fixedAdmin412()||dbBusy416)return;const m=dbEnsure416();if(['label','revenue','goods','variable','direct','hours'].includes(key))m[key]=String(value).slice(0,120)}\nfunction dbAction416(action){if(!fixedAdmin412()||dbBusy416)return;const m=dbEnsure416();if(action==='mode')m.mode=m.mode==='pro'?'amateur':'pro';if(action==='allocation')m.allocation=!m.allocation;if(action==='payout')m.payout=!m.payout;if(action==='basis')m.basis=dbBasis416();dbRender416()}\nfunction dbMove416(direction){if(!fixedAdmin412()||dbBusy416)return;const m=dbEnsure416();if(direction>0&&m.step<4){try{dbNumber416(m[['revenue','goods','variable','direct'][m.step]],DB_STEPS416[m.step])}catch(e){dbError416(e.message);return false}}if(direction>0&&m.step===4){const result=dbCalculate416(m);if(result.errors.length){dbError416(result.errors.join(' '));return false}}m.step=Math.max(0,Math.min(5,m.step+direction));dbRender416(true);return true}\nfunction dbError416(message){const e=document.getElementById('dbError416');if(e){e.textContent=message;e.hidden=!message}}\nfunction dbField416(key,label,note){const m=dbEnsure416();return `<label>${esc(label)} (${esc(m.basis.currency)})<input data-adapted231=\"fixed412\" id=\"dbInput416_${key}\" aria-label=\"${esc(label)}\" inputmode=\"decimal\" placeholder=\"Noch offen, bei keinen Kosten 0\" value=\"${esc(m[key])}\" oninput=\"dbInput416('${key}',this.value)\"></label><p class=\"dbExplain416\">${esc(note)}</p>`}\nfunction dbPart416(step){const m=dbEnsure416();if(step===0)return `<label>Name dieser Berechnung (optional)<input data-adapted231=\"fixed412\" id=\"dbInput416_label\" aria-label=\"Name dieser Berechnung\" value=\"${esc(m.label)}\" maxlength=\"120\" oninput=\"dbInput416('label',this.value)\"></label>${dbField416('revenue','Nettoerlös','Wie viel bringt der gesamte Einsatz ohne Umsatzsteuer ein? Nimm den geplanten Nettoerlös oder für eine Nachrechnung den tatsächlichen Nettoerlös aus der Kasse. Eine Berechnung soll sich auf einen Monat beziehen.')}`;\n if(step===1)return dbField416('goods','Warenkosten gesamt','Was kostet die eingesetzte Ware für diese Verkäufe insgesamt? Verpackung hier nur erfassen, wenn sie nicht bei den weiteren verkaufsabhängigen Kosten steht. Nichts doppelt zählen.');\n if(step===2)return dbField416('variable','Weitere verkaufsabhängige Kosten gesamt','Kosten, die durch die Verkäufe entstehen, etwa Verpackung oder Zahlungsgebühren. Umsatzprovisionen zuerst als Eurobetrag für den eingetragenen Erlös berechnen. Miete gehört nicht hierher.');\n if(step===3)return dbField416('direct','Weitere Kosten dieses Einsatzes gesamt','Zum Beispiel zusätzliche Standgebühr, eigens gebuchtes Personal oder Fahrt. Nur Beträge, die noch nicht in der Ware, bei verkaufsabhängigen Kosten oder in den gespeicherten Betriebskosten enthalten sind. Personal ist nicht automatisch eine variable Kostenart.');\n if(step===4){const b=m.basis,stale=JSON.stringify(b)!==JSON.stringify(dbBasis416());return `<p>Die laufende Küche kostet auch dann Geld, wenn du mit dem Foodtruck unterwegs bist. Wir verteilen die gespeicherten Monatskosten nach deinem geplanten Stundenbudget.</p>${fixedSwitch412('Betriebskostenanteil berücksichtigen',m.allocation,\"dbAction416('allocation')\",'Betriebskostenanteil berücksichtigen')}<div class=\"fixedTotal412\"><span>Gespeicherte Kostenbasis${b.complete?'':' · unvollständig'}</span><strong>${fixedMoney412(b.monthly,b.currency)} / Monat</strong><span>${esc(b.monthlyHours||'Noch keine')} geplante Einsatzstunden / Monat</span></div>${b.issues?.length?`<p class=\"fixedNote412\">Offene Kostenangaben: ${b.issues.map(esc).join(', ')}.</p>`:''}${stale?'<p class=\"fixedNote412\">Die Betriebseinstellungen wurden seit dieser Berechnung geändert. Gespeicherte Rechenwerte bleiben erhalten. Übernimm die neue Basis bewusst.</p>':''}<button type=\"button\" class=\"soft\" onclick=\"dbAction416('basis')\">Aktuelle Kostenbasis übernehmen</button><p>Das Monatsbudget hinterlegst du als Admin unter Einstellungen → Betrieb → Betriebskosten → Arbeitszeit & Auszahlung.</p>${m.allocation?`<label>Stunden dieses Einsatzes<input data-adapted231=\"fixed412\" id=\"dbInput416_hours\" aria-label=\"Stunden dieses Einsatzes\" inputmode=\"decimal\" placeholder=\"Noch offen\" value=\"${esc(m.hours)}\" oninput=\"dbInput416('hours',this.value)\"></label><p>Gleicher Maßstab wie beim Monatsbudget: Vorbereitung, Fahrt, Verkauf und Nachbereitung. Keine Multiplikation mit der Personenzahl. Die Summe der Anteile aller Einsätze eines Monats darf 100 % nicht überschreiten.</p>${b.payoutEnabled?`${fixedSwitch412('Auszahlungsziel anteilig berücksichtigen',m.payout,\"dbAction416('payout')\",'Auszahlungsziel zusätzlich zeigen')}<p>Gespeichertes Auszahlungsziel aller Selbstständigen: ${fixedMoney412(b.payoutMonthly,b.currency)} / Monat. Das ist ein persönliches Planungsziel. Steuern, Sozialversicherung und tatsächliche Auszahlungen werden damit nicht berechnet. Der eigene Arbeitsstundensatz wird nicht automatisch dazugezählt.</p>`:''}`:'<p class=\"fixedNote412\">Ohne Betriebskostenanteil zeigt die Rechnung nur den Beitrag nach den hier erfassten direkten Kosten.</p>'}`}\n return dbResult416(m);\n}\nfunction dbResult416(m){const r=dbCalculate416(m);if(r.errors.length)return `<div class=\"fixedNote412\" role=\"status\"><b>Rechnung noch offen</b><p>${r.errors.map(esc).join(' ')}</p></div>`;const money=n=>fixedMoney412(n,m.basis.currency),row=(label,n,note='')=>`<div class=\"dbResultRow416\"><span>${esc(label)}${note?`<small>${esc(note)}</small>`:''}</span><b>${money(n)}</b></div>`;\n return `<section class=\"dbLadder416\"><h3>Was bleibt von deinem Erlös?</h3>${row('Nettoerlös',r.revenue)}${row('− Warenkosten',-r.goods)}${row('Beitrag nach Warenkosten',r.goodsContribution)}${row('− Weitere verkaufsabhängige Kosten',-r.variable)}${row('Deckungsbeitrag',r.contribution,'Dieser Betrag hilft, die übrigen Kosten zu decken.')}${row('− Weitere Einsatzkosten',-r.direct)}${row('Beitrag nach direkt erfassten Kosten',r.afterDirect)}${r.allocated?`${row('− Geplanter Betriebskostenanteil',-r.fixed,(r.share*100).toLocaleString('de-AT',{maximumFractionDigits:2})+' % der gespeicherten Monatskosten')}${row('Ergebnis nach erfassten Kosten',r.afterFixed,'Planungsrechnung mit Stundenverteilung, keine Buchhaltung.')}${m.payout?`${row('− Anteil des gewünschten Auszahlungsziels',-r.payout)}${row('Über / unter dem Auszahlungsziel',r.afterTarget,'Das Auszahlungsziel bleibt vom Betriebsergebnis getrennt.')}`:''}`:'<p class=\"fixedNote412\">Betriebskosten und Auszahlungsziele sind hier nicht eingerechnet. Diesen Beitrag bitte nicht als vollständigen Gewinn lesen.</p>'}<p>${r.quote===null?'Bei 0 € Erlös gibt es keine sinnvolle Prozentquote.':'Deckungsbeitragsquote: '+r.quote.toLocaleString('de-AT',{maximumFractionDigits:2})+' % des Nettoerlöses.'}</p><p>Alle Beträge beziehen sich auf diese Berechnung. Bei pauschalen Einsatzkosten lässt sich daraus keine mengenabhängige Gewinnschwelle ableiten. Umsatzabhängige Gebühren ändern sich bei einem anderen Erlös ebenfalls.</p></section>`;\n}\nfunction dbRender416(reset=false){const host=document.getElementById('calc143_contribution');if(!host||!fixedAdmin412())return;const m=dbEnsure416(),position=fixedViewPosition413(host),pro=m.mode==='pro';host.innerHTML=`<article class=\"calcCard143 dbAssistant416\"><header><h2 tabindex=\"-1\">DB-Assistent</h2><p>Was bleibt vom Verkauf? Wir rechnen Schritt für Schritt. Alle Beträge sind netto bzw. kostenwirksame Beträge.</p>${fixedSwitch412('Pro-Ansicht im DB-Assistenten',pro,\"dbAction416('mode')\",pro?'Pro · alle Angaben':'Amateur · Schritt für Schritt')}</header>${pro?'<p>Dieselben Werte und dieselbe Rechnung wie in der geführten Ansicht.</p>':`<p>Schritt ${m.step+1} von 6 · ${DB_STEPS416[m.step]}</p><progress value=\"${m.step+1}\" max=\"6\" aria-label=\"Fortschritt DB-Assistent\"></progress>`}${pro?[0,1,2,3,4,5].map(i=>`<section><h3>${DB_STEPS416[i]}</h3>${dbPart416(i)}</section>`).join(''):dbPart416(m.step)}<p id=\"dbError416\" role=\"alert\" hidden></p><footer>${!pro&&m.step>0?'<button type=\"button\" class=\"soft\" onclick=\"if(!(event.detail>1))dbMove416(-1)\">Zurück</button>':''}${!pro&&m.step<5?'<button type=\"button\" class=\"primary\" onclick=\"if(!(event.detail>1))dbMove416(1)\">Weiter</button>':''}${pro?'<button type=\"button\" class=\"soft\" onclick=\"dbRender416()\">Ergebnis berechnen</button>':''}<button type=\"button\" class=\"soft\" onclick=\"dbSave416()\">Berechnung speichern / später fortsetzen</button></footer><p id=\"dbSaveStatus416\" role=\"status\"></p><p>Eine gespeicherte Berechnung bleibt getrennt von Einsätzen und Tagesabschlüssen. Es werden keine Preise, Kassenerlöse oder Abschlüsse geändert.</p><p><a href=\"hilfe-db416.html\" target=\"_blank\" rel=\"noopener\">DB-Assistent verständlich erklärt</a></p></article>`;fixedRestoreView413(host,position,reset);if(reset)host.querySelector('h2')?.scrollIntoView({block:'start'});}\nfunction dbSave416(){if(!fixedAdmin412()||dbBusy416)return false;const candidate=clone(dbEnsure416()),base=dbBase416,scope=dbScope416;candidate.updatedAt=new Date().toISOString();dbBusy416=true;const result=operationTransaction('DB-Berechnung gespeichert','settings',()=>{if(!fixedAdmin412())throw Error('Nur ein Admin kann diese Berechnung speichern.');if(fixedScope412()!==scope||JSON.stringify(state.dbAssistant416||null)!==base)throw Error('Die Berechnung wurde inzwischen geändert. Bitte erneut öffnen.');state.dbAssistant416=candidate});return afterOperation275(result,ok=>{dbBusy416=false;if(fixedScope412()!==scope)return ok;if(ok){dbBase416=JSON.stringify(candidate);dbDraft416=clone(candidate);const e=document.getElementById('dbSaveStatus416');if(e)e.textContent='Berechnung gespeichert. Du kannst hier später fortsetzen.'}else dbError416('Nicht gespeichert. Deine Eingaben bleiben erhalten. Bitte erneut versuchen.');return ok})}\nconst dbRenderBefore416=renderContribution143;\nrenderContribution143=function(...args){if(!fixedAdmin412())return dbRenderBefore416(...args);dbRender416()};\nfunction fixedCapacityInput416(value){if(!fixedAdmin412()||!fixedDraft412||fixedBusy412)return;fixedDraft412.allocation416??={};fixedDraft412.allocation416.monthlyHours=String(value).slice(0,120)}\nfunction fixedCapacityValid416(model){const raw=model?.allocation416?.monthlyHours;if(!String(raw||'').trim())return;const n=fixedNumber412(raw);if(n<=0)throw Error('Geplante Einsatzstunden pro Monat müssen größer als 0 sein.')}\nconst fixedRenderBefore416=fixedRender412;\nfixedRender412=function(reset=false){const d=document.getElementById('fixedCostsDialog412'),position=d?fixedViewPosition413(d):null;fixedRenderBefore416(reset);if(d&&fixedDraft412&&fixedStep412===3){const section=document.createElement('section');section.innerHTML=`<h3>Betriebskosten auf Einsätze verteilen</h3><label>Geplante Einsatzstunden pro Monat<input data-adapted231=\"fixed412\" id=\"fcCapacity416\" aria-label=\"Geplante Einsatzstunden pro Monat\" inputmode=\"decimal\" placeholder=\"Optional, für die Verteilung erforderlich\" value=\"${esc(fixedDraft412.allocation416?.monthlyHours||'')}\" oninput=\"fixedCapacityInput416(this.value)\"></label><p>Alle Einsatzstunden eines üblichen Monats: Vorbereitung, Fahrt, Verkauf und Nachbereitung. Nicht mit der Personenzahl multiplizieren.</p>`;d.querySelector('#fixedBody412')?.prepend(section);if(position)fixedRestoreView413(d,position,reset)}};\nconst fixedReviewBefore416=fixedReview412;\nfixedReview412=function(){return fixedReviewBefore416()+`<p>Geplante Einsatzstunden pro Monat: <b>${esc(fixedDraft412.allocation416?.monthlyHours||'Noch offen')}</b>. Im DB-Assistenten kannst du daraus bewusst einen Anteil berechnen.</p>`};\nconst fixedSaveBefore416=fixedSave412;\nfixedSave412=function(status='saved'){if(status==='saved'&&fixedAdmin412()&&fixedDraft412)try{fixedCapacityValid416(fixedDraft412)}catch(e){fixedError412(e.message);return false}return fixedSaveBefore416(status)};\nconst fixedCardBefore416=fixedCard412;\nfixedCard412=function(){fixedCardBefore416();const card=document.getElementById('fixedCostsCard412');if(!card||card.hidden||document.getElementById('dbLaunch416'))return;const p=document.createElement('p');p.id='dbLaunch416';p.innerHTML='<button type=\"button\" class=\"soft\" onclick=\"openCalculations143(\\'contribution\\')\">DB-Assistent öffnen</button>';card.appendChild(p)};\nconst fixedHelpBefore416=fixedHelp412;\nfixedHelp412=function(){fixedHelpBefore416();const host=document.querySelector('#tabHelp .helpPanel');if(!host||document.getElementById('dbHelp416'))return;const card=document.createElement('section');card.id='dbHelp416';card.className='dashboardCard';card.innerHTML='<h2>Deckungsbeitrag verstehen</h2><p>Geführte Berechnung, Betriebskostenanteile und Auszahlungsziele.</p><a href=\"hilfe-db416.html\" target=\"_blank\" rel=\"noopener\">Anleitung zum DB-Assistenten öffnen</a>';host.appendChild(card)};\nwindow.addEventListener('beforeunload',event=>{if(dbDraft416&&JSON.stringify(dbDraft416)!==dbBase416){event.preventDefault();event.returnValue=''}});\nfixedCard412();\n"},{"src":"practice-ui418.js?v=418.1","code":"'use strict';\n// Presentation only. Existing calculation and durable transaction guards remain authoritative.\nlet dbStarted418=false,dbComputed418=false,dbErrors418={},dbReturn418=null;\nconst DB_LABELS418={revenue:'Erlös netto',goods:'Warenkosten',variable:'Weitere Verkaufskosten gesamt',direct:'Zusätzliche Einsatzkosten'};\nfunction dbIssues418(m){\n const issues={};\n for(const key of Object.keys(DB_LABELS418))try{dbNumber416(m[key],DB_LABELS418[key])}catch(e){issues[key]=String(m[key]??'').trim()?'Bitte eine gültige Zahl ab 0 eingeben.':'Bitte Betrag eingeben. Keine Kosten? Dann 0.'}\n if(m.allocation){\n  if(!m.basis?.complete)issues.basis='Deine Betriebskosten sind noch unvollständig. Bitte in den Einstellungen prüfen.';\n  else try{const n=fixedNumber412(m.basis.monthlyHours);if(n===null||n<=0)throw Error();}catch{issues.basis='Bitte zuerst deine geplanten Monatsstunden in den Betriebskosten hinterlegen. Sie müssen größer als 0 sein.'}\n  try{const n=fixedNumber412(m.hours);if(n===null)issues.hours='Wie viele Stunden dauert dieser Einsatz insgesamt?';else if(!issues.basis&&n>fixedNumber412(m.basis.monthlyHours))issues.hours='Die Einsatzstunden dürfen die geplanten Monatsstunden nicht überschreiten.'}catch{issues.hours='Bitte gültige Stunden ab 0 eingeben.'}\n }\n return issues;\n}\nfunction dbField418(key){const m=dbEnsure416(),error=dbErrors418[key];return `<label for=\"dbInput416_${key}\">${DB_LABELS418[key]} (${esc(m.basis.currency)})</label><input data-adapted231=\"fixed412\" id=\"dbInput416_${key}\" inputmode=\"decimal\" value=\"${esc(m[key])}\" placeholder=\"Betrag eingeben\" aria-invalid=\"${!!error}\" aria-describedby=\"dbFieldError418_${key}\" oninput=\"dbInput416('${key}',this.value)\"><p class=\"dbFieldError418\" id=\"dbFieldError418_${key}\" ${error?'':'hidden'}>${esc(error||'')}</p>`}\nconst DB_NOTES418={revenue:'Deine Einnahmen ohne Umsatzsteuer. Für die Nachrechnung nimmst du den tatsächlichen Kassenerlös netto.',goods:'Die eingesetzte Ware für diese Verkäufe. Verpackung nur einmal erfassen.',variable:'Zum Beispiel Verpackung oder Zahlungsgebühren. Keine Kosten? Trage 0 ein.',direct:'Zum Beispiel zusätzliche Standgebühr, Fahrt oder eigens gebuchtes Personal. Bereits erfasste Kosten nicht doppelt zählen.'};\nfunction dbDetails418(title,body){return `<details class=\"dbMore418\"><summary>${title}</summary><div>${body}</div></details>`}\nfunction dbAllocation418(){const m=dbEnsure416(),b=m.basis,stale=JSON.stringify(b)!==JSON.stringify(dbBasis416());return `${fixedSwitch412('Betriebskostenanteil berücksichtigen',m.allocation,\"dbAction416('allocation')\",'Laufende Betriebskosten anteilig einrechnen')}${m.allocation?`<div id=\"dbBasis418\" tabindex=\"-1\" class=\"dbBasis418\"><p><b>${fixedMoney412(b.monthly,b.currency)} / Monat</b> · ${esc(b.monthlyHours||'Monatsstunden fehlen')} ${b.monthlyHours?'Stunden / Monat':''}</p>${dbErrors418.basis?`<p class=\"dbFieldError418\" role=\"alert\">${esc(dbErrors418.basis)}</p>`:''}${stale?'<p class=\"fixedNote412\">Es gibt neuere Betriebseinstellungen.</p>':''}<div class=\"dbBasisActions418\"><button type=\"button\" class=\"soft\" onclick=\"dbAction416('basis')\">Aktuelle Werte übernehmen</button><button type=\"button\" class=\"soft\" onclick=\"dbCosts418()\">Betriebskosten bearbeiten</button></div></div><label for=\"dbInput416_hours\">Stunden dieses Einsatzes</label><input data-adapted231=\"fixed412\" id=\"dbInput416_hours\" inputmode=\"decimal\" value=\"${esc(m.hours)}\" placeholder=\"Vorbereitung bis Nachbereitung\" aria-invalid=\"${!!dbErrors418.hours}\" aria-describedby=\"dbFieldError418_hours\" oninput=\"dbInput416('hours',this.value)\"><p id=\"dbFieldError418_hours\" class=\"dbFieldError418\" ${dbErrors418.hours?'':'hidden'}>${esc(dbErrors418.hours||'')}</p>${b.payoutEnabled?fixedSwitch412('Auszahlungsziel anteilig berücksichtigen',m.payout,\"dbAction416('payout')\",'Gewünschte Auszahlung zusätzlich zeigen'):''}${dbDetails418('Wie wird verteilt?','<p>Monatskosten ÷ geplante Monatsstunden × Stunden dieses Einsatzes. Zähle Vorbereitung, Fahrt, Verkauf und Nachbereitung. Verwende beim Monat und beim Einsatz denselben Maßstab, ohne Multiplikation mit der Personenzahl.</p><p>Alle Einsätze zusammen dürfen das Monatsbudget nicht überschreiten. Das wird derzeit nicht automatisch über mehrere Berechnungen geprüft. Auszahlungsziele werden getrennt vom Betriebsergebnis gezeigt.</p>')}`:'<p class=\"dbHint418\">Ohne diesen Anteil bleibt es eine Rechnung vor laufenden Betriebskosten.</p>'}`}\nfunction dbResult418(){const m=dbEnsure416();if(!dbComputed418)return '<section id=\"dbResult418\" tabindex=\"-1\"><p class=\"dbHint418\">'+(Object.keys(dbErrors418).length?'Rechnung noch offen. Bitte die markierten Angaben ergänzen.':'Klicke auf „Ergebnis berechnen“, wenn alle Angaben passen.')+'</p></section>';const r=dbCalculate416(m);if(r.errors.length)return '<section id=\"dbResult418\" tabindex=\"-1\"><p class=\"dbFieldError418\">Rechnung noch offen. Bitte die markierten Angaben prüfen.</p></section>';const row=(label,n,cls='')=>`<div class=\"dbResultRow416 ${cls}\"><span>${label}</span><b>${fixedMoney412(n,m.basis.currency)}</b></div>`;return `<section id=\"dbResult418\" tabindex=\"-1\" aria-labelledby=\"dbResultTitle418\"><h3 id=\"dbResultTitle418\">Dein Ergebnis</h3>${row('Deckungsbeitrag',r.contribution,'dbMain418')}${r.allocated?row('Nach Einsatz- und Betriebskosten',r.afterFixed,'dbMain418'):row('Nach zusätzlichen Einsatzkosten',r.afterDirect,'dbMain418')}<p class=\"dbHint418\">${r.allocated?'Mit dem gewählten Betriebskostenanteil.':'Laufende Betriebskosten sind noch nicht abgezogen.'}</p>${m.allocation&&m.payout?row('Nach gewünschter Auszahlung',r.afterTarget):''}${dbDetails418('Rechenweg ansehen',`${row('Erlös netto',r.revenue)}${row('− Warenkosten',-r.goods)}${row('− Weitere Verkaufskosten',-r.variable)}${row('= Deckungsbeitrag',r.contribution)}${row('− Zusätzliche Einsatzkosten',-r.direct)}${r.allocated?row('− Betriebskostenanteil',-r.fixed):''}${m.allocation&&m.payout?row('− Gewünschte Auszahlung',-r.payout):''}<p>${r.quote===null?'Bei 0 Erlös wird keine Prozentquote berechnet.':'Deckungsbeitrag: '+r.quote.toLocaleString('de-AT',{maximumFractionDigits:2})+' % vom Nettoerlös.'}</p><p>Planungsrechnung für diesen Einsatz, keine Buchhaltung. Auszahlungsziele bleiben getrennt von Betriebskosten. Pauschale Kosten ergeben keine mengenabhängige Gewinnschwelle.</p>`)}</section>`}\nfunction dbDialog418(){let d=document.getElementById('dbDialog418');if(!d){d=document.createElement('dialog');d.id='dbDialog418';d.className='dbAssistant416';d.setAttribute('aria-labelledby','dbTitle418');d.addEventListener('cancel',e=>{e.preventDefault();dbClose418()});d.addEventListener('click',fixedClickGuard412,true);document.body.appendChild(d)}return d}\nfunction dbOpen418(){if(!fixedAdmin412()||dbBusy416||busyOperation275())return false;dbEnsure416();dbStarted418=false;dbErrors418={};dbComputed418=false;dbReturn418=document.activeElement;fixedClickLast412=null;const d=dbDialog418();dbRender416(true);if(!d.open)d.showModal();return true}\nfunction dbStart418(mode){if(dbBusy416||!fixedAdmin412())return;const m=dbEnsure416();m.mode=mode==='pro'?'pro':'amateur';dbStarted418=true;dbErrors418={};dbComputed418=false;dbRender416(true)}\nfunction dbEntry418(){if(dbBusy416)return;dbStarted418=false;dbRender416(true)}\nfunction dbFocus418(id){const e=document.getElementById(id);e?.focus({preventScroll:true});e?.scrollIntoView({block:'center',behavior:'smooth'})}\nfunction dbCalculateAction418(){if(!fixedAdmin412()||dbBusy416)return false;const m=dbEnsure416();dbErrors418=dbIssues418(m);if(Object.keys(dbErrors418).length){dbComputed418=false;const key=Object.keys(dbErrors418)[0];if(m.mode!=='pro')m.step=['revenue','goods','variable','direct'].includes(key)?['revenue','goods','variable','direct'].indexOf(key):4;dbRender416();dbFocus418(key==='basis'?'dbBasis418':'dbInput416_'+key);return false}dbComputed418=true;if(m.mode!=='pro')m.step=5;dbRender416();dbFocus418('dbResult418');const s=document.getElementById('dbSaveStatus416');if(s)s.textContent='Ergebnis berechnet.';return true}\nconst dbInputBefore418=dbInput416;\ndbInput416=function(key,value){dbInputBefore418(key,value);if(!fixedAdmin412()||dbBusy416)return;delete dbErrors418[key];const input=document.getElementById('dbInput416_'+key),error=document.getElementById('dbFieldError418_'+key);input?.setAttribute('aria-invalid','false');if(error)error.hidden=true;if(dbComputed418){dbComputed418=false;const result=document.getElementById('dbResult418');if(result)result.innerHTML='<p class=\"dbHint418\" role=\"status\">Angaben geändert. Bitte neu berechnen.</p>'}const s=document.getElementById('dbSaveStatus416');if(s)s.textContent=''};\nconst dbActionBefore418=dbAction416;\ndbAction416=function(action){if(!fixedAdmin412()||dbBusy416)return;dbComputed418=false;dbErrors418={};dbActionBefore418(action)};\ndbMove416=function(direction){if(!fixedAdmin412()||dbBusy416)return false;const m=dbEnsure416();dbErrors418={};if(direction>0){if(m.step===4)return dbCalculateAction418();const key=['revenue','goods','variable','direct'][m.step];if(key){const issues=dbIssues418({...m,allocation:false});if(issues[key]){dbErrors418[key]=issues[key];dbRender416();dbFocus418('dbInput416_'+key);return false}}}m.step=Math.max(0,Math.min(5,m.step+direction));dbRender416(true);return true};\ndbRender416=function(reset=false){const d=document.getElementById('dbDialog418');if(!d||!fixedAdmin412())return;const position=fixedViewPosition413(d),m=dbEnsure416(),pro=m.mode==='pro';let body='';\n if(!dbStarted418)body=`<p>Wie möchtest du rechnen?</p><div class=\"dbEntry418\"><button type=\"button\" class=\"${pro?'soft':'primary'}\" onclick=\"dbStart418('amateur')\">Amateur<span>Eine Frage nach der anderen</span></button><button type=\"button\" class=\"${pro?'primary':'soft'}\" onclick=\"dbStart418('pro')\">Pro<span>Alle Angaben auf einen Blick</span></button></div><p class=\"dbHint418\">Deine bisherigen Eingaben bleiben erhalten.</p>`;\n else if(pro)body=`<div class=\"dbFields418\">${Object.keys(DB_LABELS418).map(key=>`<div>${dbField418(key)}</div>`).join('')}</div>${dbDetails418('Was gehört in welches Feld?',Object.keys(DB_LABELS418).map(k=>`<p><b>${DB_LABELS418[k]}:</b> ${DB_NOTES418[k]}</p>`).join(''))}<section><h3>Laufende Betriebskosten</h3>${dbAllocation418()}</section>${dbResult418()}`;\n else if(m.step<4){const key=['revenue','goods','variable','direct'][m.step];body=`<p class=\"dbHint418\">Schritt ${m.step+1} von 6</p><h3>${['Wie viel nimmst du ein?','Was kostet deine Ware?','Welche Kosten entstehen je Verkauf?','Was kostet dieser Einsatz zusätzlich?'][m.step]}</h3>${dbField418(key)}${dbDetails418('Beispiel und Erklärung',`<p>${DB_NOTES418[key]}</p>`)}`}\n else if(m.step===4)body='<p class=\"dbHint418\">Schritt 5 von 6</p><h3>Laufende Kosten einrechnen?</h3>'+dbAllocation418();\n else body=dbResult418();\n d.innerHTML=`<header class=\"dbHeader418\"><h2 id=\"dbTitle418\" tabindex=\"-1\">DB-Assistent</h2><button type=\"button\" class=\"soft dbClose418\" aria-label=\"Berechnung speichern und Fenster schließen\" onclick=\"dbClose418()\">×</button></header>${dbStarted418?'<p class=\"dbNet418\">Alle Beträge netto. Kosten nur einmal erfassen.</p>':''}<div class=\"dbBody418\">${body}</div><p id=\"dbError416\" class=\"dbFieldError418\" role=\"alert\" hidden></p><footer>${dbStarted418?`<button type=\"button\" class=\"soft\" onclick=\"dbEntry418()\">Zur Moduswahl</button>${!pro&&m.step>0?'<button type=\"button\" class=\"soft\" onclick=\"if(!(event.detail>1))dbMove416(-1)\">Zurück</button>':''}${!pro&&m.step<4?'<button type=\"button\" class=\"primary\" onclick=\"if(!(event.detail>1))dbMove416(1)\">Weiter</button>':`<button type=\"button\" class=\"primary\" onclick=\"dbCalculateAction418()\">${dbComputed418?'Neu berechnen':'Ergebnis berechnen'}</button>`}`:''}<button type=\"button\" class=\"soft\" onclick=\"dbClose418()\">Speichern & schließen</button></footer><p id=\"dbSaveStatus416\" role=\"status\"></p><a class=\"dbHelp418\" href=\"hilfe-db416.html\" target=\"_blank\" rel=\"noopener\">Hilfe zur Berechnung</a>`;\n fixedRestoreView413(d,position,reset);\n};\nfunction dbClose418(){if(dbBusy416||!fixedAdmin412())return false;const close=()=>{document.getElementById('dbDialog418')?.close();dbReturn418?.focus?.({preventScroll:true});return true};if(JSON.stringify(dbEnsure416())===dbBase416)return close();return afterOperation275(dbSave416(),ok=>ok?close():false)}\nfunction dbCosts418(){if(dbBusy416)return;document.getElementById('dbDialog418')?.close();dbOpenCosts418=true;fixedOpen412();fixedStep412=3;fixedRender412(true)}\nlet dbOpenCosts418=false;\nconst fixedSaveBefore418=fixedSave412;\nfixedSave412=function(status='saved'){return afterOperation275(fixedSaveBefore418(status),ok=>{if(ok&&dbOpenCosts418){dbOpenCosts418=false;const d=dbDialog418();dbRender416(true);if(!d.open)d.showModal()}return ok})};\nrenderContribution143=function(...args){if(!fixedAdmin412())return dbRenderBefore416(...args);const host=document.getElementById('calc143_contribution');if(host)host.innerHTML='<article class=\"calcCard143 dbAssistant416\"><h2>Deckungsbeitrag</h2><p>Was bleibt von deinem Verkauf?</p><button type=\"button\" class=\"primary\" onclick=\"dbOpen418()\">DB-Assistent öffnen</button></article>'};\nconst fixedCardBefore418=fixedCard412;\nfixedCard412=function(){fixedCardBefore418();const button=document.querySelector('#dbLaunch416 button');if(button)button.setAttribute('onclick','dbOpen418()');settingsInstall418()};\nconst dbBackBefore418=window.zapasioHandleBack;\nwindow.zapasioHandleBack=function(...args){if(document.getElementById('dbDialog418')?.open){dbClose418();return true}return dbBackBefore418?.apply(this,args)||false};\n\nfixedPayoutReview415=function(s,model=fixedDraft412){if(!model?.payoutEnabled)return '';return `<section class=\"fixedPayout415\"><h3>Gewünschte Auszahlungen</h3><div class=\"fixedReviewRows412\">${(model.payouts||[]).map(p=>{let value='Betrag fehlt';try{const n=fixedPayoutMonthly415(p);if(n!==null)value=fixedMoney412(n,s.currency)+' / Monat'}catch{value='Eingabe prüfen'}return `<div><span>${esc(p.name||'Name fehlt')}</span><b>${esc(value)}</b></div>`}).join('')}</div><div class=\"fixedTotal412\"><span>Betriebskosten + gewünschte Auszahlungen${s.complete?'':' · vorläufig'}</span><strong>${fixedMoney412(s.planningMonthly,s.currency)} / Monat</strong></div><p class=\"tiny\">Auszahlungen sind persönliche Ziele, keine Betriebskosten.</p></section>`};\nfixedReview412=function(){const s=fixedSummary412({...fixedDraft412,status:'saved'});return `<div class=\"fixedTotal412\"><span>${s.complete?'Betriebskosten':'Bekannte Betriebskosten · vorläufig'}</span><strong>${fixedMoney412(s.monthly)} / Monat</strong><span>${fixedMoney412(s.annual)} / Jahr</span></div>${s.unresolved.length?`<p class=\"fixedNote412\">Noch offen: ${s.unresolved.map(esc).join(', ')}. Fehlende Angaben sind nicht eingerechnet.</p>`:''}${s.invalid.length?`<p role=\"alert\">${s.invalid.map(esc).join(' ')}</p>`:''}<div class=\"fixedReviewRows412\">${fixedDraft412.rows.filter(r=>!r.excluded).map(r=>{const v=fixedRowDisplay414(r);return `<div class=\"${v.pending?'fixedPending414':''}\"><span>${esc(r.label)}${v.note?`<small>${esc(v.note)}</small>`:''}</span><b>${esc(v.value)}</b></div>`}).join('')}</div>${fixedPayoutReview415(s)}${fixedDraft412.ownerEnabled?`<p>Eigener Stundensatz: ${s.ownerHourly===null?'noch offen':fixedMoney412(s.ownerHourly)+' / Stunde'}. Nicht in der Monatssumme.</p>`:''}${dbDetails418('Wie werden die Kosten verwendet?','<p>Diese Kosten gelten auch bei auswärtigen Einsätzen. Zusätzliche Standgebühren oder Fahrten gehören zum jeweiligen Einsatz. Im DB-Assistenten kannst du einen Anteil einrechnen. Gespeicherte Einsätze und Abschlüsse ändern sich dadurch nicht.</p>')}`};\n\n// Consistent settings sections. State is UI-only and never changes a business value.\nconst settingsOpen418=new Map();\nfunction settingsInstall418(){const root=document.getElementById('tabSettings');if(!root)return;\n for(const d of root.querySelectorAll('.settingsWrap418')){const key=d.dataset.foldKey418,card=document.getElementById(key),body=d.querySelector(':scope>div');if(card&&body&&!d.contains(card)&&root.contains(card))body.appendChild(card)}\n for(const panel of root.querySelectorAll('[id^=\"settingsPanel130_\"]'))for(const card of [...panel.children]){if(card.matches('section.card,div.card')&&card.querySelector('h2,h3')&&!card.closest('details')){const title=card.querySelector('h2,h3'),d=document.createElement('details');d.className='card settingsFold settingsWrap418';d.dataset.foldKey418=card.id||title.textContent.trim();const summary=document.createElement('summary');summary.textContent=title.textContent;const body=document.createElement('div');card.before(d);d.append(summary,body);body.append(card);title.hidden=true;card.classList.remove('card');d.hidden=card.hidden}}\n for(const d of root.querySelectorAll('details')){const summary=[...d.children].find(x=>x.tagName==='SUMMARY');if(!summary)continue;if(d.classList.contains('settingsWrap418')){const card=d.querySelector(':scope>div>section,:scope>div>div'),title=card?.querySelector('h2,h3');if(card)d.hidden=card.hidden;if(title){title.hidden=true;const text=summary.firstChild;if(text?.nodeType===3&&text.textContent!==title.textContent)text.textContent=title.textContent}}const key=d.dataset.foldKey418||d.id||summary.textContent.replace(/[+−]/g,'').trim();d.dataset.foldKey418=key;for(const icon of summary.querySelectorAll('.foldPlus')){if(icon.textContent)icon.textContent='';icon.setAttribute('aria-hidden','true')}if(!summary.querySelector('.foldPlus')){const icon=document.createElement('span');icon.className='foldPlus';icon.setAttribute('aria-hidden','true');summary.appendChild(icon)}if(!d.dataset.bound418){d.dataset.bound418='true';if(settingsOpen418.has(key))d.open=settingsOpen418.get(key);d.addEventListener('toggle',()=>{settingsOpen418.set(key,d.open);d.querySelector('summary')?.setAttribute('aria-expanded',String(d.open))})}summary.setAttribute('aria-expanded',String(d.open));}\n}\nlet settingsPending418=false;\nnew MutationObserver(()=>{if(settingsPending418)return;settingsPending418=true;requestAnimationFrame(()=>{settingsPending418=false;settingsInstall418()})}).observe(document.getElementById('tabSettings')||document.body,{childList:true,subtree:true});\nconst renderBefore418=render;\nrender=function(...args){const result=renderBefore418(...args);if(!fixedAdmin412()||dbScope416&&dbScope416!==fixedScope412()){document.getElementById('dbDialog418')?.close();dbStarted418=false;dbComputed418=false;dbErrors418={};dbDraft416=null}settingsInstall418();return result};\nfixedCard412();settingsInstall418();\n"},{"src":"db-draft419.js?v=419","code":"'use strict';\n// A blocked shared write must not trap the user in the calculator. Keep a\n// separate device-only worksheet; never replace the shared record or outbox.\nlet dbLocal419=false;\nfunction dbKey419(){const user=currentAccessUser();if(!user?.id)throw Error('Bitte zuerst den Zugang prüfen.');return 'zapasio_db_draft419:'+encodeURIComponent(fixedScope412())+':'+encodeURIComponent(user.id)}\nfunction dbConflict419(){const c=typeof cloudRuntime==='function'?cloudRuntime():{};return !!(c.enabled&&(c.blocked||c.base===null))||JSON.stringify(state.dbAssistant416||null)!==dbBase416}\nfunction dbEqual419(a,b){if(!a||!b)return a===b;const left=clone(a),right=clone(b);delete left.updatedAt;delete right.updatedAt;return JSON.stringify(left)===JSON.stringify(right)}\nfunction dbRecover419(){const raw=localStorage.getItem(dbKey419());if(!raw)return false;const saved=JSON.parse(raw);if(saved.schema!==1||saved.scope!==fixedScope412()||saved.user!==currentAccessUser().id||!saved.draft?.basis||typeof saved.draft.revenue!=='string'||!(saved.base===null||typeof saved.base==='string'))throw Error('Der lokale Zwischenstand ist nicht lesbar. Bitte zuerst die Hilfe öffnen.');if(dbEqual419(saved.draft,state.dbAssistant416)){localStorage.removeItem(dbKey419());return false}dbDraft416=clone(saved.draft);dbBase416=saved.base;dbScope416=saved.scope;dbLocal419=true;return true}\nasync function dbKeepLocal419(){if(!fixedAdmin412()||dbBusy416||busyOperation275())return false;const draft=clone(dbEnsure416()),base=dbBase416,scope=fixedScope412(),user=currentAccessUser().id,key=dbKey419(),store=window.deviceStore257;if(!store){dbError416('Der Gerätespeicher ist noch nicht bereit. Bitte das Fenster offen lassen.');return false}dbBusy416=true;let release;\n try{release=store.pause();await store.flush();if(!fixedAdmin412()||fixedScope412()!==scope||currentAccessUser().id!==user)throw Error('Der Zugang wurde geändert. Bitte die Eingabe erneut prüfen.');localStorage.setItem(key,JSON.stringify({schema:1,scope,user,base,draft,at:new Date().toISOString()}));await store.flush();if(!fixedAdmin412()||fixedScope412()!==scope||currentAccessUser().id!==user)throw Error('Der Zugang wurde geändert. Der Zwischenstand bleibt beim bisherigen Zugang.');dbLocal419=true;document.getElementById('dbDialog418')?.close();dbDraft416=null;dbReturn418?.focus?.({preventScroll:true});showFeedback('Zwischenstand auf diesem Gerät gespeichert','info',7000,'Du kannst schließen und später fortsetzen. Der gemeinsame Datenabgleich ist noch offen.');return true;\n }catch(error){dbError416('Noch nicht gesichert. '+(typeof window.storageErrorText257==='function'?window.storageErrorText257(error):'Bitte erneut versuchen.'));return false}finally{release?.();dbBusy416=false}\n}\nconst dbOpenBefore419=dbOpen418;\ndbOpen418=function(){if(!fixedAdmin412()||dbBusy416||busyOperation275())return false;dbEnsure416();dbLocal419=false;let issue='';try{dbRecover419()}catch(e){issue=e.message}const ok=dbOpenBefore419();if(issue)dbError416(issue);return ok};\nconst dbCloseBefore419=dbClose418;\ndbClose418=function(){if(!fixedAdmin412()||dbBusy416||busyOperation275())return false;dbEnsure416();if(dbConflict419())return dbKeepLocal419();return afterOperation275(dbCloseBefore419(),ok=>!ok&&dbConflict419()?dbKeepLocal419():ok)};\nasync function dbSync419(){if(await dbKeepLocal419())openCloudSettings()}\nconst dbRenderBefore419=dbRender416;\ndbRender416=function(...args){dbRenderBefore419(...args);const d=document.getElementById('dbDialog418');if(!d||!fixedAdmin412()||document.getElementById('dbNotice419')||!dbLocal419&&!dbConflict419())return;const note=document.createElement('div');note.id='dbNotice419';note.className='fixedNote412';note.setAttribute('role','status');note.innerHTML='<p>Gemeinsamer Abgleich offen. Dein Zwischenstand kann auf diesem Gerät gesichert werden.</p><button type=\"button\" class=\"soft\" onclick=\"dbSync419()\">Abgleich öffnen</button>';d.querySelector('header')?.after(note)};\n"},{"src":"stock-practice420.js?v=423","code":"'use strict';\nfunction stockText420(de,en,pl){return {de,en,pl}[recognizedLanguage278()]||de}\nfunction stockPolicy420(policy){\n const d=productGuide194;if(!d?.existing||d.quickMode198!=='in'||d.quickPending198||currentAccessUser().role!=='admin'||!['required','optional','none'].includes(policy))return;\n captureStockWizard300(d);const w=d.stockWizard300;w.policy420=policy;const steps422=stockWizardSteps300(d,state.products[d.existing.productCode]);w.step=steps422[steps422.indexOf('policy')+1];renderStockWizard311();\n}\nfunction bookingBlock420(){\n const c=cloudRuntime();if(globalThis.gsBootBlocked)return 'Die gesicherten Daten müssen zuerst wiederhergestellt werden.';\n if(!c.enabled)return '';\n if(!c.user)return 'Bitte zuerst anmelden. Es wurde nichts gebucht.';\n if(c.base===null)return 'Bitte zuerst die Datenübernahme abschließen. Es wurde nichts gebucht.';\n if(c.blocked)return 'Der gemeinsame Datenabgleich ist offen. Bitte abgleichen und danach erneut buchen. Deine Eingaben bleiben hier erhalten.';\n return '';\n}\nfunction bookingNotice420(message){\n const box=document.querySelector('#guideBox194 .guidedCard300')||document.getElementById('guideBox194');if(!box)return;\n let notice=document.getElementById('bookingNotice420');if(!notice){notice=document.createElement('div');notice.id='bookingNotice420';notice.setAttribute('role','alert');box.appendChild(notice)}\n notice.textContent=message+' Noch nichts gebucht.';\n const button=document.createElement('button');button.type='button';button.className='soft';button.textContent='Datenabgleich öffnen';button.onclick=()=>openCloudSettings();notice.appendChild(button);\n}\nconst confirmBefore420=confirmRecognized198;\nconfirmRecognized198=function(...args){const message=bookingBlock420();if(message){bookingNotice420(message);showFeedback('Noch nicht gebucht','warn',7000,message);return false}return confirmBefore420(...args)};\nconst feedbackBefore420=showFeedback;\nshowFeedback=function(title,kind,...rest){\n if(['Gemeinsam gespeichert','Auf dem Server gespeichert'].includes(title)&&productGuide194?.stockWizard300&&document.getElementById('articleModal')?.style.display==='flex')return feedbackBefore420('Datenabgleich aktuell','info',3500,'Die angezeigte Lagerbuchung ist noch nicht gebucht.');\n return feedbackBefore420(title,kind,...rest);\n};\nfunction wasteButton420(code,a){return hasPermission('scan')&&Number(a.stock)>0?`<button type=\"button\" class=\"soft wasteShortcut420\" aria-label=\"${esc(stockText420('Ausschuss buchen','Record waste','Zapisz odpad')+' '+a.name)}\" title=\"${esc(stockText420('Ausschuss buchen','Record waste','Zapisz odpad'))}\" data-code=\"${esc(code)}\" onclick=\"productWaste420(this.dataset.code)\"><svg viewBox=\"0 0 24 24\" width=\"19\" height=\"19\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.7\" aria-hidden=\"true\"><path d=\"M4 7h16M9 7V4h6v3M6 7l1 14h10l1-14M10 10v7m4-7v7\"/></svg></button>`:''}\nfunction productWaste420(code){\n if(busyOperation275()||!hasPermission('scan')||!state.products?.[code])return false;\n // Reuse the single waste transaction; never call a stock withdrawal as well.\n if(document.getElementById('articleModal')?.style.display==='flex')closeArticleEditor(false);\n openWasteProduct362(code);\n const form=document.getElementById('wasteForm362');if(form){const note=document.createElement('p');note.className='wasteMode420';note.textContent=stockText420('Entnahme · Ausschuss','Withdrawal · Waste','Wydanie · Odpad');form.prepend(note);const lots=wasteLots362(code);if(lots.length===1)document.getElementById('wasteLot362').value=lots[0].id;}\n}\nconst compactBefore420=recognizedCompact198;\nrecognizedCompact198=function(...args){const result=compactBefore420(...args),d=productGuide194;if(!d?.existing)return result;const code=d.existing.productCode,a=state.products[code],box=document.getElementById('guideBox194');if(!a||!box)return result;const out=box.querySelector('button[onclick=\"chooseRecognized198(\\'out\\')\"]');out?.classList.add('actionMinus365');const head=box.querySelector('.recognizedHead198>div');if(head&&!head.querySelector('.wasteShortcut420'))head.insertAdjacentHTML('beforeend',wasteButton420(code,a));return result};\nconst variantBefore420=mobileVariant322;\nmobileVariant322=function(code,a){return variantBefore420(code,a).replace('</div></article>',wasteButton420(code,a)+'</div></article>')};\nconst stockRowsBefore420=hubStockRows362;\nhubStockRows362=function(...args){return stockRowsBefore420(...args).replace(/(<button[^>]+onclick=\"hubStockAction362\\('([^']+)','info'\\)\"[^>]*>)/g,(all,button,code)=>{const a=state.products[code];return (a?wasteButton420(code,a):'')+button})};\n// Recognition is a proposal: always let the operator check the editable name.\nconst startArticleBefore420=startGuidedArticle300;\nstartGuidedArticle300=function(fromLookup=false){return startArticleBefore420(fromLookup)};\n\nconst stockActionsBefore420=stockMovementActions301;\nstockMovementActions301=function(code,a){return stockActionsBefore420(code,a).replace('</div>',wasteButton420(code,a)+'</div>')};\n// Keep the result under the finger when focusing it would expand search chrome.\n// Default click and keyboard activation still execute the original action once.\ndocument.addEventListener('pointerdown',event=>{if(window.innerWidth<=820&&event.button===0&&event.target?.closest?.('#mobileSearchResults button'))event.preventDefault()},true);\nconst prefillBefore420=prefillNewArticle;\nprefillNewArticle=function(name='',unit='',...rest){const result=prefillBefore420(name,unit,...rest);if(guidedArticle300){guidedArticle300.step='name';renderGuidedArticle300()}return result};\n// The outer article saver owns durable completion/rollback. Resume only afterwards.\nlet articleContinuationSerial420=0;\nconst articleOpenBefore420=openArticleEditor;\nopenArticleEditor=function(...args){articleContinuationSerial420++;return articleOpenBefore420(...args)};\nasync function queueArticleContinuation420(code,resume){\n const serial=articleContinuationSerial420;\n try{await window.deviceStore257.flush();await new Promise(resolve=>setTimeout(resolve,0));if(serial!==articleContinuationSerial420||current!==code||!state.products?.[code]||document.getElementById('articleModal')?.style.display==='flex'||articleSaving257)return false;resume();return true}catch{return false}\n}\n// Validation or a failed device commit returns to the same guided question.\nconst saveGuidedBefore420=saveGuidedArticle300;\nsaveGuidedArticle300=function(...args){\n const draft=guidedArticle300;if(!draft||articleSaving257||busyOperation275())return false;\n const result=saveGuidedBefore420(...args);\n const restore=()=>{if(!editingArticleCode&&!productGuide194&&!articleSaving257&&document.getElementById('articleModal')?.style.display==='flex'){guidedArticle300=draft;document.getElementById('articleModal').classList.add('guidedArticleMode300');renderGuidedArticle300()}};\n if(window.deviceStore257.pending||articleSaving257)window.deviceStore257.flush().then(restore,restore);else restore();return result;\n};\n"},{"src":"select-controls421.js?v=421","code":"'use strict';\n// Presentation adapter: original select, value, permissions and change handler remain authoritative.\nconst selectAdapters421=new WeakMap();let selectPicker421=null,selectFrame421=false;\nfunction selectWords421(de,en,pl){return {de,en,pl}[typeof recognizedLanguage278==='function'?recognizedLanguage278():'de']||de}\nfunction selectLabel421(select){const explicit=select.getAttribute('aria-label')||select.labels?.[0];if(typeof explicit==='string'&&explicit)return explicit;if(explicit){const text=[...explicit.childNodes].filter(n=>n.nodeType===3).map(n=>n.textContent).join(' ').trim();if(text)return text}return select.title||select.closest('.guidedCard300,.guidedArticle300')?.querySelector('h3')?.textContent||selectWords421('Auswahl','Choose','Wybierz')}\nfunction selectRows421(select){return [...select.options].map((o,index)=>({index,value:o.value,label:o.textContent.trim(),disabled:select.disabled||o.disabled||o.parentElement?.disabled===true,group:o.parentElement?.tagName==='OPTGROUP'?o.parentElement.label:''}))}\nfunction selectSet421(select,index){const row=selectRows421(select).find(o=>o.index===index);if(!select.isConnected||!row||row.disabled||select.multiple)return false;if(select.selectedIndex===index)return true;select.selectedIndex=index;select.dispatchEvent(new Event('input',{bubbles:true}));select.dispatchEvent(new Event('change',{bubbles:true}));return true}\nfunction selectClose421(focus=true){const d=selectPicker421;if(!d)return;selectPicker421=null;d.dialog.close();d.dialog.remove();d.button?.setAttribute('aria-expanded','false');if(focus&&d.button?.isConnected)d.button.focus({preventScroll:true})}\nfunction selectOpen421(select,button){\n if(select.disabled||!select.isConnected)return;selectClose421(false);\n const dialog=document.createElement('dialog');dialog.className='selectDialog421';dialog.setAttribute('aria-labelledby','selectTitle421');\n const header=document.createElement('div');header.className='selectHeader421';const title=document.createElement('h2');title.id='selectTitle421';title.textContent=selectLabel421(select);const cancel=document.createElement('button');cancel.type='button';cancel.className='selectClose421';cancel.setAttribute('aria-label',selectWords421('Auswahl schließen','Close choices','Zamknij wybór'));cancel.textContent='×';cancel.onclick=()=>selectClose421();header.append(title,cancel);dialog.append(header);\n const rows=selectRows421(select),list=document.createElement('div');list.className='selectList421';list.setAttribute('role','listbox');list.setAttribute('aria-label',title.textContent);\n function draw(query=''){list.replaceChildren();let group='';const filtered=rows.filter(o=>!query||o.label.toLocaleLowerCase().includes(query.toLocaleLowerCase()));for(const row of filtered){if(row.group&&row.group!==group){group=row.group;const h=document.createElement('p');h.className='selectGroup421';h.textContent=group;list.append(h)}const item=document.createElement('button');item.type='button';item.className='selectOption421';item.setAttribute('role','option');item.setAttribute('aria-selected',String(row.index===select.selectedIndex));item.textContent=row.label||selectWords421('Keine Auswahl','No selection','Bez wyboru');item.disabled=row.disabled;item.onclick=event=>{event.preventDefault();event.stopPropagation();selectClose421(false);selectSet421(select,row.index);selectSync421(select);if(button.isConnected)button.focus({preventScroll:true})};list.append(item)}if(!filtered.length){const empty=document.createElement('p');empty.textContent=selectWords421('Kein passender Eintrag','No matching option','Brak wyników');list.append(empty)}}\n if(rows.length>7){const search=document.createElement('input');search.type='search';search.className='selectSearch421';search.placeholder=selectWords421('Suchen …','Search …','Szukaj …');search.setAttribute('aria-label',selectWords421('Auswahl durchsuchen','Search choices','Szukaj opcji'));search.oninput=()=>draw(search.value);dialog.append(search)}\n draw();dialog.append(list);dialog.addEventListener('cancel',event=>{event.preventDefault();selectClose421()});dialog.addEventListener('keydown',event=>{if(!['ArrowDown','ArrowUp','Home','End'].includes(event.key))return;const items=[...list.querySelectorAll('button:not(:disabled)')];if(!items.length)return;event.preventDefault();const at=items.indexOf(document.activeElement),index=event.key==='Home'?0:event.key==='End'?items.length-1:event.key==='ArrowDown'?Math.min(items.length-1,at+1):Math.max(0,at-1);items[index].focus()});\n document.body.append(dialog);selectPicker421={dialog,select,button};button.setAttribute('aria-expanded','true');dialog.showModal();(list.querySelector('[aria-selected=\"true\"]:not(:disabled)')||list.querySelector('button:not(:disabled)')||cancel).focus({preventScroll:true});\n}\nfunction selectSync421(select){\n const a=selectAdapters421.get(select);if(!a||!select.isConnected)return;\n const rows=selectRows421(select),label=selectLabel421(select),signature=JSON.stringify([rows,select.selectedIndex,select.required,label]);if(a.signature===signature)return;a.signature=signature;a.box.replaceChildren();\n const choices=rows.filter(o=>o.value!==''),short=!select.matches('.priceUnit131')&&choices.length>=2&&choices.length<=3&&choices.every(o=>o.label.length<=30)&&!rows.some(o=>o.group||o.value==='');\n a.box.className=short?'selectChoices421':'selectWrap421';a.box.setAttribute('role',short?'radiogroup':'group');a.box.setAttribute('aria-label',label);\n if(short){a.box.setAttribute('aria-required',String(select.required));for(const row of choices){const b=document.createElement('button');b.type='button';b.className='selectChoice421';b.setAttribute('role','radio');b.setAttribute('aria-checked',String(select.selectedIndex===row.index));b.textContent=row.label;b.disabled=row.disabled;b.tabIndex=select.selectedIndex===row.index?0:-1;b.onclick=event=>{event.preventDefault();event.stopPropagation();selectSet421(select,row.index);selectSync421(select);a.box.querySelector('[aria-checked=\"true\"]')?.focus({preventScroll:true})};a.box.append(b)}a.box.onkeydown=event=>{if(!['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Home','End'].includes(event.key))return;const items=[...a.box.querySelectorAll('button:not(:disabled)')];if(!items.length)return;event.preventDefault();const at=items.indexOf(document.activeElement),index=event.key==='Home'?0:event.key==='End'?items.length-1:['ArrowRight','ArrowDown'].includes(event.key)?(at+1)%items.length:(at-1+items.length)%items.length;items[index].click()}}\n else {const b=document.createElement('button');b.type='button';b.className='selectButton421';b.disabled=select.disabled;b.setAttribute('aria-label',label);b.setAttribute('aria-haspopup','dialog');b.setAttribute('aria-expanded','false');b.setAttribute('aria-required',String(select.required));const text=document.createElement('span');text.textContent=rows.find(o=>o.index===select.selectedIndex)?.label||selectWords421('Bitte wählen','Choose','Wybierz');const arrow=document.createElement('span');arrow.className='selectArrow421';arrow.textContent='⌄';arrow.setAttribute('aria-hidden','true');b.append(text,arrow);b.onclick=event=>{event.preventDefault();event.stopPropagation();selectOpen421(select,b)};a.box.append(b)}\n}\nfunction installSelect421(){\n selectFrame421=false;\n for(const select of document.querySelectorAll('select:not([multiple]):not([size])')){\n  if(!select.getClientRects().length||select.closest('[hidden]'))continue;\n  let a=selectAdapters421.get(select);if(!a||!a.box.isConnected){const box=document.createElement('div');select.after(box);a={box,signature:''};selectAdapters421.set(select,a);select.classList.add('selectNative421');select.tabIndex=-1;select.setAttribute('aria-hidden','true');select.addEventListener('focus',()=>box.querySelector('button')?.focus({preventScroll:true}));select.addEventListener('invalid',event=>{event.preventDefault();box.querySelector('button')?.focus();box.scrollIntoView({block:'nearest'})});}\n  selectSync421(select);\n }\n if(selectPicker421&&(!selectPicker421.select.isConnected||selectPicker421.select.disabled))selectClose421(false);\n}\nfunction scheduleSelect421(){if(selectFrame421)return;selectFrame421=true;requestAnimationFrame(installSelect421)}\ndocument.addEventListener('change',scheduleSelect421);new MutationObserver(scheduleSelect421).observe(document.body,{subtree:true,childList:true,attributes:true,attributeFilter:['disabled','class','style','hidden','selected']});scheduleSelect421();\n"},{"src":"receipt-lots422.js?v=422","code":"'use strict';\n// Receipt-only draft rows, committed together by the existing guarded transaction.\nfunction validateReceiptLots422(w,factor,policy,whole){\n const count=Number(w.qty),rows=w.receiptLots422;\n const fail=()=>{throw Error(stockText420('Mengen und MHD prüfen. Die Teilmengen müssen genau der Gesamtmenge entsprechen.','Check quantities and dates. Parts must equal the total quantity.','Sprawdź ilości i daty. Suma musi być równa całkowitej ilości.'))};\n if(!Number.isFinite(count)||count<=0||!Number.isFinite(factor)||factor<=0||!Array.isArray(rows)||!rows.length)fail();\n const result=rows.map(r=>{const n=Number(String(r.count).replace(',','.')),mhd=r.mhd||'';if(!Number.isFinite(n)||n<=0||whole&&!Number.isInteger(n))fail();if(policy==='required'||mhd){const date=new Date(mhd+'T00:00:00Z');if(!/^\\d{4}-\\d{2}-\\d{2}$/.test(mhd)||!Number.isFinite(date.getTime())||date.toISOString().slice(0,10)!==mhd)fail();}return {count:n,qty:n*factor,mhd};});\n if(Math.abs(result.reduce((sum,r)=>sum+r.count,0)-count)>1e-8)fail();return result;\n}\nfunction captureReceiptLots422(w){\n for(const row of document.querySelectorAll?.('[data-receipt-lot422]')||[]){const index=Number(row.dataset.receiptLot422);if(w.receiptLots422?.[index]){w.receiptLots422[index].count=row.querySelector('[data-count422]').value;w.receiptLots422[index].mhd=row.querySelector('[data-mhd422]').value;}}\n}\nfunction sameMhdHtml422(d,head,back){\n const question=stockText420('Haben alle '+d.stockWizard300.qty+' dieselbe Haltbarkeit (MHD)?','Do all '+d.stockWizard300.qty+' have the same best-before date?','Czy wszystkie '+d.stockWizard300.qty+' mają tę samą datę ważności?');\n return `<div class=\"guidedCard300\">${head}<h3>${esc(question)}</h3><div class=\"guidedChoice300\"><button type=\"button\" class=\"primary\" onclick=\"sameMhdChoice422(true)\">${esc(guidedText300('yes'))}</button><button type=\"button\" class=\"soft\" onclick=\"sameMhdChoice422(false)\">${esc(guidedText300('no'))}</button></div><button type=\"button\" class=\"soft\" onclick=\"${back}\">${esc(guidedText300('back'))}</button></div>`;\n}\nfunction sameMhdChoice422(same){const d=productGuide194;if(!d?.stockWizard300||d.quickPending198)return;captureStockWizard300(d);d.stockWizard300.sameMhd422=same;d.stockWizard300.step='mhd';renderStockWizard311();}\nfunction splitMhdHtml422(d,head,back){\n const w=d.stockWizard300;w.receiptLots422??=[{count:'',mhd:w.mhd||d.receiptMhd258||''},{count:'',mhd:''}];\n return `<div class=\"guidedCard300\">${head}<h3>${esc(stockText420('Menge nach MHD aufteilen','Split quantity by date','Podziel ilość według daty'))}</h3><p>${esc(stockText420('Gesamtmenge: ','Total quantity: ','Całkowita ilość: ')+w.qty)}</p>${w.receiptLots422.map((r,i)=>`<section class=\"receiptLot422\" data-receipt-lot422=\"${i}\"><label>${esc(stockInfoText311('quantity'))}<input data-count422 inputmode=\"decimal\" value=\"${esc(String(r.count))}\"></label><label>MHD<input data-mhd422 id=\"receiptMhd422-${i}\" type=\"date\" value=\"${esc(r.mhd)}\"></label>${w.receiptLots422.length>1?`<button type=\"button\" class=\"soft\" onclick=\"changeReceiptLots422(${i})\">${esc(stockText420('Entfernen','Remove','Usuń'))}</button>`:''}</section>`).join('')}<button type=\"button\" class=\"soft\" onclick=\"changeReceiptLots422(-1)\">+ ${esc(stockText420('Weiteres MHD','Another date','Kolejna data'))}</button>${guidedActions300(back,'stockWizardGo300(1)')}</div>`;\n}\nfunction changeReceiptLots422(index){const d=productGuide194;if(!d?.stockWizard300||d.quickPending198)return;captureStockWizard300(d);const rows=d.stockWizard300.receiptLots422;if(index<0)rows.push({count:'',mhd:''});else rows.splice(index,1);renderStockWizard311();if(index<0)requestAnimationFrame(()=>{if(productGuide194!==d||d.stockWizard300.step!=='mhd')return;const row=document.querySelector(`[data-receipt-lot422=\"${rows.length-1}\"]`);row?.querySelector('[data-count422]')?.focus({preventScroll:true});row?.scrollIntoView({block:'nearest',behavior:'instant'})});}\nfunction receiptLotsReview422(w,a,unit,factor){return (w.receiptLots422||[]).map(r=>`<div><dt>${esc(formatQtySmart(Number(String(r.count).replace(',','.'))*factor,a.unit))}</dt><dd>${r.mhd?bookingDateHtml342(r.mhd):esc(stockText420('Ohne MHD','No date','Bez daty'))}</dd></div>`).join('');}\nfunction editRecognized422(){const d=productGuide194;if(currentAccessUser().role!=='admin'||busyOperation275()||d?.quickPending198||!d?.existing)return false;const code=d.existing.productCode;if(!state.products[code])return false;return openArticleEditor(code);}\nconst recognizedCompactBefore422=recognizedCompact198;\nrecognizedCompact198=function(...args){const result=recognizedCompactBefore422(...args),d=productGuide194;if(currentAccessUser().role!=='admin'||!d?.existing||d.quickMode198)return result;const head=document.querySelector('#guideBox194 .recognizedHead198>div');if(head&&!head.querySelector('.editProduct422'))head.insertAdjacentHTML('beforeend',`<button type=\"button\" class=\"soft editProduct422\" onclick=\"editRecognized422()\">${esc(stockText420('Produkt bearbeiten','Edit product','Edytuj produkt'))}</button>`);return result;};\n// Enforce the admin-only edit action even when called without its visible button.\nconst openArticleBefore422=openArticleEditor;\nopenArticleEditor=function(code,...args){if(code&&state.products?.[code]&&currentAccessUser().role!=='admin'){showFeedback(stockText420('Nur für Admins','Admins only','Tylko administrator'),'warn');return false;}return openArticleBefore422(code,...args);};\nconst saveArticleBefore422=saveArticleEditor;\nsaveArticleEditor=function(...args){if(editingArticleCode&&currentAccessUser().role!=='admin'){showFeedback(stockText420('Nur für Admins','Admins only','Tylko administrator'),'warn');return false;}return saveArticleBefore422(...args);};\n"},{"src":"start-cache425.js?v=425","code":"'use strict';\n// Optional, single-snapshot cache. It never owns stock, outbox or authentication.\nlet cacheDb425=null,cacheGeneration425=0,restoreContext425=null,restorePromise425=null,cacheWarm425=Promise.resolve();\nfunction openStartCache425(){\n if(cacheDb425)return cacheDb425;\n cacheDb425=new Promise((resolve,reject)=>{\n  let settled=false;const request=indexedDB.open('zapasio-start-cache425',1);\n  const timer=setTimeout(()=>{settled=true;cacheDb425=null;reject(Error('Startcache nicht bereit'))},1200);\n  request.onupgradeneeded=()=>request.result.createObjectStore('snapshot');\n  request.onblocked=()=>{clearTimeout(timer);settled=true;cacheDb425=null;reject(Error('Startcache belegt'))};\n  request.onerror=()=>{clearTimeout(timer);settled=true;cacheDb425=null;reject(request.error)};\n  request.onsuccess=()=>{if(settled){request.result.close();return}settled=true;clearTimeout(timer);const db=request.result;db.onversionchange=()=>{db.close();cacheDb425=null};resolve(db)};\n });return cacheDb425;\n}\nfunction cacheTransaction425(tx){return new Promise((resolve,reject)=>{tx.oncomplete=resolve;tx.onabort=()=>reject(tx.error||Error('Startcache nicht gespeichert'));tx.onerror=()=>{}})}\nconst startCacheStore425={\n async read(key){const db=await openStartCache425(),tx=db.transaction('snapshot','readonly'),done=cacheTransaction425(tx),request=tx.objectStore('snapshot').get(key);const value=new Promise((resolve,reject)=>{request.onsuccess=()=>resolve(request.result);request.onerror=()=>reject(request.error)});return (await Promise.all([value,done]))[0]},\n async write(meta,data){const db=await openStartCache425(),tx=db.transaction('snapshot','readwrite'),done=cacheTransaction425(tx);tx.objectStore('snapshot').put(meta,'meta');tx.objectStore('snapshot').put(data,'data');await done},\n async clear(){const db=await openStartCache425(),tx=db.transaction('snapshot','readwrite'),done=cacheTransaction425(tx);tx.objectStore('snapshot').clear();await done}\n};\nfunction scopeStart425(user){const cfg=cloudConfig();return cfg&&user?{url:String(cfg.url||'').replace(/\\/$/,''),workspace:String(cfg.workspace||''),user:String(user)}:null}\nfunction sameScope425(a,b){return !!a&&!!b&&a.url===b.url&&a.workspace===b.workspace&&a.user===b.user}\nfunction rightsStart425(member){return JSON.stringify([member?.role||'',Object.keys(member?.permissions||{}).sort().map(key=>[key,member.permissions[key]])])}\nasync function digestStart425(raw){const bytes=await crypto.subtle.digest('SHA-256',new TextEncoder().encode(raw));return Array.from(new Uint8Array(bytes),b=>b.toString(16).padStart(2,'0')).join('')}\nfunction cleanStart425(ctx){const c=cloudRuntime();return ctx.generation===cacheGeneration425&&sameScope425(ctx.scope,scopeStart425(ctx.user))&&!globalThis.gsBootBlocked&&!c.busy&&!c.pending&&!c.blocked&&!c.failedTask160&&!window.deviceStore257?.error&&!localStorage.getItem('zapasio_outbox254:'+ctx.scope.workspace+':'+ctx.user)}\nfunction diagnoseStart425(route,started){if(document.body?.dataset)document.body.dataset.startCache425=JSON.stringify({route,checkMs:Math.round(performance.now()-started)})}\nasync function reuseStart425(ctx){\n if(!ctx.user||!cleanStart425(ctx))return null;\n await window.deviceStore257.flush();\n const meta=await startCacheStore425.read('meta');\n if(meta?.schema!==1||!sameScope425(meta.scope,ctx.scope)||!Number.isSafeInteger(meta.revision)||meta.revision<0||meta.member?.id!==ctx.user)return null;\n const local=localStorage.getItem('gs_livetest_frankfurt_v1');if(!local||await digestStart425(local)!==meta.localHash)return null;\n // This is an authenticated server check, including current membership/rights.\n const info=await requestBefore425('/rest/v1/rpc/gs_revision',{p_workspace:ctx.scope.workspace});\n if(info?.revision!==meta.revision||rightsStart425(info)!==rightsStart425(meta.member)||!cleanStart425(ctx)||localStorage.getItem('gs_livetest_frankfurt_v1')!==local)return null;\n const raw=await startCacheStore425.read('data');if(typeof raw!=='string'||await digestStart425(raw)!==meta.dataHash)return null;\n const data=JSON.parse(raw);validBackupState(data);\n if(!cleanStart425(ctx)||localStorage.getItem('gs_livetest_frankfurt_v1')!==local)return null;\n ctx.reused=true;ctx.confirmed={raw,revision:info.revision,member:{...meta.member,role:info.role,permissions:info.permissions||{}}};\n return {data,revision:info.revision,member:{...ctx.confirmed.member}};\n}\nasync function budgetStart425(raw){\n try{const bytes=new TextEncoder().encode(raw).byteLength;if(bytes>12*1024*1024||typeof navigator.storage?.estimate!=='function')return false;const info=await navigator.storage.estimate();return Number.isFinite(info.quota)&&Number.isFinite(info.usage)&&info.quota-info.usage>bytes*8+16*1024*1024}catch{return false}\n}\nasync function warmStart425(ctx){\n try{\n  if(!ctx.confirmed||!ctx.user||!cleanStart425(ctx))return false;\n  await window.deviceStore257.flush();\n  const c=cloudRuntime();if(!cleanStart425(ctx)||c.user?.id!==ctx.user||c.revision!==ctx.confirmed.revision||rightsStart425(c.user)!==rightsStart425(ctx.confirmed.member))return false;\n  const local=localStorage.getItem('gs_livetest_frankfurt_v1');if(!local)return false;\n  const localHash=await digestStart425(local),dataHash=await digestStart425(ctx.confirmed.raw);\n  const meta={schema:1,scope:ctx.scope,revision:ctx.confirmed.revision,member:ctx.confirmed.member,localHash,dataHash};\n  const before=await startCacheStore425.read('meta');\n  if(ctx.reused&&before?.schema===1&&sameScope425(before.scope,meta.scope)&&before.revision===meta.revision&&before.localHash===localHash&&before.dataHash===dataHash&&rightsStart425(before.member)===rightsStart425(meta.member))return true;\n  if(!cleanStart425(ctx)||c.revision!==meta.revision||localStorage.getItem('gs_livetest_frankfurt_v1')!==local)return false;\n  if(!await budgetStart425(ctx.confirmed.raw))return false;\n  if(!cleanStart425(ctx)||c.revision!==meta.revision||localStorage.getItem('gs_livetest_frankfurt_v1')!==local)return false;\n  await startCacheStore425.write(meta,ctx.confirmed.raw);\n  if(ctx.generation!==cacheGeneration425)await startCacheStore425.clear();\n  return true;\n }catch{return false}\n}\nfunction invalidateStart425(){cacheGeneration425++;cacheWarm425=cacheWarm425.catch(()=>{}).then(()=>startCacheStore425.clear()).catch(()=>{})}\nconst requestBefore425=cloudRequest;\ncloudRequest=async function(path,body,...args){\n const ctx=restoreContext425;\n if(ctx&&path==='/rest/v1/rpc/gs_read'&&!ctx.attempted){\n  ctx.attempted=true;const started=performance.now();\n  try{const cached=await reuseStart425(ctx);if(cached){diagnoseStart425('verified-local',started);return cached}}catch{/* Optional cache/lookup failure falls back to the established full read. */}\n  diagnoseStart425('full-read',started);\n }\n const result=await requestBefore425(path,body,...args);\n if(ctx&&path==='/auth/v1/token?grant_type=refresh_token'&&result?.user?.id){ctx.user=String(result.user.id);ctx.scope=scopeStart425(ctx.user)}\n if(ctx&&path==='/rest/v1/rpc/gs_read'&&result?.data&&result.member?.id===ctx.user){ctx.confirmed={raw:JSON.stringify(result.data),revision:result.revision,member:JSON.parse(JSON.stringify(result.member))}}\n return result;\n};\nconst resumeBefore425=resumeCloudSession119;\nresumeCloudSession119=function(...args){\n if(restorePromise425)return restorePromise425;\n const ctx={generation:cacheGeneration425,user:null,scope:null,attempted:false,confirmed:null};restoreContext425=ctx;\n restorePromise425=Promise.resolve().then(()=>resumeBefore425(...args)).then(ok=>{if(ok)cacheWarm425=cacheWarm425.catch(()=>{}).then(()=>warmStart425(ctx));return ok}).finally(()=>{if(restoreContext425===ctx)restoreContext425=null;restorePromise425=null});\n return restorePromise425;\n};\nconst clearSessionBefore425=clearCloudSession119;\nclearCloudSession119=function(...args){invalidateStart425();return clearSessionBefore425(...args)};\nconst clearDeviceBefore425=clearSensitiveDevice139;\nclearSensitiveDevice139=function(...args){invalidateStart425();return clearDeviceBefore425(...args)};\n// A completed normal server write refreshes the confirmed snapshot as well.\nconst flushBefore425=flushCloudSave;\nflushCloudSave=async function(...args){\n const before=cloudRuntime(),revision=before.revision,scope=scopeStart425(before.user?.id);\n const result=await flushBefore425(...args),c=cloudRuntime();\n if(!restoreContext425&&c.user?.id&&Number.isSafeInteger(c.revision)&&c.revision>revision&&c.base&&sameScope425(scope,scopeStart425(c.user.id))){\n  const ctx={generation:cacheGeneration425,user:c.user.id,scope,confirmed:{raw:JSON.stringify(c.base),revision:c.revision,member:{id:c.user.id,name:c.user.name||'',role:c.user.role,permissions:JSON.parse(JSON.stringify(c.user.permissions||{}))}}};\n  cacheWarm425=cacheWarm425.catch(()=>{}).then(()=>warmStart425(ctx));\n }\n return result;\n};\n"},{"src":"start-diagnostics426.js?v=426","code":"'use strict';\n// One local, in-memory report. No identifiers, request bodies, tokens or telemetry.\nlet resumeRunning426=null;\nfunction publishStart426(){\n const timing=window.zapasioStart426;if(!timing||!document.body?.dataset)return;\n const report={version:timing.version||426};\n for(const key of ['bootStartedMs','storageMs','assetsReadyMs','scriptsMs','firstUiMs','restoreStartedMs','authMs','revisionMs','readMs','renderMs','renderCount','restoreMs','readyMs','paintMs'])if(Number.isFinite(timing[key]))report[key]=Math.round(timing[key]);\n if(typeof timing.ok==='boolean')report.ok=timing.ok;\n document.body.dataset.startup426=JSON.stringify(report);\n}\nfunction finishStart426(ok){\n const timing=window.zapasioStart426;if(!timing||timing.readyMs!==undefined)return;\n timing.ok=!!ok;timing.readyMs=performance.now();publishStart426();\n requestAnimationFrame(()=>requestAnimationFrame(()=>{timing.paintMs=performance.now();publishStart426();refreshStartReport426()}));\n}\nconst renderBefore426=render;\nrender=function(...args){\n const timing=window.zapasioStart426,started=performance.now();\n try{return renderBefore426.apply(this,args)}finally{if(timing&&timing.readyMs===undefined){timing.renderMs+=performance.now()-started;timing.renderCount++;publishStart426()}installStartReport426()}\n};\nconst resumeBefore426=resumeCloudSession119;\nresumeCloudSession119=function(...args){\n if(resumeRunning426)return resumeRunning426;\n const timing=window.zapasioStart426?.readyMs===undefined?window.zapasioStart426:null,started=performance.now();\n if(timing){timing.restoring=true;timing.restoreStartedMs=started}\n resumeRunning426=Promise.resolve().then(()=>resumeBefore426.apply(this,args)).then(ok=>{if(timing)timing.restoreMs=performance.now()-started;finishStart426(ok);return ok},error=>{if(timing)timing.restoreMs=performance.now()-started;finishStart426(false);throw error}).finally(()=>{if(timing)timing.restoring=false;resumeRunning426=null;publishStart426()});\n return resumeRunning426;\n};\nfunction startReportText426(){\n const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de',u=START_TEXT426[lang]||START_TEXT426.de,t=window.zapasioStart426;if(!t)return u.none;\n const seconds=value=>Number.isFinite(value)?(value/1000).toLocaleString({de:'de-AT',en:'en-GB',pl:'pl-PL'}[lang]||'de-AT',{minimumFractionDigits:2,maximumFractionDigits:2})+' s':u.pending;\n const rows=[u.version+' '+(t.version||426),...['ready','files','storage','assets','scripts','render','auth','revision','read'].map((key,i)=>u[key]+': '+seconds([t.paintMs??t.readyMs,t.bootStartedMs,t.storageMs,t.assetsReadyMs,t.scriptsMs,t.renderMs,t.authMs,t.revisionMs,t.readMs][i]))];\n try{const cache=JSON.parse(document.body.dataset.startCache425||'null');if(cache)rows.push(u.data+': '+(cache.route==='verified-local'?u.cached:u.full))}catch{}\n rows.push(u.parallel);\n if(t.ok===false)rows.push(u.failed);\n return rows.join('\\n');\n}\nfunction refreshStartReport426(){const text=document.getElementById('startReportText426');if(text)text.textContent=startReportText426()}\nfunction installStartReport426(){\n const version=document.getElementById('webVersion231');if(!version||document.getElementById('startReport426'))return;\n const details=document.createElement('details');details.id='startReport426';details.className='settingsFold';\n const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de',summary=document.createElement('summary');summary.textContent=(START_TEXT426[lang]||START_TEXT426.de).title;\n const text=document.createElement('p');text.id='startReportText426';text.style.whiteSpace='pre-line';\n details.append(summary,text);details.addEventListener('toggle',refreshStartReport426);version.after(details);refreshStartReport426();\n}\nconst START_TEXT426={\n de:{title:'Startzeit prüfen',none:'Für diesen Start liegt keine Messung vor.',pending:'noch offen',version:'Webstand',ready:'Ab Laden der Seite bis bereit',files:'Seite und Startmodul laden',storage:'Gerätespeicher öffnen',assets:'Weitere Startdateien bereit nach',scripts:'Startcode ausführen',render:'Oberfläche aufbauen',auth:'Anmeldung prüfen',revision:'Datenstand am Server prüfen',read:'Vollständige Daten laden',data:'Daten',cached:'bestätigten Gerätestand verwendet',full:'vollständig abgerufen',parallel:'Einige Schritte laufen parallel. Die Einzelzeiten ergeben daher keine Summe.',failed:'Die automatische Anmeldung wurde nicht abgeschlossen.'},\n en:{title:'Check startup time',none:'No measurement is available for this start.',pending:'pending',version:'Web version',ready:'From page load to ready',files:'Load page and startup module',storage:'Open device storage',assets:'Other startup files ready after',scripts:'Run startup code',render:'Build interface',auth:'Verify sign-in',revision:'Check server data version',read:'Load complete data',data:'Data',cached:'verified device snapshot used',full:'complete data retrieved',parallel:'Some steps run in parallel. The individual times do not add up to the total.',failed:'Automatic sign-in did not complete.'},\n pl:{title:'Sprawdź czas uruchamiania',none:'Brak pomiaru dla tego uruchomienia.',pending:'w toku',version:'Wersja WWW',ready:'Od ładowania strony do gotowości',files:'Ładowanie strony i modułu startowego',storage:'Otwieranie pamięci urządzenia',assets:'Pozostałe pliki startowe gotowe po',scripts:'Wykonanie kodu startowego',render:'Budowanie interfejsu',auth:'Sprawdzenie logowania',revision:'Sprawdzenie wersji danych na serwerze',read:'Pobranie wszystkich danych',data:'Dane',cached:'użyto potwierdzonego stanu urządzenia',full:'pobrano wszystkie dane',parallel:'Niektóre kroki działają równolegle. Poszczególne czasy nie sumują się do całości.',failed:'Automatyczne logowanie nie zostało ukończone.'}\n};\ninstallStartReport426();\n"},{"src":"ui-workshop432.js?v=432","code":"'use strict';\n// Presentation only. Never blur, recreate, save, or submit an input on viewport changes.\n(function workshopViewport432(){\n const viewport=window.visualViewport,root=document.documentElement;\n let frame=0;\n function update(){\n  frame=0;\n  // Keep browser zoom/panning under user control; only adapt an unzoomed viewport.\n  const unzoomed=!viewport||Math.abs(viewport.scale-1)<.05;\n  const height=unzoomed&&viewport?viewport.height:window.innerHeight;\n  const top=unzoomed&&viewport?viewport.offsetTop:0;\n  root.style.setProperty('--work-visible-height432',Math.max(1,Math.round(height))+'px');\n  root.style.setProperty('--work-visible-top432',Math.max(0,Math.round(top))+'px');\n  const field=document.activeElement;\n  if(!unzoomed||window.innerWidth>820||!field?.matches('input:not([type=hidden]),select,textarea'))return;\n  const card=field.closest('#articleModal>.card,#modal>.card,#opsDialog92');\n  if(!card)return;\n  const bounds=card.getBoundingClientRect(),rect=field.getBoundingClientRect();\n  const header=card.querySelector('.modalHeader'),headerBottom=header?.getBoundingClientRect().bottom||bounds.top;\n  const above=Math.max(bounds.top,headerBottom)+12,below=Math.min(bounds.bottom,top+height)-96;\n  // Scroll the containing sheet only. Retain the same focused node and its full value.\n  if(rect.bottom>below)card.scrollTop+=Math.max(0,Math.min(rect.bottom-below,rect.top-above));\n  else if(rect.top<above)card.scrollTop-=above-rect.top;\n }\n function schedule(){if(!frame)frame=requestAnimationFrame(update)}\n viewport?.addEventListener('resize',schedule,{passive:true});\n viewport?.addEventListener('scroll',schedule,{passive:true});\n window.addEventListener('resize',schedule,{passive:true});\n document.addEventListener('focusin',schedule);\n schedule();\n})();\n"},{"src":"practice433.js?v=433","code":"'use strict';\n// Form/display repairs only. Existing records, stock transactions and the device\n// storage contract stay in charge; no automatic catalogue or quantity migration.\nfunction nonFoodCategory433(value){return /verbrauch|verpack|hygiene|reinig|geschirr|zubehör|equipment|non.?food/i.test(String(value||''))}\n// User-defined food categories (e.g. Trockenware) are not literally named\n// Lebensmittel. Honour the explicit drain checkbox in these editor categories.\ndrainWeightEnabledInEditor=function(){return !nonFoodCategory433(document.getElementById('aeCategory')?.value)&&!!document.getElementById('aeDrainWeight')?.checked};\nconst categoryBefore433=articleCategoryChanged;\narticleCategoryChanged=function(...args){const keep=!!document.getElementById('aeDrainWeight')?.checked,result=categoryBefore433(...args);if(keep&&!nonFoodCategory433(document.getElementById('aeCategory')?.value)){document.getElementById('aeDrainWeight').checked=true;toggleDrainWeightFields()}return result};\nconst foodOptionsBefore433=updateArticleFoodOptions;\nupdateArticleFoodOptions=function(...args){const result=foodOptionsBefore433(...args),card=document.getElementById('aeFoodOptions');if(card&&!nonFoodCategory433(document.getElementById('aeCategory')?.value))card.style.display='block';return result};\nfunction drainValues433(){\n const enabled=document.getElementById('gaDrainEnabled433')?.checked;\n const raw=document.getElementById('gaDrainQty433')?.value||'',unit=document.getElementById('gaDrainUnit433')?.value||'g';\n const gross=Number(String(document.getElementById('gaContentQty300')?.value||'').replace(',','.'));\n const grossUnit=document.getElementById('gaContentUnit300')?.value||'g',qty=Number(raw.replace(',','.'));\n return {enabled,raw,qty,unit,gross,grossUnit};\n}\nfunction validateDrain433(){\n const d=drainValues433();if(!d.enabled)return true;\n const usable=convertUnit(d.qty,d.unit,d.grossUnit);\n if(!(d.qty>0)||!Number.isFinite(d.qty)||usable===null||usable>d.gross||!(d.gross>0)){\n  showFeedback('Abtropfgewicht prüfen','warn',5000,'Abtropfgewicht größer als 0 und höchstens so groß wie der Packungsinhalt eingeben.');return false;\n }return true;\n}\nfunction captureDrain433(){\n const field=document.getElementById('gaDrainEnabled433');if(!field)return;\n const d=drainValues433(),check=document.getElementById('aeDrainWeight');if(!check)return;\n check.checked=!!d.enabled;toggleDrainWeightFields();if(!d.enabled)return;\n for(const [id,value] of Object.entries({aeDrainContentQuick:String(d.gross),aeDrainContentUnit:d.grossUnit,aeDrainUsableQuick:d.raw.replace(',','.'),aeDrainUsableUnit:d.unit})){\n  const el=document.getElementById(id);if(el)el.value=value;\n }\n syncDrainQuickToPrimaryPack();updatePurchaseQuote('article');\n}\nfunction drainHtml433(){\n if(nonFoodCategory433(document.getElementById('aeCategory')?.value||''))return '';\n const enabled=!!document.getElementById('aeDrainWeight')?.checked,row=primaryDrainPackRow();\n const qty=enabled?row?.querySelector('.aePackUsable')?.value||'':'',unit=row?.querySelector('.aePackUsableUnit')?.value||'g';\n return `<details class=\"optionalMeasure433\" ${enabled?'open':''}><summary>Abtropfgewicht hinzufügen (optional)</summary><label class=\"checkRow\"><input id=\"gaDrainEnabled433\" type=\"checkbox\" ${enabled?'checked':''} onchange=\"this.closest('details').querySelector('.drainFields433').hidden=!this.checked\"> Abtropfgewicht vorhanden</label><div class=\"drainFields433\" ${enabled?'':'hidden'}><label for=\"gaDrainQty433\">Nutzbares Gewicht nach dem Abgießen</label><div class=\"guidedInline300\"><input id=\"gaDrainQty433\" inputmode=\"decimal\" value=\"${esc(qty)}\"><select id=\"gaDrainUnit433\" aria-label=\"Einheit des Abtropfgewichts\">${['g','kg'].map(u=>`<option ${u===unit?'selected':''}>${u}</option>`).join('')}</select></div></div></details>`;\n}\nfunction enhanceGuided433(box,step){\n if(step==='content'){\n  // Legacy stockContent holds usable content when drainage is enabled. Display\n  // the label's gross weight and the separate drain weight, without changing it.\n  if(document.getElementById('aeDrainWeight')?.checked){const row=primaryDrainPackRow();if(row){document.getElementById('gaContentQty300').value=row.querySelector('.aePackQty').value;document.getElementById('gaContentUnit300').value=row.querySelector('.aePackUnit').value}}\n  box.querySelector('.guidedActions300')?.insertAdjacentHTML('beforebegin',drainHtml433());\n }\n if(step==='review'){\n  const category=document.getElementById('aeCategory')?.value||'';\n  box.querySelector('dl')?.insertAdjacentHTML('beforeend',`<div><dt>Kategorie</dt><dd>${esc(category)}</dd></div>`);\n  if(document.getElementById('aeDrainWeight')?.checked){const row=primaryDrainPackRow();if(row)box.querySelector('dl')?.insertAdjacentHTML('beforeend',`<div><dt>Abtropfgewicht</dt><dd>${esc(row.querySelector('.aePackUsable').value)} ${esc(row.querySelector('.aePackUsableUnit').value)}</dd></div>`)}\n }\n}\nconst captureBefore433=captureGuidedArticle300;\ncaptureGuidedArticle300=function(){const step=guidedArticle300?.step;const out=captureBefore433();const key={category:'category',storage:'storage'}[step],id={category:'aeCategory',storage:'aeSubgroup'}[step];const shared=key&&familyDraft336?.shared340?.find(f=>f.key===key);if(shared)shared.chosen=document.getElementById(id)?.value||'';return out};\n\n// Successful recognition does not dismiss the name review. Both scan entrances\n// share the optional photo action rather than requiring photos only on one path.\nconst applyBefore433=applyGuide194;\napplyGuide194=function(...args){const before=productGuide194,result=applyBefore433(...args);if(before&&!productGuide194)clearArticleFeedback316();return result};\nfunction addProductPhotos433(){\n if(!guidedArticle300||editingArticleCode)return;captureGuidedArticle300();const resume={...guidedArticle300};endGuidedArticle300();clearArticleFeedback316();startProductGuide194();productGuide194.returnGuided433=resume;\n const barcode=articleBarcodeVisible331();productGuide194.barcode=barcode;productGuide194.step=1;productGuide194.message='Produktfoto ergänzen. Bestehende Formulareingaben bleiben erhalten.';renderGuide194();\n}\nfunction cancelProductPhotos433(){const resume=productGuide194?.returnGuided433;if(!resume)return;endGuide194();guidedArticle300=resume;document.getElementById('articleModal')?.classList.add('guidedArticleMode300');renderGuidedArticle300()}\nconst renderPhotosBefore433=renderGuide194;\nrenderGuide194=function(...args){const result=renderPhotosBefore433(...args);if(productGuide194?.returnGuided433){const button=document.querySelector('#guideBox194 button[onclick=\"endGuide194()\"]');if(button)button.setAttribute('onclick','cancelProductPhotos433()')}return result};\nconst renderArticleBefore433=renderGuidedArticle300;\nrenderGuidedArticle300=function(...args){const result=renderArticleBefore433(...args);if(guidedArticle300?.step==='name')document.getElementById('guidedArticle300')?.insertAdjacentHTML('beforeend','<button type=\"button\" class=\"soft optionalPhoto433\" onclick=\"addProductPhotos433()\">Produktfotos ergänzen (optional)</button>');return result};\nconst lookupBefore433=lookupGuideBarcode194;\nlookupGuideBarcode194=async function(...args){const d=productGuide194;await lookupBefore433(...args);if(d&&productGuide194===d&&d.step===1&&!d.existing&&!d.lookupBusy&&!d.busy&&d.lookup?.found)acceptGuideLookup241()};\n\n// A warning belongs to the failed attempt. Editing does not certify success;\n// the next Weiter/Speichern still runs all existing validation again.\ndocument.addEventListener('input',event=>{const modal=event.target?.closest?.('#articleModal');if(!modal||busyOperation275())return;for(const box of modal.querySelectorAll('.formFeedback231'))if(['warn','error','info','success'].includes(box.dataset.type))box.remove()});\ndocument.addEventListener('change',event=>{const modal=event.target?.closest?.('#articleModal');if(!modal||busyOperation275())return;for(const box of modal.querySelectorAll('.formFeedback231'))box.remove()});\n\nfunction productMeasure433(a){\n const content=a?.stockContent?.stockUnit===a?.unit&&Number(a.stockContent.qty)>0?a.stockContent:null;\n if(content)return {qty:Number(content.qty),unit:content.unit,confirmed:true};\n const measured=(a?.packs||[]).slice(1).map(p=>packDisplayMeta(p,a.unit)).filter(m=>Number(m.contentValue)>0&&physicalUnitKind(m.contentUnit));\n if(measured.length===1)return {qty:measured[0].contentValue,unit:measured[0].contentUnit,confirmed:true};\n const fromName=familyMeasure336(a?.name||'');return fromName?{...fromName,confirmed:false}:null;\n}\nfunction productMeasuresHtml433(a){\n const c=productMeasure433(a),capacity=a?.capacity431,nonfood=nonFoodCategory433(a?.overviewGroup||a?.cat||'');\n let html=c?`<span>${a?.drainWeightEnabled?'Abtropfgewicht ':''}${esc(formatQtySmart(c.qty,c.unit))} je ${esc(a.unit)}${c.confirmed?'':' · laut Name, bitte prüfen'}${nonfood&&['g','kg','dag'].includes(c.unit)?' · Einheit prüfen':''}</span>`:'<span>Packungsinhalt noch nicht erfasst</span>';\n if(a?.drainWeightEnabled){const p=(a.packs||[]).slice(1).find(p=>Number(p[7])>0);if(p)html+=`<span>Füllgewicht ${esc(formatQtySmart(Number(p[5]),p[6]))} · Abtropfgewicht ${esc(formatQtySmart(Number(p[7]),p[8]))}</span>`;else html+='<span>Füllgewicht noch nicht erfasst</span>'}\n if(capacity)html+=`<span>${esc(formatQtySmart(capacity.qty,capacity.unit))} Fassungsvermögen je Stück</span>`;\n return `<div class=\"productMeasures433\">${html}</div>`;\n}\nfunction editProductMeasure433(code){if(currentAccessUser().role!=='admin'||busyOperation275()||!state.products?.[code])return false;openArticleEditor(code);const field=document.getElementById('aeContentQty');field?.closest('[data-article-fold]')&&setArticleFold(Number(field.closest('[data-article-fold]').dataset.articleFold),true);field?.focus();field?.scrollIntoView({block:'center'});return true}\nfunction groupTools433(pool){\n if(!pool?.grouped)return '';const admin=currentAccessUser().role==='admin',key=pool.key;\n const missing=pool.summary.totalSafe?[]:(pool.summary.unsafeMembers?.length?pool.summary.unsafeMembers:pool.members.map(([code,a])=>({code,a})));\n return missing.length?`<div class=\"groupTools433\"><div class=\"conversionIssue433\" role=\"note\"><b>Gesamtmenge noch offen</b><p>Für diese Varianten ist kein gemeinsamer Inhalt bestätigt:</p>${missing.map(({code,a})=>`<div><span>${esc(a.name)} · ${esc(a.unit)}${productMeasuresHtml433(a)}</span>${admin?`<button type=\"button\" class=\"soft\" onclick=\"editProductMeasure433('${escJs(code)}')\">Inhalt prüfen</button>`:''}</div>`).join('')}<small>Die einzelnen Bestände bleiben gültig. Keine automatische Umrechnung.</small></div></div>`:'';\n}\nfunction groupText435(key){const l=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';return ({de:{options:'Gruppenoptionen',rename:'Gruppenname ändern',name:'Gruppenname',save:'Namen speichern',cancel:'Abbrechen',hint:'Variantennamen und Bestände bleiben erhalten.',error:'Der Name wurde nicht gespeichert. Bitte erneut versuchen.'},en:{options:'Group options',rename:'Rename group',name:'Group name',save:'Save name',cancel:'Cancel',hint:'Variant names and stock stay unchanged.',error:'The name was not saved. Please try again.'},pl:{options:'Opcje grupy',rename:'Zmień nazwę grupy',name:'Nazwa grupy',save:'Zapisz nazwę',cancel:'Anuluj',hint:'Nazwy wariantów i stany pozostaną bez zmian.',error:'Nazwa nie została zapisana. Spróbuj ponownie.'}}[l]||{})[key]||key}\nfunction groupMenuButton435(pool){return pool?.grouped&&currentAccessUser().role==='admin'?`<button type=\"button\" class=\"groupMenuButton435\" aria-label=\"${esc(groupText435('options')+' '+pool.summary.name)}\" aria-haspopup=\"menu\" aria-controls=\"groupActionMenu435\" aria-expanded=\"false\" onclick=\"event.preventDefault();event.stopPropagation();openGroupMenu435('${escJs(pool.key)}',this)\"><span aria-hidden=\"true\">⋮</span></button>`:''}\nlet groupMenuTrigger435=null,groupRenameTrigger435=null;\nfunction closeGroupMenu435(focus=true){const menu=document.getElementById('groupActionMenu435');if(menu){if(menu.hidePopover&&menu.matches(':popover-open'))menu.hidePopover();menu.hidden=true}groupMenuTrigger435?.setAttribute('aria-expanded','false');if(focus)groupMenuTrigger435?.focus?.({preventScroll:true})}\nfunction openGroupMenu435(key,trigger){\n if(currentAccessUser().role!=='admin'||busyOperation275())return;\n let menu=document.getElementById('groupActionMenu435');const wasOpen=menu&&!menu.hidden&&groupMenuTrigger435===trigger;closeGroupMenu435(false);if(wasOpen)return;\n if(!menu){menu=document.createElement('div');menu.id='groupActionMenu435';menu.className='groupActionMenu435';menu.setAttribute('role','menu');menu.setAttribute('popover','auto');document.body.append(menu);menu.addEventListener('toggle',e=>{if(e.newState==='closed'){menu.hidden=true;groupMenuTrigger435?.setAttribute('aria-expanded','false')}})}\n groupMenuTrigger435=trigger;menu.innerHTML=`<button type=\"button\" role=\"menuitem\" onclick=\"openGroupRename435('${escJs(key)}')\">${esc(groupText435('rename'))}</button>`;menu.hidden=false;if(menu.showPopover)menu.showPopover();\n const r=trigger.getBoundingClientRect(),m=menu.getBoundingClientRect();menu.style.left=Math.max(8,Math.min(r.right-m.width,window.innerWidth-m.width-8))+'px';menu.style.top=Math.max(8,Math.min(r.bottom+6,window.innerHeight-m.height-8))+'px';trigger.setAttribute('aria-expanded','true');menu.querySelector('button').focus();\n}\nfunction closeGroupRename435(){const d=document.getElementById('groupRenameDialog435');if(d?.dataset.saving==='true')return;d?.close()}\nfunction openGroupRename435(key){\n groupRenameTrigger435=groupMenuTrigger435;closeGroupMenu435(false);if(currentAccessUser().role!=='admin'||busyOperation275())return;\n const pool=stockPools301().find(p=>p.key===key&&p.grouped);if(!pool)return;\n let d=document.getElementById('groupRenameDialog435');if(!d){d=document.createElement('dialog');d.id='groupRenameDialog435';d.className='groupRenameDialog435';d.setAttribute('aria-labelledby','groupRenameTitle435');document.body.append(d);d.addEventListener('cancel',e=>{if(d.dataset.saving==='true')e.preventDefault()});d.addEventListener('close',()=>groupRenameTrigger435?.focus?.({preventScroll:true}))}\n d.dataset.key=key;d.innerHTML=`<form onsubmit=\"event.preventDefault();saveGroupRename435()\"><h2 id=\"groupRenameTitle435\">${esc(groupText435('rename'))}</h2><label for=\"groupRenameInput435\">${esc(groupText435('name'))}</label><input id=\"groupRenameInput435\" value=\"${esc(pool.summary.name)}\" required autocomplete=\"off\"><p>${esc(groupText435('hint'))}</p><p id=\"groupRenameError435\" role=\"alert\" hidden></p><div class=\"groupRenameActions435\"><button type=\"button\" class=\"soft\" onclick=\"closeGroupRename435()\">${esc(groupText435('cancel'))}</button><button type=\"submit\" class=\"primary\">${esc(groupText435('save'))}</button></div></form>`;d.showModal();const input=d.querySelector('input');input.focus();input.select();\n}\nasync function saveGroupRename435(){\n const d=document.getElementById('groupRenameDialog435');if(!d||d.dataset.saving==='true')return;const input=d.querySelector('input'),name=input.value.trim(),error=d.querySelector('[role=alert]');if(!name){input.focus();return}\n d.dataset.saving='true';for(const b of d.querySelectorAll('button'))b.disabled=true;error.hidden=true;\n try{if(await renameGroup433(d.dataset.key,name)){d.dataset.saving='false';d.close();render();return}error.textContent=groupText435('error');error.hidden=false}catch(e){error.textContent=groupText435('error');error.hidden=false}finally{d.dataset.saving='false';for(const b of d.querySelectorAll('button'))b.disabled=false}\n}\ndocument.addEventListener('click',e=>{const menu=document.getElementById('groupActionMenu435');if(menu&&!menu.hidden&&!menu.contains(e.target)&&!groupMenuTrigger435?.contains(e.target))closeGroupMenu435(false)});\ndocument.addEventListener('keydown',e=>{const menu=document.getElementById('groupActionMenu435');if(menu&&!menu.hidden&&['Escape','Tab'].includes(e.key)){if(e.key==='Escape'){e.preventDefault();e.stopPropagation()}closeGroupMenu435()}});\nwindow.addEventListener?.('resize',()=>closeGroupMenu435(false));\nasync function renameGroup433(key,name){\n if(currentAccessUser().role!=='admin'||busyOperation275())return false;name=String(name||'').trim();if(!name){showFeedback('Gruppenname fehlt','warn');return false}\n const pool=stockPools301().find(p=>p.key===key&&p.grouped);if(!pool)return false;\n const codes=pool.members.map(([c])=>c),family=productFamilies336().find(g=>g.members.length===codes.length&&g.members.every(([c])=>codes.includes(c)));\n if(!family)return false;\n const signature=JSON.stringify(pool.members.map(([c,a])=>[c,a.mainArticleId,a.productGroup336]));\n return commitOperation275('Gruppenname gespeichert','articles',()=>{\n  if(currentAccessUser().role!=='admin')throw Error('Nur Administratoren dürfen Gruppen umbenennen.');\n  const now=stockPools301().find(p=>p.key===key&&p.grouped);\n  if(!now||JSON.stringify(now.members.map(([c,a])=>[c,a.mainArticleId,a.productGroup336]))!==signature)throw Error('Die Gruppe hat sich geändert. Bitte erneut öffnen.');\n  for(const [code,a] of now.members){a.productGroup336={...(a.productGroup336||{}),key:family.key,name};const g=mainGroup(a.mainArticleId);if(g&&mainMembers(a.mainArticleId).every(([c])=>codes.includes(c)))g.name=name}\n });\n}\nconst mobileVariantBefore433=mobileVariant322;\nmobileVariant322=function(code,a){return mobileVariantBefore433(code,a).replace('<div class=\"mobileVariantActions322\">',productMeasuresHtml433(a)+'<div class=\"mobileVariantActions322\">')};\nconst stockVariantBefore433=stockVariantHtml301;\nstockVariantHtml301=function(code,a){return stockVariantBefore433(code,a).replace('<strong>',productMeasuresHtml433(a)+'<strong>')};\nconst mobilePoolBefore433=mobilePool322;\nmobilePool322=function(pool){return mobilePoolBefore433(pool).replace('</summary>',groupMenuButton435(pool)+'</summary>').replace('<div class=\"mobileGroupBody322\">','<div class=\"mobileGroupBody322\">'+groupTools433(pool))};\nconst stockPoolBefore433=stockPoolHtml301;\nstockPoolHtml301=function(pool){return stockPoolBefore433(pool).replace('<details class=\"stockMain301',`<details data-pool435=\"${esc(pool.key)}\" class=\"stockMain301`).replace('</summary>',groupMenuButton435(pool)+'</summary>').replace('<div class=\"stockMainBody301\">','<div class=\"stockMainBody301\">'+groupTools433(pool))};\nconst mobileInfoBefore433=mobileProductInfoHtml311;\nmobileProductInfoHtml311=function(code){const a=state.products?.[code];return mobileInfoBefore433(code).replace('<dl class=\"guidedReview300\">',productMeasuresHtml433(a)+(currentAccessUser().role==='admin'?`<button type=\"button\" class=\"soft\" onclick=\"closeArticleInfo127();editProductMeasure433('${escJs(code)}')\">Packungsinhalt bearbeiten</button>`:'')+'<dl class=\"guidedReview300\">')};\nconst webInfoBefore433=openArticleInfo127;\nopenArticleInfo127=function(code){const result=webInfoBefore433(code);if(window.innerWidth>820&&state.products?.[code])document.querySelector('#articleInfoModal127 .modalHeader')?.insertAdjacentHTML('afterend',productMeasuresHtml433(state.products[code]));return result};\n\n// Keep the current work location across display refreshes. View state is\n// transient and never enters the product snapshot or synchronization queue.\nfunction workDetailKey435(d){return d.id?'id:'+d.id:d.dataset.pool435||d.dataset.pool322?'pool:'+(d.dataset.pool435||d.dataset.pool322):d.dataset.categoryIndex!==undefined?'category:'+d.dataset.categoryIndex:'summary:'+d.className+'|'+(d.querySelector(':scope>summary')?.textContent||'').trim()}\nfunction captureWorkView435(){\n const roots=[...document.querySelectorAll('.tabPanel.active,#mobileWorkApp')].filter(n=>n.getClientRects().length),scope=roots.length?roots:[];\n const details=scope.flatMap(n=>[...n.querySelectorAll('details')].map(d=>[workDetailKey435(d),d.open]));\n const a=document.activeElement,inScope=scope.some(n=>n.contains(a)),focus=workTabTarget435&&Date.now()-workTabTarget435.at<500?workTabTarget435.focus:inScope?{id:a.id,change:a.getAttribute('onchange'),input:a.getAttribute('oninput'),start:a.selectionStart,end:a.selectionEnd}:null;\n const scroll=scope.flatMap(n=>[n,...n.querySelectorAll('[id]')]).filter(n=>n.scrollHeight>n.clientHeight+1&&n.clientHeight>0).map(n=>({id:n.id,node:n,top:n.scrollTop,left:n.scrollLeft}));\n const searches=scope.flatMap(n=>[...n.querySelectorAll('input[type=search][id]')].map(x=>({id:x.id,value:x.value})));\n return {roots:scope.map(n=>n.id),details,focus,scroll,searches,x:window.scrollX,y:window.scrollY};\n}\nfunction restoreWorkView435(v){\n if(!v)return;const roots=v.roots.map(id=>document.getElementById(id)).filter(n=>n?.getClientRects().length);if(roots.length!==v.roots.length)return;\n const detailMap=new Map(v.details);for(const root of roots)for(const d of root.querySelectorAll('details')){const key=workDetailKey435(d);if(detailMap.has(key))d.open=detailMap.get(key)}\n for(const s of v.searches){const n=document.getElementById(s.id);if(n&&n.value!==s.value){n.value=s.value;if(s.id==='mobileArticleSearch'&&typeof renderMobileSearchResults==='function')renderMobileSearchResults(s.value)}}\n if(v.focus){const f=v.focus,card=f.card?[...document.querySelectorAll('[data-mainarticle435]')].find(n=>n.dataset.mainarticle435===f.card):null,n=card?workFocusables435(card)[f.index]:f.id?document.getElementById(f.id):roots.flatMap(r=>[...r.querySelectorAll('input,select,textarea,button')]).find(n=>(f.change&&n.getAttribute('onchange')===f.change)||(f.input&&n.getAttribute('oninput')===f.input));if(n?.getClientRects().length&&!n.disabled){n.focus({preventScroll:true});if(typeof f.start==='number'&&n.setSelectionRange)try{n.setSelectionRange(f.start,f.end)}catch{}}}\n for(const s of v.scroll){const n=s.id?document.getElementById(s.id):s.node;if(n?.isConnected){n.scrollTop=s.top;n.scrollLeft=s.left}}window.scrollTo({left:v.x,top:v.y,behavior:'instant'});\n}\nlet workViewDepth435=0,workViewPass435=0,workTabTarget435=null;\nfunction workFocusables435(root){return [...root.querySelectorAll('input,select,textarea,button,a[href]')].filter(n=>!n.disabled&&n.tabIndex>=0&&n.getClientRects().length)}\ndocument.addEventListener('keydown',e=>{workTabTarget435=null;if(e.key!=='Tab')return;const card=e.target.closest?.('[data-mainarticle435]');if(!card)return;const controls=workFocusables435(card),index=controls.indexOf(e.target)+(e.shiftKey?-1:1);if(controls[index])workTabTarget435={at:Date.now(),focus:{card:card.dataset.mainarticle435,index}}},true);\nfunction preserveWorkView435(fn){return function(...args){if(workViewDepth435)return fn.apply(this,args);const view=captureWorkView435(),pass=++workViewPass435;workViewDepth435++;try{return fn.apply(this,args)}finally{workViewDepth435--;restoreWorkView435(view);requestAnimationFrame(()=>{if(pass===workViewPass435)restoreWorkView435(view)})}}}\nif(typeof render==='function')render=preserveWorkView435(render);\nif(typeof setMainArticleField==='function')setMainArticleField=preserveWorkView435(setMainArticleField);\nif(typeof refreshHub362==='function')refreshHub362=preserveWorkView435(refreshHub362);\n\n// Both fields describe the usable weight of one counted inventory unit.\n// Keep the user's chosen mass units, the labelled fill and other packs separate.\nfunction copyDrainMeasure450(sourceQty,sourceUnit,targetQty,targetUnit){\n if(!sourceQty||!sourceUnit||!targetQty||!targetUnit||physicalUnitKind(sourceUnit.value)!=='mass')return false;\n const raw=String(sourceQty.value||'').trim();\n if(!raw){targetQty.value='';return true}\n const qty=costNumber(raw);if(!Number.isFinite(qty)||qty<=0)return false;\n let unit=targetUnit.value;\n if(physicalUnitKind(unit)!=='mass'){unit=sourceUnit.value;targetUnit.value=unit}\n const converted=convertUnit(qty,sourceUnit.value,unit);if(converted===null||!Number.isFinite(converted))return false;\n targetQty.value=String(Number(converted.toPrecision(12)));return true;\n}\nfunction drainRecipeLinked450(){return drainWeightEnabledInEditor()&&isCountLikeUnit(document.getElementById('aeUnit')?.value)}\nfunction syncDrainToContent450(){\n if(!drainRecipeLinked450())return;\n copyDrainMeasure450(document.getElementById('aeDrainUsableQuick'),document.getElementById('aeDrainUsableUnit'),document.getElementById('aeContentQty'),document.getElementById('aeContentUnit'));\n}\nfunction syncContentToDrain442(){\n if(!drainRecipeLinked450())return;\n if(!copyDrainMeasure450(document.getElementById('aeContentQty'),document.getElementById('aeContentUnit'),document.getElementById('aeDrainUsableQuick'),document.getElementById('aeDrainUsableUnit')))return;\n syncDrainQuickToPrimaryPack();\n}\nconst drainQuickToBefore450=syncDrainQuickToPrimaryPack;\nsyncDrainQuickToPrimaryPack=function(...args){\n const recipeUnit=document.getElementById('aeContentUnit'),previousUnit=recipeUnit?.value;\n const result=drainQuickToBefore450(...args);\n if(recipeUnit&&physicalUnitKind(previousUnit)==='mass')recipeUnit.value=previousUnit;\n if(drainRecipeLinked450()){\n  const row=primaryDrainPackRow(),qty=document.getElementById('aeDrainUsableQuick'),unit=document.getElementById('aeDrainUsableUnit');\n  if(row&&qty&&unit){const q=row.querySelector('.aePackUsable'),u=row.querySelector('.aePackUsableUnit');if(q){q.value=qty.value;q.dataset.manual='1'}if(u)u.value=unit.value;updatePackCardSummary(row)}\n  syncDrainToContent450();\n }\n return result;\n};\nconst drainQuickFromBefore450=syncDrainQuickFromPrimaryPack;\nsyncDrainQuickFromPrimaryPack=function(...args){const result=drainQuickFromBefore450(...args);syncDrainToContent450();return result};\nfor(const event of ['input','change'])document.addEventListener(event,e=>{if(['aeContentQty','aeContentUnit'].includes(e.target?.id))syncContentToDrain442()});\n"},{"src":"stock-actions439.js?v=439","code":"'use strict';\n// Presentation only. Use the existing product-specific workflows and permissions.\nfunction stockActionWords439(key){\n const language=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';\n const words={de:{in:'Einlagern',out:'Entnehmen',options:'Produktaktionen',info:'Produktdetails',edit:'Produkt bearbeiten',waste:'Ausschuss erfassen',total:'gesamt'},en:{in:'Add stock',out:'Take stock',options:'Product actions',info:'Product details',edit:'Edit product',waste:'Record waste',total:'total'},pl:{in:'Przyjmij',out:'Wydaj',options:'Działania produktu',info:'Szczegóły produktu',edit:'Edytuj produkt',waste:'Zapisz odpad',total:'razem'}};\n return (words[language]||words.de)[key];\n}\nfunction stockActionIcon439(kind){return `<svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" aria-hidden=\"true\">${kind==='menu'?'<circle cx=\"12\" cy=\"5\" r=\"1\"/><circle cx=\"12\" cy=\"12\" r=\"1\"/><circle cx=\"12\" cy=\"19\" r=\"1\"/>':`<path d=\"M5 12h14${kind==='in'?'M12 5v14':''}\"/>`}</svg>`}\nfunction stockActions439(code,a,surface){\n const permitted=p=>typeof hasPermission==='function'&&hasPermission(p),canInfo=permitted('stock')||permitted('scan');\n return `<div class=\"${surface==='mobile'?'mobileVariantActions322':'hubActions362'} stockActions439\">${['in','out'].map(kind=>`<button type=\"button\" class=\"soft stockAction439 stockAction439--${kind}\" data-code=\"${esc(code)}\" aria-label=\"${esc(stockActionWords439(kind)+' · '+a.name)}\" ${permitted(kind==='in'?'stockAdd':'scan')?'':'disabled'} onclick=\"openVariant322(this.dataset.code,'${kind}')\">${stockActionIcon439(kind)}<span>${esc(stockActionWords439(kind))}</span></button>`).join('')}<button type=\"button\" class=\"soft stockMore439\" data-code=\"${esc(code)}\" aria-label=\"${esc(stockActionWords439('options')+' · '+a.name)}\" aria-haspopup=\"menu\" aria-controls=\"stockActionMenu439\" aria-expanded=\"false\" ${canInfo||permitted('articles')?'':'disabled'} onpointerdown=\"event.preventDefault()\" onclick=\"openStockActionMenu439(this.dataset.code,this)\">${stockActionIcon439('menu')}</button></div>`;\n}\n// Read-only total of confirmed contents; never infer from names or capacity.\nfunction totalStockContent440(a){\n const stock=Number(a?.stock);if(a?.stock===null||a?.stock===undefined||String(a.stock).trim()===''||!Number.isFinite(stock)||stock<0)return null;\n const pieces=totalStockPieces434(a);if(pieces!==null)return {qty:pieces,unit:'Stück'};\n const c=a?.stockContent;if(!c||c.stockUnit!==a.unit||!isCountLikeUnit(a.unit)||validateStockContent(a,{...c,qty:Number(c.qty)}))return null;\n const unit=normalizeUnit(c.unit),mass={g:1,dag:10,kg:1000},volume={ml:1,cl:10,dl:100,l:1000};\n const factor=mass[unit]||volume[unit];if(!factor)return null;\n const base=stock*Number(c.qty)*factor;if(!Number.isFinite(base)||Math.abs(base)>Number.MAX_SAFE_INTEGER)return null;\n return {qty:base>=1000?base/1000:base,unit:mass[unit]?(base>=1000?'kg':'g'):(base>=1000?'Liter':'ml')};\n}\nfunction formatStockTotal440(total){\n const language=recognizedLanguage278(),locale={de:'de-AT',en:'en-GB',pl:'pl-PL'}[language]||'de-AT';\n const unit=total.unit==='Liter'?({de:'Liter',en:'litres',pl:'l'}[language]||'Liter'):total.unit==='Stück'?({de:'Stück',en:'pieces',pl:'szt.'}[language]||'Stück'):total.unit;\n return new Intl.NumberFormat(locale,{maximumSignificantDigits:12}).format(total.qty)+' '+unit;\n}\nfunction stockPiecesHtml439(a){const total=totalStockContent440(a);return total===null?'':`<small class=\"stockPieces439\">= ${esc(formatStockTotal440(total))} ${esc(stockActionWords439('total'))}${a?.drainWeightEnabled?' · '+esc(({de:'Abtropfgewicht',en:'drained weight',pl:'masa po odsączeniu'}[recognizedLanguage278()]||'Abtropfgewicht')):''}</small>`}\nconst mobileVariantBefore439=mobileVariant322;\nmobileVariant322=function(code,a){return mobileVariantBefore439(code,a).replace('<strong>','<strong class=\"stockQuantity439\">').replace('</strong></button>',stockPiecesHtml439(a)+'</strong></button>').replace(/<div class=\"mobileVariantActions322\">[\\s\\S]*?<\\/div><\\/article>$/,stockActions439(code,a,'mobile')+'</article>')};\nconst hubRowsBefore439=hubStockRows362;\nhubStockRows362=function(...args){return hubRowsBefore439(...args).replace(/<div class=\"hubStockRow362\">[\\s\\S]*?<div class=\"hubActions362\">[\\s\\S]*?<\\/div><\\/div>/g,row=>{\n const action=row.match(/hubStockAction362\\('((?:\\\\.|[^'])*)','(?:in|out|info|edit)'\\)/);if(!action)return row;\n const code=action[1].replace(/\\\\'/g,\"'\").replace(/\\\\\\\\/g,'\\\\'),a=state.products?.[code];if(!a)return row;\n return row.replace(/<strong>([\\s\\S]*?)<\\/strong>/,(_,qty)=>`<strong class=\"stockQuantity439\">${qty}${stockPiecesHtml439(a)}</strong>`).replace(/<div class=\"hubActions362\">[\\s\\S]*?<\\/div><\\/div>$/,stockActions439(code,a,'web')+'</div>');\n})};\nlet stockMenuTrigger439=null;\nfunction closeStockActionMenu439(focus=true){const menu=document.getElementById('stockActionMenu439');if(menu){if(menu.hidePopover&&menu.matches(':popover-open'))menu.hidePopover();menu.hidden=true}stockMenuTrigger439?.setAttribute('aria-expanded','false');if(focus&&stockMenuTrigger439?.isConnected)stockMenuTrigger439.focus({preventScroll:true})}\nfunction stockMenuKinds439(a){return [hasPermission('stock')||hasPermission('scan')?'info':null,hasPermission('articles')?'edit':null,hasPermission('scan')&&Number(a.stock)>0?'waste':null].filter(Boolean)}\nfunction openStockActionMenu439(code,trigger){\n const a=state.products?.[code];if(!a||busyOperation275())return;\n let menu=document.getElementById('stockActionMenu439');const wasOpen=menu&&!menu.hidden&&stockMenuTrigger439===trigger;closeStockActionMenu439(false);if(wasOpen)return;\n const kinds=stockMenuKinds439(a);if(!kinds.length)return;\n if(!menu){menu=document.createElement('div');menu.id='stockActionMenu439';menu.className='stockActionMenu439';menu.setAttribute('role','menu');menu.setAttribute('popover','auto');document.body.append(menu);menu.addEventListener('toggle',e=>{if(e.newState==='closed'){menu.hidden=true;stockMenuTrigger439?.setAttribute('aria-expanded','false')}})}\n stockMenuTrigger439=trigger;menu.dataset.code=code;menu.setAttribute('aria-label',stockActionWords439('options')+' · '+a.name);\n menu.innerHTML=kinds.map(kind=>`<button type=\"button\" role=\"menuitem\" class=\"stockMenuItem439${kind==='waste'?' stockMenuWaste439':''}\" data-kind=\"${kind}\" onclick=\"runStockMenuAction439(this.dataset.kind)\">${esc(stockActionWords439(kind))}</button>`).join('');\n if(typeof finishSearchTyping321==='function')finishSearchTyping321();\n menu.hidden=false;if(menu.showPopover)menu.showPopover();\n positionStockActionMenu447();trigger.setAttribute('aria-expanded','true');menu.querySelector('button').focus({preventScroll:true});\n}\nfunction runStockMenuAction439(kind){\n const menu=document.getElementById('stockActionMenu439'),code=menu?.dataset.code,a=state.products?.[code];\n if(!menu||menu.hidden||!a||busyOperation275()||!stockMenuKinds439(a).includes(kind))return;\n closeStockActionMenu439(false);\n if(kind==='info'){if(window.innerWidth<=820)openVariant322(code,'info');else hubStockAction362(code,'info')}\n else if(kind==='edit')hubStockAction362(code,'edit');\n else if(kind==='waste')productWaste420(code);\n}\ndocument.addEventListener('click',e=>{const menu=document.getElementById('stockActionMenu439');if(menu&&!menu.hidden&&!menu.contains(e.target)&&!stockMenuTrigger439?.contains(e.target))closeStockActionMenu439(false)});\ndocument.addEventListener('keydown',e=>{const menu=document.getElementById('stockActionMenu439');if(!menu||menu.hidden)return;\n if(e.key==='Escape'){e.preventDefault();e.stopPropagation();closeStockActionMenu439();return}\n if(e.key==='Tab'){closeStockActionMenu439();return}\n if(['ArrowDown','ArrowUp','Home','End'].includes(e.key)){e.preventDefault();const items=[...menu.querySelectorAll('button')],at=items.indexOf(document.activeElement);items[e.key==='Home'?0:e.key==='End'?items.length-1:(at+(e.key==='ArrowDown'?1:-1)+items.length)%items.length]?.focus()}\n});\n// Keyboard height changes reposition the existing menu; orientation/width changes close it.\nfunction positionStockActionMenu447(){\n const menu=document.getElementById('stockActionMenu439'),trigger=stockMenuTrigger439;if(!menu||menu.hidden||!trigger)return;\n const v=window.visualViewport,plain=!v||Math.abs((v.scale||1)-1)<.05;\n const left=plain&&v?v.offsetLeft||0:0,top=plain&&v?v.offsetTop||0:0,width=plain&&v?v.width:window.innerWidth,height=plain&&v?v.height:window.innerHeight;\n menu.style.maxWidth=Math.max(1,width-16)+'px';menu.style.maxHeight=Math.max(1,height-16)+'px';menu.style.overflowY='auto';\n const r=trigger.getBoundingClientRect(),m=menu.getBoundingClientRect();menu.style.left=Math.max(left+8,Math.min(r.right-m.width,left+width-m.width-8))+'px';menu.style.top=Math.max(top+8,Math.min(r.bottom+6,top+height-m.height-8))+'px';\n}\nlet stockMenuWidth447=window.innerWidth;\nwindow.addEventListener('resize',()=>{const changed=window.innerWidth!==stockMenuWidth447;stockMenuWidth447=window.innerWidth;if(changed)closeStockActionMenu439(false);else positionStockActionMenu447()});\nwindow.visualViewport?.addEventListener('resize',positionStockActionMenu447,{passive:true});\nwindow.visualViewport?.addEventListener('scroll',positionStockActionMenu447,{passive:true});\n"},{"src":"content-entry436.js?v=436","code":"'use strict';\n// Form repair only: no catalogue migration, stock write, or inferred contents.\nlet contentEditor436=null;\nfunction contentText436(key,...args){\n const lang=typeof recognizedLanguage278==='function'?recognizedLanguage278():'de';\n const words={\n de:{unitRequired:'Bitte wählen, ob du Packungen, Flaschen, Stück oder Gewicht zählst.',heading:'Wie viel enthält 1 {0}?',unitHint:'Wähle Packung oder Flasche, wenn du ganze Packungen oder Flaschen zählst. Der Inhalt wird im nächsten Schritt erfasst.',hint:'Zum Beispiel: 1 Packung enthält 1 Liter, 500 g oder 50 Stück. Die Bestandsmenge wird danach getrennt gebucht.',required:'Bitte den Inhalt von 1 {0} mit Menge und Einheit eingeben.',unit:'Einheit wählen',per:'Inhalt je {0}',previous:'Frühere Mengenangabe aus den Preisangaben: {0}.',check:'Bitte mit der Packung vergleichen. Diese Angabe ist noch kein bestätigter Packungsinhalt.',adopt:'Als Packungsinhalt übernehmen',placeholder:'Bei früheren g-Ersatzangaben zuerst die richtige Einheit prüfen. Gramm werden nicht automatisch in Stück umgerechnet.'},\n en:{unitRequired:'Choose whether stock is counted in packs, bottles, pieces or weight.',heading:'How much does 1 {0} contain?',unitHint:'Choose pack or bottle when counting whole packs or bottles. Enter their contents in the next step.',hint:'For example: 1 pack contains 1 litre, 500 g or 50 pieces. Stock quantity is booked separately afterwards.',required:'Enter the contents of 1 {0}, including quantity and unit.',unit:'Choose unit',per:'Contents per {0}',previous:'Earlier quantity from price details: {0}.',check:'Compare this with the pack. It is not yet confirmed pack contents.',adopt:'Use as pack contents',placeholder:'Check the unit when grams were previously used as a placeholder. Grams are never automatically converted into pieces.'},\n pl:{unitRequired:'Wybierz, czy liczysz opakowania, butelki, sztuki czy wagę.',heading:'Ile zawiera 1 {0}?',unitHint:'Wybierz opakowanie lub butelkę, jeśli liczysz całe opakowania lub butelki. Zawartość podasz w następnym kroku.',hint:'Na przykład: 1 opakowanie zawiera 1 litr, 500 g lub 50 sztuk. Ilość stanu zapiszesz osobno później.',required:'Podaj zawartość 1 {0}, ilość i jednostkę.',unit:'Wybierz jednostkę',per:'Zawartość 1 {0}',previous:'Wcześniejsza ilość z danych ceny: {0}.',check:'Porównaj z opakowaniem. Ta informacja nie jest jeszcze potwierdzoną zawartością.',adopt:'Użyj jako zawartości opakowania',placeholder:'Sprawdź jednostkę, jeśli wcześniej użyto gramów zamiast sztuk. Gramy nie są automatycznie przeliczane na sztuki.'}};\n return ((words[lang]||words.de)[key]||key).replace(/\\{(\\d+)\\}/g,(_,i)=>String(args[Number(i)]??''));\n}\nfunction contentHeading436(unit){return contentText436('heading',unit)}\nfunction legacyPrice436(a){\n if(!a||a.stockContent||!isCountLikeUnit(a.unit))return null;\n const qty=Number(a.priceInputQty131),unit=a.priceInputUnit131;\n if(!Number.isFinite(qty)||qty<=0||!physicalUnitKind(unit))return null;\n return {qty,unit,stockUnit:a.unit};\n}\nfunction previousContent436(a){\n const value=legacyPrice436(a);return value&&!validateStockContent(a,value)?value:null;\n}\nfunction stockUnitOptions436(select){\n const confirmed=typeof guidedArticle300!=='undefined'&&guidedArticle300?.unitAcknowledged436;\n return `<option value=\"\" ${confirmed?'':'selected'}>${esc(contentText436('unit'))}</option>`+[...select.options].filter(o=>o.value).map(o=>`<option value=\"${esc(o.value)}\" ${confirmed&&o.value===select.value?'selected':''}>${esc(o.textContent)}</option>`).join('');\n}\nfunction contentUnitOptions436(select){return [...select.options].map(o=>`<option value=\"${esc(o.value)}\" ${o.value===select.value?'selected':''}>${esc(o.value?o.textContent:contentText436('unit'))}</option>`).join('')}\nfunction alignBasePackName436(){\n const old=typeof editingArticleCode!=='undefined'&&state.products?.[editingArticleCode],unit=document.getElementById('aeUnit')?.value;\n const name=document.querySelector('#aePacks .packEditRow:first-child .aePackName');\n if(old&&unit&&isCountLikeUnit(old.unit)&&isCountLikeUnit(unit)&&name&&normalizeUnit(name.value)===normalizeUnit(old.unit))name.value=unit;\n}\nfunction syncVisibleContent436(){\n if(typeof guidedArticle300==='undefined'||guidedArticle300?.step!=='content')return;\n for(const [source,target] of [['gaContentQty300','aeContentQty'],['gaContentUnit300','aeContentUnit']]){\n  const a=document.getElementById(source),b=document.getElementById(target);if(a&&b)b.value=a.value;\n }\n if(typeof captureGuidedCapacity431==='function')captureGuidedCapacity431();\n}\nfunction requireArticleContent436(){\n if(typeof productGuide194!=='undefined'&&productGuide194)return true;\n syncVisibleContent436();\n const unit=document.getElementById('aeUnit')?.value,old=typeof editingArticleCode!=='undefined'&&editingArticleCode?state.products?.[editingArticleCode]:null;\n if(!unit){showFeedback(contentWords431('check'),'warn',5000,contentText436('unitRequired'));if(typeof guidedArticle300!=='undefined'&&guidedArticle300){guidedArticle300.step='unit';renderGuidedArticle300()}const field=document.getElementById('gaUnit300')||document.getElementById('aeUnit');const fold=field?.dataset?.articleFold;if(fold&&typeof setArticleFold==='function')setArticleFold(Number(fold),true);field?.focus();field?.scrollIntoView?.({block:'center'});return false;}\n if(!isCountLikeUnit(unit))return true;\n const required=!old||!!old.stockContent;\n if(validateGuidedContent431(required))return true;\n if(typeof guidedArticle300!=='undefined'&&guidedArticle300){guidedArticle300.step='content';renderGuidedArticle300()}\n else{const field=document.getElementById('aeContentField'),fold=field?.dataset?.articleFold;if(fold&&typeof setArticleFold==='function')setArticleFold(Number(fold),true);if(field)field.hidden=false;}\n const input=document.getElementById('gaContentQty300')||document.getElementById('aeContentQty');input?.focus();input?.scrollIntoView?.({block:'center'});return false;\n}\nfunction mountContentRecovery436(code){\n document.getElementById('contentRecovery436')?.remove();\n const a=code&&state.products?.[code],candidate=previousContent436(a),field=document.getElementById('aeContentField');\n if(!candidate||!field)return;\n const box=document.createElement('div');box.id='contentRecovery436';box.className='contentRecovery436';\n box.innerHTML=`<p>${esc(contentText436('previous',formatQtySmart(candidate.qty,candidate.unit,{exact:true})))}</p><p>${esc(contentText436('check'))}</p>${typeof nonFoodCategory433==='function'&&nonFoodCategory433(a.overviewGroup||a.cat)&&physicalUnitKind(candidate.unit)==='mass'?`<p>${esc(contentText436('placeholder'))}</p>`:''}<button type=\"button\" class=\"soft\" onclick=\"adoptPreviousContent436()\">${esc(contentText436('adopt'))}</button>`;field.appendChild(box);\n}\nfunction adoptPreviousContent436(){\n const context=contentEditor436,code=typeof editingArticleCode!=='undefined'?editingArticleCode:null;\n if(!context||code!==context.code||currentAccessUser().role!=='admin'||busyOperation275())return false;\n const candidate=previousContent436(state.products?.[code]);if(!candidate)return false;\n const quantity=document.getElementById('aeContentQty'),unit=document.getElementById('aeContentUnit'),stockUnit=document.getElementById('aeUnit')?.value;\n if(!quantity||!unit||validateStockContent({unit:stockUnit,packs:[]},{...candidate,stockUnit}))return false;\n const match=[...unit.options].find(o=>normalizeUnit(o.value)===normalizeUnit(candidate.unit));if(!match)return false;\n quantity.value=String(candidate.qty);unit.value=match.value;updateEditorStockContent();updatePurchaseQuote('article');document.getElementById('contentRecovery436')?.remove();quantity.focus();return true;\n}\n// Opening a legacy article must not turn '1 bottle at 330 ml' into a price for\n// 330 bottles. Keep the known per-stock-unit price and retain the old evidence\n// if the user edits an unrelated field without confirming pack contents.\nfunction retainedPriceInput436(code,qty,unit){\n const c=contentEditor436;\n if(c?.code===code&&c.originalPrice442&&!c.priceTouched442&&document.getElementById('aeUnit')?.value===c.stockUnit)return {...c.originalPrice442};\n return c?.legacy&&c.code===code&&!document.getElementById('aeContentQty')?.value.trim()&&Number(qty)===1&&normalizeUnit(unit)===normalizeUnit(c.stockUnit)?{qty:c.legacy.qty,unit:c.legacy.unit}:{qty,unit};\n}\nconst openContentBefore436=openArticleEditor;\nopenArticleEditor=function(code,...args){\n contentEditor436=null;const result=openContentBefore436(code,...args);\n if(document.getElementById('articleModal')?.style.display!=='flex')return result;\n const a=code&&state.products?.[code],legacy=legacyPrice436(a),quantity=document.getElementById('aeContentQty'),unit=document.getElementById('aeContentUnit');\n contentEditor436={code:code||'',legacy,stockUnit:a?.unit,originalPrice442:a&&Number(a.priceInputQty131)>0&&a.priceInputUnit131?{qty:Number(a.priceInputQty131),unit:a.priceInputUnit131}:null,priceTouched442:false};\n const restored=!!document.getElementById('articleDraftNotice249');\n if(!a&&!restored)document.getElementById('aeUnit').value='';\n if(!a&&restored&&typeof guidedArticle300!=='undefined'&&guidedArticle300)guidedArticle300.unitAcknowledged436=!!document.getElementById('aeUnit')?.value;\n if(unit&&!quantity?.value.trim())unit.value='';\n if(legacy){fillPriceUnitPicker131('article',a.unit);document.getElementById('aePriceForQty').value='1';document.getElementById('aePriceUnit131').value=a.unit;updatePurchaseQuote('article');}\n updateEditorStockContent();mountContentRecovery436(code);if(typeof improveArticleBridgeUi135==='function')improveArticleBridgeUi135();return result;\n};\nfor(const event of ['input','change'])document.addEventListener(event,e=>{\n if(contentEditor436&&['aePrice','aePriceForQty','aePriceUnit131'].includes(e.target?.id))contentEditor436.priceTouched442=true;\n if(e.target?.id==='gaUnit300'&&typeof guidedArticle300!=='undefined'&&guidedArticle300){const field=document.getElementById('aeUnit');guidedArticle300.unitAcknowledged436=!!e.target.value;if(field&&field.value!==e.target.value){field.value=e.target.value;field.dispatchEvent(new Event('change',{bubbles:true}))}}\n if(e.target?.id==='aeUnit')alignBasePackName436();\n if(['gaContentQty300','gaContentUnit300','gaCapacityQty431','gaCapacityUnit431'].includes(e.target?.id))syncVisibleContent436();\n});\n"},{"src":"stock-workshop444.js?v=444","code":"'use strict';\n// Form and navigation repair. Reuse stockContent and the existing packs array.\n// No catalogue migration, stock correction, cloud write, or inferred unit change.\nfunction words444(de,en,pl){return stockText420(de,en,pl)}\nfunction cartonNumbers444(packs,contents){\n const count=Number(packs),qty=Number(contents);\n if(!Number.isSafeInteger(count)||count<1||!Number.isFinite(qty)||qty<=0)return null;\n const total=count*qty;return Number.isFinite(total)&&total<=Number.MAX_SAFE_INTEGER?{count,qty,total}:null;\n}\nfunction cartonRow444(){return [...document.querySelectorAll('#aePacks .packEditRow:not(.basePack)')].find(r=>normalizeUnit(r.querySelector('.aePackName')?.value)==='karton')||null}\nfunction cartonSummary444(){\n const unit=document.getElementById('aeUnit')?.value,raw=document.getElementById('aeCartonQty444')?.value||'',content=document.getElementById('aeContentQty')?.value||'',contentUnit=document.getElementById('aeContentUnit')?.value||'';\n const n=cartonNumbers444(raw,String(content).replace(',','.'));\n return unit==='Packung'&&n&&contentUnit?`1 ${words444('Karton','carton','karton')} = ${n.count} ${words444('Packungen','packs','opakowań')} × ${formatQtySmart(n.qty,contentUnit,{exact:true})} = ${formatQtySmart(n.total,contentUnit,{exact:true})}`:'';\n}\nfunction refreshCarton444(){\n const input=document.getElementById('aeCartonQty444'),box=document.getElementById('aeCartonField444');if(!input||!box)return;\n box.hidden=document.getElementById('aeUnit')?.value!=='Packung'||drainWeightEnabledInEditor();\n const summary=cartonSummary444();for(const id of ['aeCartonSummary444','gaCartonSummary444']){const n=document.getElementById(id);if(n)n.textContent=summary;}\n const row=cartonRow444();if(row)row.classList.toggle('simpleCarton444',!box.hidden);\n}\nfunction mountCarton444(){\n const field=document.getElementById('aeContentField');if(!field)return;\n let box=document.getElementById('aeCartonField444');if(!box){box=document.createElement('div');box.id='aeCartonField444';box.className='cartonField444';box.innerHTML=`<label for=\"aeCartonQty444\">${words444('Packungen je Karton (optional)','Packs per carton (optional)','Opakowania w kartonie (opcjonalnie)')}</label><input id=\"aeCartonQty444\" type=\"number\" inputmode=\"numeric\" min=\"1\" step=\"1\" placeholder=\"${words444('Zum Beispiel 6','For example 6','Na przykład 6')}\" aria-describedby=\"aeCartonSummary444\"><p id=\"aeCartonSummary444\" class=\"cartonSummary444\" aria-live=\"polite\"></p>`;field.append(box);}\n const row=cartonRow444(),quantity=row?.querySelector('.aePackQty'),unit=row?.querySelector('.aePackUnit')?.value;\n const restored=!editingArticleCode&&document.getElementById('articleDraftNotice249');\n if(!restored)document.getElementById('aeCartonQty444').value=row&&unit==='Packung'?quantity.value:'';\n else if(document.getElementById('aeCartonQty444').value)syncCarton444();\n refreshCarton444();\n}\nfunction syncCarton444(){\n const unit=document.getElementById('aeUnit')?.value,raw=document.getElementById('aeCartonQty444')?.value||'';\n if(unit!=='Packung'||drainWeightEnabledInEditor()||!Number.isSafeInteger(Number(raw))||Number(raw)<1){refreshCarton444();return false;}\n let row=cartonRow444();\n if(!row){const temp=document.createElement('div');temp.innerHTML=packRowHtml(['Karton',Number(raw),'',true,Number(raw),Number(raw),'Packung',Number(raw),'Packung'],1,document.getElementById('aeCode')?.value||'',false);row=temp.firstElementChild;document.getElementById('aePacks').append(row);wirePackDrainRow(row);}\n row.querySelector('.aePackQty').value=raw;row.querySelector('.aePackUnit').value='Packung';row.querySelector('.aePackUsable').value=raw;row.querySelector('.aePackUsableUnit').value='Packung';\n row.classList.add('simpleCarton444');updatePackCardSummary(row);refreshCarton444();return true;\n}\nfunction validateCarton444(){\n if(document.getElementById('aeUnit')?.value!=='Packung'||drainWeightEnabledInEditor())return true;\n const input=document.getElementById('aeCartonQty444');if(!input)return true;\n if(!input.value.trim()&&!cartonRow444())return true;\n if(Number.isSafeInteger(Number(input.value))&&Number(input.value)>0)return true;\n showFeedback(words444('Karton prüfen','Check carton','Sprawdź karton'),'warn',4500,words444('Bitte die Anzahl Packungen je Karton als ganze Zahl größer als 0 eingeben.','Enter a whole number of packs per carton greater than 0.','Podaj całkowitą liczbę opakowań w kartonie większą od 0.'));input.focus();return false;\n}\nfunction guidedCarton444(unit){return unit==='Packung'?`<div class=\"cartonField444\"><label for=\"gaCartonQty444\">${words444('Packungen je Karton (optional)','Packs per carton (optional)','Opakowania w kartonie (opcjonalnie)')}</label><input id=\"gaCartonQty444\" type=\"number\" inputmode=\"numeric\" min=\"1\" step=\"1\" value=\"${esc(document.getElementById('aeCartonQty444')?.value||'')}\" placeholder=\"${words444('Zum Beispiel 6','For example 6','Na przykład 6')}\"><p id=\"gaCartonSummary444\" class=\"cartonSummary444\" aria-live=\"polite\">${esc(cartonSummary444())}</p></div>`:''}\nfunction captureCarton444(){const field=document.getElementById('gaCartonQty444');if(field){document.getElementById('aeCartonQty444').value=field.value;syncCarton444();}}\nconst openBefore444=openArticleEditor;\nopenArticleEditor=function(...args){priceBasisManual452=false;const r=openBefore444(...args);document.getElementById('articleModal')?.classList.toggle('articleEditing448',!!editingArticleCode);if(document.getElementById('articleModal')?.style.display==='flex'){mountCarton444();revealProductMeasures447();renderPieceIdentity451();restorePriceBasis452(args[0])}return r};\nconst updateContentBefore444=updateEditorStockContent;\nupdateEditorStockContent=function(...args){const r=updateContentBefore444(...args);refreshCarton444();renderPieceIdentity451();return r};\nconst saveBefore444=saveArticleEditor;\nsaveArticleEditor=function(...args){if(!validateCarton444())return false;return saveBefore444(...args)};\nfor(const event of ['input','change'])document.addEventListener(event,e=>{\n const id=e.target?.id;if(id==='gaCartonQty444'){document.getElementById('aeCartonQty444').value=e.target.value;syncCarton444();}\n else if(id==='aeCartonQty444')syncCarton444();\n else if(['aeContentQty','aeContentUnit','gaContentQty300','gaContentUnit300','aeUnit'].includes(id))refreshCarton444();\n});\n\n// All stock entry points use the same existing workspace on desktop.\nconst stockTabBefore444=showTab;\nshowTab=function(which,...args){if(which==='stock'&&webDesktop360())return openHub362('stock');return stockTabBefore444(which,...args)};\nfunction stockMetricsHtml444(){\n const m=stockValueMetrics();return `<div class=\"stockMetric\"><small>${words444('Rechnerischer Lagerwert','Estimated stock value','Szacunkowa wartość stanu')}</small><b>${moneyFmt(m.value)}</b></div><button type=\"button\" class=\"stockMetric isAction ${m.missingPrice?'warn':''}\" onclick=\"openProductProblems444('price')\"><small>${words444('Artikel mit Bestand ohne EK','Stocked items without a purchase price','Artykuły bez ceny zakupu')}</small><b>${m.missingPrice}</b><span class=\"tiny\">${words444('Probleme ansehen','Review issues','Zobacz problemy')}</span></button><div class=\"stockMetric\"><small>${words444('Ausschuss letzte 30 Tage','Waste in the last 30 days','Straty w ostatnich 30 dniach')}</small><b>${moneyFmt(m.wasteValue)}</b></div><div class=\"stockMetric\"><small>${words444('Ausschuss-Buchungen 30 Tage','Waste entries in 30 days','Zapisy strat w ciągu 30 dni')}</small><b>${m.wasteCount}</b></div>`;\n}\nrenderStockValueSummary=function(){const root=document.getElementById('stockValueSummary');if(root)root.innerHTML=stockMetricsHtml444()};\nconst stockHubBefore444=renderHubStock362;\nrenderHubStock362=function(...args){return stockHubBefore444(...args).replace('<div class=\"hubSearch362\">',`<div class=\"stockValueSummary stockMetrics444\">${stockMetricsHtml444()}</div><div class=\"hubSearch362\">`)};\n\n// A read-only work list. Deferring an unknown value never fills it with zero,\n// marks it complete, or prevents another product from being edited.\nlet problems444=null;\nfunction productIssues444(a){\n const issues=[];\n if(Number(a.stock)>0&&!(Number(a.price)>0))issues.push({kind:'price',text:words444('Einkaufspreis fehlt','Purchase price missing','Brak ceny zakupu')});\n if(isCountLikeUnit(a.unit)&&normalizeUnit(a.unit)!=='stk'&&(!a.stockContent||a.stockContent.stockUnit!==a.unit||validateStockContent(a,a.stockContent)))issues.push({kind:'content',text:words444('Packungsinhalt fehlt oder muss geprüft werden','Pack contents missing or need review','Brak zawartości lub wymaga sprawdzenia')});\n if(recognizedMhdPolicy256(a)==='review')issues.push({kind:'mhd',text:words444('MHD-Regel prüfen','Review expiry rule','Sprawdź regułę terminu ważności')});\n return issues;\n}\nfunction problemRows444(){return Object.entries(state.products||{}).map(([code,a])=>({code,a,issues:productIssues444(a)})).filter(r=>r.issues.length&&(!problems444?.kind||r.issues.some(x=>x.kind===problems444.kind))&&(!problems444?.query||`${r.a.name} ${r.code}`.toLocaleLowerCase().includes(problems444.query.toLocaleLowerCase()))).sort((a,b)=>a.a.name.localeCompare(b.a.name,'de',{numeric:true}));}\nfunction closeProductProblems444(){document.getElementById('productProblems444')?.remove();const trigger=problems444?.trigger;problems444=null;if(trigger?.isConnected)trigger.focus({preventScroll:true});}\nfunction problemRow444(r,deferred){return `<li><div><strong>${esc(r.a.name)}</strong><small>${esc(formatQtySmart(Number(r.a.stock)||0,r.a.unit))} · ${esc(r.code)}</small><ul>${r.issues.map(x=>`<li>${esc(x.text)}</li>`).join('')}</ul></div><div class=\"problemActions444\"><button type=\"button\" class=\"soft\" data-code=\"${esc(r.code)}\" onclick=\"editProblem444(this.dataset.code)\" ${hasPermission('articles')?'':'disabled'}>${words444('Bearbeiten','Edit','Edytuj')}</button><button type=\"button\" class=\"soft\" data-code=\"${esc(r.code)}\" onclick=\"deferProblem444(this.dataset.code,${!deferred})\">${deferred?words444('Zurückholen','Bring back','Przywróć'):words444('Später','Later','Później')}</button></div></li>`;}\nfunction drawProblems444(){\n if(!problems444)return;const modal=document.getElementById('productProblems444'),body=modal?.querySelector('.problemList444');if(!body)return;\n const scroll=body.scrollTop,rows=problemRows444(),active=rows.filter(r=>!problems444.later.has(r.code)),later=rows.filter(r=>problems444.later.has(r.code));\n body.innerHTML=`<p class=\"problemCount444\" role=\"status\">${rows.length} ${words444('Artikel mit offenen Angaben','items with missing details','artykułów z brakującymi danymi')}</p><ul class=\"problemRows444\">${active.map(r=>problemRow444(r,false)).join('')}</ul>${!active.length?`<p>${words444('Keine weiteren Artikel in dieser Auswahl.','No more items in this selection.','Brak dalszych artykułów w tym widoku.')}</p>`:''}${later.length?`<details ${problems444.laterOpen?'open':''} ontoggle=\"if(problems444)problems444.laterOpen=this.open\"><summary>${words444('Für später','For later','Na później')} (${later.length})</summary><ul class=\"problemRows444\">${later.map(r=>problemRow444(r,true)).join('')}</ul></details>`:''}`;body.scrollTop=scroll;\n}\nfunction openProductProblems444(kind=''){\n if(busyOperation275()||!hasPermission('stock'))return;\n if(!problems444)problems444={kind,query:'',later:new Set(),trigger:document.activeElement};\n let modal=document.getElementById('productProblems444');\n if(!modal){modal=document.createElement('div');modal.id='productProblems444';modal.className='modal';modal.setAttribute('role','dialog');modal.setAttribute('aria-modal','true');modal.setAttribute('aria-labelledby','problemTitle444');modal.innerHTML=`<div class=\"card\"><header class=\"modalHeader\"><h2 id=\"problemTitle444\">${words444('Produkte prüfen','Review products','Sprawdź produkty')}</h2><button type=\"button\" class=\"close\" onclick=\"closeProductProblems444()\">${words444('Schließen','Close','Zamknij')}</button></header><p>${words444('Fehlende Angaben kannst du später ergänzen. Andere Produkte lassen sich weiter bearbeiten.','You can complete missing details later and continue with other products.','Brakujące dane możesz uzupełnić później i pracować z innymi artykułami.')}</p><div class=\"problemFilters444\"><input type=\"search\" aria-label=\"${words444('Produkte mit Problemen suchen','Search products with issues','Szukaj artykułów z problemami')}\" placeholder=\"${words444('Produkt oder Barcode suchen','Search product or barcode','Szukaj produktu lub kodu')}\" oninput=\"problems444.query=this.value;drawProblems444()\"><select aria-label=\"${words444('Problemart','Issue type','Rodzaj problemu')}\" onchange=\"problems444.kind=this.value;drawProblems444()\">${[['',words444('Alle Hinweise','All issues','Wszystkie uwagi')],['price',words444('Einkaufspreis fehlt','Purchase price missing','Brak ceny zakupu')],['content',words444('Packungsinhalt prüfen','Review pack contents','Sprawdź zawartość')],['mhd',words444('MHD-Regel prüfen','Review expiry rule','Sprawdź regułę terminu')]].map(([v,t])=>`<option value=\"${v}\" ${problems444.kind===v?'selected':''}>${t}</option>`).join('')}</select></div><div class=\"problemList444\"></div></div>`;document.body.append(modal);}\n modal.style.display='flex';drawProblems444();modal.querySelector('.close').focus({preventScroll:true});\n}\nopenFirstMissingPrice=function(){return openProductProblems444('price')};\nfunction deferProblem444(code,defer){if(!problems444)return;if(defer)problems444.later.add(code);else problems444.later.delete(code);drawProblems444();}\nfunction editProblem444(code){\n if(!problems444||!hasPermission('articles')||busyOperation275())return;const row=problemRows444().find(r=>r.code===code);if(!row)return;\n problems444.editing=code;document.getElementById('productProblems444').style.display='none';openArticleEditor(code);\n if(document.getElementById('articleModal')?.style.display!=='flex'){openProductProblems444();return;}\n const kind=problems444.kind||row.issues[0].kind,id={price:'aePrice',content:'aeContentQty',mhd:'aeMhdPolicy144'}[kind],field=document.getElementById(id),fold=field?.closest('[data-article-fold]')?.dataset.articleFold;\n const openedCode=code;setTimeout(()=>{if(editingArticleCode!==openedCode||!problems444?.editing)return;if(fold)setArticleFold(Number(fold),true);field?.scrollIntoView({block:'center'});field?.focus({preventScroll:true});},160);\n}\nconst closeBefore444=closeArticleEditor;\ncloseArticleEditor=function(...args){const r=closeBefore444(...args);if(problems444?.editing&&document.getElementById('articleModal')?.style.display!=='flex'){problems444.editing=null;openProductProblems444();}return r};\ndocument.addEventListener('keydown',e=>{const modal=document.getElementById('productProblems444');if(modal?.style.display!=='flex')return;if(e.key==='Escape'){e.preventDefault();e.stopPropagation();closeProductProblems444();}else if(e.key==='Tab'){const nodes=[...modal.querySelectorAll('button:not(:disabled),input,select,summary')].filter(n=>n.getClientRects().length),first=nodes[0],last=nodes.at(-1);if(e.shiftKey&&document.activeElement===first){e.preventDefault();last?.focus()}else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first?.focus()}}});\n// Choose the actual received pack before the existing guarded stock transaction.\nfunction stockPackChoice444(d,a){\n const packs=getPacks(a,d.existing.productCode);if(packs.length<2)return '';\n return `<label for=\"stockPack444\">${words444('Gebinde buchen','Received pack','Przyjmowane opakowanie')}</label><select id=\"stockPack444\" onchange=\"chooseStockPack444(Number(this.value))\">${packs.map((p,i)=>`<option value=\"${i}\" ${i===d.existing.packIndex?'selected':''}>${esc(i===0?a.unit:p[0])}${i>0?' · '+esc(formatQtySmart(packUsableQty(p),a.unit)):''}</option>`).join('')}</select>`;\n}\nfunction chooseStockPack444(index){\n const d=productGuide194;if(!d?.existing||d.quickPending198||busyOperation275()||!Number.isInteger(index))return false;\n const a=state.products[d.existing.productCode],pack=getPacks(a,d.existing.productCode)[index];if(!pack)return false;\n captureStockWizard300(d);d.existing.packIndex=index;d.existing.pack=pack;d.barcode=String(pack[2]);d.stockWizard300.priceChoice='';d.stockWizard300.price='';d.priceChoice214='';renderStockWizard311();return true;\n}\nfunction stockReceiptTotal444(){\n const d=productGuide194,a=d?.existing&&state.products[d.existing.productCode],box=document.getElementById('stockReceiptTotal444');if(!a||!box)return;\n const qty=Number(document.getElementById('guidedQty300')?.value),pack=getPacks(a,d.existing.productCode)[d.existing.packIndex||0],base=qty*packUsableQty(pack),c=a.stockContent;\n box.textContent=Number.isFinite(base)&&base>0?`${formatQtySmart(base,a.unit)}${c?.stockUnit===a.unit&&Number(c.qty)>0&&!validateStockContent(a,c)?' = '+formatQtySmart(base*Number(c.qty),c.unit,{exact:true}):''}`:'';\n}\ndocument.addEventListener('input',e=>{if(e.target?.id==='guidedQty300')stockReceiptTotal444()});\nmountCarton444();\n\n// Scan, search and stock administration share the same complete editor.\n// Make existing contents/drainage controls reachable without a second form or copy.\nfunction revealProductMeasures447(){\n const modal=document.getElementById('articleModal');if(!editingArticleCode||modal?.style.display!=='flex')return;\n const field=document.getElementById('aeContentField'),food=document.getElementById('aeFoodOptions');\n const fold=field?.dataset?.articleFold;if(fold&&typeof setArticleFold==='function')setArticleFold(Number(fold),true);\n if(typeof updateArticleFoodOptions==='function')updateArticleFoodOptions();\n if(food){const available=!nonFoodCategory433(document.getElementById('aeCategory')?.value);food.hidden=false;food.style.display=available?'block':'none';}\n}\n\n// A piece is one recipe piece. Purchase-pack counts belong to packs, not stockContent.\nfunction pieceIdentity451(){\n return !drainWeightEnabledInEditor()&&normalizeUnit(document.getElementById('aeUnit')?.value)==='stk'&&normalizeUnit(document.getElementById('aeContentUnit')?.value)==='stk';\n}\nfunction renderPieceIdentity451(){\n const field=document.getElementById('aeContentField'),qty=document.getElementById('aeContentQty');if(!field||!qty)return;\n const active=pieceIdentity451(),raw=String(qty.value||'').trim(),count=costNumber(raw),conflict=active&&raw&&count!==1;\n qty.readOnly=!!(active&&raw);\n const hint=field.querySelector('.bridgeHint135');if(hint)hint.hidden=active;\n let box=document.getElementById('pieceIdentity451');\n if(!active){box?.remove();return}\n const label=document.getElementById('aeContentLabel');if(label)label.textContent=words444('Für Rezepte: Einzelstücke','For recipes: individual pieces','Do przepisów: pojedyncze sztuki');\n if(!box){box=document.createElement('div');box.id='pieceIdentity451';box.className='pieceIdentity451';field.append(box)}\n const positive=Number.isSafeInteger(count)&&count>1;\n box.innerHTML=conflict?`<p>${esc(words444('Du zählst einzelne Stück. Die zusätzliche Stückzahl gehört zur Einkaufspackung. Dein Lagerbestand bleibt erhalten.','Stock counts individual pieces. The extra count belongs to the purchase pack. Stock remains unchanged.','Stan liczysz w pojedynczych sztukach. Dodatkowa liczba dotyczy opakowania zakupowego. Stan pozostaje bez zmian.'))}</p>${positive?`<button type=\"button\" class=\"soft\" onclick=\"adoptPiecePurchasePack451()\">${esc(words444(count+' Stück als Einkaufspackung übernehmen','Use '+count+' pieces as purchase pack','Ustaw '+count+' sztuk jako opakowanie zakupowe'))}</button>`:''}`:`<p>${esc(words444('1 Stück im Lager = 1 Stück im Rezept. Die Stückzahl je Einkaufspackung steht bei den Packungsgrößen.','1 stock piece = 1 recipe piece. Purchase pack sizes are listed separately.','1 sztuka w magazynie = 1 sztuka w przepisie. Rozmiar opakowania zakupowego podajesz osobno.'))}</p>`;\n}\nfunction adoptPiecePurchasePack451(){\n if(!pieceIdentity451()||busyOperation275()||!hasPermission('articles'))return false;\n const qty=document.getElementById('aeContentQty'),count=costNumber(qty?.value);if(!Number.isSafeInteger(count)||count<=1)return false;\n const root=document.getElementById('aePacks');if(!root)return false;\n let row=[...root.querySelectorAll('.packEditRow')].slice(1).find(r=>normalizeUnit(r.querySelector('.aePackUnit')?.value)==='stk'&&Number(r.querySelector('.aePackQty')?.value)===count);\n if(!row){\n  const tmp=document.createElement('div'),base=document.getElementById('aeCode')?.value||editingArticleCode||'';\n  tmp.innerHTML=packRowHtml(['Packung',count,'',true,count,count,'Stück',count,'Stück'],root.querySelectorAll('.packEditRow').length,base,false);row=tmp.firstElementChild;if(!row)return false;\n  root.appendChild(row);wirePackDrainRow(row);\n }\n qty.value='1';updatePackCardSummary(row);updatePurchaseQuote('article');renderPieceIdentity451();\n const fold=row.closest?.('[data-article-fold]')?.dataset.articleFold;if(fold&&typeof setArticleFold==='function')setArticleFold(Number(fold),true);\n showFeedback(words444('Einkaufspackung vorbereitet','Purchase pack prepared','Opakowanie zakupowe przygotowane'),'info',4500,words444('1 Packung enthält '+count+' Stück. Dein Lagerbestand wurde nicht geändert. Zum Übernehmen Artikel speichern.','1 pack contains '+count+' pieces. Stock is unchanged. Save the item to apply.','1 opakowanie zawiera '+count+' sztuk. Stan nie uległ zmianie. Zapisz artykuł.'));return true;\n}\ndocument.addEventListener('change',event=>{if(['aeUnit','aeContentUnit','aeContentQty'].includes(event.target?.id)){const qty=document.getElementById('aeContentQty');if(pieceIdentity451()&&qty&&!String(qty.value||'').trim())qty.value='1';renderPieceIdentity451()}});\n\n// A supplier quote may be for 1, 25 or 500 pieces, independently of the pack used in stock.\nlet priceBasisManual452=false;\nfor(const event of ['input','change'])document.addEventListener(event,e=>{if(['aePriceForQty','aePriceUnit131'].includes(e.target?.id))priceBasisManual452=true},true);\nfunction restorePriceBasis452(code){\n const a=code&&state.products?.[code];if(!a||a.drainWeightEnabled||document.getElementById('articleDraftNotice249'))return;\n const count=Number(a.priceInputQty131),unit=a.priceInputUnit131;if(!(count>0)||!Number.isFinite(count)||!unit)return;\n const content=priceContent193('article',a.unit),base=priceBaseQty193(count,unit,a.unit,content);\n if(!(base>0)||!Number.isFinite(base))return;\n priceBasisManual452=true;fillPriceUnitPicker131('article',unit);const select=document.getElementById('aePriceUnit131');\n if(!select||normalizeUnit(select.value)!==normalizeUnit(unit))return;\n document.getElementById('aePriceForQty').value=String(count);\n document.getElementById('aePrice').value=Number(a.price)>0?quoteInputNumber(Number(a.price)*base):'';\n updatePurchaseQuote('article');\n}\nconst updateQuoteBefore452=updatePurchaseQuote;\nupdatePurchaseQuote=function(context='article'){\n const result=updateQuoteBefore452(context);if(context!=='article'||drainWeightEnabledInEditor())return result;\n const qty=document.getElementById('aePriceForQty'),unit=document.getElementById('aePriceUnit131'),hint=document.getElementById('aePriceHint'),label=document.getElementById('aePriceLabel');\n if(qty)qty.readOnly=false;if(unit)unit.disabled=false;\n if(label)label.textContent=words444('Einkaufspreis (€)','Purchase price (€)','Cena zakupu (€)');\n if(hint)hint.textContent=words444('Preis und Menge wie beim Händler eingeben, z. B. 134,20 € für 500 Stück. Die Packungsgröße und der Lagerbestand bleiben erhalten.','Enter price and quantity as quoted by your supplier. Pack size and stock stay unchanged.','Wpisz cenę i ilość jak u dostawcy. Rozmiar opakowania i stan pozostają bez zmian.');\n const quote=editorPurchaseQuote(),box=document.getElementById('aePriceCalculation');\n if(box&&!quote.error&&!quote.missing){\n  const precise=Number(quote.amount/quote.inputQty).toLocaleString('de-AT',{minimumFractionDigits:2,maximumFractionDigits:4});\n  box.innerHTML=`<b>${esc(formatQtySmart(quote.inputQty,quote.inputUnit,{exact:true}))} kosten ${moneyFmt(quote.amount)}</b><span>1 ${esc(quote.inputUnit)} = ${esc(precise)} € netto</span>${normalizeUnit(quote.inputUnit)!==normalizeUnit(quote.unit)?`<span>1 ${esc(quote.unit)} = ${quoteMoney(quote.unitPrice)} netto</span>`:''}`;\n }\n return result;\n};\n"},{"src":"shopping-receipt456.js?v=456","code":"'use strict';\n// Compact review uses the existing receipt draft and guarded stock transaction.\nfunction srText456(de,en,pl){return typeof hubText362==='function'?hubText362(de,en,pl):de}\nfunction srPolicy456(a){if(a?.mhdPolicy144==='review')return 'review';return typeof effectiveMhd144==='function'?effectiveMhd144(a||{}):(a?.mhd?'required':'none')}\nif(typeof shoppingReceiptDefaultLines==='function'){const before=shoppingReceiptDefaultLines;shoppingReceiptDefaultLines=function(...args){return before(...args).map(l=>{const a=state.products[shoppingReceiptOption(l)?.code];if(srPolicy456(a)!=='none'&&typeof receiptMhd242==='function')l.mhd=l.mhd||receiptMhd242(a,'')||'';return l})};}\nfunction srDateValid456(value){const d=new Date(value+'T00:00:00Z');return /^\\d{4}-\\d{2}-\\d{2}$/.test(value||'')&&Number.isFinite(d.getTime())&&d.toISOString().slice(0,10)===value}\nfunction srPack456(line){const o=shoppingReceiptOption(line);return o?o.packName+' · '+formatQtySmart(o.size,line.requestedUnit):srText456('Packung wählen','Choose pack','Wybierz opakowanie')}\nfunction srTotal456(){const rows=(shoppingReceiptDraft?.lines||[]).filter(l=>l.got);return srText456('Artikel','items','produkty')+': '+new Set(rows.map(l=>l.sourceKey)).size+' · '+srText456('Gebinde','packs','opakowania')+': '+rows.reduce((n,l)=>n+(Number(l.count)||0),0)}\nfunction srCountPreview457(l){const o=shoppingReceiptOption(l);return l.got?String(l.count??'')+' × '+(o?.packName||srText456('Packung','pack','opakowanie')):srText456('Nicht gekauft','Not bought','Nie kupiono')}\nfunction srToggleProduct457(i,expanded){const l=shoppingReceiptDraft?.lines[i];if(!l)return;l.expanded457=expanded===undefined?!l.expanded457:!!expanded;const body=document.getElementById('srProductBody457-'+i),button=document.getElementById('srProductToggle457-'+i),mark=document.getElementById('srProductMark457-'+i);if(body)body.hidden=!l.expanded457;if(button)button.setAttribute('aria-expanded',String(l.expanded457));if(mark)mark.textContent=l.expanded457?'−':'+';}\nfunction srRefresh456(i){const l=shoppingReceiptDraft?.lines[i];if(!l)return;const preview=document.getElementById('srCountPreview457-'+i);if(preview)preview.textContent=srCountPreview457(l);const s=document.getElementById('shoppingReceiptSummary'+i);if(s)s.textContent=shoppingReceiptLineSummary(l);const total=document.getElementById('srTotal456');if(total)total.textContent=srTotal456();const minus=document.getElementById('srMinus456-'+i);if(minus)minus.disabled=!(Number(l.count)>1);const split=document.getElementById('srSplit456-'+i);if(split)split.hidden=!(Number(l.count)>1);const price=document.getElementById('srPrice456-'+i);if(price)price.textContent=moneyFmt(Number(l.price)||0)+' '+srText456('netto je Gebinde','net per pack','netto za opakowanie');}\nconst srUpdateBefore456=updateShoppingReceiptField;\nupdateShoppingReceiptField=function(i,key,value){const result=srUpdateBefore456(i,key,value);if((key==='price'||key==='count')&&!String(value??'').trim()&&shoppingReceiptDraft?.lines[i])shoppingReceiptDraft.lines[i][key]='';if(key==='price'){const l=shoppingReceiptDraft?.lines[i];if(l)delete l.priceError456;}srRefresh456(i);return result};\nfunction srStep456(i,delta){if(shoppingReceiptDraft?.busy456)return;const l=shoppingReceiptDraft?.lines[i];if(!l?.got)return;const n=Number(l.count);if(!Number.isInteger(n)||n<1)return;const next=Math.max(1,n+delta);updateShoppingReceiptField(i,'count',String(next));const input=document.getElementById('srCount456-'+i);if(input)input.value=String(next)}\nfunction srSplit456(i){if(shoppingReceiptDraft?.busy456)return;const l=shoppingReceiptDraft?.lines[i];if(!l?.got||!Number.isInteger(Number(l.count))||Number(l.count)<2)return;const next={...clone(l),id:l.id+'-'+Date.now()+'-'+Math.random().toString(36).slice(2),count:1,mhd:'',split456:true};l.count--;shoppingReceiptDraft.lines.splice(i+1,0,next);renderShoppingReceipt()}\nfunction srOpenMhd456(i){const l=shoppingReceiptDraft?.lines[i];if(l)l.mhdOpen456=true;const button=document.getElementById('srMhdButton456-'+i);if(button)button.hidden=true;const panel=document.getElementById('srMhd456-'+i);if(panel){panel.hidden=false;panel.querySelector('input')?.focus()}}\nfunction srCapturePrices456(){const box=document.getElementById('shoppingReceiptBody');for(const input of box?.querySelectorAll('[id^=srCost456-]')||[]){const money=input.formAdapter231?.box||input.nextElementSibling;if(money?.dataset?.invalid231){const l=shoppingReceiptDraft?.lines.find(l=>l.id===input.dataset.receiptLine456);if(l){l.priceError456=money.dataset.invalid231;l.priceRaw456=money.querySelector('.moneyAmount231')?.value||'';}}}}\nrenderShoppingReceipt=function(){srCapturePrices456();const box=document.getElementById('shoppingReceiptBody');if(!box||!shoppingReceiptDraft)return;const open=new Set([...box.querySelectorAll('details[open]')].map(d=>d.dataset.receiptDetail456));box.classList.add('receiptCompact456');const title=document.getElementById('shoppingReceiptTitle');if(title)title.textContent=shoppingReceiptDraft.supplier+' · '+srText456('Einkauf prüfen','Review purchase','Sprawdź zakupy');box.innerHTML=`<p class=\"srIntro456\">${srText456('Mengen prüfen, MHD ergänzen und gemeinsam einlagern.','Check quantities, add dates and book together.','Sprawdź ilości, uzupełnij daty i przyjmij razem.')}</p><div class=\"shoppingReceiptList\">${shoppingReceiptDraft.lines.map((l,i)=>{const o=shoppingReceiptOption(l),a=state.products[o?.code],policy=srPolicy456(a),date=!!l.mhd;return `<section class=\"shoppingReceiptRow ${l.got?'':'isMissing'}\" data-receipt456=\"${i}\"><div class=\"shoppingReceiptHead\"><button class=\"srProductToggle457\" type=\"button\" id=\"srProductToggle457-${i}\" aria-expanded=\"${!!l.expanded457}\" aria-controls=\"srProductBody457-${i}\" onclick=\"srToggleProduct457(${i})\"><span><b>${esc(l.requestedName)}</b><small id=\"srCountPreview457-${i}\">${esc(srCountPreview457(l))}</small>${l.split456?`<small>${srText456('Weitere Charge','Another batch','Kolejna partia')}</small>`:''}</span><span class=\"srProductMark457\" id=\"srProductMark457-${i}\" aria-hidden=\"true\">${l.expanded457?'−':'+'}</span></button><label class=\"shoppingReceiptGot\"><input type=\"checkbox\" ${l.got?'checked':''} onchange=\"setShoppingReceiptGot(${i},this.checked)\">${srText456('gekauft','bought','kupiono')}</label></div><div id=\"srProductBody457-${i}\" class=\"srProductBody457\" ${l.expanded457?'':'hidden'}>${l.got?`<div class=\"srQuantity456\"><span>${esc(srPack456(l))}</span><div class=\"srStepper456\"><button type=\"button\" id=\"srMinus456-${i}\" aria-label=\"${esc(srText456('Weniger','Decrease','Mniej')+' '+l.requestedName)}\" ${Number(l.count)>1?'':'disabled'} onclick=\"srStep456(${i},-1)\">−</button><input id=\"srCount456-${i}\" data-adapted231=\"quantity\" type=\"number\" min=\"1\" step=\"1\" inputmode=\"numeric\" aria-label=\"${esc(srText456('Anzahl','Quantity','Ilość')+' '+l.requestedName)}\" value=\"${esc(String(l.count??''))}\" oninput=\"updateShoppingReceiptField(${i},'count',this.value)\"><button type=\"button\" aria-label=\"${esc(srText456('Mehr','Increase','Więcej')+' '+l.requestedName)}\" onclick=\"srStep456(${i},1)\">+</button></div></div><details class=\"srEdit456\" data-receipt-detail456=\"${esc(l.id+'pack')}\"><summary>${srText456('Packung ändern','Change pack','Zmień opakowanie')}</summary><label>${srText456('Gekaufte Packung','Purchased pack','Kupione opakowanie')}<select aria-label=\"${esc(srText456('Packung','Pack','Opakowanie')+' '+l.requestedName)}\" onchange=\"setShoppingReceiptPack(${i},this.value)\">${shoppingReceiptPackOptions(l)}</select></label></details><details class=\"srEdit456\" data-receipt-detail456=\"${esc(l.id+'price')}\"><summary><span id=\"srPrice456-${i}\">${esc(moneyFmt(Number(l.price)||0))} ${srText456('netto je Gebinde','net per pack','netto za opakowanie')}</span><span>${srText456('Ändern','Edit','Zmień')}</span></summary><label>${srText456('EK netto je Gebinde (€)','Net cost per pack (€)','Cena netto za opakowanie (€)')}<input id=\"srCost456-${i}\" data-receipt-line456=\"${esc(l.id)}\" inputmode=\"decimal\" value=\"${esc(String(l.priceError456?l.priceRaw456:(l.price??'')).replace('.',','))}\" oninput=\"updateShoppingReceiptField(${i},'price',this.value)\"></label></details>${policy==='review'?`<p class=\"muted\">${srText456('Bitte zuerst die MHD-Regel im Artikel festlegen.','Set the product expiry rule first.','Najpierw ustaw regułę daty ważności produktu.')}</p>`:''}${policy!=='none'&&policy!=='review'?`<div class=\"srMhd456\" id=\"srMhd456-${i}\" ${policy==='optional'&&!date&&!l.mhdOpen456?'hidden':''}><label>${policy==='required'?'MHD':srText456('MHD optional','Expiry optional','Data opcjonalna')}<input data-adapted231=\"native456\" id=\"srDate456-${i}\" type=\"date\" aria-label=\"${esc('MHD '+l.requestedName)}\" value=\"${esc(l.mhd||'')}\" oninput=\"updateShoppingReceiptField(${i},'mhd',this.value)\"></label></div>${policy==='optional'&&!date&&!l.mhdOpen456?`<button id=\"srMhdButton456-${i}\" class=\"srLink456\" type=\"button\" onclick=\"srOpenMhd456(${i})\">${srText456('MHD angeben','Add expiry date','Dodaj datę')}</button>`:''}${`<button id=\"srSplit456-${i}\" ${Number(l.count)>1?'':'hidden'} class=\"srLink456\" type=\"button\" onclick=\"srSplit456(${i})\">${srText456('Unterschiedliche MHDs','Different expiry dates','Różne daty ważności')}</button>`}`:''}<p class=\"shoppingReceiptSummary\" id=\"shoppingReceiptSummary${i}\">${esc(shoppingReceiptLineSummary(l))}</p>`:`<p class=\"muted\">${srText456('Bleibt auf der Einkaufsliste.','Stays on the shopping list.','Pozostaje na liście zakupów.')}</p>`}</div></section>`}).join('')}</div><div class=\"shoppingReceiptActions\"><strong id=\"srTotal456\" aria-live=\"polite\">${srTotal456()}</strong><button type=\"button\" class=\"primary\" ${shoppingReceiptDraft.lines.some(l=>l.got)?'':'disabled'} onclick=\"confirmShoppingReceipt()\">${srText456('Einkauf einlagern','Book purchase into stock','Przyjmij zakupy')}</button><button type=\"button\" class=\"soft\" onclick=\"closeShoppingReceipt()\">${srText456('Noch nicht buchen','Book later','Zaksięguj później')}</button></div>`;for(const d of box.querySelectorAll('details'))if(open.has(d.dataset.receiptDetail456))d.open=true;}\n// Validate each selected line, including optional dates, before entering the legacy transaction.\nconst srConfirmBefore456=confirmShoppingReceipt;\nconfirmShoppingReceipt=function(...args){const d=shoppingReceiptDraft;if(!d||d.busy456)return false;srCapturePrices456();for(const l of d.lines.filter(l=>l.got)){const o=shoppingReceiptOption(l),a=state.products[o?.code],policy=srPolicy456(a);if(!o||!a||!(Number(o.stockQty)>0)||!(Number(o.size)>0)||!Number.isInteger(Number(l.count))||Number(l.count)<1||l.priceError456||l.price===''||l.price==null||!Number.isFinite(Number(l.price))||Number(l.price)<0||policy==='review'||policy==='required'&&!l.mhd||l.mhd&&!srDateValid456(l.mhd)){showFeedback(srText456('Eingaben prüfen','Check entries','Sprawdź dane'),'warn',4500,l.requestedName+': '+srText456('Menge, Packung, Preis oder MHD prüfen.','Check quantity, pack, price or expiry.','Sprawdź ilość, opakowanie, cenę lub datę.'));const i=d.lines.indexOf(l);srToggleProduct457(i,true);const field=document.getElementById('srCost456-'+i);if(l.priceError456&&field){const details=field.closest('details');if(details)details.open=true;(field.formAdapter231?.display||field).focus();}return false;}}d.busy456=true;let result;try{result=srConfirmBefore456(...args)}catch(e){d.busy456=false;throw e}if(result?.then)return result.finally(()=>{d.busy456=false});d.busy456=false;return result;};\n// Quantity ledger preserves explicitly requested remainders and restores them on receipt reversal.\nfunction shoppingRequestRemaining456(item){const key='requested363:'+item.id;let received=0;for(const r of state.shoppingReceipts||[]){if(r.reversedAt||!(r.sourceKeys||[]).includes(key))continue;const rows=(r.items||[]).filter(x=>x.sourceKey456===key);if(rows.length)received+=rows.reduce((n,x)=>n+(Number(x.receivedRequested456)||0),0);else if((state.movements||[]).some(m=>m.type==='in'&&m.reason==='Einkaufsliste '+r.id&&(m.shoppingRequestKeys363||[]).includes(key)))return 0;}return Math.max(0,Number(item.qty)-received)}\n"}]