feat: dedicated list view for document batch downloading
This commit is contained in:
@@ -8,7 +8,6 @@ use App\Service\Upload\UploadHandler;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\HeaderUtils;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
@@ -32,9 +31,9 @@ class BatchDownloadController extends AbstractController
|
||||
defaults: ['type' => Upload::TYPE_INVOICE]
|
||||
)]
|
||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||
public function index(string $type, Request $request): Response
|
||||
public function index(string $type): Response
|
||||
{
|
||||
$status = $request->query->get('status', Upload::STATUS_PAID);
|
||||
$status = Upload::TYPE_INVOICE === $type ? Upload::STATUS_PAID : Upload::STATUS_CHECKED;
|
||||
|
||||
$uploads = $this
|
||||
->uploadRepository
|
||||
@@ -47,7 +46,7 @@ class BatchDownloadController extends AbstractController
|
||||
return $this->redirectToRoute('app_administrative_document_index');
|
||||
}
|
||||
|
||||
$zipFilename = sprintf('Honorarnoten_%s.zip', date('YmdHi'));
|
||||
$zipFilename = sprintf('%ss_%s.zip', $type, date('YmdHi'));
|
||||
|
||||
$response = new StreamedResponse(function () use ($uploads, $zipFilename) {
|
||||
$zip = new ZipStream(
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Controller\Administrative\Document;
|
||||
|
||||
use App\Entity\Upload;
|
||||
use App\Model\DocumentFilterDto;
|
||||
use App\Repository\UploadRepository;
|
||||
use App\Service\Common\DocumentFilterHandler;
|
||||
use Knp\Component\Pager\PaginatorInterface;
|
||||
@@ -42,4 +44,32 @@ class IndexController extends AbstractController
|
||||
'filterDto' => $filterDto,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(
|
||||
path: '/administrative/document/accepted/{type}',
|
||||
name: 'app_administrative_document_accepted',
|
||||
defaults: ['type' => Upload::TYPE_INVOICE]
|
||||
)]
|
||||
#[IsGranted('ROLE_ADMINISTRATIVE')]
|
||||
public function accepted(string $type, Request $request): Response
|
||||
{
|
||||
$status = Upload::TYPE_INVOICE === $type ? Upload::STATUS_PAID : Upload::STATUS_CHECKED;
|
||||
|
||||
$query = $this
|
||||
->uploadRepository
|
||||
->getBatchDownloadListQuery($type, $status)
|
||||
;
|
||||
|
||||
$pagination = $this->paginator->paginate(
|
||||
$query,
|
||||
$request->query->getInt('page', 1),
|
||||
$request->query->getInt('limit', 50),
|
||||
);
|
||||
|
||||
return $this->render('administrative/document/accepted.html.twig', [
|
||||
'pagination' => $pagination,
|
||||
'type' => $type,
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -33,12 +33,14 @@ class DownloadController extends AbstractController
|
||||
$inline = (bool) $request->get('inline');
|
||||
$disposition = $inline ? ResponseHeaderBag::DISPOSITION_INLINE : ResponseHeaderBag::DISPOSITION_ATTACHMENT;
|
||||
|
||||
// mark file downloaded
|
||||
// mark file downloaded if applicable
|
||||
if ($this->isGranted('ROLE_ADMINISTRATIVE' && false === $inline)) {
|
||||
$upload
|
||||
->setDownloaded()
|
||||
->setDownloadedBy($this->getUser()->getUserIdentifier())
|
||||
;
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
$this->logger->info('Download file', [
|
||||
'file_id' => $upload->getId(),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Menu;
|
||||
|
||||
use App\Entity\Upload;
|
||||
use Knp\Menu\ItemInterface;
|
||||
|
||||
class AdminMenuBuilder extends AbstractMenuBuilder
|
||||
@@ -40,7 +41,7 @@ class AdminMenuBuilder extends AbstractMenuBuilder
|
||||
'icon' => 'mail',
|
||||
],
|
||||
]);
|
||||
$menu->addChild('Dokumentenübersicht', [
|
||||
$documentMenu = $menu->addChild('Dokumentenübersicht', [
|
||||
'route' => 'app_administrative_document_index',
|
||||
'linkAttributes' => [
|
||||
'title' => 'Dokumentenübersicht',
|
||||
@@ -49,6 +50,24 @@ class AdminMenuBuilder extends AbstractMenuBuilder
|
||||
'icon' => 'document',
|
||||
],
|
||||
]);
|
||||
$documentMenu->addChild('akzeptierte Honorarnoten', [
|
||||
'route' => 'app_administrative_document_accepted',
|
||||
'routeParameters' => [
|
||||
'type' => Upload::TYPE_INVOICE,
|
||||
],
|
||||
'linkAttributes' => [
|
||||
'title' => 'akzeptierte Honorarnoten',
|
||||
],
|
||||
]);
|
||||
$documentMenu->addChild('geprüfte Honorverträge', [
|
||||
'route' => 'app_administrative_document_accepted',
|
||||
'routeParameters' => [
|
||||
'type' => Upload::TYPE_CONTRACT,
|
||||
],
|
||||
'linkAttributes' => [
|
||||
'title' => 'geprüfte Honorverträge',
|
||||
],
|
||||
]);
|
||||
$menu->addChild('Feedbackübersicht', [
|
||||
'route' => 'app_administrative_feedback_index',
|
||||
'linkAttributes' => [
|
||||
|
||||
@@ -80,12 +80,21 @@ class UploadRepository extends ServiceEntityRepository
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getBatchDownloadList(
|
||||
public function getBatchDownloadListQuery(
|
||||
string $type = Upload::TYPE_INVOICE,
|
||||
string $status = Upload::STATUS_PAID
|
||||
): array {
|
||||
): Query {
|
||||
$qb = $this->createQueryBuilder('upload');
|
||||
|
||||
$qb
|
||||
->select('upload', 'disposition', 'assignment', 'job_profile', 'destination', 'teamer')
|
||||
->innerJoin('upload.disposition', 'disposition')
|
||||
->innerJoin('disposition.assignment', 'assignment')
|
||||
->innerJoin('assignment.jobProfile', 'job_profile')
|
||||
->innerJoin('assignment.destination', 'destination')
|
||||
->innerJoin('disposition.teamer', 'teamer')
|
||||
;
|
||||
|
||||
return $qb->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('upload.type', ':type'),
|
||||
@@ -95,6 +104,15 @@ class UploadRepository extends ServiceEntityRepository
|
||||
->setParameter('type', $type)
|
||||
->setParameter('status', $status)
|
||||
->getQuery()
|
||||
;
|
||||
}
|
||||
|
||||
public function getBatchDownloadList(
|
||||
string $type = Upload::TYPE_INVOICE,
|
||||
string $status = Upload::STATUS_PAID
|
||||
): array {
|
||||
return $this
|
||||
->getBatchDownloadListQuery($type, $status)
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
{% extends 'administrative/layout.html.twig' %}
|
||||
|
||||
{% block title %}Dokumentenübersicht{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="flex items-start justify-between pb-4">
|
||||
<h1 class="text-2xl font-bold">
|
||||
{% if type == constant('App\\Entity\\Upload::TYPE_INVOICE') %}
|
||||
Übersicht akzeptierte Honorarnoten
|
||||
{% elseif type == constant('App\\Entity\\Upload::TYPE_CONTRACT') %}
|
||||
Übersicht geprüfte Honorarverträge
|
||||
{% else %}
|
||||
Übersicht
|
||||
{% endif %}
|
||||
</h1>
|
||||
<div class="flex flex-col items-end space-y-2 md:flex-row md:items-center md:space-x-2 md:space-y-0">
|
||||
<a href="{{ path('app_administrative_document_batch_download', { 'type': type, 'status': status }) }}"
|
||||
class="btn btn--small">
|
||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||
<span>alle herunterladen</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-table-wrapper">
|
||||
<div class="data-table-wrapper__inner">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Einsatz­zeitraum', 'destination.dateFrom') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Destination', 'destination.product') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Jobprofil', 'job_profile.name') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Dokument', 'upload.type') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Teamer:in', 'teamer.lastName') }}
|
||||
</th>
|
||||
<th>
|
||||
{{ knp_pagination_sortable(pagination, 'Status', 'upload.status') }}
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for upload in pagination %}
|
||||
{% set disposition = upload.disposition %}
|
||||
{% set assignment = disposition.assignment %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ assignment.destination.dateFrom|date('d.m.Y') }}
|
||||
-
|
||||
{{ assignment.destination.dateTo|date('d.m.Y') }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-start space-x-2">
|
||||
{{ icon('flag-' ~ assignment.destination.country, 'w-5 h-5 shrink-0') }}
|
||||
<div class="flex-1">
|
||||
{{ assignment.destination.product }}
|
||||
<br>
|
||||
{{ assignment.destination.hotel }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{{ assignment.jobProfile.name }}
|
||||
</td>
|
||||
<td>
|
||||
{{ ('label.document.type.' ~ upload.type)|trans }}
|
||||
</td>
|
||||
<td>
|
||||
{{ disposition.teamer }}
|
||||
</td>
|
||||
<td>
|
||||
{{ upload.status|upload_status_badge }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center space-x-2 justify-end">
|
||||
{% if is_granted('DELETE', upload) %}
|
||||
<button type="button"
|
||||
class="text-red-500"
|
||||
hx-get="{{ path('app_administrative_document_delete', { 'uuid': upload.uuid }) }}"
|
||||
hx-target="body"
|
||||
hx-swap="beforeend">
|
||||
{{ icon('delete') }}
|
||||
</button>
|
||||
{% endif %}
|
||||
<a href="{{ path('app_common_download', { 'uuid': upload.uuid, 'inline': false }) }}"
|
||||
target="_blank"
|
||||
title="Download">
|
||||
{{ icon('download') }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
Keine Daten...
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ knp_pagination_render(pagination) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -8,10 +8,6 @@
|
||||
Dokumentenübersicht
|
||||
</h1>
|
||||
<div class="flex flex-col items-end space-y-2 md:flex-row md:items-center md:space-x-2 md:space-y-0">
|
||||
<a href="{{ path('app_administrative_document_batch_download') }}" class="btn btn--small btn--secondary">
|
||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||
<span>best. Honorarnoten</span>
|
||||
</a>
|
||||
<button type="button"
|
||||
class="{{ html_classes('btn btn--small', { 'btn--secondary': filterDto.active }) }}"
|
||||
title="Filter"
|
||||
|
||||
Reference in New Issue
Block a user