bmc_hub/app/workflows/frontend/resources.html
Christian 85e019783b Add comprehensive test coverage for case creation, assignment, and remote recovery
- Introduced tests for case creation commands, ensuring proper handling of UUIDs and payloads.
- Added tests for v5 assignment functionality, validating employee identification and group assignment.
- Implemented tests for remote recovery scenarios, including handling network failures and response validation.
- Created tests for hardware label generation and validation, ensuring proper URL formatting and QR code generation.
- Developed extensive tests for remote control functionality, covering connection isolation, command execution, and user permissions.
- Added tests for workflow timer registration, ensuring correct handling of timer states and ownership validation.
2026-10-15 12:17:09 +02:00

15 lines
5.8 KiB
HTML
Raw Permalink 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 %}Aftaledækning – 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">Administration · afregning</div><h1>Aftaler og klippekort</h1><p>Vælg hvilke tags aftalen eller klippekortet dækker. Gem dækning opretter og vedligeholder automatisk afregningsvalget. Du skal ikke oprette en separat workflowregel.</p></div></header>{% include "workflows/frontend/_nav.html" %}<div class="alert alert-info mt-3" id="resource-activation-help">Automatisk afregning bruges, når workflowmotoren og workflows for kunden er aktive. Aktivér kunden på kundekortet i CRM. Hvis flere aftaler eller klippekort matcher, skal afregningen vurderes manuelt. Tid godkendes og afregnes fortsat separat.</div><div id="resource-message" role="status" class="my-2"></div><label class="d-block my-3">Kunde<select id="resource-customer" class="form-select" style="max-width:420px"><option value="">Alle kunder</option></select></label><div id="resource-list"><div class="workflow-empty">Henter aftaler og klippekort…</div></div></div>{% endblock %}
{% block extra_js %}<script>
(()=>{'use strict';const $=id=>document.getElementById('resource-'+id);let tags=[],customers=[];
async function api(path,body){const r=await fetch('/api/v1/workflows'+path,{method:body?'PUT':'GET',credentials:'same-origin',headers:{'Content-Type':'application/json'},body:body?JSON.stringify(body):undefined});const d=await r.json();if(!r.ok)throw Error(typeof d.detail==='string'?d.detail:JSON.stringify(d.detail));return d;}
function el(tag,text,cls){const e=document.createElement(tag);if(text)e.textContent=text;if(cls)e.className=cls;return e;}
function choices(selected){const e=el('select','','form-select');e.multiple=true;e.size=6;for(const t of tags){const o=el('option',t.label||t.name);o.value=t.id;o.selected=(selected||[]).includes(t.id);e.append(o);}return e;}
async function load(initialData){const data=initialData||await api('/resources'+($('customer').value?'?customer_id='+encodeURIComponent($('customer').value):''));$('list').replaceChildren();if(!data.agreements.length&&!data.cards.length)$('list').append(el('div','Ingen aftaler eller klippekort for det valgte firma.','workflow-empty'));for(const [kind,rows] of [['agreements',data.agreements],['cards',[...data.cards].sort((a,b)=>(a.status==='active'?0:1)-(b.status==='active'?0:1)||Number(b.id)-Number(a.id))]])for(const row of rows.filter((r,i,all)=>kind!=='cards'||i===all.findIndex(x=>Number(x.customer_id)===Number(r.customer_id)))){const coverage=row.workflow_coverage||{},card=el('section','','card p-3 my-3'),name=customers.find(c=>Number(c.id)===Number(row.customer_id))?.name||'Ukendt kunde';card.append(el('h5',`${kind==='agreements'?'Fastpris '+(row.agreement_number||row.id):'Klippekort · fælles tagdækning'} · ${name}`),el('p',kind==='cards'?'Gælder kundens nuværende og fremtidige klippekort. Saldo og afregning følger det aktive kort.':'Status: '+row.status,'small text-muted'));const include=choices(coverage.tag_ids),exclude=choices(coverage.excluded_tag_ids),grid=el('div','','row g-2');for(const [title,control] of [['Dækker et af disse tags · tomt betyder alle',include],['Udelukker disse tags',exclude]]){const label=el('label',title,'col-md-6');label.append(control);grid.append(label);}card.append(grid);const label=el('label',' Medtag undertags','my-2'),descendants=el('input');descendants.type='checkbox';descendants.checked=!!coverage.include_descendants;label.prepend(descendants);card.append(label);let price=null;if(kind==='agreements'){const label=el('label','Månedspris (tom = eksisterende beregning)','d-block');price=el('input','','form-control');price.type='number';price.min='0';price.step='0.01';price.value=row.monthly_price??'';label.append(price);card.append(label);}const save=el('button','Gem dækning','btn btn-primary mt-2');save.onclick=()=>run(async()=>{const payload={workflow_coverage:{tag_ids:[...include.selectedOptions].map(o=>Number(o.value)),excluded_tag_ids:[...exclude.selectedOptions].map(o=>Number(o.value)),include_descendants:descendants.checked}};if(price)payload.monthly_price=price.value===''?null:Number(price.value);if(kind==='cards')await api(`/resources/customers/${row.customer_id}/card-coverage`,payload.workflow_coverage);else await api(`/resources/${kind}/${row.id}/coverage`,payload);$('message').className='alert alert-success';$('message').textContent='Dækningen er gemt og bruges automatisk ved afregningsvalg, når workflows er aktive for kunden. Ændringen er gemt i historikken.';await load();});card.append(save);$('list').append(card);}}
let requestBusy=false;async function run(fn){if(requestBusy)return;requestBusy=true;document.querySelector('.workflow-page').setAttribute('aria-busy','true');try{await fn();}catch(e){$('message').className='alert alert-danger';$('message').textContent=e.message;}finally{requestBusy=false;document.querySelector('.workflow-page').setAttribute('aria-busy','false');}}
$('customer').onchange=()=>run(load);run(async()=>{customers=await api('/customers');tags=await api('/tags');const map=new Map(tags.map(t=>[t.id,t]));function path(t,seen=new Set()){if(seen.has(t.id))return t.name;seen.add(t.id);const p=map.get(t.parent_id);return p?path(p,seen)+' › '+t.name:t.name;}tags=tags.map(t=>({...t,label:path(t)}));const resources=await api('/resources');const customerIds=new Set([...resources.agreements,...resources.cards].map(row=>Number(row.customer_id)));customers.filter(c=>customerIds.has(Number(c.id))).forEach(c=>{const o=el('option',c.name);o.value=c.id;$('customer').append(o);});await load(resources);});
})();</script>{% endblock %}