diff --git a/config/packages/kimai.yaml b/config/packages/kimai.yaml index d0004ba3..d99d7e4c 100644 --- a/config/packages/kimai.yaml +++ b/config/packages/kimai.yaml @@ -4,8 +4,10 @@ kimai: invoice: renderer: default: 'App\Controller\InvoiceController::invoiceAction' + timesheet: 'App\Controller\InvoiceController::timesheetAction' calculator: default: 'App\Invoice\DefaultCalculator' + short: 'App\Invoice\ShortInvoiceCalculator' number_generator: default: 'App\Invoice\DateNumberGenerator' #random: 'App\Invoice\RandomNumberGenerator' diff --git a/src/Controller/InvoiceController.php b/src/Controller/InvoiceController.php index 8cc17ddf..e23ed9c3 100644 --- a/src/Controller/InvoiceController.php +++ b/src/Controller/InvoiceController.php @@ -231,6 +231,17 @@ class InvoiceController extends AbstractController ]); } + /** + * @param InvoiceModel $model + * @return \Symfony\Component\HttpFoundation\Response + */ + public function timesheetAction(InvoiceModel $model) + { + return $this->render('invoice/timesheet.html.twig', [ + 'model' => $model, + ]); + } + /** * @param InvoiceQuery $query * @return \Symfony\Component\Form\FormInterface diff --git a/src/Form/CustomerEditForm.php b/src/Form/CustomerEditForm.php index 848985f3..08eee5a9 100644 --- a/src/Form/CustomerEditForm.php +++ b/src/Form/CustomerEditForm.php @@ -44,6 +44,7 @@ class CustomerEditForm extends AbstractType ]) ->add('number', TextType::class, [ 'label' => 'label.customer_number', + 'required' => false, ]) ->add('comment', TextareaType::class, [ 'label' => 'label.comment', diff --git a/src/Invoice/CalculatorInterface.php b/src/Invoice/CalculatorInterface.php index 3e0d1d7e..107ab858 100644 --- a/src/Invoice/CalculatorInterface.php +++ b/src/Invoice/CalculatorInterface.php @@ -11,6 +11,7 @@ namespace App\Invoice; +use App\Entity\Timesheet; use App\Model\InvoiceModel; /** @@ -22,26 +23,43 @@ interface CalculatorInterface { /** + * Return the timesheet records that will be displayed on the invoice. + * + * @return Timesheet[] + */ + public function getEntries(); + + /** + * Set the invoice model and can be used to fetch the customer. + * * @param InvoiceModel $model */ public function setModel(InvoiceModel $model); /** + * Returns the subtotal before taxes. + * * @return float */ public function getSubtotal(): float; /** + * Returns the tax amount for this invoice. + * * @return float */ public function getTax(): float; /** + * Returns the total amount for this invoice including taxes. + * * @return float */ public function getTotal(): float; /** + * Returns the currency for the invoices amounts. + * * @return string */ public function getCurrency(): string; diff --git a/src/Invoice/DefaultCalculator.php b/src/Invoice/DefaultCalculator.php index d8170141..77e51342 100644 --- a/src/Invoice/DefaultCalculator.php +++ b/src/Invoice/DefaultCalculator.php @@ -11,6 +11,7 @@ namespace App\Invoice; +use App\Entity\Timesheet; use App\Model\InvoiceModel; /** @@ -34,6 +35,14 @@ class DefaultCalculator implements CalculatorInterface */ protected $model; + /** + * @return Timesheet[] + */ + public function getEntries() + { + return $this->model->getEntries(); + } + /** * @param InvoiceModel $model */ diff --git a/src/Invoice/ShortInvoiceCalculator.php b/src/Invoice/ShortInvoiceCalculator.php new file mode 100644 index 00000000..151bdadf --- /dev/null +++ b/src/Invoice/ShortInvoiceCalculator.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace App\Invoice; + +use App\Entity\Timesheet; + +/** + * A calculator that sums up all timesheet records from the model and returns only one + * entry for a compact invoice version. + * + * @author Kevin Papst + */ +class ShortInvoiceCalculator extends DefaultCalculator +{ + + /** + * @return Timesheet[] + */ + public function getEntries() + { + $timesheet = new Timesheet(); + + foreach ($this->model->getEntries() as $entry) { + $timesheet->setDuration($timesheet->getDuration() + $entry->getDuration()); + if ($timesheet->getActivity() === null) { + $timesheet->setActivity($entry->getActivity()); + $timesheet->setEnd($entry->getEnd()); + } + $timesheet->setBegin($entry->getBegin()); + } + + return [$timesheet]; + } +} diff --git a/templates/invoice/print.html.twig b/templates/invoice/print.html.twig index 6d587cbf..36515197 100644 --- a/templates/invoice/print.html.twig +++ b/templates/invoice/print.html.twig @@ -33,11 +33,11 @@

