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\Invoice;
|
||||
use App\Entity\InvoiceTemplate;
|
||||
use App\Event\InvoiceDocumentsEvent;
|
||||
use App\Export\Spreadsheet\AnnotatedObjectExporter;
|
||||
use App\Export\Spreadsheet\Writer\BinaryFileResponseWriter;
|
||||
use App\Export\Spreadsheet\Writer\XlsxWriter;
|
||||
@@ -338,17 +339,45 @@ final class InvoiceController extends AbstractController
|
||||
$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;
|
||||
$uploadError = null;
|
||||
|
||||
if (\count($documents) >= $event->getMaximumAllowedDocuments()) {
|
||||
$uploadError = 'invoice_document.max_reached';
|
||||
$canUpload = false;
|
||||
}
|
||||
|
||||
if (!file_exists($invoiceDir)) {
|
||||
@mkdir($invoiceDir, 0777);
|
||||
}
|
||||
|
||||
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;
|
||||
} elseif (!is_writable($invoiceDir)) {
|
||||
$this->flashError(sprintf('Invoice directory "%s" cannot be written.', $dir));
|
||||
$uploadError = 'error.directory_protected';
|
||||
$canUpload = false;
|
||||
}
|
||||
|
||||
@@ -369,7 +398,10 @@ final class InvoiceController extends AbstractController
|
||||
'Any-Latin; Latin-ASCII; [^A-Za-z0-9_] remove; Lower()',
|
||||
$originalFilename
|
||||
);
|
||||
$newFilename = $safeFilename . '.' . $uploadedFile->guessExtension();
|
||||
|
||||
$extension = $uploadedFile->guessExtension();
|
||||
|
||||
$newFilename = substr($safeFilename, 0, 20) . '.' . $extension;
|
||||
|
||||
try {
|
||||
$uploadedFile->move($invoiceDir, $newFilename);
|
||||
@@ -383,12 +415,56 @@ final class InvoiceController extends AbstractController
|
||||
}
|
||||
|
||||
return $this->render('invoice/document_upload.html.twig', [
|
||||
'error_replacer' => ['%max%' => $event->getMaximumAllowedDocuments(), '%dir%' => $dir],
|
||||
'upload_error' => $uploadError,
|
||||
'can_upload' => $canUpload,
|
||||
'form' => $form->createView(),
|
||||
'documents' => $this->service->getDocuments(true),
|
||||
'documents' => $documents,
|
||||
'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/{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
|
||||
{
|
||||
/**
|
||||
* @var InvoiceDocumentRepository
|
||||
*/
|
||||
private $repository;
|
||||
|
||||
public function __construct(InvoiceDocumentRepository $repository)
|
||||
@@ -36,6 +33,12 @@ class InvoiceDocumentUploadForm extends AbstractType
|
||||
*/
|
||||
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
|
||||
->add('document', FileType::class, [
|
||||
'label' => 'label.invoice_renderer',
|
||||
@@ -43,13 +46,12 @@ class InvoiceDocumentUploadForm extends AbstractType
|
||||
'help' => 'help.upload',
|
||||
'mapped' => false,
|
||||
'required' => true,
|
||||
'attr' => [
|
||||
'accept' => implode(',', $mimetypes)
|
||||
],
|
||||
'constraints' => [
|
||||
new File([
|
||||
'mimeTypes' => [
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.oasis.opendocument.spreadsheet',
|
||||
],
|
||||
'mimeTypes' => $mimetypes,
|
||||
'mimeTypesMessage' => 'This file type is not allowed',
|
||||
]),
|
||||
new Callback([$this, 'validateDocument'])
|
||||
|
||||
@@ -50,6 +50,14 @@ final class InvoiceDocumentRepository
|
||||
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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user