bmc_hub/app/fixed_price/frontend/detail.html
Christian e4b9091a1b feat: Implement fixed-price agreements frontend views and related templates
- Added views for listing fixed-price agreements, displaying agreement details, and a reporting dashboard.
- Created HTML templates for listing, detailing, and reporting on fixed-price agreements.
- Introduced API endpoint to fetch active customers for agreement creation.
- Added migration scripts for creating necessary database tables and views for fixed-price agreements, billing periods, and reporting.
- Implemented triggers for auto-generating agreement numbers and updating timestamps.
- Enhanced ticket management with archived ticket views and filtering capabilities.
2026-02-08 01:45:00 +01:00

323 lines
16 KiB
HTML

{% extends "shared/frontend/base.html" %}
{% block title %}{{ agreement.agreement_number }} - Fastpris Aftale{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<!-- Header -->
<div class="row mb-4">
<div class="col">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/fixed-price-agreements">Fastpris Aftaler</a></li>
<li class="breadcrumb-item active">{{ agreement.agreement_number }}</li>
</ol>
</nav>
<h1 class="h3 mb-0">📋 {{ agreement.agreement_number }}</h1>
<p class="text-muted">{{ agreement.customer_name }}</p>
</div>
<div class="col-auto">
{% if agreement.status == 'active' %}
<span class="badge bg-success fs-6">Aktiv</span>
{% elif agreement.status == 'suspended' %}
<span class="badge bg-warning fs-6">Suspenderet</span>
{% elif agreement.status == 'expired' %}
<span class="badge bg-danger fs-6">Udløbet</span>
{% elif agreement.status == 'cancelled' %}
<span class="badge bg-secondary fs-6">Annulleret</span>
{% endif %}
</div>
</div>
<!-- Overview Cards -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<h6 class="text-muted mb-2">Månedlige Timer</h6>
<h3 class="mb-0">{{ '%.0f'|format(agreement.monthly_hours or 0) }} t</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<h6 class="text-muted mb-2">Timepris</h6>
<h3 class="mb-0">{{ '%.0f'|format(agreement.hourly_rate or 0) }} kr</h3>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<h6 class="text-muted mb-2">Denne Måned</h6>
<h3 class="mb-0">{{ '%.1f'|format(agreement.current_used_hours or 0) }} / {{ '%.0f'|format(agreement.monthly_hours or 0) }} t</h3>
<small class="text-muted">{{ '%.1f'|format(agreement.current_remaining_hours or 0) }}t tilbage</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm">
<div class="card-body">
<h6 class="text-muted mb-2">Binding</h6>
<h3 class="mb-0">{{ agreement.binding_months }} mdr</h3>
{% if agreement.binding_end_date %}
<small class="text-muted">Til {{ agreement.binding_end_date }}</small>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Tabs -->
<ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#details-tab">Detaljer</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#periods-tab">
Perioder <span class="badge bg-secondary">{{ periods|length }}</span>
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#sager-tab">
Sager <span class="badge bg-secondary">{{ sager|length }}</span>
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#time-tab">
Tidsregistreringer <span class="badge bg-secondary">{{ time_entries|length }}</span>
</button>
</li>
</ul>
<!-- Tab Content -->
<div class="tab-content">
<!-- Details Tab -->
<div class="tab-pane fade show active" id="details-tab">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h5 class="mb-3">Aftale Information</h5>
<table class="table table-sm">
<tr>
<th width="40%">Kunde ID:</th>
<td>{{ agreement.customer_id }}</td>
</tr>
<tr>
<th>Kunde:</th>
<td>{{ agreement.customer_name }}</td>
</tr>
<tr>
<th>Start Dato:</th>
<td>{{ agreement.start_date }}</td>
</tr>
{% if agreement.end_date %}
<tr>
<th>Slut Dato:</th>
<td>{{ agreement.end_date }}</td>
</tr>
{% endif %}
<tr>
<th>Oprettet:</th>
<td>{{ agreement.created_at }}</td>
</tr>
</table>
</div>
<div class="col-md-6">
<h5 class="mb-3">Priser & Vilkår</h5>
<table class="table table-sm">
<tr>
<th width="40%">Månedlige Timer:</th>
<td>{{ '%.0f'|format(agreement.monthly_hours or 0) }} timer</td>
</tr>
<tr>
<th>Normal Timepris:</th>
<td>{{ '%.0f'|format(agreement.hourly_rate or 0) }} kr</td>
</tr>
<tr>
<th>Overtid Timepris:</th>
<td>{{ '%.0f'|format(agreement.overtime_rate or 0) }} kr {% if agreement.overtime_rate and agreement.hourly_rate %}({{ '%.0f'|format((agreement.overtime_rate / agreement.hourly_rate - 1) * 100) }}%){% endif %}</td>
</tr>
<tr>
<th>Afrunding:</th>
<td>{% if agreement.rounding_minutes == 0 %}Ingen{% else %}{{ agreement.rounding_minutes }} min{% endif %}</td>
</tr>
<tr>
<th>Bindingsperiode:</th>
<td>{{ agreement.binding_months }} måneder</td>
</tr>
<tr>
<th>Opsigelsesfrist:</th>
<td>{{ agreement.notice_period_days }} dage</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Periods Tab -->
<div class="tab-pane fade" id="periods-tab">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>Periode</th>
<th>Status</th>
<th>Brugte Timer</th>
<th>Resterende Timer</th>
<th>Overtid</th>
<th>Månedlig Værdi</th>
</tr>
</thead>
<tbody>
{% for period in periods %}
<tr>
<td>
<strong>{{ period.period_start }}</strong> til {{ period.period_end }}
</td>
<td>
{% if period.status == 'active' %}
<span class="badge bg-success">Aktiv</span>
{% elif period.status == 'pending_approval' %}
<span class="badge bg-warning">⚠️ Overtid</span>
{% elif period.status == 'ready_to_bill' %}
<span class="badge bg-info">Klar til faktura</span>
{% elif period.status == 'billed' %}
<span class="badge bg-secondary">Faktureret</span>
{% endif %}
</td>
<td>{{ '%.1f'|format(period.used_hours or 0) }}t</td>
<td>{{ '%.1f'|format(period.remaining_hours or 0) }}t</td>
<td>
{% if period.overtime_hours and period.overtime_hours > 0 %}
<span class="text-danger">+{{ '%.1f'|format(period.overtime_hours) }}t</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>{{ '%.0f'|format(period.base_amount or 0) }} kr</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Sager Tab -->
<div class="tab-pane fade" id="sager-tab">
<div class="card border-0 shadow-sm">
<div class="card-body">
{% if sager %}
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>Sag ID</th>
<th>Titel</th>
<th>Status</th>
<th>Oprettet</th>
<th>Handlinger</th>
</tr>
</thead>
<tbody>
{% for sag in sager %}
<tr>
<td><strong>#{{ sag.id }}</strong></td>
<td>{{ sag.titel }}</td>
<td>
{% if sag.status == 'open' %}
<span class="badge bg-success">Åben</span>
{% elif sag.status == 'in_progress' %}
<span class="badge bg-primary">I gang</span>
{% elif sag.status == 'closed' %}
<span class="badge bg-secondary">Lukket</span>
{% endif %}
</td>
<td>{{ sag.created_at.strftime('%Y-%m-%d') if sag.created_at else '-' }}</td>
<td>
<a href="/sag/{{ sag.id }}" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye"></i> Vis
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center text-muted py-5">
<i class="bi bi-inbox fs-1 mb-3"></i>
<p>Ingen sager endnu</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Time Entries Tab -->
<div class="tab-pane fade" id="time-tab">
<div class="card border-0 shadow-sm">
<div class="card-body">
{% if time_entries %}
<div class="table-responsive">
<table class="table table-sm table-hover">
<thead class="table-light">
<tr>
<th>Dato</th>
<th>Sag</th>
<th>Beskrivelse</th>
<th>Timer</th>
<th>Afrundet</th>
</tr>
</thead>
<tbody>
{% for entry in time_entries %}
<tr>
<td>{{ entry.created_at.strftime('%Y-%m-%d') if entry.created_at else '-' }}</td>
<td>
{% if entry.sag_id %}
<a href="/sag/{{ entry.sag_id }}">#{{ entry.sag_id }}</a>
{% if entry.sag_titel %}
<br><small class="text-muted">{{ entry.sag_titel[:30] }}</small>
{% endif %}
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
<small>{{ entry.note[:50] if entry.note else '-' }}</small>
</td>
<td>{{ '%.2f'|format(entry.approved_hours or entry.original_hours or 0) }}t</td>
<td>
{% if entry.rounded_to %}
<span class="badge bg-info">{{ entry.rounded_to }} min</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center text-muted py-5">
<i class="bi bi-clock fs-1 mb-3"></i>
<p>Ingen tidsregistreringer endnu</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}