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 @@
+
+
+
+
+
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
-
-
-
- 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
-
-
-
- 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
+
+
+
+ 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
+
+
+
+ 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 %}
+