wip: implement invoice generator for teamers
This commit is contained in:
1 parent
6c608063ad
commit
bbdd887dc2
25 files changed
+1584
-21
No files matched your search
@@ -0,0 +1,50 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
body { font-family: DejaVu Sans, sans-serif; font-size: 12px; color: #111827; }
|
||||
h1 { font-size: 20px; margin: 0 0 10px; }
|
||||
.muted { color: #6b7280; }
|
||||
.section { margin-top: 16px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 8px; }
|
||||
th, td { border: 1px solid #d1d5db; padding: 6px; text-align: left; }
|
||||
th { background: #f3f4f6; }
|
||||
.right { text-align: right; }
|
||||
.total-row td { font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Honorarnote</h1>
|
||||
<div class="muted">Einsatz: {{ assignment.destination }}</div>
|
||||
<div class="muted">Teamer: {{ teamer }}</div>
|
||||
<div class="muted">Datum: {{ "now"|date("d.m.Y") }}</div>
|
||||
|
||||
<div class="section">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Bezeichnung</th>
|
||||
<th class="right">Betrag</th>
|
||||
<th class="right">Anzahl</th>
|
||||
<th class="right">Zeilensumme</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in invoice.items %}
|
||||
<tr>
|
||||
<td>{{ item.labelSnapshot }}</td>
|
||||
<td class="right">{{ item.unitPriceCentsSnapshot|format_money }}</td>
|
||||
<td class="right">{{ item.quantity }}</td>
|
||||
<td class="right">{{ item.lineTotalCents|format_money }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="total-row">
|
||||
<td colspan="3" class="right">Gesamt</td>
|
||||
<td class="right">{{ invoice.totalCents|format_money }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,94 @@
|
||||
{% macro collectionRow(form, disposition, refreshPath) %}
|
||||
{% set inputAttr = {
|
||||
'hx-post': refreshPath,
|
||||
'hx-target': '#invoice-editor-form',
|
||||
'hx-swap': 'outerHTML',
|
||||
'hx-sync': 'closest form:replace',
|
||||
'hx-trigger': 'focusout, change',
|
||||
} %}
|
||||
<div {{ stimulus_target('form-collection', 'field') }} class="grid lg:grid-cols-12 gap-3 items-end border border-gray-200 rounded p-3">
|
||||
<div class="lg:col-span-4">
|
||||
{{ form_row(form.feeId, { attr: inputAttr }) }}
|
||||
</div>
|
||||
<div class="lg:col-span-3">
|
||||
{{ form_row(form.label, { attr: inputAttr }) }}
|
||||
</div>
|
||||
<div class="lg:col-span-2">
|
||||
{{ form_row(form.unitPriceCents, { attr: inputAttr }) }}
|
||||
</div>
|
||||
<div class="lg:col-span-2">
|
||||
{{ form_row(form.quantity, { attr: inputAttr }) }}
|
||||
</div>
|
||||
<div class="lg:col-span-1 flex justify-end pb-2">
|
||||
<button type="button" {{ stimulus_action('form-collection', 'removeItem') }}>
|
||||
{{ icon('delete', 'w-5 h-5 pointer-events-none text-red-500') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% set savePath = path('app_teamer_disposition_invoice_save', { uuid: disposition.uuid }) %}
|
||||
{% set submitPath = path('app_teamer_disposition_invoice_submit', { uuid: disposition.uuid }) %}
|
||||
{% set refreshPath = path('app_teamer_disposition_invoice_refresh', { uuid: disposition.uuid }) %}
|
||||
|
||||
<div id="invoice-editor-form">
|
||||
{{ form_start(form, {
|
||||
attr: {
|
||||
'hx-post': savePath,
|
||||
'hx-target': '#invoice-editor-form',
|
||||
'hx-swap': 'outerHTML'
|
||||
}
|
||||
}) }}
|
||||
|
||||
<div class="pb-4">
|
||||
<twig:MessageBox message="Bitte erfasse deine Honorarnote digital. Du kannst Zwischenschritte als Entwurf speichern." type="info"/>
|
||||
</div>
|
||||
|
||||
{% if disposition.invoiceDraft and disposition.invoiceDraft.status == constant('App\\Entity\\DispositionInvoice::STATUS_REJECTED') %}
|
||||
<div class="pb-4">
|
||||
<twig:MessageBox message="Deine Honorarnote wurde abgelehnt. Bitte überarbeite sie." type="error"/>
|
||||
{% if disposition.invoiceDraft.rejectionComment %}
|
||||
<div class="pt-2 text-sm">
|
||||
<strong>Begründung:</strong> {{ disposition.invoiceDraft.rejectionComment|nl2br }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div {{ stimulus_controller('form-collection', { 'prototype': _self.collectionRow(form.items.vars.prototype, disposition, refreshPath)|json_encode }) }}>
|
||||
<div {{ stimulus_target('form-collection', 'fields') }} class="flex flex-col space-y-3 pb-4">
|
||||
{% do form.items.setRendered %}
|
||||
{%- for item in form.items -%}
|
||||
{{- _self.collectionRow(item, disposition, refreshPath) -}}
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end pb-4">
|
||||
<button type="button" class="btn btn--secondary" {{ stimulus_action('form-collection', 'addItem') }}>
|
||||
Position hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{ form_errors(form.items) }}
|
||||
</div>
|
||||
|
||||
<div class="pb-4 text-right font-bold">
|
||||
Gesamt: {{ dto.totalCents|format_money }}
|
||||
</div>
|
||||
|
||||
<div class="pb-4">
|
||||
{{ form_row(form.confirmFinal) }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<button type="submit" class="btn" formaction="{{ savePath }}">
|
||||
Entwurf speichern
|
||||
</button>
|
||||
<button type="submit" class="btn" formaction="{{ submitPath }}">
|
||||
Final einreichen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
@@ -95,28 +95,36 @@
|
||||
|
||||
{# New invoice #}
|
||||
{% if workflow_can(disposition, 'upload_invoice') %}
|
||||
{% set formAttr = stimulus_controller('form-upload-guard')|stimulus_action('form-upload-guard', 'guard', 'submit') %}
|
||||
{{ form_start(form, { 'attr': formAttr.toArray() }) }}
|
||||
<div class="flex flex-col space-y-4">
|
||||
<a href="{{ path('app_teamer_disposition_invoicepdf', { 'uuid': disposition.uuid }) }}" target="_blank" class="inline-block">
|
||||
<img src="{{ asset('build/images/invoice_thumb.jpg') }}" alt="Honorarnote" class="block w-32 h-auto shadow-md">
|
||||
</a>
|
||||
<div>
|
||||
Bitte lade sie ausgefüllt und unterschrieben hier wieder hoch:
|
||||
{% if invoice_editor_enabled %}
|
||||
<div
|
||||
hx-get="{{ path('app_teamer_disposition_invoice_editor', { 'uuid': disposition.uuid }) }}"
|
||||
hx-trigger="load"
|
||||
hx-target="this"
|
||||
hx-swap="innerHTML"
|
||||
>
|
||||
{% include '_partials/_spinner.html.twig' with { 'class': 'w-8 h-8' } %}
|
||||
</div>
|
||||
{% include '_partials/_upload_collection_form.html.twig' with {
|
||||
'endpoint_upload': path('_uploader_upload_invoice'),
|
||||
'accepted_files': 'image/jpg,image/jpeg,application/pdf',
|
||||
} %}
|
||||
{% if form.confirmInvoiceData is defined %}
|
||||
{{ form_row(form.confirmInvoiceData) }}
|
||||
{% endif %}
|
||||
<button type="submit" class="btn" {{ stimulus_target('form-upload-guard', 'submit') }}>
|
||||
Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form) }}
|
||||
{% else %}
|
||||
{% set formAttr = stimulus_controller('form-upload-guard')|stimulus_action('form-upload-guard', 'guard', 'submit') %}
|
||||
{{ form_start(form, { 'attr': formAttr.toArray() }) }}
|
||||
<div class="flex flex-col space-y-4">
|
||||
<a href="{{ path('app_teamer_disposition_invoicepdf', { 'uuid': disposition.uuid }) }}" target="_blank" class="inline-block">
|
||||
<img src="{{ asset('build/images/invoice_thumb.jpg') }}" alt="Honorarnote" class="block w-32 h-auto shadow-md">
|
||||
</a>
|
||||
<div>
|
||||
Bitte lade sie ausgefüllt und unterschrieben hier wieder hoch:
|
||||
</div>
|
||||
{% include '_partials/_upload_collection_form.html.twig' with {
|
||||
'endpoint_upload': path('_uploader_upload_invoice'),
|
||||
'accepted_files': 'image/jpg,image/jpeg,application/pdf',
|
||||
} %}
|
||||
<button type="submit" class="btn" {{ stimulus_target('form-upload-guard', 'submit') }}>
|
||||
Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
{{ form_rest(form) }}
|
||||
{{ form_end(form) }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% for blocker in workflow_transition_blockers(disposition, 'upload_invoice') %}
|
||||
<twig:MessageBox message="{{ blocker.message }}"/>
|
||||
|
||||
Reference in new issue
Block a user