feat: pickup selection depending on bus chaperon

This commit is contained in:
Björn Fromme
2024-01-10 18:09:39 +01:00
parent 5b93ce421c
commit cf90ffcd19
13 changed files with 207 additions and 58 deletions
+3
View File
@@ -157,6 +157,9 @@
<symbol id="icon-folder" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"> <symbol id="icon-folder" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" stroke-linecap="round" stroke-linejoin="round"/> <path d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" stroke-linecap="round" stroke-linejoin="round"/>
</symbol> </symbol>
<symbol id="icon-power" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" />
</symbol>
<symbol id="icon-bus" xmlns="http://www.w3.org/2000/svg" stroke-width="1.5" viewBox="0 0 24 24" stroke="currentColor" fill="none"> <symbol id="icon-bus" xmlns="http://www.w3.org/2000/svg" stroke-width="1.5" viewBox="0 0 24 24" stroke="currentColor" fill="none">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/> <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M6 17m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /> <path d="M6 17m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" />

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

+33
View File
@@ -0,0 +1,33 @@
<?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 Version20240110164517 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 application ADD pickup INT DEFAULT NULL');
$this->addSql('ALTER TABLE disposition ADD pickup INT DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE disposition DROP pickup');
$this->addSql('ALTER TABLE application DROP pickup');
}
}
@@ -6,6 +6,7 @@ use App\Controller\Traits\ReturnUrlTrait;
use App\Entity\Disposition; use App\Entity\Disposition;
use App\Entity\Upload; use App\Entity\Upload;
use App\Entity\User; use App\Entity\User;
use App\Form\TeamerDispositionType;
use App\Model\UploadSessionDto; use App\Model\UploadSessionDto;
use App\Service\Upload\UploadHandler; use App\Service\Upload\UploadHandler;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@@ -36,18 +37,21 @@ class DetailController extends AbstractController
#[IsGranted('VIEW', subject: 'disposition')] #[IsGranted('VIEW', subject: 'disposition')]
public function index(Disposition $disposition, Request $request): Response public function index(Disposition $disposition, Request $request): Response
{ {
$isUploadContract = $this->workflow->can($disposition, 'upload_contract');
$isUploadInvoice = $this->workflow->can($disposition, 'upload_invoice');
// Create dummy form without fields to generate POST requests // Create dummy form without fields to generate POST requests
$form = $this->createFormBuilder()->getForm(); $form = $this->createForm(TeamerDispositionType::class, $disposition, ['enable_pickup' => $isUploadContract]);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$uploadSession = $this->uploadHandler->getUploadSession(); $uploadSession = $this->uploadHandler->getUploadSession();
if (0 < $uploadSession->getCount()) { if (0 < $uploadSession->getCount()) {
if (true === $this->workflow->can($disposition, 'upload_contract')) { if (true === $isUploadContract) {
$this->uploadContract($disposition, $uploadSession); $this->uploadContract($disposition, $uploadSession);
} }
if (true === $this->workflow->can($disposition, 'upload_invoice')) { if (true === $isUploadInvoice) {
$this->uploadInvoice($disposition, $uploadSession); $this->uploadInvoice($disposition, $uploadSession);
} }
} }
+15
View File
@@ -34,6 +34,9 @@ class Application implements TimestampableEntityInterface
#[ORM\Column(type: Types::TEXT, nullable: true)] #[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $requests = null; private ?string $requests = null;
#[ORM\Column(nullable: true)]
private ?int $pickup = null;
#[ORM\Column(length: 64)] #[ORM\Column(length: 64)]
private ?string $status; private ?string $status;
@@ -97,6 +100,18 @@ class Application implements TimestampableEntityInterface
return $this; return $this;
} }
public function getPickup(): ?int
{
return $this->pickup;
}
public function setPickup(?int $pickup): static
{
$this->pickup = $pickup;
return $this;
}
public function getStatus(): ?string public function getStatus(): ?string
{ {
return $this->status; return $this->status;
+16
View File
@@ -42,6 +42,9 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
#[ORM\ManyToOne(inversedBy: 'dispositions')] #[ORM\ManyToOne(inversedBy: 'dispositions')]
private ?Teamer $teamer; private ?Teamer $teamer;
#[ORM\Column(nullable: true)]
private ?int $pickup = null;
#[ORM\Column(type: Types::TEXT, nullable: true)] #[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $remarks; private ?string $remarks;
@@ -58,6 +61,7 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
$this->status = static::STATUS_NEW; $this->status = static::STATUS_NEW;
$this->assignment = $application->getAssignment(); $this->assignment = $application->getAssignment();
$this->teamer = $application->getTeamer(); $this->teamer = $application->getTeamer();
$this->pickup = $application->getPickup();
$this->documents = new ArrayCollection(); $this->documents = new ArrayCollection();
} }
@@ -107,6 +111,18 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
return $this; return $this;
} }
public function getPickup(): ?int
{
return $this->pickup;
}
public function setPickup(?int $pickup): static
{
$this->pickup = $pickup;
return $this;
}
public function getRemarks(): ?string public function getRemarks(): ?string
{ {
return $this->remarks; return $this->remarks;
+22 -17
View File
@@ -5,6 +5,7 @@ namespace App\Form;
use App\Entity\Application; use App\Entity\Application;
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\ChoiceType;
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;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
@@ -27,32 +28,36 @@ class TeamerApplicationType extends AbstractType
->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) { ->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
/** @var Application $application */ /** @var Application $application */
$application = $event->getData(); $application = $event->getData();
$form = $event->getForm();
if (null === $application) { if (null === $application) {
return; return;
} }
$assignment = $application->getAssignment(); $assignment = $application->getAssignment();
if (0 === (int) $assignment->getPickup()) {
return;
}
$teamer = $application->getTeamer(); $teamer = $application->getTeamer();
if (in_array($assignment->getPickup(), $teamer->getPickups())) { if (0 === (int) $assignment->getPickup()) {
return; $pickupChoices = [];
foreach ($assignment->getDestination()->getPickups() as $pickup) {
$pickupChoices[$pickup['city']] = $pickup['busProId'];
}
ksort($pickupChoices);
$form->add('pickup', ChoiceType::class, [
'label' => 'Zustieg in',
'choices' => $pickupChoices,
]);
} elseif (false === in_array($assignment->getPickup(), $teamer->getPickups())) {
$form->add('confirmPickup', CheckboxType::class, [
'label' => 'Ich bestätige den abweichenden Buszustieg',
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'Deine Bestätigung ist erforderlich',
]),
],
]);
} }
$event->getForm()->add('confirmPickup', CheckboxType::class, [
'label' => 'Ich bestätige den abweichenden Buszustieg',
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'Deine Bestätigung ist erforderlich',
]),
],
]);
}) })
; ;
} }
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace App\Form;
use App\Entity\Disposition;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TeamerDispositionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
if (true === $options['enable_pickup']) {
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
/** @var Disposition $disposition */
$disposition = $event->getData();
$assignment = $disposition->getAssignment();
// Add pickup select field only to assignments that are not including bus chaperon
if (0 === (int) $assignment->getPickup()) {
$pickupChoices = [];
foreach ($assignment->getDestination()->getPickups() as $pickup) {
$pickupChoices[$pickup['city']] = $pickup['busProId'];
}
ksort($pickupChoices);
$event->getForm()->add('pickup', ChoiceType::class, [
'label' => 'Zustieg in',
'choices' => $pickupChoices,
]);
}
});
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Disposition::class,
'enable_pickup' => false,
]);
}
}
@@ -43,6 +43,16 @@
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if application is defined and application.pickup is not null %}
<div class="py-6 first:pt-0 last:pb-0 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="text-sm font-bold leading-6 text-gray-900">
Gwählter Zustieg
</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
{{ application.pickup|bpn_pickup_label }}
</dd>
</div>
{% endif %}
<div class="py-6 first:pt-0 last:pb-0 sm:grid sm:grid-cols-3 sm:gap-4"> <div class="py-6 first:pt-0 last:pb-0 sm:grid sm:grid-cols-3 sm:gap-4">
<dt class="text-sm font-bold leading-6 text-gray-900"> <dt class="text-sm font-bold leading-6 text-gray-900">
Du bekommst Du bekommst
@@ -1,5 +1,5 @@
<ul class="pb-8 divide-y divide-gray-200"> <ul class="pb-8 divide-y divide-gray-200">
{% if assignment.pickup and assignment.effectivePickupDate %} {% if assignment.pickup %}
<li class="py-2"> <li class="py-2">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="flex-1">Busbegleitung {{ assignment.pickup|bpn_pickup_label }} am {{ assignment.effectivePickupDate|date('d.m.Y') }}</span> <span class="flex-1">Busbegleitung {{ assignment.pickup|bpn_pickup_label }} am {{ assignment.effectivePickupDate|date('d.m.Y') }}</span>
@@ -22,5 +22,12 @@
{% endif %} {% endif %}
</div> </div>
</li> </li>
{% else %}
<li class="py-2">
<div class="flex items-center justify-between">
<span class="flex-1">Keine</span>
{{ icon('check', 'w-5 h-5 text-green-500 shrink-0') }}
</div>
</li>
{% endfor %} {% endfor %}
</ul> </ul>
+23 -15
View File
@@ -18,16 +18,19 @@
<table class="data-table"> <table class="data-table">
<thead> <thead>
<tr> <tr>
<th class="w-1/3"> <th class="w-1/4">
Name Name
</th> </th>
<th class="w-1/3"> <th class="w-1/4">
Zustieg
</th>
<th>
Wünsche Wünsche
</th> </th>
<th class="w-1/6"> <th>
Status Status
</th> </th>
<th class="w-1/6"></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -44,6 +47,10 @@
<span>{{ application.teamer }}</span> <span>{{ application.teamer }}</span>
{{ icon('info', 'w-4 h-4') }} {{ icon('info', 'w-4 h-4') }}
</button> </button>
{% include '_partials/_rating_stars.html.twig' with { 'teamer': application.teamer } %}
</td>
<td>
{{ application.pickup|bpn_pickup_label|default('-') }}
</td> </td>
<td> <td>
{{ application.requests|nl2br|default('-') }} {{ application.requests|nl2br|default('-') }}
@@ -55,35 +62,36 @@
<div class="flex items-center justify-end space-x-2"> <div class="flex items-center justify-end space-x-2">
{% if is_granted('DISPOSE', application) %} {% if is_granted('DISPOSE', application) %}
<button type="button" <button type="button"
class="btn btn--small" class="text-green-500"
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }} {{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
{{ stimulus_action('modal-button', 'confirmation', null, { {{ stimulus_action('modal-button', 'confirmation', null, {
'title': 'Bist du sicher?', 'title': 'Bist du sicher?',
'content': 'Möchtest du ' ~ application.teamer ~ ' wirklich einteilen?', 'content': 'Möchtest du ' ~ application.teamer ~ ' wirklich einteilen?',
'target-url': path('app_admin_application_dispose', { 'uuid': application.uuid }) 'target-url': path('app_admin_application_dispose', { 'uuid': application.uuid })
}) }}> }) }}>
einteilen {{ icon('check') }}
</button> </button>
{% endif %} {% endif %}
{% if is_granted('STATUS', application) %} {% if is_granted('STATUS', application) %}
<button type="button" class="btn btn--secondary btn--small" <button type="button"
class="text-primary"
{{ stimulus_controller('modal-button', [], [], {'ajax-modal': '#ajax-modal'}) }} {{ stimulus_controller('modal-button', [], [], {'ajax-modal': '#ajax-modal'}) }}
{{ stimulus_action('modal-button', 'ajax', null, { {{ stimulus_action('modal-button', 'ajax', null, {
'title': 'Bewerbungsstatus bearbeiten', 'title': 'Bewerbungsstatus bearbeiten',
'url': path('app_admin_application_status', { 'uuid': application.uuid }) 'url': path('app_admin_application_status', { 'uuid': application.uuid })
}) }}> }) }}>
Status {{ icon('power') }}
</button> </button>
{% endif %} {% endif %}
{% if is_granted('DELETE', application) %} {% if is_granted('DELETE', application) %}
<button type="button" class="btn btn--secondary btn--small" <button type="button" class="text-red-500"
{{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }} {{ stimulus_controller('modal-button', [], [], {'confirmation-modal': '#confirmation-modal'}) }}
{{ stimulus_action('modal-button', 'confirmation', null, { {{ stimulus_action('modal-button', 'confirmation', null, {
'title': 'Bist du sicher?', 'title': 'Bist du sicher?',
'content': 'Möchtest du die Bewerbung von ' ~ application.teamer ~ ' wirklich löschen?', 'content': 'Möchtest du die Bewerbung von ' ~ application.teamer ~ ' wirklich löschen?',
'target-url': path('app_admin_application_delete', { 'uuid': application.uuid }) 'target-url': path('app_admin_application_delete', { 'uuid': application.uuid })
}) }}> }) }}>
löschen {{ icon('delete') }}
</button> </button>
{% endif %} {% endif %}
</div> </div>
@@ -108,16 +116,16 @@
<table class="data-table"> <table class="data-table">
<thead> <thead>
<tr> <tr>
<th class="w-1/3"> <th class="w-1/4">
Name Name
</th> </th>
<th class="w-1/3"> <th class="w-1/4">
Anmerkungen Anmerkungen
</th> </th>
<th class="w-1/6"> <th>
Status Status
</th> </th>
<th class="w-1/6"></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -169,7 +177,7 @@
</table> </table>
</div> </div>
</div> </div>
<a href="{{ returnUrl }}" class="underline"> <a href="{{ returnUrl }}" class="btn">
zurück zurück
</a> </a>
</div> </div>
@@ -17,6 +17,9 @@
{% include '_partials/_assignment_requirements_info.html.twig' %} {% include '_partials/_assignment_requirements_info.html.twig' %}
{{ 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">
{% if form.pickup is defined %}
{{ form_row(form.pickup) }}
{% endif %}
{% if form.confirmPickup is defined %} {% if form.confirmPickup is defined %}
{{ form_row(form.confirmPickup) }} {{ form_row(form.confirmPickup) }}
{% endif %} {% endif %}
+2 -2
View File
@@ -16,7 +16,7 @@
<div class="flex flex-col items-start space-y-4"> <div class="flex flex-col items-start space-y-4">
{% if disposition is not null %} {% if disposition is not null %}
{% include '_partials/_infobox.html.twig' with { {% include '_partials/_infobox.html.twig' with {
'message': 'Du wurdest am ' ~ disposition.createdAt|date('d.m.Y') ~ 'für diesen Einsatz eingeteilt.' 'message': 'Du wurdest am ' ~ disposition.createdAt|date('d.m.Y') ~ ' für diesen Einsatz eingeteilt.'
} %} } %}
{% elseif application is not null %} {% elseif application is not null %}
{% if application.status == constant('App\\Entity\\Application::STATUS_REJECTED') %} {% if application.status == constant('App\\Entity\\Application::STATUS_REJECTED') %}
@@ -35,7 +35,7 @@
{% endif %} {% endif %}
{% else %} {% else %}
{% include '_partials/_infobox.html.twig' with { {% include '_partials/_infobox.html.twig' with {
'message': 'Du hast dich am ' ~ application.createdAt|date('d.m.Y') ~ 'auf diesen Einsatz beworben.' 'message': 'Du hast dich am ' ~ application.createdAt|date('d.m.Y') ~ ' auf diesen Einsatz beworben.'
} %} } %}
<button type="button" <button type="button"
class="btn btn--secondary" class="btn btn--secondary"
+19 -20
View File
@@ -66,24 +66,25 @@
{# New upload #} {# New upload #}
{% if workflow_can(disposition, 'upload_contract') %} {% if workflow_can(disposition, 'upload_contract') %}
<div class="pb-4"> {{ form_start(form) }}
<div class="flex flex-col space-y-4">
<a href="{{ path('app_teamer_disposition_contractpdf', { 'uuid': disposition.uuid }) }}" target="_blank" class="inline-block"> <a href="{{ path('app_teamer_disposition_contractpdf', { 'uuid': disposition.uuid }) }}" target="_blank" class="inline-block">
<img src="{{ asset('build/images/contract_thumb.jpg') }}" alt="Honorarvertrag" class="block w-32 h-auto shadow-md"> <img src="{{ asset('build/images/contract_thumb.jpg') }}" alt="Honorarvertrag" class="block w-32 h-auto shadow-md">
</a> </a>
</div> <div>
<p class="pb-4"> Bitte lade ihn unterschrieben hier wieder hoch:
Bitte lade ihn unterschrieben hier wieder hoch: </div>
</p>
{{ form_start(form) }}
<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'),
'accepted_files': 'image/jpg,image/jpeg,application/pdf', 'accepted_files': 'image/jpg,image/jpeg,application/pdf',
} %} } %}
{% if form.pickup is defined %}
{{ form_row(form.pickup) }}
{% endif %}
<button type="submit" class="btn">
Aktualisieren
</button>
</div> </div>
<button type="submit" class="btn">
Aktualisieren
</button>
{{ form_rest(form) }} {{ form_rest(form) }}
{{ form_end(form) }} {{ form_end(form) }}
{% else %} {% else %}
@@ -99,24 +100,22 @@
Honorarnote Honorarnote
</h2> </h2>
{% if workflow_can(disposition, 'upload_invoice') %} {% if workflow_can(disposition, 'upload_invoice') %}
<div class="pb-4"> {{ form_start(form) }}
<div class="flex flex-col space-y-4">
<a href="{{ path('app_teamer_disposition_invoicepdf', { 'uuid': disposition.uuid }) }}" target="_blank" class="inline-block"> <a href="{{ path('app_teamer_disposition_invoicepdf', { 'uuid': disposition.uuid }) }}" target="_blank" class="inline-block">
<img src="{{ asset('build/images/invoice_thumb.jpg') }}" alt="Honorarnote" class="block w-32 h-auto shadow-md"> <img src="{{ asset('build/images/invoice_thumb.jpg') }}" alt="Honorarnote" class="block w-32 h-auto shadow-md">
</a> </a>
</div> <div>
<p class="pb-4"> Bitte lade sie ausgefüllt und unterschrieben hier wieder hoch:
Bitte lade sie ausgefüllt und unterschrieben hier wieder hoch: </div>
</p>
{{ form_start(form) }}
<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_invoice'), 'endpoint_upload': path('_uploader_upload_invoice'),
'accepted_files': 'image/jpg,image/jpeg,application/pdf', 'accepted_files': 'image/jpg,image/jpeg,application/pdf',
} %} } %}
<button type="submit" class="btn">
Aktualisieren
</button>
</div> </div>
<button type="submit" class="btn">
Aktualisieren
</button>
{{ form_rest(form) }} {{ form_rest(form) }}
{{ form_end(form) }} {{ form_end(form) }}
{% else %} {% else %}