- {{ 'invoice.number'|trans }} {{ model.numberGenerator.invoiceNumber }} + {{ 'invoice.number'|trans }}: {{ model.numberGenerator.invoiceNumber }}

{{ 'invoice.due_days'|trans }}: {{ model.dueDate|date(kimai_context.date_1) }}
- {{ 'label.customer_number'|trans }}: {{ model.customer.number }}
+ {% if model.customer.number is not empty %}{{ 'label.customer_number'|trans }}: {{ model.customer.number }}
{% endif %}

@@ -54,7 +54,7 @@ - {% for entry in model.entries %} + {% for entry in model.calculator.entries %} {{ entry.begin|date(kimai_context.date_1) }} {{ entry.activity.name }} / {{ entry.activity.project.name }} diff --git a/templates/invoice/timesheet.html.twig b/templates/invoice/timesheet.html.twig new file mode 100644 index 00000000..b3b6fb2d --- /dev/null +++ b/templates/invoice/timesheet.html.twig @@ -0,0 +1,94 @@ +{% import "macros/widgets.html.twig" as widgets %} + +
+
+ +
+
+ +
+
+ + + + + + + + + + + + + +
{{ 'invoice.from'|trans }} + {% if model.query.user is not empty %} + {{ widgets.username(model.query.user) }} + {% else %} + {{ model.template.company }} + {% endif %} +
{{ 'label.date'|trans }}{{ model.invoiceDate|date('F Y') }}
{{ 'label.customer'|trans }} + {% if model.customer.number is not empty %}[{{ model.customer.number }}]{% endif %} + {{ model.customer.name }} / {{ model.customer.contact }} +
+
+
+ +
+
+ + + + + + + + + + {% for entry in model.calculator.entries %} + + + + + + {% endfor %} + +
{{ 'label.date'|trans }}{{ 'label.activity'|trans }}{{ 'label.hours'|trans }}
{{ entry.begin|date(kimai_context.date_1) }}{{ entry.activity.name }} / {{ entry.activity.project.name }}{{ entry.duration|duration }}
+
+
+ +
+
+ {% if model.template.paymentTerms is not empty %} +

{{ 'label.payment_terms'|trans }}

+ +

+ {{ model.template.paymentTerms|trim|nl2br }} +

+ {% endif %} + + +
+ + + + + + + + + +
{{ 'invoice.signature_user'|trans }}
{{ 'invoice.signature_customer'|trans }}
+
+
+
+ +
+
+ +
+
diff --git a/translations/messages.de.xliff b/translations/messages.de.xliff index 3ae70787..d13d4a40 100644 --- a/translations/messages.de.xliff +++ b/translations/messages.de.xliff @@ -673,7 +673,11 @@ invoice_renderer.default - HTML Rechnung (Standard) + Rechnung (Standard) + + + invoice_renderer.timesheet + Stundenzettel label.invoice_number_generator @@ -695,6 +699,18 @@ invoice_calculator.default Stundenbasiert (Standard) + + invoice_calculator.short + Stundenbasiert - Kurzversion (nur ein Eintrag) + + + invoice.signature_user + Leistungsbestätigung: Datum / Name Berater / Unterschrift + + + invoice.signature_customer + Leistungsbestätigung: Datum / Name Kunde / Unterschrift +