WIP: Implement availabilities

This commit is contained in:
Björn Fromme
2023-10-02 16:20:12 +02:00
parent d3a0021b7d
commit 96dbb875a0
7 changed files with 206 additions and 90 deletions
+35
View File
@@ -0,0 +1,35 @@
<?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 Version20231002134820 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('CREATE TABLE teamer_availability (teamer_id INT NOT NULL, availability_id INT NOT NULL, INDEX IDX_9CF7958B4302FF75 (teamer_id), INDEX IDX_9CF7958B61778466 (availability_id), PRIMARY KEY(teamer_id, availability_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE teamer_availability ADD CONSTRAINT FK_9CF7958B4302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE teamer_availability ADD CONSTRAINT FK_9CF7958B61778466 FOREIGN KEY (availability_id) REFERENCES availability (id) ON DELETE CASCADE');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE teamer_availability DROP FOREIGN KEY FK_9CF7958B4302FF75');
$this->addSql('ALTER TABLE teamer_availability DROP FOREIGN KEY FK_9CF7958B61778466');
$this->addSql('DROP TABLE teamer_availability');
}
}
@@ -2,17 +2,59 @@
namespace App\Controller\Teamer\Profile;
use App\Entity\Availability;
use App\Entity\User;
use App\Repository\AvailabilityRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
class AvailabilitiesController extends AbstractController
{
public function __construct(
private readonly AvailabilityRepository $availabilityRepository,
private readonly EntityManagerInterface $entityManager
) {
}
#[Route('/teamer/profile/availabilities', name: 'app_teamer_profile_availabilities')]
#[IsGranted('ROLE_TEAMER')]
public function index(): Response
public function index(Request $request): Response
{
return $this->render('teamer/profile/availabilities.html.twig');
/** @var User $user */
$user = $this->getUser();
$teamer = $user->getTeamer();
$selectableAvailabilities = $this->availabilityRepository->getSelectable($teamer);
$form = $this->createFormBuilder($teamer)
->add('availabilities', EntityType::class, [
'label' => false,
'class' => Availability::class,
'choice_label' => function($choice) {
return sprintf('%s - %s', $choice->getDateFrom()->format('d.m.Y'), $choice->getDateTo()->format('d.m.Y'));
},
'choices' => $selectableAvailabilities,
'multiple' => true,
'expanded' => true,
])
->getForm()
;
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->flush();
return $this->redirectToRoute('app_teamer_profile_availabilities');
}
return $this->render('teamer/profile/availabilities.html.twig', [
'availabilities' => $teamer->getAvailabilities(),
'selectableAvailabilities' => $selectableAvailabilities,
'form' => $form,
]);
}
}
+38 -1
View File
@@ -5,6 +5,8 @@ namespace App\Entity;
use App\Entity\Traits\BlameableEntity;
use App\Entity\Traits\TimestampableEntity;
use App\Repository\AvailabilityRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
@@ -25,9 +27,17 @@ class Availability implements BlameableEntityInterface, TimestampableEntityInter
#[ORM\Column(type: Types::DATE_IMMUTABLE)]
private ?\DateTimeImmutable $dateTo = null;
#[ORM\ManyToOne(inversedBy: 'availabilities')]
#[ORM\ManyToOne(inversedBy: 'customAvailabilities')]
private ?Teamer $owner = null;
#[ORM\ManyToMany(targetEntity: Teamer::class, mappedBy: 'availabilities')]
private Collection $teamers;
public function __construct()
{
$this->teamers = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
@@ -68,4 +78,31 @@ class Availability implements BlameableEntityInterface, TimestampableEntityInter
return $this;
}
/**
* @return Collection<int, Teamer>
*/
public function getTeamers(): Collection
{
return $this->teamers;
}
public function addTeamer(Teamer $teamer): static
{
if (!$this->teamers->contains($teamer)) {
$this->teamers->add($teamer);
$teamer->addAvailability($this);
}
return $this;
}
public function removeTeamer(Teamer $teamer): static
{
if ($this->teamers->removeElement($teamer)) {
$teamer->removeAvailability($this);
}
return $this;
}
}
+33 -5
View File
@@ -83,9 +83,12 @@ class Teamer implements TimestampableEntityInterface
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $remarks = null;
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: Availability::class)]
#[ORM\ManyToMany(targetEntity: Availability::class, inversedBy: 'teamers')]
private Collection $availabilities;
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: Availability::class)]
private Collection $customAvailabilities;
#[ORM\OneToOne(cascade: ['persist', 'remove'], fetch: 'EAGER')]
#[Assert\NotNull(message: 'Bitte lade ein Foto von dir hoch', groups: ['profile', 'profile_preflight'])]
private ?Upload $photo = null;
@@ -136,6 +139,7 @@ class Teamer implements TimestampableEntityInterface
$this->bookmarks = new ArrayCollection();
$this->applications = new ArrayCollection();
$this->dispositions = new ArrayCollection();
$this->customAvailabilities = new ArrayCollection();
}
public function toPayload(): array
@@ -375,7 +379,6 @@ class Teamer implements TimestampableEntityInterface
{
if (!$this->availabilities->contains($availability)) {
$this->availabilities->add($availability);
$availability->setOwner($this);
}
return $this;
@@ -383,10 +386,35 @@ class Teamer implements TimestampableEntityInterface
public function removeAvailability(Availability $availability): static
{
if ($this->availabilities->removeElement($availability)) {
$this->availabilities->removeElement($availability);
return $this;
}
/**
* @return Collection<int, Availability>
*/
public function getCustomAvailabilities(): Collection
{
return $this->customAvailabilities;
}
public function addCustomAvailability(Availability $customAvailability): static
{
if (!$this->customAvailabilities->contains($customAvailability)) {
$this->customAvailabilities->add($customAvailability);
$customAvailability->setOwner($this);
}
return $this;
}
public function removeCustomAvailability(Availability $customAvailability): static
{
if ($this->customAvailabilities->removeElement($customAvailability)) {
// set the owning side to null (unless already changed)
if ($availability->getOwner() === $this) {
$availability->setOwner(null);
if ($customAvailability->getOwner() === $this) {
$customAvailability->setOwner(null);
}
}
-43
View File
@@ -20,47 +20,4 @@ class ApplicationRepository extends ServiceEntityRepository
{
parent::__construct($registry, Application::class);
}
public function save(Application $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function remove(Application $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
// /**
// * @return Application[] Returns an array of Application objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('a')
// ->andWhere('a.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('a.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Application
// {
// return $this->createQueryBuilder('a')
// ->andWhere('a.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}
+23 -39
View File
@@ -3,6 +3,7 @@
namespace App\Repository;
use App\Entity\Availability;
use App\Entity\Teamer;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
@@ -21,46 +22,29 @@ class AvailabilityRepository extends ServiceEntityRepository
parent::__construct($registry, Availability::class);
}
public function save(Availability $entity, bool $flush = false): void
public function getSelectable(Teamer $teamer): array
{
$this->getEntityManager()->persist($entity);
$qb = $this->createQueryBuilder('availability');
if ($flush) {
$this->getEntityManager()->flush();
}
return $qb
->leftJoin('availability.teamers', 'teamer')
->where($qb->expr()->andX(
$qb->expr()->gt('availability.dateFrom', ':today'),
$qb->expr()->orX(
$qb->expr()->isNull('teamer'),
$qb->expr()->neq('teamer', ':teamer')
),
$qb->expr()->orX(
$qb->expr()->isNull('availability.owner'),
$qb->expr()->eq('availability.owner', ':owner')
)
))
->orderBy('availability.dateFrom', 'ASC')
->setParameter('teamer', $teamer)
->setParameter('owner', $teamer)
->setParameter('today', new \DateTimeImmutable())
->getQuery()
->getResult()
;
}
public function remove(Availability $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
// /**
// * @return Availability[] Returns an array of Availability objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('a')
// ->andWhere('a.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('a.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?Availability
// {
// return $this->createQueryBuilder('a')
// ->andWhere('a.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}
@@ -3,4 +3,37 @@
{% block title %}Meine Verfügbarkeit{% endblock %}
{% block content %}
<h1 class="text-2xl font-bold pb-4">
Meine Vergfügbarkeit
</h1>
<p class="pb-8">
Hier kannst du angeben, wann du allgemein Zeit und Lust hättest, Einsätze zu absolvieren. Wenn wir in diesem
Zeitraum einen Einsatz zu vergeben haben, dann nehmen wir telefonisch oder per E-Mail Kontakt zu dir auf.
</p>
<h2 class="text-xl font-bold pb-2">
Ich bin verfügbar
</h2>
<ul class="list-disc pl-4 pb-8">
{% for availability in availabilities %}
<li>
{{ availability.dateFrom|date('d.m.Y') }} - {{ availability.dateTo|date('d.m.Y') }}
</li>
{% else %}
<li class="text-sm">
Du hast bisher keine Verfügbarkeiten hinterlegt.
</li>
{% endfor %}
</ul>
<h2 class="text-xl font-bold pb-2">
Weitere Verfügbarkeiten
</h2>
{{ form_start(form) }}
{{ form_row(form.availabilities) }}
<button type="submit" class="btn">
Aktualisieren
</button>
{{ form_rest(form) }}
{{ form_end(form) }}
{% endblock %}