feat: enable document uploads for admins on behalf of teamer
closes #869btf5bm
This commit is contained in:
@@ -31,22 +31,33 @@ class DocumentUploadController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Route(
|
#[Route(
|
||||||
path: '/administrative/disposition/document-upload/{uuid}',
|
path: '/administrative/disposition/document-upload/{uuid}/{type}',
|
||||||
name: 'app_administrative_disposition_document_upload'
|
name: 'app_administrative_disposition_document_upload'
|
||||||
)]
|
)]
|
||||||
#[IsGranted('CONTRACT_SUPPLEMENTARY', subject: 'disposition')]
|
#[IsGranted('ADMIN_DOCUMENT_UPLOAD', subject: 'disposition')]
|
||||||
public function index(Disposition $disposition, Request $request): Response
|
public function index(Disposition $disposition, string $type, Request $request): Response
|
||||||
{
|
{
|
||||||
|
if (false === in_array($type, [Upload::TYPE_CONTRACT, Upload::TYPE_INVOICE], true)) {
|
||||||
|
throw $this->createNotFoundException('Unsupported document type');
|
||||||
|
}
|
||||||
|
|
||||||
$teamer = $disposition->getTeamer();
|
$teamer = $disposition->getTeamer();
|
||||||
$modalTitle = sprintf('Honorarvertrag für %s hochladen', $teamer);
|
$modalTitle = Upload::TYPE_CONTRACT === $type
|
||||||
|
? sprintf('Honorarvertrag für %s hochladen', $teamer)
|
||||||
|
: sprintf('Honorarnote für %s hochladen', $teamer);
|
||||||
|
|
||||||
$form = $this->createFormBuilder()->getForm();
|
$form = $this->createFormBuilder()->getForm();
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
$this->processUpload($disposition);
|
$this->processUpload($disposition, $type);
|
||||||
|
|
||||||
$this->addFlash('success', 'Der Honorarvertrag wurde hochgeladen');
|
$this->addFlash(
|
||||||
|
'success',
|
||||||
|
Upload::TYPE_CONTRACT === $type
|
||||||
|
? 'Der Honorarvertrag wurde hochgeladen'
|
||||||
|
: 'Die Honorarnote wurde hochgeladen'
|
||||||
|
);
|
||||||
|
|
||||||
return new HxRedirectResponse($this->generateUrl('app_administrative_assignment_detail', [
|
return new HxRedirectResponse($this->generateUrl('app_administrative_assignment_detail', [
|
||||||
'uuid' => $disposition->getAssignment()->getUuid(),
|
'uuid' => $disposition->getAssignment()->getUuid(),
|
||||||
@@ -56,13 +67,16 @@ class DocumentUploadController extends AbstractController
|
|||||||
return $this->render('administrative/disposition/modal_document_upload.html.twig', [
|
return $this->render('administrative/disposition/modal_document_upload.html.twig', [
|
||||||
'disposition' => $disposition,
|
'disposition' => $disposition,
|
||||||
'modalTitle' => $modalTitle,
|
'modalTitle' => $modalTitle,
|
||||||
|
'endpointUpload' => Upload::TYPE_CONTRACT === $type
|
||||||
|
? '_uploader_upload_contract'
|
||||||
|
: '_uploader_upload_invoice',
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processUpload(Disposition $disposition): void
|
private function processUpload(Disposition $disposition, string $type): void
|
||||||
{
|
{
|
||||||
if (null !== $existingUpload = $disposition->getDocumentByType(Upload::TYPE_CONTRACT)) {
|
if (null !== $existingUpload = $disposition->getDocumentByType($type)) {
|
||||||
$this->entityManager->remove($existingUpload);
|
$this->entityManager->remove($existingUpload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,23 +90,28 @@ class DocumentUploadController extends AbstractController
|
|||||||
$upload = Upload::fromUploadDto(
|
$upload = Upload::fromUploadDto(
|
||||||
$uploadSession->getUploads()->first(),
|
$uploadSession->getUploads()->first(),
|
||||||
$teamer->getUser(),
|
$teamer->getUser(),
|
||||||
Upload::TYPE_CONTRACT
|
$type
|
||||||
);
|
);
|
||||||
|
|
||||||
$upload
|
if (Upload::TYPE_CONTRACT === $type) {
|
||||||
->setStatus(Upload::STATUS_CHECKED)
|
$upload
|
||||||
->setApprovedAt(new \DateTimeImmutable())
|
->setStatus(Upload::STATUS_CHECKED)
|
||||||
->setApprovedBy($user->getInitials())
|
->setApprovedAt(new \DateTimeImmutable())
|
||||||
;
|
->setApprovedBy($user->getInitials())
|
||||||
|
;
|
||||||
|
|
||||||
$disposition
|
$disposition->setStatus(Disposition::STATUS_CONFIRMED);
|
||||||
->addDocument($upload)
|
} else {
|
||||||
->setStatus(Disposition::STATUS_CONFIRMED)
|
$upload->setStatus(Upload::STATUS_NEW);
|
||||||
;
|
|
||||||
|
$disposition->setStatus(Disposition::STATUS_CHECKING_INVOICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
$disposition->addDocument($upload);
|
||||||
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
$this->uploadHandler->moveUploadSessionFilesFromOrphanage(Upload::TYPE_CONTRACT, $uploadSession);
|
$this->uploadHandler->moveUploadSessionFilesFromOrphanage($type, $uploadSession);
|
||||||
$this->uploadHandler->destroyUploadSession();
|
$this->uploadHandler->destroyUploadSession();
|
||||||
|
|
||||||
if (true === $this->featureManager->isActive('sanitize_uploads')) {
|
if (true === $this->featureManager->isActive('sanitize_uploads')) {
|
||||||
@@ -100,14 +119,16 @@ class DocumentUploadController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->eventDispatcher->dispatch(new DocumentUploadedEvent($upload), DocumentUploadedEvent::NAME);
|
$this->eventDispatcher->dispatch(new DocumentUploadedEvent($upload), DocumentUploadedEvent::NAME);
|
||||||
$this->eventDispatcher->dispatch(new DocumentConfirmedEvent($upload), DocumentConfirmedEvent::NAME);
|
if (Upload::TYPE_CONTRACT === $type) {
|
||||||
|
$this->eventDispatcher->dispatch(new DocumentConfirmedEvent($upload), DocumentConfirmedEvent::NAME);
|
||||||
|
}
|
||||||
|
|
||||||
$this->logger->info('Document upload for teamer', [
|
$this->logger->info('Administrative document upload for teamer', [
|
||||||
'teamer_id' => $teamer->getId(),
|
'teamer_id' => $teamer->getId(),
|
||||||
'teamer_name' => $teamer->getFullName(),
|
'teamer_name' => $teamer->getFullName(),
|
||||||
'disposition_id' => $disposition->getId(),
|
'disposition_id' => $disposition->getId(),
|
||||||
'document_id' => $upload->getId(),
|
'document_id' => $upload->getId(),
|
||||||
'upload_type' => Upload::TYPE_CONTRACT,
|
'upload_type' => $type,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class DispositionVoter extends Voter
|
|||||||
public const DELETE = 'DELETE';
|
public const DELETE = 'DELETE';
|
||||||
public const CONTRACT = 'CONTRACT';
|
public const CONTRACT = 'CONTRACT';
|
||||||
public const CONTRACT_SUPPLEMENTARY = 'CONTRACT_SUPPLEMENTARY';
|
public const CONTRACT_SUPPLEMENTARY = 'CONTRACT_SUPPLEMENTARY';
|
||||||
|
public const ADMIN_DOCUMENT_UPLOAD = 'ADMIN_DOCUMENT_UPLOAD';
|
||||||
public const INVOICE = 'INVOICE';
|
public const INVOICE = 'INVOICE';
|
||||||
public const FEEDBACK = 'FEEDBACK';
|
public const FEEDBACK = 'FEEDBACK';
|
||||||
public const CALL_OFF = 'CALL_OFF';
|
public const CALL_OFF = 'CALL_OFF';
|
||||||
@@ -36,6 +37,7 @@ class DispositionVoter extends Voter
|
|||||||
static::DELETE,
|
static::DELETE,
|
||||||
static::CONTRACT,
|
static::CONTRACT,
|
||||||
static::CONTRACT_SUPPLEMENTARY,
|
static::CONTRACT_SUPPLEMENTARY,
|
||||||
|
static::ADMIN_DOCUMENT_UPLOAD,
|
||||||
static::INVOICE,
|
static::INVOICE,
|
||||||
static::FEEDBACK,
|
static::FEEDBACK,
|
||||||
static::CALL_OFF,
|
static::CALL_OFF,
|
||||||
@@ -54,6 +56,7 @@ class DispositionVoter extends Voter
|
|||||||
static::FEEDBACK => $this->security->isGranted('ROLE_ADMINISTRATIVE')
|
static::FEEDBACK => $this->security->isGranted('ROLE_ADMINISTRATIVE')
|
||||||
|| $this->assertHouseManagerAccess($token, $disposition),
|
|| $this->assertHouseManagerAccess($token, $disposition),
|
||||||
static::CONTRACT_SUPPLEMENTARY => $this->assertContractUploadAllowed($disposition),
|
static::CONTRACT_SUPPLEMENTARY => $this->assertContractUploadAllowed($disposition),
|
||||||
|
static::ADMIN_DOCUMENT_UPLOAD => $this->assertAdminDocumentUploadAllowed($disposition),
|
||||||
static::CALL_OFF => $this->security->isGranted('ROLE_ADMINISTRATIVE')
|
static::CALL_OFF => $this->security->isGranted('ROLE_ADMINISTRATIVE')
|
||||||
&& Disposition::STATUS_CALLED_OFF !== $disposition->getStatus(),
|
&& Disposition::STATUS_CALLED_OFF !== $disposition->getStatus(),
|
||||||
default => false,
|
default => false,
|
||||||
@@ -110,4 +113,14 @@ class DispositionVoter extends Voter
|
|||||||
|
|
||||||
return null === $existingContract || Upload::STATUS_REJECTED === $existingContract->getStatus();
|
return null === $existingContract || Upload::STATUS_REJECTED === $existingContract->getStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function assertAdminDocumentUploadAllowed(Disposition $disposition): bool
|
||||||
|
{
|
||||||
|
if (false === $this->security->isGranted('ROLE_ADMINISTRATIVE')
|
||||||
|
&& false === $this->security->isGranted('ROLE_ADMIN')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Disposition::STATUS_CALLED_OFF !== $disposition->getStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,54 +176,67 @@
|
|||||||
{{ disposition.remarks|default('-')|nl2br }}
|
{{ disposition.remarks|default('-')|nl2br }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-start space-x-2">
|
{{ disposition.specialAgreements|default('-')|nl2br }}
|
||||||
<div class="flex-1">
|
|
||||||
{{ disposition.specialAgreements|default('-')|nl2br }}
|
|
||||||
</div>
|
|
||||||
<button type="button"
|
|
||||||
title="zusätzliche Absprachen {{ disposition.teamer }}"
|
|
||||||
hx-get="{{ path('app_administrative_disposition_special_agreements', { 'uuid': disposition.uuid }) }}"
|
|
||||||
hx-target="body"
|
|
||||||
hx-swap="beforeend">
|
|
||||||
{{ icon('edit') }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="flex items-center justify-end space-x-2">
|
<div class="flex items-center justify-end space-x-2">
|
||||||
<button type="button" hx-post="{{ path('app_administrative_disposition_bpn_sync_toggle', { 'uuid': disposition.uuid }) }}" hx-swap="none">
|
<button type="button" hx-post="{{ path('app_administrative_disposition_bpn_sync_toggle', { 'uuid': disposition.uuid }) }}" hx-swap="none">
|
||||||
{{ icon('chair', html_classes('w-5 h-5', {'text-green-500': disposition.syncedWithBusProNet })) }}
|
{{ icon('chair', html_classes('w-5 h-5', {'text-green-500': disposition.syncedWithBusProNet })) }}
|
||||||
</button>
|
</button>
|
||||||
{% if is_granted('CONTRACT_SUPPLEMENTARY', disposition) %}
|
<twig:DropDown>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
title="Dokumentenupload"
|
class="text-gray-700 hover:text-primary block px-4 py-2 text-sm w-full text-left"
|
||||||
hx-get="{{ path('app_administrative_disposition_document_upload', { 'uuid': disposition.uuid }) }}"
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
hx-get="{{ path('app_administrative_disposition_special_agreements', { 'uuid': disposition.uuid }) }}"
|
||||||
hx-target="body"
|
hx-target="body"
|
||||||
hx-swap="beforeend">
|
hx-swap="beforeend">
|
||||||
{{ icon('upload') }}
|
zusätzliche Absprachen
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% if is_granted('ADMIN_DOCUMENT_UPLOAD', disposition) %}
|
||||||
{% if is_granted('CALL_OFF', disposition) %}
|
<button type="button"
|
||||||
<button type="button"
|
class="text-gray-700 hover:text-primary block px-4 py-2 text-sm w-full text-left"
|
||||||
title="Absage"
|
role="menuitem"
|
||||||
class="text-red-500"
|
tabindex="-1"
|
||||||
hx-get="{{ path('app_administrative_disposition_call_off', { 'uuid': disposition.uuid }) }}"
|
hx-get="{{ path('app_administrative_disposition_document_upload', { 'uuid': disposition.uuid, 'type': 'contract' }) }}"
|
||||||
hx-target="body"
|
hx-target="body"
|
||||||
hx-swap="beforeend">
|
hx-swap="beforeend">
|
||||||
{{ icon('cancel') }}
|
Honorarvertrag hochladen
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
<button type="button"
|
||||||
{% if is_granted('DELETE', disposition) %}
|
class="text-gray-700 hover:text-primary block px-4 py-2 text-sm w-full text-left"
|
||||||
<button type="button"
|
role="menuitem"
|
||||||
class="text-red-500"
|
tabindex="-1"
|
||||||
title="Einteilung löschen"
|
hx-get="{{ path('app_administrative_disposition_document_upload', { 'uuid': disposition.uuid, 'type': 'invoice' }) }}"
|
||||||
hx-get="{{ path('app_admin_disposition_delete', { 'uuid': disposition.uuid }) }}"
|
hx-target="body"
|
||||||
hx-target="body"
|
hx-swap="beforeend">
|
||||||
hx-swap="beforeend">
|
Honorarnote hochladen
|
||||||
{{ icon('delete') }}
|
</button>
|
||||||
</button>
|
{% endif %}
|
||||||
{% endif %}
|
{% if is_granted('CALL_OFF', disposition) %}
|
||||||
|
<button type="button"
|
||||||
|
class="text-red-500 hover:text-primary block px-4 py-2 text-sm w-full text-left"
|
||||||
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
hx-get="{{ path('app_administrative_disposition_call_off', { 'uuid': disposition.uuid }) }}"
|
||||||
|
hx-target="body"
|
||||||
|
hx-swap="beforeend">
|
||||||
|
absagen
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('DELETE', disposition) %}
|
||||||
|
<button type="button"
|
||||||
|
class="text-red-500 hover:text-primary block px-4 py-2 text-sm w-full text-left"
|
||||||
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
hx-get="{{ path('app_admin_disposition_delete', { 'uuid': disposition.uuid }) }}"
|
||||||
|
hx-target="body"
|
||||||
|
hx-swap="beforeend">
|
||||||
|
löschen
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</twig:DropDown>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
{{ form_start(form, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' }|merge(formAttr.toArray()) }) }}
|
{{ form_start(form, { 'attr': { 'hx-post': app.request.uri, 'hx-target': '#htmx-modal', 'hx-swap': 'outerHTML' }|merge(formAttr.toArray()) }) }}
|
||||||
<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(endpointUpload),
|
||||||
'max_filesize': 5,
|
'max_filesize': 5,
|
||||||
'max_files': 1,
|
'max_files': 1,
|
||||||
'accepted_files': 'image/jpg,image/jpeg,application/pdf',
|
'accepted_files': 'image/jpg,image/jpeg,application/pdf',
|
||||||
@@ -18,4 +18,4 @@
|
|||||||
</button>
|
</button>
|
||||||
{{ form_rest(form) }}
|
{{ form_rest(form) }}
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user