bmc_hub/static/js/time-billing-review.js
Christian 4871dd2d9e Add comprehensive test coverage for various modules
- Introduced tests for mail templates including ranking, rendering, and attachment handling.
- Added tests for time billing selection and approval processes, ensuring proper validation and state management.
- Implemented tests for case types in JavaScript, focusing on loading and editing functionality.
- Created tests for contact mail AI integration, ensuring proper parsing and error handling.
- Developed tests for dark theme CSS contrast and loading order.
- Added tests for invoice fee service, covering various scenarios including exemptions and invalid inputs.
- Implemented vendor resource tests, ensuring proper file handling and query validation.
2026-10-08 08:58:02 +02:00

35 lines
3.4 KiB
JavaScript
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.

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)?`<span class="badge text-bg-warning">${labels[entry.billing_method || entry._billing_method]} · Chefkontrol</span>`:'';
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='<div class="modal-dialog"><form class="modal-content"><div class="modal-header"><h5 class="modal-title">Chefkontrol – vælg afregning</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Luk"></button></div><div class="modal-body"><p data-label></p><label class="form-label">Endelig afregning</label><select class="form-select" required><option value="">Vælg afregning…</option></select><div class="small mt-3" role="status"></div></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuller</button><button type="submit" class="btn btn-success">Godkend</button></div></form></div>';
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};
})();