allow to delete invoice documents (#2968)
This commit is contained in:
@@ -13,6 +13,7 @@ use App\Configuration\SystemConfiguration;
|
|||||||
use App\Entity\Customer;
|
use App\Entity\Customer;
|
||||||
use App\Entity\Invoice;
|
use App\Entity\Invoice;
|
||||||
use App\Entity\InvoiceTemplate;
|
use App\Entity\InvoiceTemplate;
|
||||||
|
use App\Event\InvoiceDocumentsEvent;
|
||||||
use App\Export\Spreadsheet\AnnotatedObjectExporter;
|
use App\Export\Spreadsheet\AnnotatedObjectExporter;
|
||||||
use App\Export\Spreadsheet\Writer\BinaryFileResponseWriter;
|
use App\Export\Spreadsheet\Writer\BinaryFileResponseWriter;
|
||||||
use App\Export\Spreadsheet\Writer\XlsxWriter;
|
use App\Export\Spreadsheet\Writer\XlsxWriter;
|
||||||
@@ -338,17 +339,45 @@ final class InvoiceController extends AbstractController
|
|||||||
$invoiceDir = $projectDirectory . DIRECTORY_SEPARATOR . $dir;
|
$invoiceDir = $projectDirectory . DIRECTORY_SEPARATOR . $dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$used = [];
|
||||||
|
foreach ($this->templateRepository->findAll() as $template) {
|
||||||
|
$used[$template->getRenderer()] = $template;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event = new InvoiceDocumentsEvent($this->service->getDocuments(true));
|
||||||
|
$this->dispatcher->dispatch($event);
|
||||||
|
|
||||||
|
$documents = [];
|
||||||
|
foreach ($event->getInvoiceDocuments() as $document) {
|
||||||
|
$isUsed = \array_key_exists($document->getId(), $used);
|
||||||
|
$template = null;
|
||||||
|
if ($isUsed) {
|
||||||
|
$template = $used[$document->getId()];
|
||||||
|
}
|
||||||
|
$documents[] = [
|
||||||
|
'document' => $document,
|
||||||
|
'template' => $template,
|
||||||
|
'used' => $isUsed,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
$canUpload = true;
|
$canUpload = true;
|
||||||
|
$uploadError = null;
|
||||||
|
|
||||||
|
if (\count($documents) >= $event->getMaximumAllowedDocuments()) {
|
||||||
|
$uploadError = 'invoice_document.max_reached';
|
||||||
|
$canUpload = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!file_exists($invoiceDir)) {
|
if (!file_exists($invoiceDir)) {
|
||||||
@mkdir($invoiceDir, 0777);
|
@mkdir($invoiceDir, 0777);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_dir($invoiceDir)) {
|
if (!is_dir($invoiceDir)) {
|
||||||
$this->flashError(sprintf('Invoice directory "%s" is not existing and could not be created.', $dir));
|
$uploadError = 'error.directory_missing';
|
||||||
$canUpload = false;
|
$canUpload = false;
|
||||||
} elseif (!is_writable($invoiceDir)) {
|
} elseif (!is_writable($invoiceDir)) {
|
||||||
$this->flashError(sprintf('Invoice directory "%s" cannot be written.', $dir));
|
$uploadError = 'error.directory_protected';
|
||||||
$canUpload = false;
|
$canUpload = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +398,10 @@ final class InvoiceController extends AbstractController
|
|||||||
'Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()',
|
'Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()',
|
||||||
$originalFilename
|
$originalFilename
|
||||||
);
|
);
|
||||||
$newFilename = $safeFilename . '.' . $uploadedFile->guessExtension();
|
|
||||||
|
$extension = $uploadedFile->guessExtension();
|
||||||
|
|
||||||
|
$newFilename = substr($safeFilename, 0, 20) . '.' . $extension;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$uploadedFile->move($invoiceDir, $newFilename);
|
$uploadedFile->move($invoiceDir, $newFilename);
|
||||||
@@ -383,12 +415,56 @@ final class InvoiceController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('invoice/document_upload.html.twig', [
|
return $this->render('invoice/document_upload.html.twig', [
|
||||||
|
'error_replacer' => ['%max%' => $event->getMaximumAllowedDocuments(), '%dir%' => $dir],
|
||||||
|
'upload_error' => $uploadError,
|
||||||
|
'can_upload' => $canUpload,
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
'documents' => $this->service->getDocuments(true),
|
'documents' => $documents,
|
||||||
'baseDirectory' => $projectDirectory . DIRECTORY_SEPARATOR,
|
'baseDirectory' => $projectDirectory . DIRECTORY_SEPARATOR,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route(path="/document/{id}/delete/{token}", name="invoice_document_delete", methods={"GET", "POST"})
|
||||||
|
* @Security("is_granted('manage_invoice_template')")
|
||||||
|
*/
|
||||||
|
public function deleteDocument(string $id, string $token, CsrfTokenManagerInterface $csrfTokenManager, InvoiceDocumentRepository $documentRepository): Response
|
||||||
|
{
|
||||||
|
$document = $documentRepository->findByName($id);
|
||||||
|
if ($document === null) {
|
||||||
|
throw $this->createNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$csrfTokenManager->isTokenValid(new CsrfToken('invoice.delete_document', $token))) {
|
||||||
|
$this->flashError('action.csrf.error');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_invoice_document_upload');
|
||||||
|
}
|
||||||
|
|
||||||
|
$csrfTokenManager->refreshToken('invoice.delete_document');
|
||||||
|
|
||||||
|
foreach ($documentRepository->findBuiltIn() as $document) {
|
||||||
|
if ($document->getId() === $id) {
|
||||||
|
throw new \Exception('Document is built-in and cannot be deleted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->templateRepository->findAll() as $template) {
|
||||||
|
if ($template->getRenderer() === $id) {
|
||||||
|
throw new \Exception('Document is used and cannot be deleted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$documentRepository->remove($document);
|
||||||
|
$this->flashSuccess('action.delete.success');
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$this->flashDeleteException($ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_invoice_document_upload');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route(path="/template/create", name="admin_invoice_template_create", methods={"GET", "POST"})
|
* @Route(path="/template/create", name="admin_invoice_template_create", methods={"GET", "POST"})
|
||||||
* @Route(path="/template/create/{id}", name="admin_invoice_template_copy", methods={"GET", "POST"})
|
* @Route(path="/template/create/{id}", name="admin_invoice_template_copy", methods={"GET", "POST"})
|
||||||
|
|||||||
65
src/Event/InvoiceDocumentsEvent.php
Normal file
65
src/Event/InvoiceDocumentsEvent.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Event;
|
||||||
|
|
||||||
|
use App\Entity\InvoiceDocument;
|
||||||
|
use Symfony\Contracts\EventDispatcher\Event;
|
||||||
|
|
||||||
|
final class InvoiceDocumentsEvent extends Event
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var InvoiceDocument[]
|
||||||
|
*/
|
||||||
|
private $documents;
|
||||||
|
/**
|
||||||
|
* Maximum amount of allowed invoice documents.
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $maximum = 99;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InvoiceDocument[] $documents
|
||||||
|
*/
|
||||||
|
public function __construct(array $documents)
|
||||||
|
{
|
||||||
|
$this->documents = $documents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return InvoiceDocument[]
|
||||||
|
*/
|
||||||
|
public function getInvoiceDocuments(): array
|
||||||
|
{
|
||||||
|
return $this->documents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addInvoiceDocuments(InvoiceDocument $document): void
|
||||||
|
{
|
||||||
|
$this->documents[] = $document;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param InvoiceDocument[] $documents
|
||||||
|
*/
|
||||||
|
public function setInvoiceDocuments(array $documents): void
|
||||||
|
{
|
||||||
|
$this->documents = $documents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMaximumAllowedDocuments(int $max): void
|
||||||
|
{
|
||||||
|
$this->maximum = $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMaximumAllowedDocuments(): int
|
||||||
|
{
|
||||||
|
return $this->maximum;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/EventSubscriber/Actions/InvoiceDocumentSubscriber.php
Normal file
37
src/EventSubscriber/Actions/InvoiceDocumentSubscriber.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\EventSubscriber\Actions;
|
||||||
|
|
||||||
|
use App\Entity\InvoiceDocument;
|
||||||
|
use App\Event\PageActionsEvent;
|
||||||
|
|
||||||
|
class InvoiceDocumentSubscriber extends AbstractActionsSubscriber
|
||||||
|
{
|
||||||
|
public static function getActionName(): string
|
||||||
|
{
|
||||||
|
return 'invoice_document';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onActions(PageActionsEvent $event): void
|
||||||
|
{
|
||||||
|
$payload = $event->getPayload();
|
||||||
|
|
||||||
|
/** @var InvoiceDocument|null $document */
|
||||||
|
$document = $payload['document'];
|
||||||
|
|
||||||
|
if ($document === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isGranted('manage_invoice_template')) {
|
||||||
|
$event->addDelete($this->path('invoice_document_delete', ['id' => $document->getId(), 'token' => $payload['token']]), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,9 +21,6 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
|||||||
|
|
||||||
class InvoiceDocumentUploadForm extends AbstractType
|
class InvoiceDocumentUploadForm extends AbstractType
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @var InvoiceDocumentRepository
|
|
||||||
*/
|
|
||||||
private $repository;
|
private $repository;
|
||||||
|
|
||||||
public function __construct(InvoiceDocumentRepository $repository)
|
public function __construct(InvoiceDocumentRepository $repository)
|
||||||
@@ -36,6 +33,12 @@ class InvoiceDocumentUploadForm extends AbstractType
|
|||||||
*/
|
*/
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
{
|
{
|
||||||
|
$mimetypes = [
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet',
|
||||||
|
];
|
||||||
|
|
||||||
$builder
|
$builder
|
||||||
->add('document', FileType::class, [
|
->add('document', FileType::class, [
|
||||||
'label' => 'label.invoice_renderer',
|
'label' => 'label.invoice_renderer',
|
||||||
@@ -43,13 +46,12 @@ class InvoiceDocumentUploadForm extends AbstractType
|
|||||||
'help' => 'help.upload',
|
'help' => 'help.upload',
|
||||||
'mapped' => false,
|
'mapped' => false,
|
||||||
'required' => true,
|
'required' => true,
|
||||||
|
'attr' => [
|
||||||
|
'accept' => implode(',', $mimetypes)
|
||||||
|
],
|
||||||
'constraints' => [
|
'constraints' => [
|
||||||
new File([
|
new File([
|
||||||
'mimeTypes' => [
|
'mimeTypes' => $mimetypes,
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
||||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
||||||
'application/vnd.oasis.opendocument.spreadsheet',
|
|
||||||
],
|
|
||||||
'mimeTypesMessage' => 'This file type is not allowed',
|
'mimeTypesMessage' => 'This file type is not allowed',
|
||||||
]),
|
]),
|
||||||
new Callback([$this, 'validateDocument'])
|
new Callback([$this, 'validateDocument'])
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ final class InvoiceDocumentRepository
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function remove(InvoiceDocument $invoiceDocument): void
|
||||||
|
{
|
||||||
|
@unlink($invoiceDocument->getFilename());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated since 1.10 - will be removed with 2.0 - use getUploadDirectory() instead
|
* @deprecated since 1.10 - will be removed with 2.0 - use getUploadDirectory() instead
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -33,3 +33,9 @@
|
|||||||
{% set event = actions(app.user, 'invoice_template', view, {'template': template, 'token': csrf_token('invoice.delete_template')}) %}
|
{% set event = actions(app.user, 'invoice_template', view, {'template': template, 'token': csrf_token('invoice.delete_template')}) %}
|
||||||
{{ widgets.table_actions(event.actions) }}
|
{{ widgets.table_actions(event.actions) }}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro invoice_document(document, view) %}
|
||||||
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
|
{% set event = actions(app.user, 'invoice_document', view, {'document': document, 'token': csrf_token('invoice.delete_document')}) %}
|
||||||
|
{{ widgets.table_actions(event.actions) }}
|
||||||
|
{% endmacro %}
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
{% extends app.request.xmlHttpRequest ? 'form.html.twig' : 'base.html.twig' %}
|
{% extends 'base.html.twig' %}
|
||||||
{% import "invoice/actions.html.twig" as actions %}
|
{% import "invoice/actions.html.twig" as actions %}
|
||||||
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
|
|
||||||
{% block page_title %}{{ 'admin_invoice_template.title'|trans }}{% endblock %}
|
{% block page_title %}{{ 'label.invoice_renderer'|trans({}, 'invoice-renderer') }}{% endblock %}
|
||||||
{% block page_actions %}{{ actions.invoice_upload('index') }}{% endblock %}
|
{% block page_actions %}{{ actions.invoice_upload('index') }}{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
{% if form is not null %}
|
{% if can_upload and form is not null %}
|
||||||
{% form_theme form '@AdminLTE/layout/form-theme-horizontal.html.twig' %}
|
{% form_theme form '@AdminLTE/layout/form-theme-horizontal.html.twig' %}
|
||||||
{% set formEditTemplate = app.request.xmlHttpRequest ? 'default/_form_modal.html.twig' : 'default/_form.html.twig' %}
|
|
||||||
{% set formOptions = {
|
{% set formOptions = {
|
||||||
'title': 'upload'|trans,
|
'title': 'upload'|trans,
|
||||||
'form': form,
|
'form': form,
|
||||||
'back': path('admin_invoice_template')
|
'back': false,
|
||||||
|
'reset': false
|
||||||
} %}
|
} %}
|
||||||
|
|
||||||
{% embed formEditTemplate with formOptions %}
|
{% embed 'default/_form.html.twig' with formOptions %}
|
||||||
{% block form_body %}
|
{% block form_body %}
|
||||||
{{ form_row(form.document) }}
|
{{ form_row(form.document) }}
|
||||||
{{ form_widget(form) }}
|
{{ form_widget(form) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endembed %}
|
{% endembed %}
|
||||||
|
{% elseif upload_error is not null %}
|
||||||
|
{{ widgets.callout('warning', upload_error|trans(error_replacer)) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if documents|length > 0 %}
|
{% if documents|length > 0 %}
|
||||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'documents': documents} %}
|
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'documents': documents} %}
|
||||||
{% import "project/actions.html.twig" as actions %}
|
{% import "invoice/actions.html.twig" as actions %}
|
||||||
{% import "macros/widgets.html.twig" as widgets %}
|
{% import "macros/widgets.html.twig" as widgets %}
|
||||||
{% block box_title %}{{ 'label.invoice_renderer'|trans({}, 'invoice-renderer') }}{% endblock %}
|
{% block box_title %}{{ 'label.invoice_renderer'|trans({}, 'invoice-renderer') }}{% endblock %}
|
||||||
{% block box_attributes %}
|
{% block box_attributes %}
|
||||||
@@ -33,12 +36,30 @@
|
|||||||
{% block box_body_class %}no-padding{% endblock %}
|
{% block box_body_class %}no-padding{% endblock %}
|
||||||
{% block box_body %}
|
{% block box_body %}
|
||||||
<table class="table table-hover dataTable">
|
<table class="table table-hover dataTable">
|
||||||
<tbody>
|
<thead>
|
||||||
{% for document in documents %}
|
<tr>
|
||||||
|
<th>{{ 'file'|trans }}</th>
|
||||||
|
<th>{{ 'updated_at'|trans }}</th>
|
||||||
|
<th>{{ 'label.template'|trans }}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for config in documents %}
|
||||||
|
{% set document = config.document %}
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 33%">{{ document.id }}</td>
|
|
||||||
<td style="width: 33%">{{ document.lastChange|date }}</td>
|
|
||||||
<td>{{ document.name }}</td>
|
<td>{{ document.name }}</td>
|
||||||
|
<td>{{ document.lastChange|date }}</td>
|
||||||
|
<td>
|
||||||
|
{% if config.template is not null %}
|
||||||
|
{{ config.template.name }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="actions">
|
||||||
|
{% if not config.used %}
|
||||||
|
{{ actions.invoice_document(document, 'index') }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
40
tests/Event/InvoiceDocumentsEventTest.php
Normal file
40
tests/Event/InvoiceDocumentsEventTest.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Tests\Event;
|
||||||
|
|
||||||
|
use App\Entity\InvoiceDocument;
|
||||||
|
use App\Event\InvoiceDocumentsEvent;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\Event\InvoiceDocumentsEvent
|
||||||
|
*/
|
||||||
|
class InvoiceDocumentsEventTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testDefaultValues()
|
||||||
|
{
|
||||||
|
$sut = new InvoiceDocumentsEvent([]);
|
||||||
|
|
||||||
|
self::assertEquals([], $sut->getInvoiceDocuments());
|
||||||
|
self::assertEquals(99, $sut->getMaximumAllowedDocuments());
|
||||||
|
|
||||||
|
$sut->setMaximumAllowedDocuments(10);
|
||||||
|
self::assertEquals(10, $sut->getMaximumAllowedDocuments());
|
||||||
|
|
||||||
|
$file = new \SplFileInfo(__FILE__);
|
||||||
|
$document = new InvoiceDocument($file);
|
||||||
|
|
||||||
|
$sut->setInvoiceDocuments([$document]);
|
||||||
|
self::assertEquals([$document], $sut->getInvoiceDocuments());
|
||||||
|
|
||||||
|
$sut->addInvoiceDocuments(new InvoiceDocument($file));
|
||||||
|
self::assertCount(2, $sut->getInvoiceDocuments());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,13 +9,26 @@
|
|||||||
|
|
||||||
namespace App\Tests\EventSubscriber\Actions;
|
namespace App\Tests\EventSubscriber\Actions;
|
||||||
|
|
||||||
|
use App\EventSubscriber\Actions\AbstractActionsSubscriber;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\EventSubscriber\Actions\AbstractActionsSubscriber
|
* @covers \App\EventSubscriber\Actions\AbstractActionsSubscriber
|
||||||
*/
|
*/
|
||||||
abstract class AbstractActionsSubscriberTest extends TestCase
|
abstract class AbstractActionsSubscriberTest extends TestCase
|
||||||
{
|
{
|
||||||
|
protected function createSubscriber(string $className, ...$grants): AbstractActionsSubscriber
|
||||||
|
{
|
||||||
|
$auth = $this->createMock(AuthorizationCheckerInterface::class);
|
||||||
|
$auth->method('isGranted')->willReturnOnConsecutiveCalls(...$grants);
|
||||||
|
$router = $this->createMock(UrlGeneratorInterface::class);
|
||||||
|
$router->method('generate')->willReturnArgument(0);
|
||||||
|
|
||||||
|
return new $className($auth, $router);
|
||||||
|
}
|
||||||
|
|
||||||
protected function assertGetSubscribedEvent(string $className, string $name)
|
protected function assertGetSubscribedEvent(string $className, string $name)
|
||||||
{
|
{
|
||||||
$this->assertTrue(method_exists($className, 'getSubscribedEvents'));
|
$this->assertTrue(method_exists($className, 'getSubscribedEvents'));
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Tests\EventSubscriber\Actions;
|
||||||
|
|
||||||
|
use App\Entity\InvoiceDocument;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Event\PageActionsEvent;
|
||||||
|
use App\EventSubscriber\Actions\InvoiceDocumentSubscriber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \App\EventSubscriber\Actions\InvoiceDocumentSubscriber
|
||||||
|
*/
|
||||||
|
class InvoiceDocumentSubscriberTest extends AbstractActionsSubscriberTest
|
||||||
|
{
|
||||||
|
public function testEventName()
|
||||||
|
{
|
||||||
|
$this->assertGetSubscribedEvent(InvoiceDocumentSubscriber::class, 'invoice_document');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testActions()
|
||||||
|
{
|
||||||
|
$sut = $this->createSubscriber(InvoiceDocumentSubscriber::class, true);
|
||||||
|
|
||||||
|
$event = new PageActionsEvent(new User(), ['document' => new InvoiceDocument(new \SplFileInfo(__FILE__)), 'token' => uniqid()], 'invoice_document', 'index');
|
||||||
|
$sut->onActions($event);
|
||||||
|
|
||||||
|
$actions = $event->getActions();
|
||||||
|
self::assertGreaterThanOrEqual(1, \count($actions));
|
||||||
|
self::assertArrayHasKey('trash', $actions);
|
||||||
|
self::assertEquals('invoice_document_delete', $actions['trash']['url']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -250,6 +250,14 @@
|
|||||||
<source>error.too_many_entries</source>
|
<source>error.too_many_entries</source>
|
||||||
<target>Die Anfrage konnte nicht verarbeitet werden. Es wurden zu viele Ergebnisse gefunden.</target>
|
<target>Die Anfrage konnte nicht verarbeitet werden. Es wurden zu viele Ergebnisse gefunden.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="GzWKgBk" resname="error.directory_missing">
|
||||||
|
<source>error.directory_missing</source>
|
||||||
|
<target>Das Verzeichnis "%dir%" existiert nicht und konnte auch nicht erstellt werden.</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="C2TGff0" resname="error.directory_protected">
|
||||||
|
<source>error.directory_protected</source>
|
||||||
|
<target>Das Verzeichnis "%dir%" ist schreibgeschützt.</target>
|
||||||
|
</trans-unit>
|
||||||
<!--
|
<!--
|
||||||
General labels
|
General labels
|
||||||
-->
|
-->
|
||||||
@@ -1291,6 +1299,14 @@
|
|||||||
<source>label.hours_24</source>
|
<source>label.hours_24</source>
|
||||||
<target>24 Stunden</target>
|
<target>24 Stunden</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="PCEFPeg" resname="updated_at">
|
||||||
|
<source>Updated at</source>
|
||||||
|
<target>Aktualisiert am</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="nlwMf5w" resname="invoice_document.max_reached">
|
||||||
|
<source>invoice_document.max_reached</source>
|
||||||
|
<target>Maximale Anzahl von %max% Rechnungsdokumenten erreicht. Um weitere hinzufügen müssen Sie zunächst eins löschen.</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
@@ -250,6 +250,14 @@
|
|||||||
<source>error.too_many_entries</source>
|
<source>error.too_many_entries</source>
|
||||||
<target>The request could not be processed. Too many results were found.</target>
|
<target>The request could not be processed. Too many results were found.</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="GzWKgBk" resname="error.directory_missing">
|
||||||
|
<source>error.directory_missing</source>
|
||||||
|
<target>Directory "%dir%" is not existing and could not be created.</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="C2TGff0" resname="error.directory_protected">
|
||||||
|
<source>error.directory_protected</source>
|
||||||
|
<target>Directory "%dir%" is write protected.</target>
|
||||||
|
</trans-unit>
|
||||||
<!--
|
<!--
|
||||||
General labels
|
General labels
|
||||||
-->
|
-->
|
||||||
@@ -1291,6 +1299,14 @@
|
|||||||
<source>label.hours_24</source>
|
<source>label.hours_24</source>
|
||||||
<target>24 hours</target>
|
<target>24 hours</target>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="PCEFPeg" resname="updated_at">
|
||||||
|
<source>Updated at</source>
|
||||||
|
<target>Updated at</target>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="nlwMf5w" resname="invoice_document.max_reached">
|
||||||
|
<source>invoice_document.max_reached</source>
|
||||||
|
<target>Reached maximum amount of %max% invoice documents. You can add more after removing one.</target>
|
||||||
|
</trans-unit>
|
||||||
</body>
|
</body>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|||||||
Reference in New Issue
Block a user