bmc_hub/app/workflows/frontend/decision.html

24 lines
7.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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 %}