From 8dcfa2f43cf7ad478c994245ed537e57b9d700a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Fri, 13 Oct 2023 11:52:32 +0200 Subject: [PATCH] Feat: Implement workflow for disposition document uploads --- composer.lock | 42 ++--- config/packages/workflow.yaml | 6 +- .../Teamer/Disposition/DetailController.php | 12 +- .../DispositionWorkflowGuardSubscriber.php | 78 ++++++++++ temp/graph.svg | 84 ++++++++++ templates/teamer/disposition/detail.html.twig | 147 +++++++++++------- translations/messages.de.yaml | 8 + 7 files changed, 296 insertions(+), 81 deletions(-) create mode 100644 src/EventListener/DispositionWorkflowGuardSubscriber.php create mode 100644 temp/graph.svg diff --git a/composer.lock b/composer.lock index 0c34105..aa27e7d 100644 --- a/composer.lock +++ b/composer.lock @@ -101,16 +101,16 @@ }, { "name": "doctrine/collections", - "version": "2.1.3", + "version": "2.1.4", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "3023e150f90a38843856147b58190aa8b46cc155" + "reference": "72328a11443a0de79967104ad36ba7b30bded134" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/3023e150f90a38843856147b58190aa8b46cc155", - "reference": "3023e150f90a38843856147b58190aa8b46cc155", + "url": "https://api.github.com/repos/doctrine/collections/zipball/72328a11443a0de79967104ad36ba7b30bded134", + "reference": "72328a11443a0de79967104ad36ba7b30bded134", "shasum": "" }, "require": { @@ -118,7 +118,7 @@ "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^10.0", + "doctrine/coding-standard": "^12", "ext-json": "*", "phpstan/phpstan": "^1.8", "phpstan/phpstan-phpunit": "^1.0", @@ -167,7 +167,7 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/2.1.3" + "source": "https://github.com/doctrine/collections/tree/2.1.4" }, "funding": [ { @@ -183,7 +183,7 @@ "type": "tidelift" } ], - "time": "2023-07-06T15:15:36+00:00" + "time": "2023-10-03T09:22:33+00:00" }, { "name": "doctrine/common", @@ -278,16 +278,16 @@ }, { "name": "doctrine/dbal", - "version": "3.7.0", + "version": "3.7.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf" + "reference": "5b7bd66c9ff58c04c5474ab85edce442f8081cb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/00d03067f07482f025d41ab55e4ba0db5eca2cdf", - "reference": "00d03067f07482f025d41ab55e4ba0db5eca2cdf", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/5b7bd66c9ff58c04c5474ab85edce442f8081cb2", + "reference": "5b7bd66c9ff58c04c5474ab85edce442f8081cb2", "shasum": "" }, "require": { @@ -371,7 +371,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.7.0" + "source": "https://github.com/doctrine/dbal/tree/3.7.1" }, "funding": [ { @@ -387,7 +387,7 @@ "type": "tidelift" } ], - "time": "2023-09-26T20:56:55+00:00" + "time": "2023-10-06T05:06:20+00:00" }, { "name": "doctrine/deprecations", @@ -1322,16 +1322,16 @@ }, { "name": "egulias/email-validator", - "version": "4.0.1", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", - "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", + "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", "shasum": "" }, "require": { @@ -1340,8 +1340,8 @@ "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^4.30" + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -1377,7 +1377,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" }, "funding": [ { @@ -1385,7 +1385,7 @@ "type": "github" } ], - "time": "2023-01-14T14:17:03+00:00" + "time": "2023-10-06T06:47:41+00:00" }, { "name": "imagine/imagine", diff --git a/config/packages/workflow.yaml b/config/packages/workflow.yaml index 01eaa51..bacf49d 100644 --- a/config/packages/workflow.yaml +++ b/config/packages/workflow.yaml @@ -1,10 +1,10 @@ framework: workflows: disposition: - type: state_machine + type: 'state_machine' marking_store: - type: method - property: status + type: 'method' + property: 'status' supports: - App\Entity\Disposition initial_marking: new diff --git a/src/Controller/Teamer/Disposition/DetailController.php b/src/Controller/Teamer/Disposition/DetailController.php index 40df4ca..e79dc0f 100644 --- a/src/Controller/Teamer/Disposition/DetailController.php +++ b/src/Controller/Teamer/Disposition/DetailController.php @@ -11,6 +11,7 @@ use App\Service\Upload\UploadHandler; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -24,7 +25,8 @@ class DetailController extends AbstractController public function __construct( private readonly UploadHandler $uploadHandler, private readonly EntityManagerInterface $entityManager, - private readonly WorkflowInterface $dispositionStateMachine, + #[Target('disposition')] + private readonly WorkflowInterface $workflow, private readonly LoggerInterface $logger ) { } @@ -41,10 +43,10 @@ class DetailController extends AbstractController $uploadSession = $this->uploadHandler->getUploadSession(); if (0 < $uploadSession->getCount()) { - if (true === $this->dispositionStateMachine->can($disposition, 'upload_contract')) { + if (true === $this->workflow->can($disposition, 'upload_contract')) { $this->uploadContract($disposition, $uploadSession); } - if (true === $this->dispositionStateMachine->can($disposition, 'upload_invoice')) { + if (true === $this->workflow->can($disposition, 'upload_invoice')) { $this->uploadInvoice($disposition, $uploadSession); } } @@ -74,7 +76,7 @@ class DetailController extends AbstractController $upload->setStatus(Upload::STATUS_NEW); $disposition->addDocument($upload); - $this->dispositionStateMachine->apply($disposition, 'upload_contract'); + $this->workflow->apply($disposition, 'upload_contract'); $this->entityManager->flush(); @@ -101,7 +103,7 @@ class DetailController extends AbstractController $upload->setStatus(Upload::STATUS_NEW); $disposition->addDocument($upload); - $this->dispositionStateMachine->apply($disposition, 'upload_invoice'); + $this->workflow->apply($disposition, 'upload_invoice'); $this->entityManager->flush(); diff --git a/src/EventListener/DispositionWorkflowGuardSubscriber.php b/src/EventListener/DispositionWorkflowGuardSubscriber.php new file mode 100644 index 0000000..a602386 --- /dev/null +++ b/src/EventListener/DispositionWorkflowGuardSubscriber.php @@ -0,0 +1,78 @@ + ['guardUploadContract'], + 'workflow.disposition.guard.upload_invoice' => ['guardUploadInvoice'], + ]; + } + + public function guardUploadContract(GuardEvent $event): void + { + /** @var Disposition $disposition */ + $disposition = $event->getSubject(); + $assignment = $disposition->getAssignment(); + $assignmentPeriod = $assignment->getTotalPeriod(); + $today = new \DateTimeImmutable(); + + $dueDateFrom = $disposition->getCreatedAt()->modify('+ 7days'); + $dueDateTo = $assignmentPeriod->end->modify('-1 day'); + + if ($dueDateTo < $today) { + $message = $this + ->translator + ->trans('message.upload_contract.too_late', [ + '%date_from%' => $dueDateFrom->format('d.m.Y'), + '%date_to%' => $dueDateTo->format('d.m.Y'), + ]) + ; + $event->setBlocked(true, $message); + } + } + + public function guardUploadInvoice(GuardEvent $event): void + { + /** @var Disposition $disposition */ + $disposition = $event->getSubject(); + $assignment = $disposition->getAssignment(); + $destination = $assignment->getDestination(); + $today = new \DateTimeImmutable(); + + $earliestDate = $destination->getDateFrom(); + + if ($earliestDate > $today) { + $message = $this + ->translator + ->trans('message.upload_invoice.too_early', [ + '%date%' => $earliestDate->format('d.m.Y'), + ]) + ; + $event->setBlocked(true, $message); + } + + $dueDate = $destination->getDateTo()->modify('+14 days'); + + if ($dueDate < $today) { + $message = $this + ->translator + ->trans('message.upload_invoice.too_late', [ + '%date%' => $dueDate->format('d.m.Y'), + ]) + ; + $event->setBlocked(true, $message); + } + } +} \ No newline at end of file diff --git a/temp/graph.svg b/temp/graph.svg new file mode 100644 index 0000000..a6a9762 --- /dev/null +++ b/temp/graph.svg @@ -0,0 +1,84 @@ + + + + + + +workflow + + + +place_c2a6b03f190dfb2b4aa91f8af8d477a9bc3401dc + +new + + + +place_1c0139083383c818fd7ddf51e98b729b6b19803d + +checking_contract + + + +place_c2a6b03f190dfb2b4aa91f8af8d477a9bc3401dc->place_1c0139083383c818fd7ddf51e98b729b6b19803d + + +upload_contract + + + +place_d35ce8acb0d0ca09297349a4e2d4240c2819c523 + +confirmed + + + +place_1c0139083383c818fd7ddf51e98b729b6b19803d->place_d35ce8acb0d0ca09297349a4e2d4240c2819c523 + + +confirm_contract + + + +place_7b74ca224881f644aaf11239b7a05c62a7834047 + +checking_invoice + + + +place_d35ce8acb0d0ca09297349a4e2d4240c2819c523->place_7b74ca224881f644aaf11239b7a05c62a7834047 + + +upload_invoice + + + +place_231e564db4cdb44a6545583a8d460edc7f9f97ca + +completed + + + +place_d35ce8acb0d0ca09297349a4e2d4240c2819c523->place_231e564db4cdb44a6545583a8d460edc7f9f97ca + + +complete + + + +place_9e1f1120d2eedc498808e1d855cfdbbd5564f22b + +paid + + + +place_7b74ca224881f644aaf11239b7a05c62a7834047->place_9e1f1120d2eedc498808e1d855cfdbbd5564f22b + + +confirm_invoice + + + diff --git a/templates/teamer/disposition/detail.html.twig b/templates/teamer/disposition/detail.html.twig index 3d2946b..e8ef6ae 100644 --- a/templates/teamer/disposition/detail.html.twig +++ b/templates/teamer/disposition/detail.html.twig @@ -2,6 +2,13 @@ {% block title %}Einsatzdetails{% endblock %} +{% macro infobox(message) %} +
+ {{ icon('info', 'w-6 h-6 shrink-0') }} + {{ message }} +
+{% endmacro %} + {% block content %}

Einsatzdetails @@ -20,71 +27,107 @@
{{ assignment.contact }}, {{ assignment.contact.email }}

- {% if workflow_can(disposition, 'upload_contract') %} -

- Hier findest du deinen Honorarvertrag -

-
- - Honorarvertrag - -
-

- Bitte lade ihn unterschrieben hier wieder hoch: -

- {{ form_start(form) }} -
- {% include '_partials/_upload_collection_form.html.twig' with { - 'endpoint_upload': path('_uploader_upload_contract'), - 'accepted_files': 'image/jpg,image/jpeg,application/pdf', - } %} -
- - {{ form_rest(form) }} - {{ form_end(form) }} - {% elseif workflow_can(disposition, 'upload_invoice') %} -

- Hier findest du deine Honorarnote -

-
- - Honorarvertrag - -
-

- Bitte lade sie ausgefüllt und unterschrieben hier wieder hoch: -

- {{ form_start(form) }} -
- {% include '_partials/_upload_collection_form.html.twig' with { - 'endpoint_upload': path('_uploader_upload_invoice'), - 'accepted_files': 'image/jpg,image/jpeg,application/pdf', - } %} -
- - {{ form_rest(form) }} - {{ form_end(form) }} - {% elseif workflow_has_marked_place(disposition, 'checking_contract') %} + + {# Upload contract #} + {% if workflow_has_marked_place(disposition, 'new') %} + {% if workflow_can(disposition, 'upload_contract') %} +

+ Hier findest du deinen Honorarvertrag +

+
+ + Honorarvertrag + +
+

+ Bitte lade ihn unterschrieben hier wieder hoch: +

+ {{ form_start(form) }} +
+ {% include '_partials/_upload_collection_form.html.twig' with { + 'endpoint_upload': path('_uploader_upload_contract'), + 'accepted_files': 'image/jpg,image/jpeg,application/pdf', + } %} +
+ + {{ form_rest(form) }} + {{ form_end(form) }} + {% else %} + {% for blocker in workflow_transition_blockers(disposition, 'upload_contract') %} + {{ _self.infobox(blocker.message) }} + {% endfor %} + {% endif %} + {% endif %} + + {# Upload invoice #} + {% if workflow_has_marked_place(disposition, 'confirmed') %} + {% if workflow_can(disposition, 'upload_invoice') %} +

+ Hier findest du deine Honorarnote +

+
+ + Honorarvertrag + +
+

+ Bitte lade sie ausgefüllt und unterschrieben hier wieder hoch: +

+ {{ form_start(form) }} +
+ {% include '_partials/_upload_collection_form.html.twig' with { + 'endpoint_upload': path('_uploader_upload_invoice'), + 'accepted_files': 'image/jpg,image/jpeg,application/pdf', + } %} +
+ + {{ form_rest(form) }} + {{ form_end(form) }} + {% else %} + {% for blocker in workflow_transition_blockers(disposition, 'upload_invoice') %} + {{ _self.infobox(blocker.message) }} + {% endfor %} + {% endif %} + {% endif %} + + {# Contract uploaded #} + {% if workflow_has_marked_place(disposition, 'checking_contract') %}

Danke für den Upload des Honorarvertrags

- {% elseif workflow_has_marked_place(disposition, 'checking_invoice') %} +
+ Wir prüfen das Dokument schnellstmöglich. +
+ {% endif %} + + {# Invoice uploaded #} + {% if workflow_has_marked_place(disposition, 'checking_invoice') %}

Danke für den Upload der Honorarnote

- {% elseif workflow_has_marked_place(disposition, 'paid') %} +
+ Wir prüfen das Dokument schnellstmöglich. +
+ {% endif %} + + {# Invoice paid #} + {% if workflow_has_marked_place(disposition, 'paid') %}

Dein Honorar wurde überwiesen

- {% else %} + {% endif %} + + {# No invoice required #} + {% if workflow_has_marked_place(disposition, 'completed') %}

Dein Einsatz ist abgeschlossen

{% endif %} +
zurück diff --git a/translations/messages.de.yaml b/translations/messages.de.yaml index 8bba378..5a10c9d 100644 --- a/translations/messages.de.yaml +++ b/translations/messages.de.yaml @@ -22,12 +22,20 @@ label: payed: bezahlt error: Fehler +message: + upload_contract: + too_late: 'Die Frist für den Upload des Honorarvertrages ist abgelaufen (%date_from% - %date_to%). Bitte wende dich an deinen Ansprechpartner' + upload_invoice: + too_early: 'Du kannst deine Honorarnote mit Beginn deines Einsatzes ab dem %date% einreichen.' + too_late: 'Du hättest deine Honorarnote bis zum %date% einreichen müssen. Bitte wende dich an deinen Ansprechpartner.' + uploader: drop_files: Dateien hier ablegen oder click_here: hier klicken file_too_big: Die maximale Dateigröße beträgt %maxFilesize%MB invalid_file_type: Der Dateityp ist nicht erlaubt max_files_exceeded: Die maximale Anzahl von Dateien is %maxFilesExceeded% + paginator: previous: vorherige next: nächste