24 lines
7.5 KiB
HTML
24 lines
7.5 KiB
HTML
{% extends "shared/frontend/base.html" %}
|
||
{% set wp = workflow_permissions|default({}) %}
|
||
{% block title %}Afregningsbeslutning – BMC Hub{% endblock %}
|
||
{% block extra_css %}{% include "workflows/frontend/_style.html" %}{% endblock %}
|
||
{% block content %}
|
||
<div class="container-fluid workflow-page"><header class="workflow-heading"><div><div class="workflow-eyebrow">Økonomi · tidsregistrering #{{ time_id }}</div><h1>Afregningsbeslutning</h1><p>Se hvorfor tiden har fået sin afregning, og følg tidligere beslutninger.</p></div></header>{% include "workflows/frontend/_nav.html" %}<div id="decision-message" role="status"></div><div id="decision-summary" class="alert alert-info" aria-live="polite">Henter afregningsbeslutningen…</div>
|
||
<h5>Hvorfor?</h5><div id="decision-explanation" class="mb-3"></div><details><summary>Tekniske detaljer</summary><pre id="decision-details" class="bg-light p-3" style="max-height:400px;overflow:auto"></pre></details>
|
||
<div class="card p-3" {% if not wp.get("override") %}hidden{% endif %}><h5>Manuel overstyring</h5><p>Overstyring ændrer kun beslutningen. Bogholderiets godkendelse udfører afregningen.</p>
|
||
<div class="row g-2"><label class="col-md-4">Metode<select id="decision-method" class="form-select"><option value="invoice">Faktura</option><option value="subscription">Fastpris</option><option value="prepaid">Klippekort</option><option value="internal">Intern tid</option><option value="non_billable">Ikke fakturerbar</option></select></label><label class="col-md-3">Aftale-ID<input id="decision-agreement" type="number" min="1" class="form-control"></label><label class="col-md-3">Klippekort-ID<input id="decision-card" type="number" min="1" class="form-control"></label></div>
|
||
<label class="d-block mt-2">Varenummer<input id="decision-product" class="form-control"></label><label class="d-block mt-2">Begrundelse<textarea id="decision-reason" class="form-control" maxlength="2000"></textarea></label>
|
||
<div class="workflow-actions my-2"><button id="decision-override" class="btn btn-primary">Gem overstyring</button><button id="decision-recalculate" class="btn btn-outline-secondary">Genberegn (bevar overstyring)</button><button id="decision-reset" {% if not wp.get("activate") %}hidden disabled{% endif %} class="btn btn-outline-danger">Ophæv overstyring og genberegn</button></div></div>
|
||
<h5 class="mt-3">Historik</h5><div id="decision-history"><div class="workflow-empty">Henter historik…</div></div></div>
|
||
{% endblock %}
|
||
{% block extra_js %}<script>
|
||
(()=>{'use strict';const stateNames={decided:'Valgt',manual_review:'Manuel vurdering',overridden:'Overstyret',settled:'Afregnet',shadow:'Skyggetilstand',active:'Aktiv'};const methodNames={invoice:'Faktura',subscription:'Fastpris',prepaid:'Klippekort',internal:'Intern tid',non_billable:'Ikke fakturerbar'},reasons={matched_rule:'En regel valgte afregningen.',manual_override:'Afregningen er valgt manuelt.',no_matching_rule:'Ingen regel valgte en afregningsmetode.',conflicting_rules:'Flere regler valgte forskellige metoder. Bogholderiet skal gennemgå tiden.',pending_tag_approval:'Afventer godkendelse af tags, der kan påvirke afregningen.',insufficient_card_balance:'Klippekortet har ikke timer nok.',ambiguous_or_missing_cards:'Klippekort mangler eller kan ikke vælges entydigt.',ambiguous_or_missing_agreements:'Fastprisaftale mangler eller kan ikke vælges entydigt.'};function explain(result){$('explanation').replaceChildren();const p=document.createElement('p');p.textContent=reasons[result.reason]||'Beslutningen skal gennemgås.';$('explanation').append(p);if(result.override_reason){const p=document.createElement('p');p.textContent='Begrundelse: '+result.override_reason;$('explanation').append(p);}for(const trace of result.trace||[]){const p=document.createElement('div');p.className='small';p.textContent=`Workflow #${trace.workflow_id}, gren ${trace.branch+1}: ${trace.matched?'betingelser opfyldt':'betingelser ikke opfyldt'}`;$('explanation').append(p);const walk=(node,depth=0)=>{if(!node)return;const line=document.createElement('div');line.className='small';line.style.paddingLeft=(depth*16)+'px';const fields={customer_id:'Kunde',description:'Beskrivelse',hours:'Timer',tags:'Tags',tag_ids:'Tags',status:'Status'};line.textContent=(node.matched?'✓ ':'× ')+(node.children?(node.kind==='all'?'Alle betingelser':'Mindst én betingelse'):node.kind==='else'?'Ellers':(fields[node.field]||node.field));$('explanation').append(line);for(const child of node.children||[])walk(child,depth+1);};walk(trace.condition);}}const id={{ time_id|tojson }},$=name=>document.getElementById('decision-'+name),base='/api/v1/timetracking/time/'+id;
|
||
async function api(path,body){const r=await fetch(base+path,{method:body?'POST':'GET',credentials:'same-origin',headers:{'Content-Type':'application/json'},body:body?JSON.stringify(body):undefined});const data=await r.json();if(!r.ok)throw Error(typeof data.detail==='string'?data.detail:JSON.stringify(data.detail));return data;}
|
||
function message(text,error=false){$('message').className='alert '+(error?'alert-danger':'alert-info');$('message').textContent=text;}
|
||
async function load(){const data=await api('/workflow-decision');const d=data.current,shadow=!d&&data.executions?.[0];$('summary').textContent=d?`Metode: ${methodNames[d.method]||'Manuel vurdering'} · status: ${stateNames[d.status]||d.status}${d.manual_override?' · manuelt overstyret':''}${data.stale?' · forældet beslutning':''}`:shadow?'Skyggetilstand · foreslået metode: '+(methodNames[shadow.result.method]||'Manuel vurdering')+' · ingen afregning ændret':'Ingen beslutning endnu';explain(d?.details||shadow?.result||{});$('details').textContent=JSON.stringify(d?.details||shadow?.result||{},null,2);$('history').replaceChildren();if(!data.history.length&&!data.executions?.length)$('history').append(Object.assign(document.createElement('div'),{className:'workflow-empty',textContent:'Der er ingen tidligere beslutninger.'}));[...data.history,...(data.executions||[]).map(x=>({created_at:x.created_at,method:x.result.method,status:x.mode,details:x.result,is_current:false}))].forEach(row=>{const detail=document.createElement('details'),summary=document.createElement('summary'),pre=document.createElement('pre');summary.textContent=`${row.created_at} · ${methodNames[row.method]||'Manuel vurdering'} · ${stateNames[row.status]||row.status}${row.is_current?' · aktuel':''}`;pre.textContent=JSON.stringify(row.details,null,2);detail.append(summary,pre);$('history').append(detail);});}
|
||
let requestBusy=false;async function run(fn){if(requestBusy)return;requestBusy=true;document.querySelector('.workflow-page').setAttribute('aria-busy','true');try{await fn();await load();}catch(e){message(e.message,true);}finally{requestBusy=false;document.querySelector('.workflow-page').setAttribute('aria-busy','false');}}
|
||
$('override').onclick=()=>run(async()=>{const body={method:$('method').value,reason:$('reason').value};[['agreement','agreement_id'],['card','card_id'],['product','product_number']].forEach(([field,key])=>{if($(field).value)body[key]=key==='product_number'?$(field).value:Number($(field).value);});await api('/workflow-override',body);message('Overstyring gemt med begrundelse.');});
|
||
$('recalculate').onclick=()=>run(async()=>{const data=await api('/workflow-recalculate',{});message(data.preserved_override?'Den manuelle overstyring er bevaret.':'Beslutningen er genberegnet.');});
|
||
$('reset').onclick=()=>run(async()=>{await api('/workflow-recalculate',{reset_override:true,reason:$('reason').value});message('Overstyringen er ophævet; ny beslutning er gemt.');});run(load);
|
||
})();</script>{% endblock %}
|