WIP: Implement document handling
This commit is contained in:
@@ -18,6 +18,12 @@ oneup_uploader:
|
|||||||
namer: app.upload_namer
|
namer: app.upload_namer
|
||||||
storage:
|
storage:
|
||||||
directory: '%kernel.project_dir%/uploads/contract'
|
directory: '%kernel.project_dir%/uploads/contract'
|
||||||
|
document:
|
||||||
|
frontend: dropzone
|
||||||
|
use_orphanage: true
|
||||||
|
namer: app.upload_namer
|
||||||
|
storage:
|
||||||
|
directory: '%kernel.project_dir%/uploads/document'
|
||||||
chunks:
|
chunks:
|
||||||
maxage: 86400
|
maxage: 86400
|
||||||
storage:
|
storage:
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20231012074633 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE assignment_upload (assignment_id INT NOT NULL, upload_id INT NOT NULL, INDEX IDX_2F9CC602D19302F8 (assignment_id), INDEX IDX_2F9CC602CCCFBA31 (upload_id), PRIMARY KEY(assignment_id, upload_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('ALTER TABLE assignment_upload ADD CONSTRAINT FK_2F9CC602D19302F8 FOREIGN KEY (assignment_id) REFERENCES assignment (id) ON DELETE CASCADE');
|
||||||
|
$this->addSql('ALTER TABLE assignment_upload ADD CONSTRAINT FK_2F9CC602CCCFBA31 FOREIGN KEY (upload_id) REFERENCES upload (id) ON DELETE CASCADE');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE assignment_upload DROP FOREIGN KEY FK_2F9CC602D19302F8');
|
||||||
|
$this->addSql('ALTER TABLE assignment_upload DROP FOREIGN KEY FK_2F9CC602CCCFBA31');
|
||||||
|
$this->addSql('DROP TABLE assignment_upload');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20231012082751 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE upload ADD display_name VARCHAR(255) DEFAULT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE upload DROP display_name');
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -9,7 +9,7 @@ use Symfony\Component\HttpFoundation\Request;
|
|||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
class BpnDateController extends AbstractController
|
class DestinationController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly DestinationRepository $bpnDateRepository)
|
public function __construct(private readonly DestinationRepository $bpnDateRepository)
|
||||||
{}
|
{}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Admin\Autocomplete;
|
||||||
|
|
||||||
|
use App\Repository\UploadRepository;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
|
class DocumentController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(private readonly UploadRepository $uploadRepository)
|
||||||
|
{}
|
||||||
|
|
||||||
|
#[Route('/admin/autocomplete/document', name: 'app_admin_autocomplete_document')]
|
||||||
|
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||||
|
public function index(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$query = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
$queryString = $query['search'];
|
||||||
|
} catch (\JsonException $e) {
|
||||||
|
throw $this->createNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$dates = $this->uploadRepository->getAutocompletionData($queryString);
|
||||||
|
|
||||||
|
return $this->json($dates);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Admin\Document;
|
||||||
|
|
||||||
|
use App\Entity\Upload;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
|
class DeleteController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly LoggerInterface $logger
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/admin/document/delete/{uuid}', name: 'app_admin_document_delete')]
|
||||||
|
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||||
|
#[IsGranted('DELETE', subject: 'document')]
|
||||||
|
public function index(Upload $document): Response
|
||||||
|
{
|
||||||
|
$this->entityManager->remove($document);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Das Dokument wurde gelöscht');
|
||||||
|
$this->logger->info('Delete document', [
|
||||||
|
'document' => $document->getOriginalFilename(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_admin_document_index');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,17 +2,40 @@
|
|||||||
|
|
||||||
namespace App\Controller\Admin\Document;
|
namespace App\Controller\Admin\Document;
|
||||||
|
|
||||||
|
use App\Repository\UploadRepository;
|
||||||
|
use Knp\Component\Pager\PaginatorInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
class IndexController extends AbstractController
|
class IndexController extends AbstractController
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UploadRepository $uploadRepository,
|
||||||
|
private readonly PaginatorInterface $paginator
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/admin/document', name: 'app_admin_document_index')]
|
#[Route('/admin/document', name: 'app_admin_document_index')]
|
||||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||||
public function index(): Response
|
public function index(Request $request): Response
|
||||||
{
|
{
|
||||||
return $this->render('admin/document/index.html.twig');
|
$query = $this->uploadRepository->getDocumentListQuery();
|
||||||
|
|
||||||
|
$pagination = $this->paginator->paginate(
|
||||||
|
$query,
|
||||||
|
$request->query->getInt('page', 1),
|
||||||
|
10,
|
||||||
|
[
|
||||||
|
'defaultSortFieldName' => 'upload.originalFilename',
|
||||||
|
'defaultSortDirection' => 'asc',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->render('admin/document/index.html.twig', [
|
||||||
|
'pagination' => $pagination,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Admin\Document;
|
||||||
|
|
||||||
|
use App\Entity\Upload;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Model\AjaxModalResponseDto;
|
||||||
|
use App\Service\Upload\UploadHandler;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
|
class ReplaceController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UploadHandler $uploadHandler,
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly LoggerInterface $logger
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/admin/document/replace/{uuid}', name: 'app_admin_document_replace')]
|
||||||
|
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||||
|
public function index(Upload $upload, Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
// Handle upload independently from form submission to avoid issues with failing validation
|
||||||
|
$uploadSession = $this->uploadHandler->getUploadSession();
|
||||||
|
if (true === $request->isMethod('POST') && 0 < $uploadSession->getCount()) {
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->getUser();
|
||||||
|
Upload::fromUploadDto($uploadSession->getUploads()->first(), $user, Upload::TYPE_DOCUMENT, $upload);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$this->uploadHandler->moveUploadSessionFilesFromOrphanage(Upload::TYPE_DOCUMENT, $uploadSession);
|
||||||
|
$this->uploadHandler->destroyUploadSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = new AjaxModalResponseDto();
|
||||||
|
$formAction = $this->generateUrl('app_admin_document_replace', ['uuid' => $upload->getUuid()]);
|
||||||
|
|
||||||
|
$form = $this
|
||||||
|
->createFormBuilder(null, ['action' => $formAction, 'ajax_submit' => true])
|
||||||
|
->getForm()
|
||||||
|
;
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$this->addFlash('success', 'Das Dokument wurden ersetzt');
|
||||||
|
$this->logger->info('Replace document');
|
||||||
|
|
||||||
|
$response->setCloseAndRedirect($this->generateUrl('app_admin_document_index'));
|
||||||
|
} else {
|
||||||
|
$response->setContent($this->renderView('admin/document/replace.html.twig', [
|
||||||
|
'form' => $form,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json($response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller\Admin\Document;
|
||||||
|
|
||||||
|
use App\Entity\Upload;
|
||||||
|
use App\Entity\User;
|
||||||
|
use App\Model\AjaxModalResponseDto;
|
||||||
|
use App\Service\Upload\UploadHandler;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
|
||||||
|
class UploadController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly UploadHandler $uploadHandler,
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly LoggerInterface $logger
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/admin/document/upload', name: 'app_admin_document_upload')]
|
||||||
|
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||||
|
public function index(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
// Handle upload independently from form submission to avoid issues with failing validation
|
||||||
|
$uploadSession = $this->uploadHandler->getUploadSession();
|
||||||
|
if (true === $request->isMethod('POST') && 0 < $uploadSession->getCount()) {
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->getUser();
|
||||||
|
foreach ($uploadSession->getUploads() as $upload) {
|
||||||
|
$document = Upload::fromUploadDto($upload, $user, Upload::TYPE_DOCUMENT);
|
||||||
|
$this->entityManager->persist($document);
|
||||||
|
}
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$this->uploadHandler->moveUploadSessionFilesFromOrphanage(Upload::TYPE_DOCUMENT, $uploadSession);
|
||||||
|
$this->uploadHandler->destroyUploadSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = new AjaxModalResponseDto();
|
||||||
|
$formAction = $this->generateUrl('app_admin_document_upload');
|
||||||
|
|
||||||
|
$form = $this
|
||||||
|
->createFormBuilder(null, ['action' => $formAction, 'ajax_submit' => true])
|
||||||
|
->getForm()
|
||||||
|
;
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$this->addFlash('success', 'Das/die Dokument/e wurden hochgeladen');
|
||||||
|
$this->logger->info('Upload document');
|
||||||
|
|
||||||
|
$response->setCloseAndRedirect($this->generateUrl('app_admin_document_index'));
|
||||||
|
} else {
|
||||||
|
$response->setContent($this->renderView('admin/document/upload.html.twig', [
|
||||||
|
'form' => $form,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->json($response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,6 +76,9 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
#[ORM\ManyToMany(targetEntity: Teamer::class, mappedBy: 'bookmarks')]
|
#[ORM\ManyToMany(targetEntity: Teamer::class, mappedBy: 'bookmarks')]
|
||||||
private Collection $teamers;
|
private Collection $teamers;
|
||||||
|
|
||||||
|
#[ORM\ManyToMany(targetEntity: Upload::class)]
|
||||||
|
private Collection $documents;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->uuid = Uuid::v4();
|
$this->uuid = Uuid::v4();
|
||||||
@@ -84,6 +87,7 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
$this->dispositions = new ArrayCollection();
|
$this->dispositions = new ArrayCollection();
|
||||||
$this->teamers = new ArrayCollection();
|
$this->teamers = new ArrayCollection();
|
||||||
$this->benefits = "Anreise im E&P Reisebus\nUnterkunft\nVerpflegung\nSkipass\n";
|
$this->benefits = "Anreise im E&P Reisebus\nUnterkunft\nVerpflegung\nSkipass\n";
|
||||||
|
$this->documents = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@@ -344,4 +348,28 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Upload>
|
||||||
|
*/
|
||||||
|
public function getDocuments(): Collection
|
||||||
|
{
|
||||||
|
return $this->documents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addDocument(Upload $document): static
|
||||||
|
{
|
||||||
|
if (!$this->documents->contains($document)) {
|
||||||
|
$this->documents->add($document);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeDocument(Upload $document): static
|
||||||
|
{
|
||||||
|
$this->documents->removeElement($document);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-2
@@ -19,6 +19,7 @@ class Upload implements BlameableEntityInterface, TimestampableEntityInterface
|
|||||||
public const TYPE_CERTIFICATE = 'certificate';
|
public const TYPE_CERTIFICATE = 'certificate';
|
||||||
public const TYPE_CONTRACT = 'contract';
|
public const TYPE_CONTRACT = 'contract';
|
||||||
public const TYPE_INVOICE = 'invoice';
|
public const TYPE_INVOICE = 'invoice';
|
||||||
|
public const TYPE_DOCUMENT = 'document';
|
||||||
|
|
||||||
public const STATUS_NEW = 'new';
|
public const STATUS_NEW = 'new';
|
||||||
public const STATUS_IN_PROCESS = 'in_process';
|
public const STATUS_IN_PROCESS = 'in_process';
|
||||||
@@ -46,6 +47,9 @@ class Upload implements BlameableEntityInterface, TimestampableEntityInterface
|
|||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
private ?string $originalFilename = null;
|
private ?string $originalFilename = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private ?string $displayName = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private ?int $size = null;
|
private ?int $size = null;
|
||||||
|
|
||||||
@@ -60,9 +64,9 @@ class Upload implements BlameableEntityInterface, TimestampableEntityInterface
|
|||||||
$this->uuid = Uuid::v4();
|
$this->uuid = Uuid::v4();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fromUploadDto(UploadDto $uploadDto, User $owner, string $type): static
|
public static function fromUploadDto(UploadDto $uploadDto, User $owner, string $type, Upload $instance = null): static
|
||||||
{
|
{
|
||||||
$instance = new static();
|
$instance = $instance ?? new static();
|
||||||
$instance
|
$instance
|
||||||
->setFilename($uploadDto->getFilename())
|
->setFilename($uploadDto->getFilename())
|
||||||
->setOriginalFilename($uploadDto->getOriginalFilename())
|
->setOriginalFilename($uploadDto->getOriginalFilename())
|
||||||
@@ -133,6 +137,18 @@ class Upload implements BlameableEntityInterface, TimestampableEntityInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDisplayName(): ?string
|
||||||
|
{
|
||||||
|
return $this->displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDisplayName(?string $displayName): static
|
||||||
|
{
|
||||||
|
$this->displayName = $displayName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getSize(): ?int
|
public function getSize(): ?int
|
||||||
{
|
{
|
||||||
return $this->size;
|
return $this->size;
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ use App\Entity\Assignment;
|
|||||||
use App\Entity\Destination;
|
use App\Entity\Destination;
|
||||||
use App\Entity\Fee;
|
use App\Entity\Fee;
|
||||||
use App\Entity\JobProfile;
|
use App\Entity\JobProfile;
|
||||||
|
use App\Entity\Upload;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
@@ -87,6 +89,19 @@ class AssignmentType extends AbstractType
|
|||||||
;
|
;
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
->add('documents', CollectionType::class, [
|
||||||
|
'label' => 'Dokumente',
|
||||||
|
'entry_type' => AutocompleteEntityType::class,
|
||||||
|
'entry_options' => [
|
||||||
|
'class' => Upload::class,
|
||||||
|
'endpoint_route' => 'app_admin_autocomplete_document',
|
||||||
|
'label_property' => 'originalFilename',
|
||||||
|
'placeholder' => 'Dokument...',
|
||||||
|
],
|
||||||
|
'allow_add' => true,
|
||||||
|
'allow_delete' => true,
|
||||||
|
'by_reference' => false,
|
||||||
|
])
|
||||||
->add('remarks', TextareaType::class, [
|
->add('remarks', TextareaType::class, [
|
||||||
'label' => 'Anmerkungen',
|
'label' => 'Anmerkungen',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
use App\Entity\Upload;
|
use App\Entity\Upload;
|
||||||
|
use App\Repository\Traits\QueryHelperTrait;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,51 +18,54 @@ use Doctrine\Persistence\ManagerRegistry;
|
|||||||
*/
|
*/
|
||||||
class UploadRepository extends ServiceEntityRepository
|
class UploadRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
|
use QueryHelperTrait;
|
||||||
|
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
{
|
{
|
||||||
parent::__construct($registry, Upload::class);
|
parent::__construct($registry, Upload::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(Upload $entity, bool $flush = false): void
|
public function getDocumentListQuery(): Query
|
||||||
{
|
{
|
||||||
$this->getEntityManager()->persist($entity);
|
$qb = $this->createQueryBuilder('upload');
|
||||||
|
|
||||||
if ($flush) {
|
return $qb
|
||||||
$this->getEntityManager()->flush();
|
->where($qb->expr()->eq('upload.type', ':type'))
|
||||||
}
|
->setParameter('type', Upload::TYPE_DOCUMENT)
|
||||||
|
->getQuery()
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function remove(Upload $entity, bool $flush = false): void
|
public function getAutocompletionData(string $search): array
|
||||||
{
|
{
|
||||||
$this->getEntityManager()->remove($entity);
|
$qb = $this->createQueryBuilder('upload');
|
||||||
|
|
||||||
if ($flush) {
|
$documents = $qb
|
||||||
$this->getEntityManager()->flush();
|
->where($qb->expr()->orX(
|
||||||
|
$qb->expr()->like('upload.originalFilename', ':filename'),
|
||||||
|
$qb->expr()->like('upload.displayName', ':filename')
|
||||||
|
))
|
||||||
|
->setParameter('filename', '%'.$this->escapeLikeWildcards($search).'%')
|
||||||
|
->orderBy('upload.originalFilename', 'ASC')
|
||||||
|
->getQuery()
|
||||||
|
->getResult()
|
||||||
|
;
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
[
|
||||||
|
'value' => '',
|
||||||
|
'text' => '...',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($documents as $document) {
|
||||||
|
/** @var Upload $document */
|
||||||
|
$data[] = [
|
||||||
|
'value' => $document->getId(),
|
||||||
|
'text' => $document->getOriginalFilename(),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @return Upload[] Returns an array of Upload objects
|
|
||||||
// */
|
|
||||||
// public function findByExampleField($value): array
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('u')
|
|
||||||
// ->andWhere('u.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->orderBy('u.id', 'ASC')
|
|
||||||
// ->setMaxResults(10)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public function findOneBySomeField($value): ?Upload
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('u')
|
|
||||||
// ->andWhere('u.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getOneOrNullResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
{% macro collectionRow(form) %}
|
||||||
|
<div {{ stimulus_target('form-collection', 'field') }}>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
{{ form_widget(form) }}
|
||||||
|
<button type="button" {{ stimulus_action('form-collection', 'removeItem') }}>
|
||||||
|
{{ icon('delete', 'w-4 h-4 mt-2 pointer-events-none') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{ form_errors(form) }}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{{ form_start(form) }}
|
{{ form_start(form) }}
|
||||||
<div class="flex flex-col space-y-4 pb-8">
|
<div class="flex flex-col space-y-4 pb-8">
|
||||||
<div class="grid lg:grid-cols-5 gap-x-8 gap-y-4">
|
<div class="grid lg:grid-cols-5 gap-x-8 gap-y-4">
|
||||||
@@ -25,6 +37,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="grid lg:grid-cols-2 gap-x-8 gap-y-4">
|
<div class="grid lg:grid-cols-2 gap-x-8 gap-y-4">
|
||||||
{{ form_row(form.remarks) }}
|
{{ form_row(form.remarks) }}
|
||||||
|
<div {{ stimulus_controller('form-collection', { 'prototype': _self.collectionRow(form.documents.vars.prototype)|json_encode }) }}>
|
||||||
|
<h4 class="font-bold pb-2">
|
||||||
|
Dokumente
|
||||||
|
</h4>
|
||||||
|
<div {{ stimulus_target('form-collection', 'fields')}} class="flex flex-col space-y-4 mb-4">
|
||||||
|
{% do form.documents.setRendered %}
|
||||||
|
{%- for document in form.documents -%}{{- _self.collectionRow(document) -}}{%- endfor -%}
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<button type="button"
|
||||||
|
{{ stimulus_action('form-collection', 'addItem') }}
|
||||||
|
title="Dokument hinzufügen">
|
||||||
|
{{ icon('plus', 'w-4 h-4 pointer-events-none') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
|
|||||||
@@ -6,16 +6,86 @@
|
|||||||
<h1 class="text-2xl font-bold pb-8">
|
<h1 class="text-2xl font-bold pb-8">
|
||||||
Dokumentenübersicht
|
Dokumentenübersicht
|
||||||
</h1>
|
</h1>
|
||||||
<div class="data-table-wrapper">
|
<div class="data-table-wrapper">
|
||||||
<div class="data-table-wrapper__inner">
|
<div class="data-table-wrapper__inner">
|
||||||
<table class="data-table">
|
<table class="data-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
{{ knp_pagination_sortable(pagination, 'Dateiname', 'upload.originalFilename') }}
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Dateigröße
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Upload am
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Upload von
|
||||||
|
</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for document in pagination %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ document.originalFilename }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ document.size|file_size }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ document.createdAt|date('d.m.Y') }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ document.createdBy }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="flex items-center space-x-2 justify-end">
|
||||||
|
<button type="button"
|
||||||
|
class="text-red-500"
|
||||||
|
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
|
||||||
|
{{ stimulus_action('modal-button', 'confirmation', null, {
|
||||||
|
'title': 'Bist du sicher?',
|
||||||
|
'content': 'Möchtest du das Dokument wirklich löschen?',
|
||||||
|
'target-url': path('app_admin_document_delete', { 'uuid': document.uuid })
|
||||||
|
}) }}>
|
||||||
|
{{ icon('delete') }}
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
{{ stimulus_controller('modal-button', [], [], {'ajax-modal': '#ajax-modal'}) }}
|
||||||
|
{{ stimulus_action('modal-button', 'ajax', null, {
|
||||||
|
'title': 'Dokument ersetzen',
|
||||||
|
'url': path('app_admin_document_replace', { 'uuid': document.uuid })
|
||||||
|
}) }}>
|
||||||
|
{{ icon('refresh') }}
|
||||||
|
</button>
|
||||||
|
<a href="{{ path('app_common_download', { 'uuid': document.uuid }) }}"
|
||||||
|
title="Download">
|
||||||
|
{{ icon('download') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
{% else %}
|
||||||
<tbody>
|
<tr>
|
||||||
</tbody>
|
<td colspan="7">
|
||||||
</table>
|
Keine Daten...
|
||||||
</div>
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button"
|
||||||
|
class="btn"
|
||||||
|
{{ stimulus_controller('modal-button', [], [], {'ajax-modal': '#ajax-modal'}) }}
|
||||||
|
{{ stimulus_action('modal-button', 'ajax', null, {
|
||||||
|
'title': 'Dokumente hinzufügen',
|
||||||
|
'url': path('app_admin_document_upload')
|
||||||
|
}) }}>
|
||||||
|
Dokumente hinzufügen
|
||||||
|
</button>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{{ form_start(form) }}
|
||||||
|
<div class="pb-4">
|
||||||
|
{% include '_partials/_upload_collection_form.html.twig' with {
|
||||||
|
'endpoint_upload': path('_uploader_upload_document'),
|
||||||
|
'max_filesize': 5,
|
||||||
|
'max_files': 1,
|
||||||
|
'accepted_files': 'application/pdf',
|
||||||
|
} %}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn">
|
||||||
|
ersetzen
|
||||||
|
</button>
|
||||||
|
{{ form_rest(form) }}
|
||||||
|
{{ form_end(form) }}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{{ form_start(form) }}
|
||||||
|
<div class="pb-4">
|
||||||
|
{% include '_partials/_upload_collection_form.html.twig' with {
|
||||||
|
'endpoint_upload': path('_uploader_upload_document'),
|
||||||
|
'max_filesize': 5,
|
||||||
|
'accepted_files': 'application/pdf',
|
||||||
|
} %}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn">
|
||||||
|
Uploads speichern
|
||||||
|
</button>
|
||||||
|
{{ form_rest(form) }}
|
||||||
|
{{ form_end(form) }}
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
<div class="pb-4">
|
<div class="pb-4">
|
||||||
{% include '_partials/_upload_collection_form.html.twig' with {
|
{% include '_partials/_upload_collection_form.html.twig' with {
|
||||||
'endpoint_upload': path('_uploader_upload_contract'),
|
'endpoint_upload': path('_uploader_upload_contract'),
|
||||||
'upload_session_params': null,
|
|
||||||
'accepted_files': 'image/jpg,image/jpeg,application/pdf',
|
'accepted_files': 'image/jpg,image/jpeg,application/pdf',
|
||||||
} %}
|
} %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user