0 tidsregistreringer valgtVælg forhåndsvisning før afregning.
@@ -51,9 +56,9 @@
function syncWorkflowControls(){const mode=selectionMode(selectedRows()),active=mode!=='legacy';$('workflowLineModeHost').classList.toggle('d-none',mode!=='workflow');for(const id of ['settlementMethod','prepaidCard','agreementId','setMethodBtn']){$(id).disabled=active;if(active&&id!=='setMethodBtn')$(id).value='';}$('previewBtn').disabled=mode==='mixed';$('stickyPreviewBtn').disabled=mode==='mixed';$('selectionText').textContent=mode==='mixed'?'Blandet udvalg: vælg workflowtider og øvrige tider hver for sig.':`${ids().length} tidsregistreringer valgt`;return active;}
function payload(){if(allWorkflowSelected())return {ids:ids(),line_mode:$('workflowLineMode').value};const method=$('settlementMethod').value,card=Number($('prepaidCard').value),agreement=Number($('agreementId').value);return {ids:ids(),...(method?{billing_method:method}:{}),...(card?{prepaid_card_id:card}:{}),...(agreement?{fixed_price_agreement_id:agreement}:{})}}
function fingerprint(){return JSON.stringify({generation:state.previewGeneration||0,request:payload(),method:$('settlementMethod').value,card:$('prepaidCard').value,agreement:$('agreementId').value,line:$('workflowLineMode').value,triage:state.triage,filters:['filterMethod','filterCustomer','filterStatus','filterCaseState','filterBillable','filterQuery'].map(id=>$(id).value)});}
- function status(v){return `${esc(labels[v]||v||'Ukendt')}`} function method(v){const m=norm(v),cls=m==='invoice'?'text-bg-primary':m==='prepaid'?'text-bg-purple':m==='subscription'?'text-bg-success':'text-bg-secondary';return `${esc(labels[m]||m)}`}
+ function status(v){return `${esc(labels[v]||v||'Ukendt')}`} function method(v){const m=norm(v),cls='queue-method-badge';return `${esc(labels[m]||m)}`}
function isClosedCase(value){return ['lukket','løst','closed','resolved','afsluttet','done'].includes(String(value||'').trim().toLocaleLowerCase('da-DK'))} function caseStatus(v){if(!v)return 'Uden sag';return `Sag: ${esc(v)}`}
- function renderStats(){const selected=state.items.filter(x=>state.selected.has(Number(x.id)));$('statEntries').textContent=state.items.length;$('statHours').textContent=`${h(state.items.reduce((a,x)=>a+Number(x.approved_hours||x.original_hours||0),0))} t`;$('statInvoice').textContent=state.items.filter(x=>effectiveMethod(x)==='invoice'&&x.billable!==false).length;$('statClosedCases').textContent=state.items.filter(x=>isClosedCase(x.case_status)).length;$('statSelected').textContent=selected.length;$('selectedBadge').textContent=`${selected.length} valgt`;$('selectionText').textContent=`${selected.length} tidsregistrering${selected.length===1?'':'er'} valgt`;$('selectionActions').classList.toggle('d-none',!selected.length)}
+ function renderStats(){const selected=state.items.filter(x=>state.selected.has(Number(x.id)));$('statEntries').textContent=state.items.length;$('statHours').textContent=`${h(state.items.reduce((a,x)=>a+Number(x.approved_hours||x.original_hours||0),0))} t`;$('statInvoice').textContent=state.items.filter(x=>effectiveMethod(x)==='invoice'&&x.billable!==false).length;$('statClosedCases').textContent=state.items.filter(x=>isClosedCase(x.case_status)).length;$('statSelected').textContent=selected.length;$('selectedBadge').textContent=`${selected.length} valgt`;$('selectionText').textContent=`${selected.length} tidsregistrering${selected.length===1?'':'er'} valgt`;$('selectionActions').classList.toggle('d-none',!selected.length);$('billingSelectionPanel').classList.toggle('d-none',!selected.length)}
function selectedRows(){return state.items.filter(x=>state.selected.has(Number(x.id)))}
function selectedCardCustomerIds(){return [...new Set(selectedRows().map(x=>Number(x.billing_customer_id||x.hub_customer_id||0)).filter(Boolean))]}
function selectedHours(){return selectedRows().reduce((total,x)=>total+Number(x.approved_hours||x.original_hours||0),0)}
@@ -72,7 +77,7 @@
$('previewPanel').classList.remove('d-none');
const errors=d.errors||[],invoice=d.invoice_groups||[],prepaid=d.prepaid_groups||[],sub=d.subscription_groups||[],items=d.items||[];
const itemRows=items.map(x=>`
${caseStatus(x.case_status)} Sagstatus er kun information; løst/lukket sag må gerne afregnes.
${h(x.hours)} t
Faktisk ${h(x.actual_hours)} t · ${esc(labels[x.method]||x.method_label||x.method)}
${x.message?`
${esc(x.message)}
`:''}
`).join('');
- $('previewBody').innerHTML=`
Hvad gør forhåndsvisning? Den kontrollerer valgte registreringer, kunde, klippekort/aftale, timer og beløb. Den gemmer ikke noget og trækker ikke timer. Det sker først efter “Gennemfør afregning”.
${errors.length?`
Afregningen kan ikke gennemføres endnu.
${errors.map(x=>`
Tid #${Number(x.id)}: ${esc(x.message)}
`).join('')}
`:'
Alle valgte tider kan afregnes. Løst/lukket sagsstatus blokerer ikke.
${Number(x.amount_ex_vat).toLocaleString('da-DK',{minimumFractionDigits:2})} kr. ekskl. moms
Gebyr og evt. fragt vises i ordrekladde.
`).join('')}${prepaid.map(x=>`
Klippekort · ${esc(x.card_number||'#'+x.card_id)}
Forbrug: ${h(x.hours)} t · Tilbage før træk: ${h(x.remaining_hours)} t · efter: ${h(Number(x.remaining_hours)-Number(x.hours))} t
Afrunding: ${Number(x.rounding_minutes)||30} min. pr. registrering
`).join('')}${sub.map(x=>`
Abonnement / fast pris · aftale #${Number(x.agreement_id)}
${x.entries.length} tider · ${h(x.hours)} timer knyttes til aftalen uden særskilt fakturalinje.
`).join('')}
Valgte tidsregistreringer (${items.length})
${itemRows||'
Ingen registreringer.
'}
`;
+ $('previewBody').innerHTML=`
Hvad gør forhåndsvisning? Den kontrollerer valgte registreringer, kunde, klippekort/aftale, timer og beløb. Den gemmer ikke noget og trækker ikke timer. Det sker først efter “Gennemfør afregning”.
${errors.length?`
Afregningen kan ikke gennemføres endnu.
${errors.map(x=>`
Tid #${Number(x.id)}: ${esc(x.message)}
`).join('')}
`:'
Alle valgte tider kan afregnes. Løst/lukket sagsstatus blokerer ikke.