added pdf template and other invoice improvements (#1693)
This commit is contained in:
@@ -32,7 +32,7 @@
|
||||
{% block main %}
|
||||
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found('kimai.activityUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.activityUpdate'}) }}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
{% block main %}
|
||||
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found('kimai.customerUpdate kimai.customerTeamUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.customerUpdate kimai.customerTeamUpdate'}) }}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{% block main %}
|
||||
|
||||
{% if widgets is empty %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{% for row in widgets %}
|
||||
{{ render_widget(row) }}
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
{% if preview_show %}
|
||||
{% if entries is empty %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
||||
{% set totalAmount = {} %}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
{% set actions = actions|merge({'visibility': '#modal_invoice_template'}) %}
|
||||
|
||||
{% if is_granted('manage_invoice_template') %}
|
||||
{% set actions = actions|merge({'create': path('admin_invoice_template_create')}) %}
|
||||
{% set actions = actions|merge({'create': {'url': path('admin_invoice_template_create'), 'class': 'modal-ajax-form'}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('upload_invoice_template') %}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
{% if preview %}
|
||||
{% if model.calculator is empty or model.calculator.entries is empty %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||
{% set entries = model.calculator.entries %}
|
||||
|
||||
@@ -29,11 +29,11 @@
|
||||
{% block main %}
|
||||
|
||||
{% if entries is empty %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {}) }}
|
||||
{% for entry in entries %}
|
||||
<tr>
|
||||
<tr class="alternative-link open-edit" data-href="{{ path('admin_invoice_download', {'id': entry.id}) }}">
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'date') }}">{{ entry.createdAt|date_short }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.user_avatar(entry.user) }} {{ widgets.username(entry.user) }}</td>
|
||||
<td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.customer) }}</td>
|
||||
|
||||
160
templates/invoice/renderer/default-pdf.pdf.twig
Normal file
160
templates/invoice/renderer/default-pdf.pdf.twig
Normal file
@@ -0,0 +1,160 @@
|
||||
<!DOCTYPE html>
|
||||
{% set fallback = app.request is not null ? app.request.locale : 'en' %}
|
||||
{% set language = model.template.language|default(fallback) %}
|
||||
{% set isDecimal = model.template.decimalDuration|default(false) %}
|
||||
{% set currency = model.currency %}
|
||||
<html lang="{{ language }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||
<title>{% block title %}{{ model.invoiceNumber }}-{{ model.customer.company|default(model.customer.name)|u.snake }}{% endblock %}</title>
|
||||
<style type="text/css">
|
||||
{{ encore_entry_css_source('invoice-pdf')|raw }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!--mpdf
|
||||
<htmlpageheader name="header">
|
||||
<table class="header">
|
||||
<tr>
|
||||
<td class="title">
|
||||
{{ model.template.title }}
|
||||
</td>
|
||||
<td class="date">
|
||||
{{ 'label.date'|trans({}, 'messages', language) }}: {{ model.invoiceDate|date_short }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</htmlpageheader>
|
||||
<sethtmlpageheader name="header" page="ALL" value="on" show-this-page="1" />
|
||||
<htmlpagefooter name="footer">
|
||||
<table class="footer">
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ 'label.contact'|trans({}, 'messages', language) }}</strong>:
|
||||
{{ model.template.contact|replace({"\n": ' – ', "\r\n": ' – ', "\r": ' – '})|raw }}
|
||||
<br>
|
||||
<strong>{{ 'label.invoice_bank_account'|trans({}, 'messages', language) }}</strong>:
|
||||
{{ model.template.paymentDetails|replace({"\n": ' – ', "\r\n": ' – ', "\r": ' – '})|raw }}
|
||||
</td>
|
||||
<td align="right">
|
||||
{{ 'export.page_of'|trans({'%page%': '{PAGENO}', '%pages%': '{nb}'}) }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</htmlpagefooter>
|
||||
<sethtmlpagefooter page="ALL" value="on" name="footer" show-this-page="1" />
|
||||
mpdf-->
|
||||
<div class="wrapper">
|
||||
<table class="addresses">
|
||||
<tr>
|
||||
<td width="60%">
|
||||
{{ 'invoice.to'|trans({}, 'messages', language) }}
|
||||
<br>
|
||||
<strong>{{ model.customer.company|default(model.customer.name) }}</strong>
|
||||
<br>
|
||||
{{ model.customer.address|nl2br }}
|
||||
{% if model.customer.vatId is not empty %}
|
||||
<br>
|
||||
{{ 'label.vat_id'|trans({}, 'messages', language) }}: {{ model.customer.vatId }}
|
||||
{% endif %}
|
||||
{% if model.customer.number is not empty %}
|
||||
<br>
|
||||
{{ 'label.number'|trans({}, 'messages', language) }}: {{ model.customer.number }}
|
||||
{% endif %}
|
||||
{% if model.query.project is not empty and model.query.project.orderNumber is not empty %}
|
||||
<br>
|
||||
{{ 'label.orderNumber'|trans({}, 'messages', language) }}: {{ model.query.project.orderNumber }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ 'invoice.from'|trans({}, 'messages', language) }}
|
||||
<br>
|
||||
<strong>{{ model.template.company }}</strong>
|
||||
<br>
|
||||
{{ model.template.address|trim|nl2br }}
|
||||
{% if model.template.vatId is not empty %}
|
||||
<br>
|
||||
{{ 'label.vat_id'|trans({}, 'messages', language) }}:
|
||||
{{ model.template.vatId }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
<strong>{{ 'invoice.number'|trans({}, 'messages', language) }}:</strong>
|
||||
{{ model.invoiceNumber }}
|
||||
|
||||
<br>
|
||||
<strong>{{ 'invoice.due_days'|trans({}, 'messages', language) }}:</strong>
|
||||
{{ model.dueDate|date_short }}
|
||||
</p>
|
||||
|
||||
<table class="items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="first">{{ 'label.date'|trans({}, 'messages', language) }}</th>
|
||||
<th>{{ 'label.description'|trans({}, 'messages', language) }}</th>
|
||||
<th class="text-right">{{ 'label.unit_price'|trans({}, 'messages', language) }}</th>
|
||||
<th class="text-right">{{ 'label.amount'|trans({}, 'messages', language) }}</th>
|
||||
<th class="last text-right">{{ 'label.total_rate'|trans({}, 'messages', language) }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for id, entry in model.calculator.entries %}
|
||||
{% set duration = entry.duration|duration(isDecimal) %}
|
||||
{% if entry.fixedRate %}
|
||||
{% set rate = entry.fixedRate %}
|
||||
{% set duration = entry.amount|amount %}
|
||||
{% else %}
|
||||
{% set rate = entry.hourlyRate %}
|
||||
{% endif %}
|
||||
<tr{# class="{{ cycle(['odd', 'even'], id) }}"#}>
|
||||
<td nowrap class="first text-nowrap">{{ entry.begin|date_short }}</td>
|
||||
<td contenteditable="true">
|
||||
{% if entry.description is not empty %}
|
||||
{{ entry.description|nl2br }}
|
||||
{% else %}
|
||||
{% if entry.activity is not null %}{{ entry.activity.name }} / {% endif %}{{ entry.project.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td nowrap class="text-nowrap text-right">{{ rate|money(currency) }}</td>
|
||||
<td nowrap class="text-nowrap text-right">{{ duration }}</td>
|
||||
<td nowrap class="last text-nowrap text-right">{{ entry.rate|money(currency) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="4" class="text-right">
|
||||
{{ 'invoice.subtotal'|trans({}, 'messages', language) }}
|
||||
</td>
|
||||
<td class="last text-right">{{ model.calculator.subtotal|money(currency) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="text-right">
|
||||
{{ 'invoice.tax'|trans({}, 'messages', language) }} ({{ model.calculator.vat }}%)
|
||||
</td>
|
||||
<td class="last text-right">{{ model.calculator.tax|money(currency) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="4" class="text-right text-nowrap">
|
||||
<strong>{{ 'invoice.total'|trans({}, 'messages', language) }}</strong>
|
||||
</td>
|
||||
<td class="last text-right">
|
||||
<strong>{{ model.calculator.total|money(currency) }}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
{% if model.template.paymentTerms is not empty %}
|
||||
<p>
|
||||
{{ model.template.paymentTerms|md2html }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
{% block main %}
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found('kimai.invoiceTemplateUpdate') }}
|
||||
{% endif %}
|
||||
|
||||
{{ tables.datatable_header(tableName, columns, null, {'reload': 'kimai.invoiceTemplateUpdate'}) }}
|
||||
|
||||
@@ -458,3 +458,14 @@
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro nothing_found(events) %}
|
||||
{{ _self.callout('warning', 'error.no_entries_found') }}
|
||||
{% if events is not empty %}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
KimaiReloadPageWidget.create('{{ events }}');
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
{% block main %}
|
||||
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found('kimai.projectUpdate kimai.projectTeamUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.projectUpdate kimai.projectTeamUpdate'}) }}
|
||||
|
||||
|
||||
@@ -11,12 +11,7 @@
|
||||
{% block main %}
|
||||
|
||||
{% if tags|length == 0 %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
KimaiReloadPageWidget.create('kimai.tagUpdate');
|
||||
});
|
||||
</script>
|
||||
{{ widgets.nothing_found('kimai.tagUpdate') }}
|
||||
{% else %}
|
||||
|
||||
{% set columns = {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% block main %}
|
||||
|
||||
{% if teams|length == 0 %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found('kimai.teamUpdate') }}
|
||||
{% else %}
|
||||
|
||||
{% set columns = {
|
||||
|
||||
@@ -57,12 +57,7 @@
|
||||
{% block main %}
|
||||
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('kimai.initialized', function() {
|
||||
KimaiReloadPageWidget.create('kimai.timesheetUpdate');
|
||||
});
|
||||
</script>
|
||||
{{ widgets.nothing_found('kimai.timesheetUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'striped': not showSummary, 'reload': 'kimai.timesheetUpdate'}) }}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
{% block main %}
|
||||
|
||||
{% if entries.count == 0 %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found('kimai.userUpdate') }}
|
||||
{% else %}
|
||||
{{ tables.datatable_header(tableName, columns, query, {'reload': 'kimai.userUpdate'}) }}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{% block profile_content %}
|
||||
|
||||
{% if years is empty %}
|
||||
{{ widgets.callout('warning', 'error.no_entries_found') }}
|
||||
{{ widgets.nothing_found() }}
|
||||
{% endif %}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
Reference in New Issue
Block a user