{% extends "base.html" %} {% from "_macros.html" import money, pdf_lang_picker %} {% block title %}{{ order.order_no }}{% endblock %} {% block body_data %}data-page-type="sales_order" data-page-id="{{ order.id }}" data-page-label="{{ order.order_no }}" data-page-sub="{{ order.customer_name_snapshot }}"{% endblock %} {% block breadcrumb %}

Aufträge / {{ order.order_no }}

{% endblock %} {% block heading %}{{ order.order_no }} · {{ order.customer_name_snapshot }}{% endblock %} {% block topbar_actions %} {# ---- Status + Priority badges (klein, links) ---- #}
{% set color = STATUS_COLOR.get(order.status, 'neutral') %} {{ STATUS_LABELS.get(order.status, order.status) }} {% if order.priority and order.priority != 'normal' %} {{ PRIORITY_LABELS.get(order.priority, order.priority) }} {% endif %}
{# ---- Aktion-Buttons (alle identisch dimensioniert) ---- #} Auftrag bearbeiten
Zeichnungen {{ pdf_lang_picker('sales-orders', order.id, order.lang or 'de') }} AB-PDF AB senden
{# Mängel-Beseitigung: Storno-Workflow #} {% if order.status == 'cancelled' %} 📄 Storno-PDF {% else %} {% endif %} {% endblock %} {% block content %} {% include "_workflow_stepper.html" %} {# v1.19.1: Nachkalkulations-Banner — prominent oben wenn signifikante Abweichung. #} {% if nachkalk and nachkalk.has_plan and nachkalk.variance.hours.pct is not none %} {% set pct = nachkalk.variance.hours.pct %} {% set abs_pct = pct if pct >= 0 else -pct %} {% if abs_pct >= 10 %} {% if pct > 0 %} {# Über Plan = schlecht (Job lief teurer als geschätzt) #}
⚠️
Job lag {{ "%.0f"|format(pct) }} % über Schätzung
Plan: {{ "%.1f"|format(nachkalk.planned.hours) }} h · Ist: {{ "%.1f"|format(nachkalk.actual.hours) }} h · Mehrkosten: {{ money(nachkalk.variance.hour_cost.diff) }} {% if pct >= 30 %} — bei nächstem Angebot diesen Kundentyp 20–30 % Puffer einrechnen{% endif %}
↓ Detail anzeigen
{% else %} {# Unter Plan = gut (Job lief günstiger als geschätzt) #}
Job lag {{ "%.0f"|format(abs_pct) }} % unter Schätzung
Plan: {{ "%.1f"|format(nachkalk.planned.hours) }} h · Ist: {{ "%.1f"|format(nachkalk.actual.hours) }} h · Marge: +{{ money(-nachkalk.variance.hour_cost.diff) }}
↓ Detail anzeigen
{% endif %} {% endif %} {% endif %}
{# ---------- LEFT: Header + Lines (2/3) ---------- #}

Stammdaten

Kunde

{{ order.customer_name_snapshot }}

{{ order.customer_country_iso_snapshot or '—' }} {% if order.customer_vat_id_snapshot %}· {{ order.customer_vat_id_snapshot }}{% endif %} {% if not order.is_business_snapshot %}· B2C{% endif %}

{% if order.customer_contact %}

{{ order.customer_contact }} · {{ order.customer_email or '—' }}

{% endif %}

Belegdaten

Auftragsdatum: {{ order.order_date }}

Liefertermin: {{ order.due_date or '—' }}

{% if order.customer_reference %}

Ihre Bestell-Nr.: {{ order.customer_reference }}

{% endif %}

Zahlungsziel: {{ order.payment_terms_days }} Tage

Format: {{ order.preferred_invoice_format }}

{% if order.shipping_address and order.shipping_address != order.billing_address %}

Abweichende Lieferadresse

{{ order.shipping_address }}

{% endif %}

Tags

Interne Notiz

{{ order.notes or '' }}
{# ---------- Zeichnungen / Anhänge ---------- #}

Zeichnungen & Anhänge

PDF, JPG, PNG, SVG · max. 8 MB

{% if drawings %} {% else %}

Noch keine Zeichnungen hochgeladen.

{% endif %}

Positionen

{{ lines|length }} {% if lines|length == 1 %}Position{% else %}Positionen{% endif %} · Rechtsklick auf Position öffnet Zeichnung

{% for ln in lines %} {% set editable = (ln.qty_invoiced or 0) == 0 and order.status not in ('cancelled', 'paid', 'invoiced') %} {% if editable %} {% endif %} {% endfor %} {% if order.vat_amount %} {% endif %}
# Beschreibung Menge Einh. Einzelpr. Rabatt USt Netto
{{ ln.line_no }}
{{ ln.description }}
{% if ln.item_ident %}
{{ ln.item_ident }}
{% endif %} {% if ln.qty_delivered %}
geliefert: {{ "%.0f"|format(ln.qty_delivered) if ln.qty_delivered == ln.qty_delivered|int else "%.2f"|format(ln.qty_delivered) }}
{% endif %} {% if ln.qty_invoiced %}
abgerechnet: {{ "%.0f"|format(ln.qty_invoiced) if ln.qty_invoiced == ln.qty_invoiced|int else "%.2f"|format(ln.qty_invoiced) }}
{% endif %}
{{ "%.0f"|format(ln.qty_ordered) if ln.qty_ordered == ln.qty_ordered|int else "%.2f"|format(ln.qty_ordered) }} {{ ln.unit }} {{ money(ln.unit_price) }} {{ "%.0f"|format(ln.discount_pct) if ln.discount_pct else '—' }}{% if ln.discount_pct %} %{% endif %} {% if ln.vat_kind == 'eu_b2b_reverse' %}RC {% elif ln.vat_kind == 'export' %}Export {% else %}{{ "%.0f"|format(ln.vat_rate) }} %{% endif %} {{ money(ln.line_total_net) }} {% if editable %} {% endif %}
Zwischensumme netto {{ money(order.subtotal_net) }}
USt {{ money(order.vat_amount) }}
Gesamtbetrag {{ money(order.total_gross) }} {{ order.currency }}
{% set first = lines[0] if lines else None %} {% if first and first.vat_kind == 'eu_b2b_reverse' %}
Hinweis Reverse Charge: Steuerschuldnerschaft des Leistungsempfängers nach anwendbarem Recht.
{% elif first and first.vat_kind == 'export' %}
Hinweis Ausfuhr: Steuerfreie Ausfuhrlieferung (§ 4 Nr. 1a, § 6 UStG).
{% endif %}
{% if nachkalk %}

Nachkalkulation · Plan vs. Ist

Stundensatz {{ "%.0f"|format(nachkalk.rate) }} €/h {% if nachkalk.inquiry_no or nachkalk.quote_no %} · Plan aus {% if nachkalk.inquiry_no %}{{ nachkalk.inquiry_no }}{% endif %} {% if nachkalk.quote_no %} / {{ nachkalk.quote_no }}{% endif %} {% else %} · Kein Plan hinterlegt — nur Ist-Werte {% endif %}

Größe Plan Ist Differenz Abw. %
Stunden {% if nachkalk.has_plan %}{{ "%.1f"|format(nachkalk.planned.hours) }} h{% else %}—{% endif %} {{ "%.1f"|format(nachkalk.actual.hours) }} h {% if nachkalk.has_plan %}{% if nachkalk.variance.hours.diff > 0 %}+{% endif %}{{ "%.1f"|format(nachkalk.variance.hours.diff) }} h{% else %}—{% endif %} {% if nachkalk.variance.hours.pct is not none %}{% if nachkalk.variance.hours.pct > 0 %}+{% endif %}{{ "%.1f"|format(nachkalk.variance.hours.pct) }} %{% else %}—{% endif %}
Stunden-Kosten {% if nachkalk.has_plan %}{{ money(nachkalk.planned.hour_cost) }}{% else %}—{% endif %} {{ money(nachkalk.actual.hour_cost) }} {% if nachkalk.has_plan %}{% if nachkalk.variance.hour_cost.diff > 0 %}+{% endif %}{{ money(nachkalk.variance.hour_cost.diff) }}{% else %}—{% endif %} {% if nachkalk.variance.hour_cost.pct is not none %}{% if nachkalk.variance.hour_cost.pct > 0 %}+{% endif %}{{ "%.1f"|format(nachkalk.variance.hour_cost.pct) }} %{% else %}—{% endif %}
Material {% if nachkalk.has_plan %}{{ money(nachkalk.planned.material) }}{% else %}—{% endif %} {{ money(nachkalk.actual.material) }} {% if nachkalk.has_plan %}{% if nachkalk.variance.material.diff > 0 %}+{% endif %}{{ money(nachkalk.variance.material.diff) }}{% else %}—{% endif %} {% if nachkalk.variance.material.pct is not none %}{% if nachkalk.variance.material.pct > 0 %}+{% endif %}{{ "%.1f"|format(nachkalk.variance.material.pct) }} %{% else %}—{% endif %}
Selbstkosten gesamt {% if nachkalk.has_plan %}{{ money(nachkalk.planned.total) }}{% else %}—{% endif %} {{ money(nachkalk.actual.total) }} {% if nachkalk.has_plan %}{% if nachkalk.variance.total.diff > 0 %}+{% endif %}{{ money(nachkalk.variance.total.diff) }}{% else %}—{% endif %} {% if nachkalk.variance.total.pct is not none %}{% if nachkalk.variance.total.pct > 0 %}+{% endif %}{{ "%.1f"|format(nachkalk.variance.total.pct) }} %{% else %}—{% endif %}
Verkaufspreis (netto) {% if nachkalk.has_plan %}{{ money(nachkalk.planned.price) }}{% else %}—{% endif %} {{ money(nachkalk.sold) }}
Marge {% if nachkalk.has_plan %}{{ money(nachkalk.planned.margin) }}{% else %}—{% endif %} {{ money(nachkalk.actual.margin) }} {% if nachkalk.has_plan %}{% if nachkalk.variance.margin.diff > 0 %}+{% endif %}{{ money(nachkalk.variance.margin.diff) }}{% else %}—{% endif %} {% if nachkalk.variance.margin.pct is not none %}{% if nachkalk.variance.margin.pct > 0 %}+{% endif %}{{ "%.1f"|format(nachkalk.variance.margin.pct) }} %{% else %}—{% endif %}
{% if not nachkalk.has_plan %}
Hinweis: Für eine vollständige Plan-vs-Ist-Auswertung lege im Anfrage-Detail (vor dem Angebot) eine Prozess-Planung mit Stunden + Material an. Die Werte werden dann hier automatisch verglichen.
{% elif nachkalk.actual.hours == 0 %}
Noch keine Ist-Stunden auf diesem Auftrag verbucht — sobald Mitarbeiter Zeit auf den Fertigungsauftrag erfassen, erscheinen die Werte hier automatisch.
{% endif %}
{% endif %} {# v1.7.0: Arbeitsplan — Detail-Planung vor Werkstatt-Freigabe #} {% if workplan_jobs %}

📋 Arbeitsplan ({{ workplan_jobs|length }} Position{% if workplan_jobs|length != 1 %}en{% endif %})

Maschine + Rüstplan zuweisen, dann freigeben → Jobs werden im Aktions-Dashboard und Shopfloor sichtbar

{% for j in workplan_jobs %} {% endfor %}
Position Stk Maschine Rüstplan Smart-Vorschlag
{{ j.item_ident or '—' }} {% if j.item_description %}
{{ j.item_description[:40] }}
{% endif %}
Job #{{ j.id }} · {{ j.ident }}
{{ j.qty_target }} {% if j.suggested_machine_name %}
🤖 {{ j.suggested_machine_name }} ({{ j.suggested_count }}× in History)
{% endif %} {% if j.suggested_setup_plan_name %}
📋 Plan: {{ j.suggested_setup_plan_name }}
{% endif %} {% if j.suggested_machine_id or j.suggested_setup_plan_id %} {% endif %}
{# Tier-A.4: Verfügbarkeits-Check vor Release #}
{% endif %} {% if fa_jobs %}

Fertigungsaufträge

Verknüpfte Produktionsjobs

{% for j in fa_jobs %} {% endfor %}
Auftrags-NrMaschineOperatorFortschrittStatus
{{ j.ident }} {{ j.machine_name or '—' }} {{ (j.first_name ~ ' ' ~ j.last_name) if j.first_name else '—' }} {{ j.qty_done }} / {{ j.qty_target }} {{ j.status }}
{% endif %}
{# ---------- RIGHT: Status + Aktionen (1/3) ---------- #}
{# ---------- Priorität & Material-Status ---------- #}

Priorität & Material

Priorität

Material-Status

{% if order.material_status %}

{{ MATERIAL_STATUS_LABELS.get(order.material_status, order.material_status) }}

{% endif %} {% if order.material_note %}

{{ order.material_note }}

{% endif %}
{# ---------- Prozess-Schritt (aktueller Stand) ---------- #}

Prozess-Status

{% if progress.total %}

{{ progress.completed }} / {{ progress.total }} Schritte erledigt

{% else %}

Noch kein Routing zugewiesen

{% endif %}
{% if progress.current_step %}

Aktuelle Stufe

{{ progress.current_step.step_name }} {{ 'läuft' if progress.current_step.status == 'running' else 'wartet' }}

{% endif %} {% if progress.total %} {% set pct = (progress.completed * 100 / progress.total) if progress.total else 0 %}

{{ pct|round(0,'common')|int }} %

{% endif %} {# Graphischer Stage-Flow — alle Stages immer sichtbar #}

Pipeline

{% for s in step_view %} {% if not loop.first %}{% endif %}
{{ s.name }}
{% endfor %}
{% if not progress.total and fa_jobs %}

Routing einem Fertigungsauftrag zuweisen:

{% for j in fa_jobs %}
{% endfor %}
{% endif %} Zur Werkstatt-Pipeline →

Status

Aktueller Status

{% set color = STATUS_COLOR.get(order.status, 'neutral') %}

{{ STATUS_LABELS.get(order.status, order.status) }}

Erstellt: {{ order.created_at[:16] }}
{% if order.confirmed_at %}Bestätigt: {{ order.confirmed_at[:16] }}
{% endif %} {% if order.closed_at %}Abgeschlossen: {{ order.closed_at[:16] }}
{% endif %}
{# v1.11.0 Quick-Action: 1-Klick-Komplettabschluss #} {% if has_open_qty and order.status not in ('cancelled',) %}

Auftrag in 1 Klick abschließen

Erstellt Lieferschein + Rechnung mit allen offenen Mengen.

{{ csrf_field(request)|safe }}

Klassischer Workflow (LS einzeln → Rechnung einzeln) unten weiterhin verfügbar.

{% endif %}

Lieferscheine

{% if has_open_qty and order.status not in ('cancelled',) %} + Lieferschein erstellen {% else %}

Alle Positionen sind ausgeliefert.

{% endif %} {% if delivery_notes %}
{% for d in delivery_notes %} {{ d.dn_no }} {{ DN_STATUS_LABELS.get(d.status, d.status) }} {% endfor %}
{% endif %}

Rechnungen

{% if has_invoiceable_qty and order.status not in ('cancelled',) %} + Rechnung erstellen {% else %}

{% if not delivery_notes %}Erst Lieferschein erzeugen, dann Rechnung.{% else %}Alle gelieferten Mengen sind abgerechnet.{% endif %}

{% endif %} {# Anzahlungen — nur sichtbar solange Auftrag offen #} {% if order.status not in ('cancelled', 'paid') %}
+ Anzahlung erstellen
oder
{% endif %} {# Schlussrechnung — angeboten wenn schon Anzahlungen existieren UND noch Restmenge offen #} {% set has_partial = invoices|selectattr('kind', 'equalto', 'partial')|list %} {% if has_partial and has_invoiceable_qty %} Schlussrechnung erstellen {% endif %} {% if invoices %} {% endif %}
{% if audit_entries %}
Audit-Log ({{ audit_entries|length }} Einträge) ▾ Aufklappen
{% for a in audit_entries %} {% endfor %}
Zeit Aktion Benutzer Detail
{{ a.created_at }} {{ a.action }} {{ a.user_name or '—' }} {{ a.details or '—' }}
{% endif %} {# Pro-Stück-Seriennummern für Lines mit item.tracks_serials = 1 #} {% for ln in lines %} {% if ln.item_tracks_serials %}
{% endif %} {% endfor %} {# v1.12.2: Drag&Drop für Zeichnungen — alle Geräte im WLAN sehen sofort #} {% with attach_entity_type='sales_order', attach_entity_id=order.id, attach_title='Zeichnungen & Dokumente' %} {% include '_attachments_dropzone.html' %} {% endwith %}
{% endblock %}