diff --git a/UPGRADING.md b/UPGRADING.md index 7e63c4956..e9b1bded7 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -17,6 +17,7 @@ Perform EACH version specific task between your version and the new one, otherwi - HTML invoice templates are now treated like other files and offered as download. If you are using relative URLs for including assets (CSS, images) you need to either inline them (see the default templates) or use absolute URLs. - Invoice templates that use the templates variables `${activity.X}` or `${project.X}` should be checked and possibly adapted, as multi-select is now possible for filtering +- Invoice templates have access to all meta-fields as variables, not only the ones marked as visible Permission changes: - `history_invoice` - NEW: grants all features for the new invoice archive (by default for all admins) diff --git a/src/Command/InvoiceCreateCommand.php b/src/Command/InvoiceCreateCommand.php index e0af29b07..76986985b 100644 --- a/src/Command/InvoiceCreateCommand.php +++ b/src/Command/InvoiceCreateCommand.php @@ -27,6 +27,9 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class InvoiceCreateCommand extends Command @@ -55,6 +58,10 @@ class InvoiceCreateCommand extends Command * @var EventDispatcherInterface */ private $eventDispatcher; + /** + * @var string|null + */ + private $previewDirectory; public function __construct( ServiceInvoice $serviceInvoice, @@ -94,6 +101,7 @@ class InvoiceCreateCommand extends Command ->addOption('template-meta', null, InputOption::VALUE_OPTIONAL, 'Fetch invoice template from a meta-field', null) ->addOption('search', null, InputOption::VALUE_OPTIONAL, 'Search term to filter invoice entries', null) ->addOption('exported', null, InputOption::VALUE_OPTIONAL, 'Exported filter for invoice entries (possible values: exported, all), by default only "not exported" items are fetched', null) + ->addOption('preview', null, InputOption::VALUE_OPTIONAL, 'Absolute path for a rendered preview of the invoice, which will neither be saved nor the items be marked as exported.', null) ; } @@ -208,7 +216,14 @@ class InvoiceCreateCommand extends Command } $markAsExported = false; - if ($input->getOption('set-exported')) { + if ($input->getOption('preview') !== null) { + $this->previewDirectory = rtrim($input->getOption('preview'), '/') . '/'; + if (!is_dir($this->previewDirectory) || !is_writable($this->previewDirectory)) { + $io->error('Invalid preview directory given'); + + return 1; + } + } elseif ($input->getOption('set-exported')) { $markAsExported = true; } @@ -283,7 +298,11 @@ class InvoiceCreateCommand extends Command $query->setTemplate($tpl); try { - $invoices[] = $this->serviceInvoice->createInvoice($query, $this->eventDispatcher); + if (null !== $this->previewDirectory) { + $invoices[] = $this->saveInvoicePreview($this->serviceInvoice->renderInvoice($query, $this->eventDispatcher)); + } else { + $invoices[] = $this->serviceInvoice->createInvoice($query, $this->eventDispatcher); + } } catch (\Exception $ex) { $io->error(sprintf('Failed to create invoice for project "%s" with: %s', $project->getName(), $ex->getMessage())); } @@ -292,6 +311,34 @@ class InvoiceCreateCommand extends Command return $invoices; } + private function saveInvoicePreview(Response $response) + { + $filename = uniqid('invoice_'); + + if ($response->headers->has('Content-Disposition')) { + $disposition = $response->headers->get('Content-Disposition'); + $parts = explode(';', $disposition); + foreach ($parts as $part) { + if (stripos($part, 'filename=') === false) { + continue; + } + $filename = explode('filename=', $part); + if (\count($filename) > 1) { + $filename = $filename[1]; + } + } + } + + if ($response instanceof BinaryFileResponse) { + $file = $response->getFile(); + $file->move($this->previewDirectory, $filename); + } else { + (new Filesystem())->dumpFile($this->previewDirectory . $filename, $response->getContent()); + } + + return $this->previewDirectory . $filename; + } + /** * @param Customer[] $customers * @param InvoiceQuery $defaultQuery @@ -318,7 +365,11 @@ class InvoiceCreateCommand extends Command $query->setTemplate($tpl); try { - $invoices[] = $this->serviceInvoice->createInvoice($query, $this->eventDispatcher); + if (null !== $this->previewDirectory) { + $invoices[] = $this->saveInvoicePreview($this->serviceInvoice->renderInvoice($query, $this->eventDispatcher)); + } else { + $invoices[] = $this->serviceInvoice->createInvoice($query, $this->eventDispatcher); + } } catch (\Exception $ex) { $io->error(sprintf('Failed to create invoice for customer "%s" with: %s', $customer->getName(), $ex->getMessage())); } @@ -343,6 +394,22 @@ class InvoiceCreateCommand extends Command return 0; } + if (null !== $this->previewDirectory) { + $columns = ['Filename']; + + $table = new Table($output); + $table->setHeaderTitle(sprintf('Created %s invoice(s)', \count($invoices))); + $table->setHeaders($columns); + + foreach ($invoices as $invoiceFile) { + $table->addRow([$invoiceFile]); + } + + $table->render(); + + return 0; + } + $columns = ['ID', 'Customer', 'Total', 'Filename']; $table = new Table($output); diff --git a/src/Controller/InvoiceController.php b/src/Controller/InvoiceController.php index c903b462c..49f1ac28f 100644 --- a/src/Controller/InvoiceController.php +++ b/src/Controller/InvoiceController.php @@ -189,11 +189,26 @@ final class InvoiceController extends AbstractController { try { $this->service->changeInvoiceStatus($invoice, $status); - } catch (\InvalidArgumentException $ex) { - throw $this->createNotFoundException($ex->getMessage()); + $this->flashSuccess('action.update.success'); + } catch (\Exception $ex) { + $this->flashError('action.update.error'); } - $this->flashSuccess('action.update.success'); + return $this->redirectToRoute('admin_invoice_list'); + } + + /** + * @Route(path="/delete/{id}", name="admin_invoice_delete", methods={"GET"}) + * @Security("is_granted('history_invoice')") + */ + public function deleteInvoiceAction(Invoice $invoice): Response + { + try { + $this->service->deleteInvoice($invoice); + $this->flashSuccess('action.delete.success'); + } catch (\Exception $ex) { + $this->flashError('action.delete.error'); + } return $this->redirectToRoute('admin_invoice_list'); } diff --git a/src/Entity/InvoiceTemplate.php b/src/Entity/InvoiceTemplate.php index c020f4f30..5719b4d17 100644 --- a/src/Entity/InvoiceTemplate.php +++ b/src/Entity/InvoiceTemplate.php @@ -145,7 +145,7 @@ class InvoiceTemplate private $decimalDuration = false; /** - * Used when rendering HTML templates. + * Used for translations and locale dependent number and date formats. * * @var string * diff --git a/src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php b/src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php index 32c409fb0..bc9f7940f 100644 --- a/src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php +++ b/src/Invoice/Hydrator/InvoiceItemDefaultHydrator.php @@ -95,7 +95,7 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator 'entry.activity_id' => $activity->getId(), ]); - foreach ($activity->getVisibleMetaFields() as $metaField) { + foreach ($activity->getMetaFields() as $metaField) { $values = array_merge($values, [ 'entry.activity.meta.' . $metaField->getName() => $metaField->getValue(), ]); @@ -108,7 +108,7 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator 'entry.project_id' => $project->getId(), ]); - foreach ($project->getVisibleMetaFields() as $metaField) { + foreach ($project->getMetaFields() as $metaField) { $values = array_merge($values, [ 'entry.project.meta.' . $metaField->getName() => $metaField->getValue(), ]); @@ -121,7 +121,7 @@ class InvoiceItemDefaultHydrator implements InvoiceItemHydrator 'entry.customer_id' => $customer->getId(), ]); - foreach ($customer->getVisibleMetaFields() as $metaField) { + foreach ($customer->getMetaFields() as $metaField) { $values = array_merge($values, [ 'entry.customer.meta.' . $metaField->getName() => $metaField->getValue(), ]); diff --git a/src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php b/src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php index cad891dc1..44c17db76 100644 --- a/src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php +++ b/src/Invoice/Hydrator/InvoiceModelCustomerHydrator.php @@ -46,7 +46,7 @@ class InvoiceModelCustomerHydrator implements InvoiceModelHydrator // remaining time-budget? ]; - foreach ($customer->getVisibleMetaFields() as $metaField) { + foreach ($customer->getMetaFields() as $metaField) { $values = array_merge($values, [ 'customer.meta.' . $metaField->getName() => $metaField->getValue(), ]); diff --git a/src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php b/src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php index f92bc0636..245024ab0 100644 --- a/src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php +++ b/src/Invoice/Hydrator/InvoiceModelDefaultHydrator.php @@ -27,6 +27,7 @@ class InvoiceModelDefaultHydrator implements InvoiceModelHydrator 'invoice.date' => $formatter->getFormattedDateTime($model->getInvoiceDate()), 'invoice.number' => $model->getInvoiceNumber(), 'invoice.currency' => $currency, + 'invoice.language' => $model->getTemplate()->getLanguage(), // since 1.9 'invoice.currency_symbol' => $formatter->getCurrencySymbol($currency), 'invoice.vat' => $model->getCalculator()->getVat(), 'invoice.tax' => $formatter->getFormattedMoney($tax, $currency), @@ -52,11 +53,19 @@ class InvoiceModelDefaultHydrator implements InvoiceModelHydrator 'template.payment_details' => $model->getTemplate()->getPaymentDetails(), 'query.begin' => $formatter->getFormattedDateTime($model->getQuery()->getBegin()), - 'query.day' => $model->getQuery()->getBegin()->format('d'), - 'query.end' => $formatter->getFormattedDateTime($model->getQuery()->getEnd()), - 'query.month' => $formatter->getFormattedMonthName($model->getQuery()->getBegin()), - 'query.month_number' => $model->getQuery()->getBegin()->format('m'), - 'query.year' => $model->getQuery()->getBegin()->format('Y'), + 'query.day' => $model->getQuery()->getBegin()->format('d'), // @deprecated + 'query.month' => $formatter->getFormattedMonthName($model->getQuery()->getBegin()), // @deprecated + 'query.month_number' => $model->getQuery()->getBegin()->format('m'), // @deprecated + 'query.year' => $model->getQuery()->getBegin()->format('Y'), // @deprecated + 'query.begin_day' => $model->getQuery()->getBegin()->format('d'), + 'query.begin_month' => $formatter->getFormattedMonthName($model->getQuery()->getBegin()), + 'query.begin_month_number' => $model->getQuery()->getBegin()->format('m'), + 'query.begin_year' => $model->getQuery()->getBegin()->format('Y'), + 'query.end' => $formatter->getFormattedDateTime($model->getQuery()->getEnd()), // since 1.9 + 'query.end_day' => $model->getQuery()->getEnd()->format('d'), // since 1.9 + 'query.end_month' => $formatter->getFormattedMonthName($model->getQuery()->getEnd()), // since 1.9 + 'query.end_month_number' => $model->getQuery()->getEnd()->format('m'), // since 1.9 + 'query.end_year' => $model->getQuery()->getEnd()->format('Y'), // since 1.9 ]; return $values; diff --git a/src/Invoice/ServiceInvoice.php b/src/Invoice/ServiceInvoice.php index fb954a7ad..cbc32cca3 100644 --- a/src/Invoice/ServiceInvoice.php +++ b/src/Invoice/ServiceInvoice.php @@ -396,6 +396,15 @@ final class ServiceInvoice ); } + public function deleteInvoice(Invoice $invoice) + { + $invoiceDirectory = $this->getInvoicesDirectory(); + if (is_file($invoiceDirectory . $invoice->getInvoiceFilename())) { + $this->fileHelper->removeFile($invoiceDirectory . $invoice->getInvoiceFilename()); + } + $this->invoiceRepository->deleteInvoice($invoice); + } + /** * @param InvoiceQuery $query * @return InvoiceModel diff --git a/src/Repository/InvoiceRepository.php b/src/Repository/InvoiceRepository.php index 172009812..a885add82 100644 --- a/src/Repository/InvoiceRepository.php +++ b/src/Repository/InvoiceRepository.php @@ -28,6 +28,13 @@ class InvoiceRepository extends EntityRepository $entityManager->flush(); } + public function deleteInvoice(Invoice $invoice) + { + $entityManager = $this->getEntityManager(); + $entityManager->remove($invoice); + $entityManager->flush(); + } + private function getCounterFor(\DateTime $start, \DateTime $end): int { $qb = $this->getEntityManager()->createQueryBuilder(); diff --git a/src/Utils/FileHelper.php b/src/Utils/FileHelper.php index 8a803d0e3..142bcd494 100644 --- a/src/Utils/FileHelper.php +++ b/src/Utils/FileHelper.php @@ -58,4 +58,9 @@ final class FileHelper { $this->filesystem->dumpFile($filename, $data); } + + public function removeFile(string $filename) + { + $this->filesystem->remove($filename); + } } diff --git a/templates/invoice/actions.html.twig b/templates/invoice/actions.html.twig index b43c6b3a7..69b6491f0 100644 --- a/templates/invoice/actions.html.twig +++ b/templates/invoice/actions.html.twig @@ -57,11 +57,8 @@ {% set actions = actions|merge({'invoice.paid': path('admin_invoice_status', {'id': invoice.id, 'status': 'paid'})}) %} {% endif %} - {% if actions|length > 0 %} - {% set actions = actions|merge({'divider': null}) %} - {% endif %} - {% set actions = actions|merge({'download': {'url': path('admin_invoice_download', {'id': invoice.id}), 'target': '_blank'}}) %} + {% set actions = actions|merge({'trash': {'url': path('admin_invoice_delete', {'id' : invoice.id}), 'class': 'confirmation-link', 'attr': {'data-question': 'confirm.delete'}}}) %} {% endif %} {% set event = trigger('actions.invoice', {'actions': actions, 'invoice': invoice}) %} @@ -107,7 +104,7 @@ {% if is_granted('manage_invoice_template') %} {% set actions = actions|merge({'edit': {'url': path('admin_invoice_template_edit', {'id' : template.id}), 'class': 'modal-ajax-form'}}) %} {% set actions = actions|merge({'copy': path('admin_invoice_template_copy', {'id' : template.id})}) %} - {% set actions = actions|merge({'trash': path('admin_invoice_template_delete', {'id' : template.id})}) %} + {% set actions = actions|merge({'trash': {'url': path('admin_invoice_template_delete', {'id' : template.id}), 'class': 'confirmation-link', 'attr': {'data-question': 'confirm.delete'}}}) %} {% endif %} {% set event = trigger('actions.invoice_template', {'actions': actions, 'view': view, 'template': template}) %} diff --git a/templates/invoice/renderer/text.txt.twig b/templates/invoice/renderer/text.txt.twig index 7e8b3077d..046b030df 100644 --- a/templates/invoice/renderer/text.txt.twig +++ b/templates/invoice/renderer/text.txt.twig @@ -1,19 +1,16 @@ {% for key, value in model.toArray %} -{{ key }}: +{{ key }} {% if value is not empty %} -{{ value|multiline_indent(' ') }} +{{ value|multiline_indent(' ') }} {% endif %} - {% endfor %} {% for entry in model.calculator.entries %} --- - {% set items = model.itemToArray(entry) %} {% for key, value in items %} -{{ key }}: +{{ key }} {% if value is not empty %} -{{ value|multiline_indent(' ') }} +{{ value|multiline_indent(' ') }} {% endif %} - {% endfor %} {% endfor %} diff --git a/tests/Command/InvoiceCreateCommandTest.php b/tests/Command/InvoiceCreateCommandTest.php index a3c61b7c5..eb6742f3f 100644 --- a/tests/Command/InvoiceCreateCommandTest.php +++ b/tests/Command/InvoiceCreateCommandTest.php @@ -157,6 +157,11 @@ class InvoiceCreateCommandTest extends KernelTestCase $this->assertCommandErrors(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--customer' => 1, '--template' => 'x', '--start' => '2020-01-01', '--end' => 'öäüß'], 'Invalid end date given'); } + public function testCreateWithInvalidPreviewDirectory() + { + $this->assertCommandErrors(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--customer' => 1, '--template' => 'x', '--start' => '2020-01-01', '--end' => '2020-01-02', '--preview' => '/kjhg/'], 'Invalid preview directory given'); + } + public function testCreateWithInvalidCustomer() { $this->assertCommandErrors(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--customer' => 3, '--template' => 'x'], 'Unknown customer ID: 3'); @@ -245,4 +250,17 @@ class InvoiceCreateCommandTest extends KernelTestCase $output = $commandTester->getDisplay(); $this->assertStringContainsString('Created 1 invoice(s) ', $output); } + + public function testCreateInvoiceByProjectWithPreview() + { + $start = new \DateTime('-2 months'); + $end = new \DateTime(); + + $this->prepareFixtures($start); + + $commandTester = $this->createInvoice(['--user' => UserFixtures::USERNAME_SUPER_ADMIN, '--exported' => 'all', '--preview' => sys_get_temp_dir(), '--by-project' => null, '--template-meta' => 'template', '--start' => $start->format('Y-m-d'), '--end' => $end->format('Y-m-d')]); + + $output = $commandTester->getDisplay(); + $this->assertStringContainsString('Created 1 invoice(s) ', $output); + } } diff --git a/tests/Controller/InvoiceControllerTest.php b/tests/Controller/InvoiceControllerTest.php index 012f48d2f..6c642ec42 100644 --- a/tests/Controller/InvoiceControllerTest.php +++ b/tests/Controller/InvoiceControllerTest.php @@ -266,7 +266,7 @@ class InvoiceControllerTest extends ControllerBaseTest $this->assertEquals('invoice_print', $node->getIterator()[0]->getAttribute('class')); } - public function testCreateActionAsAdminWithDownloadAndStatusChange() + public function testCreateActionAsAdminWithDownloadAndStatusChangeAndDelete() { $client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN); /** @var EntityManager $em */ @@ -350,6 +350,11 @@ class InvoiceControllerTest extends ControllerBaseTest $this->assertIsRedirect($client, '/invoice/show'); $client->followRedirect(); $this->assertTrue($client->getResponse()->isSuccessful()); + + $this->request($client, '/invoice/delete/1'); + $this->assertIsRedirect($client, '/invoice/show'); + $client->followRedirect(); + $this->assertTrue($client->getResponse()->isSuccessful()); } public function testEditTemplateAction() diff --git a/tests/Entity/InvoiceTemplateTest.php b/tests/Entity/InvoiceTemplateTest.php index 97532ed14..61a11585c 100644 --- a/tests/Entity/InvoiceTemplateTest.php +++ b/tests/Entity/InvoiceTemplateTest.php @@ -76,6 +76,15 @@ class InvoiceTemplateTest extends TestCase self::assertInstanceOf(InvoiceTemplate::class, $sut->setLanguage('de')); self::assertEquals('de', $sut->getLanguage()); + self::assertInstanceOf(InvoiceTemplate::class, $sut->setNumberGenerator('foo')); + self::assertEquals('foo', $sut->getNumberGenerator()); + + self::assertInstanceOf(InvoiceTemplate::class, $sut->setRenderer('bar')); + self::assertEquals('bar', $sut->getRenderer()); + + self::assertInstanceOf(InvoiceTemplate::class, $sut->setCalculator('fooBar')); + self::assertEquals('fooBar', $sut->getCalculator()); + self::assertEquals($sut, clone $sut); } diff --git a/tests/Entity/UserTest.php b/tests/Entity/UserTest.php index 260b64319..4f4829292 100644 --- a/tests/Entity/UserTest.php +++ b/tests/Entity/UserTest.php @@ -184,13 +184,15 @@ class UserTest extends TestCase { $sut = new User(); $sut->setAlias('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'); - self::assertEquals(60, \strlen($sut->getAlias())); + self::assertEquals(60, mb_strlen($sut->getAlias())); $sut->setAlias('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxAAAAA'); - self::assertEquals(60, \strlen($sut->getAlias())); + self::assertEquals(60, mb_strlen($sut->getAlias())); + $sut->setAlias('万政提質打録施熟活者韓症写気当。規談表有部確暑将回優隊見竜能南事。竹阪板府入違護究兵厚能提。済伸知題熱正写場京誉事週在複今徳際供。審利世連手阿量携泉指済像更映刊政病世。熱楽時予資方賀月改洋者職原桜提増脚職。気公誌荒原輝文治察専及唱戦白廃模書。着授健出山力集出止員捉害実載措明国無今。棋出陶供供知機使協物確講最新両。'); + self::assertEquals(60, mb_strlen($sut->getAlias())); $sut->setTitle('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'); - self::assertEquals(50, \strlen($sut->getTitle())); + self::assertEquals(50, mb_strlen($sut->getTitle())); $sut->setTitle('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxAAAAAA'); - self::assertEquals(50, \strlen($sut->getTitle())); + self::assertEquals(50, mb_strlen($sut->getTitle())); } public function testPreferencesCollectionIsCreatedOnBrokenUser() diff --git a/tests/Invoice/Hydrator/InvoiceModelDefaultHydratorTest.php b/tests/Invoice/Hydrator/InvoiceModelDefaultHydratorTest.php index 1a179f2f6..36fd8169f 100644 --- a/tests/Invoice/Hydrator/InvoiceModelDefaultHydratorTest.php +++ b/tests/Invoice/Hydrator/InvoiceModelDefaultHydratorTest.php @@ -39,6 +39,7 @@ class InvoiceModelDefaultHydratorTest extends TestCase 'invoice.currency', 'invoice.currency_symbol', 'invoice.vat', + 'invoice.language', 'invoice.tax', 'invoice.tax_nc', 'invoice.tax_plain', @@ -60,11 +61,19 @@ class InvoiceModelDefaultHydratorTest extends TestCase 'template.contact', 'template.payment_details', 'query.begin', - 'query.day', 'query.end', + 'query.day', 'query.month', 'query.month_number', 'query.year', + 'query.begin_day', + 'query.begin_month', + 'query.begin_month_number', + 'query.begin_year', + 'query.end_day', + 'query.end_month', + 'query.end_month_number', + 'query.end_year', ]; $givenKeys = array_keys($model); diff --git a/tests/Invoice/Renderer/DebugRendererTest.php b/tests/Invoice/Renderer/DebugRendererTest.php index e6034430d..d14a5cb96 100644 --- a/tests/Invoice/Renderer/DebugRendererTest.php +++ b/tests/Invoice/Renderer/DebugRendererTest.php @@ -84,6 +84,7 @@ class DebugRendererTest extends TestCase 'invoice.currency_symbol', 'invoice.vat', 'invoice.tax', + 'invoice.language', 'invoice.tax_nc', 'invoice.tax_plain', 'invoice.total_time', @@ -109,6 +110,14 @@ class DebugRendererTest extends TestCase 'query.month', 'query.month_number', 'query.year', + 'query.begin_day', + 'query.begin_month', + 'query.begin_month_number', + 'query.begin_year', + 'query.end_day', + 'query.end_month', + 'query.end_month_number', + 'query.end_year', 'customer.id', 'customer.address', 'customer.name', diff --git a/tests/Invoice/Renderer/RendererTestTrait.php b/tests/Invoice/Renderer/RendererTestTrait.php index 4a1ee665a..928a416ce 100644 --- a/tests/Invoice/Renderer/RendererTestTrait.php +++ b/tests/Invoice/Renderer/RendererTestTrait.php @@ -115,6 +115,7 @@ trait RendererTestTrait $template = new InvoiceTemplate(); $template->setTitle('a very *long* test invoice / template title with [special] character'); $template->setVat(19); + $template->setLanguage('en'); $project = new Project(); $project->setName('project name'); diff --git a/tests/Invoice/Renderer/TextRendererTest.php b/tests/Invoice/Renderer/TextRendererTest.php index fd49fd3e0..e22c703a1 100644 --- a/tests/Invoice/Renderer/TextRendererTest.php +++ b/tests/Invoice/Renderer/TextRendererTest.php @@ -69,9 +69,19 @@ class TextRendererTest extends KernelTestCase foreach ($model->toArray() as $key => $value) { if (null === $value || '' === $value) { - self::assertStringContainsString(sprintf("%s:\n\n", $key), $content); + self::assertStringContainsString(sprintf("%s\n", $key), $content); } else { - self::assertStringContainsString(sprintf("%s:\n %s", $key, explode("\n", $value)[0]), $content); + self::assertStringContainsString(sprintf("%s\n %s", $key, explode("\n", $value)[0]), $content); + } + } + + foreach ($model->getCalculator()->getEntries() as $entry) { + foreach ($model->itemToArray($entry) as $key => $value) { + if (null === $value || '' === $value) { + self::assertStringContainsString(sprintf("%s\n", $key), $content); + } else { + self::assertStringContainsString(sprintf("%s\n %s", $key, explode("\n", $value)[0]), $content); + } } } self::assertEquals(\count($model->getCalculator()->getEntries()), substr_count($content, PHP_EOL . '---' . PHP_EOL));