window.TimeBillingReview = (() => { const labels={draft:'Kladde',operations:'Drift',development:'Udvikling'}; const needsReview=entry=>Object.hasOwn(labels,entry.billing_method || entry._billing_method || ''); const badge=entry=>needsReview(entry)?`${labels[entry.billing_method || entry._billing_method]} · Chefkontrol`:''; const methods={invoice:'Faktura',prepaid:'Klippekort',subscription:'Fastpris',fixed_price:'Fastpris',internal:'Intern tid',non_billable:'Ikke fakturerbar',draft:'Kladde',operations:'Drift',development:'Udvikling'}; const settled=entry=>entry.status==='billed'||(entry.workflow_active===true&&entry.status==='approved')||!!entry.economy_settled_at||!!entry.economy_order_draft_id||!!entry.billed_via_thehub_id; const canChoose=entry=>!settled(entry)&&(entry.workflow_active===true?entry.workflow_can_override===true:needsReview(entry)); const displayBadge=entry=>{ const method=entry.selected_billing_method||entry.workflow_decision?.method||entry.billing_method||'draft'; const text=method==='manual_review'?'Manuel vurdering':methods[method]||'Afregning afventer'; return `${text}${entry.workflow_stale?' · skal genberegnes':''}`; }; async function choose(entry) { if(!canChoose(entry))throw new Error('Afregningen kan ikke ændres for denne tidsregistrering.'); if(entry.workflow_active!==true)return approve(entry); const optionsResponse=await fetch(`/api/v1/timetracking/time/${Number(entry.id)}/settlement-options`,{credentials:'include'}); if(!optionsResponse.ok)throw new Error('Afregningsmuligheder kunne ikke hentes'); const data=await optionsResponse.json(); const decisionResponse=await fetch(`/api/v1/timetracking/time/${Number(entry.id)}/workflow-decision`,{credentials:'include'}); if(!decisionResponse.ok)throw new Error('Afregningsbeslutningen kunne ikke hentes'); const history=await decisionResponse.json(),current=history.current; if(current?.status==='settled')throw new Error('Afregnet tid kræver en særskilt korrektion.'); const host=document.createElement('div');host.className='modal fade';host.tabIndex=-1; host.innerHTML='
'; const select=host.querySelector('select');select.add(new Option('Faktura','invoice'));select.add(new Option('Intern tid','internal'));select.add(new Option('Ikke fakturerbar','non_billable')); (data.prepaid_cards||[]).forEach(c=>select.add(new Option('Klippekort '+(c.card_number||c.id),'card_'+c.id))); (data.agreements||[]).forEach(a=>select.add(new Option('Fastpris '+(a.agreement_number||a.id),'fpa_'+a.id))); const details=current?.details||{},billing=details.billing||details.decision||{}; select.value=current?.method==='prepaid'?'card_'+billing.card_id:current?.method==='subscription'?'fpa_'+billing.agreement_id:current?.method||entry.selected_billing_method||'invoice'; if(!select.value)select.value='invoice'; document.body.append(host);const modal=new bootstrap.Modal(host); return new Promise(resolve=>{ let changed=false; host.addEventListener('hidden.bs.modal',()=>{modal.dispose();host.remove();resolve(changed);},{once:true}); host.querySelector('form').onsubmit=async event=>{ event.preventDefault();const reason=host.querySelector('textarea').value.trim(); if(reason.length<3){host.querySelector('[role=status]').textContent='Angiv en kort begrundelse.';return;} const value=select.value,payload={method:value,reason},button=host.querySelector('[type=submit]');button.disabled=true; if(value.startsWith('card_')){payload.method='prepaid';payload.card_id=Number(value.slice(5));} if(value.startsWith('fpa_')){payload.method='subscription';payload.agreement_id=Number(value.slice(4));} if(billing.product_number)payload.product_number=billing.product_number; try{const response=await fetch(`/api/v1/timetracking/time/${Number(entry.id)}/workflow-override`,{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});const result=await response.json().catch(()=>({}));if(!response.ok)throw new Error(typeof result.detail==='string'?result.detail:'Afregningsvalget kunne ikke gemmes');changed=true;modal.hide();}catch(error){host.querySelector('[role=status]').textContent=error.message;button.disabled=false;} };modal.show(); }); } async function approve(entry,hours) { const response=await fetch(`/api/v1/timetracking/time/${Number(entry.id)}/settlement-options`,{credentials:'include'}); if(!response.ok)throw new Error('Afregningsmuligheder kunne ikke hentes'); const data=await response.json(); const host=document.createElement('div');host.className='modal fade';host.tabIndex=-1; host.innerHTML=''; host.querySelector('[data-label]').textContent=labels[entry.billing_method || entry._billing_method]+' · '+(entry.description || entry.beskrivelse || 'Tidsregistrering'); const select=host.querySelector('select');select.add(new Option('Faktura','invoice')); (data.prepaid_cards||[]).forEach(c=>select.add(new Option(`Klippekort #${c.card_number || c.id}`,`card_${c.id}`))); (data.agreements||[]).forEach(a=>select.add(new Option(`Fastpris #${a.agreement_number || a.id}`,`fpa_${a.id}`))); document.body.append(host);const modal=new bootstrap.Modal(host); return new Promise(resolve=>{ let approved=false; host.addEventListener('hidden.bs.modal',()=>{modal.dispose();host.remove();resolve(approved);},{once:true}); host.querySelector('form').onsubmit=async event=>{ event.preventDefault();const button=host.querySelector('[type=submit]');button.disabled=true; const value=select.value,payload={billing_method:value}; if(value.startsWith('card_')){payload.billing_method='prepaid';payload.prepaid_card_id=Number(value.slice(5));} if(value.startsWith('fpa_')){payload.billing_method='subscription';payload.fixed_price_agreement_id=Number(value.slice(4));} if(Number.isFinite(Number(hours))&&Number(hours)>0)payload.fakturerbar_tid_min=Math.round(Number(hours)*60); try { const result=await fetch(`/api/v1/timetracking/time/${Number(entry.id)}/approve`,{method:'POST',credentials:'include',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); const body=await result.json().catch(()=>({}));if(!result.ok)throw new Error(body.detail || 'Kunne ikke godkende'); approved=true;modal.hide(); }catch(error){host.querySelector('[role=status]').textContent=error.message;button.disabled=false;} };modal.show(); }); } return {needsReview,badge,approve,choose,canChoose,displayBadge}; })();