feat: refactor staffing status assignment and filtering

This commit is contained in:
Björn Fromme
2024-07-16 17:34:50 +02:00
parent fa9c68c463
commit 5dd2f79c2d
14 changed files with 128 additions and 52 deletions
+61
View File
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use App\Entity\Assignment;
use App\Repository\AssignmentRepository;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240716063200 extends AbstractMigration implements ContainerAwareInterface
{
private ContainerInterface $container;
public function getDescription(): string
{
return '';
}
public function setContainer(?ContainerInterface $container): void
{
$this->container = $container;
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE assignment ADD staffing_status VARCHAR(64) NOT NULL');
}
public function postUp(Schema $schema): void
{
$entityManager = $this->container->get('doctrine.orm.entity_manager');
$assignmentRepository = $entityManager->getRepository(Assignment::class);
$assignments = $assignmentRepository->findAll();
foreach ($assignments as $assignment) {
if (Assignment::STATUS_DRAFT === $assignment->getStatus() || null !== $assignment->getDeletedAt()) {
continue;
}
$assignment->updateStaffingStatus();
}
$entityManager->flush();
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE assignment DROP staffing_status');
}
}
@@ -26,6 +26,9 @@ class DeleteController extends AbstractController
$this->entityManager->remove($application); $this->entityManager->remove($application);
$this->entityManager->flush(); $this->entityManager->flush();
$application->getAssignment()->updateStaffingStatus();
$this->entityManager->flush();
$this->addFlash('success', 'Die Bewerbung wurde gelöscht'); $this->addFlash('success', 'Die Bewerbung wurde gelöscht');
$this->logger->info('Delete application', [ $this->logger->info('Delete application', [
'destination' => (string) $application->getAssignment()->getDestination(), 'destination' => (string) $application->getAssignment()->getDestination(),
@@ -39,6 +39,10 @@ class DisposeController extends AbstractController
$this->entityManager->remove($application); $this->entityManager->remove($application);
$this->entityManager->flush(); $this->entityManager->flush();
// Update staffing status of related assignment
$disposition->getAssignment()->updateStaffingStatus();
$this->entityManager->flush();
$this->eventDispatcher->dispatch(new DispositionCreatedEvent($disposition), DispositionCreatedEvent::NAME); $this->eventDispatcher->dispatch(new DispositionCreatedEvent($disposition), DispositionCreatedEvent::NAME);
$this->addFlash('success', 'Teamer:in wurde eingeteilt'); $this->addFlash('success', 'Teamer:in wurde eingeteilt');
@@ -54,6 +54,10 @@ class DeleteController extends AbstractController
$this->entityManager->remove($disposition); $this->entityManager->remove($disposition);
$this->entityManager->flush(); $this->entityManager->flush();
// Update staffing status of related assignment
$disposition->getAssignment()->updateStaffingStatus();
$this->entityManager->flush();
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(),
])); ]));
@@ -36,7 +36,6 @@ class IndexController extends AbstractController
$request->query->getInt('page', 1), $request->query->getInt('page', 1),
10, 10,
[ [
'wrap-queries' => true, // Required for query with 'having' clause
'defaultSortFieldName' => 'destination.dateFrom', 'defaultSortFieldName' => 'destination.dateFrom',
'defaultSortDirection' => 'desc', 'defaultSortDirection' => 'desc',
] ]
@@ -46,6 +46,9 @@ class CheckController extends AbstractController
switch ($formData->getStatus()) { switch ($formData->getStatus()) {
case Upload::STATUS_CHECKED: case Upload::STATUS_CHECKED:
$this->confirmDocument($document, 'confirm_contract', Upload::STATUS_CHECKED, $formData); $this->confirmDocument($document, 'confirm_contract', Upload::STATUS_CHECKED, $formData);
// Contract confirmed: Update staffing status of related assignment
$document->getDisposition()->getAssignment()->updateStaffingStatus();
$this->entityManager->flush();
break; break;
case Upload::STATUS_PAID: case Upload::STATUS_PAID:
$this->confirmDocument($document, 'confirm_invoice', Upload::STATUS_PAID, $formData); $this->confirmDocument($document, 'confirm_invoice', Upload::STATUS_PAID, $formData);
@@ -39,6 +39,10 @@ class CreateController extends AbstractController
$this->entityManager->persist($application); $this->entityManager->persist($application);
$this->entityManager->flush(); $this->entityManager->flush();
// Update staffing status of related assignment
$assignment->updateStaffingStatus();
$this->entityManager->flush();
$this->addFlash('success', 'Deine Bewerbung wurde entgegengenommen'); $this->addFlash('success', 'Deine Bewerbung wurde entgegengenommen');
$this->logger->info('Create application', [ $this->logger->info('Create application', [
'teamer_id' => $teamer->getId(), 'teamer_id' => $teamer->getId(),
@@ -37,6 +37,10 @@ class WithdrawController extends AbstractController
$this->entityManager->remove($application); $this->entityManager->remove($application);
$this->entityManager->flush(); $this->entityManager->flush();
// Update staffing status of related assignment
$assignment->updateStaffingStatus();
$this->entityManager->flush();
$this->logger->info('Withdraw application', [ $this->logger->info('Withdraw application', [
'teamer_id' => $teamer->getId(), 'teamer_id' => $teamer->getId(),
'teamer_name' => (string) $teamer, 'teamer_name' => (string) $teamer,
+30 -19
View File
@@ -95,10 +95,14 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
#[ORM\Column(length: 64)] #[ORM\Column(length: 64)]
private ?string $status; private ?string $status;
#[ORM\Column(length: 64)]
private ?string $staffingStatus;
public function __construct() public function __construct()
{ {
$this->uuid = Uuid::v4(); $this->uuid = Uuid::v4();
$this->status = static::STATUS_DRAFT; $this->status = static::STATUS_DRAFT;
$this->staffingStatus = static::STATUS_UNSTAFFED;
$this->fees = new ArrayCollection(); $this->fees = new ArrayCollection();
$this->applications = new ArrayCollection(); $this->applications = new ArrayCollection();
$this->dispositions = new ArrayCollection(); $this->dispositions = new ArrayCollection();
@@ -129,6 +133,27 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
return $instance; return $instance;
} }
public function updateStaffingStatus(): void
{
$confirmedDispositions = $this->getConfirmedDispositions();
if ($this->availableDispositions === $confirmedDispositions->count()) {
$this->setStaffingStatus(Assignment::STATUS_STAFFED);
} elseif (
$this->availableDispositions > $confirmedDispositions->count()
&& 0 < $confirmedDispositions->count()
) {
$this->setStaffingStatus(Assignment::STATUS_PARTLY_STAFFED);
} elseif (
$this->availableDispositions > $this->dispositions->count()
&& 0 < $this->applications->count()
) {
$this->setStaffingStatus(Assignment::STATUS_STAFFING);
} else {
$this->setStaffingStatus(Assignment::STATUS_UNSTAFFED);
}
}
public function getId(): ?int public function getId(): ?int
{ {
return $this->id; return $this->id;
@@ -488,29 +513,15 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
return $this; return $this;
} }
public function isStaffed(): bool public function getStaffingStatus(): ?string
{ {
$confirmedDispositions = $this->getConfirmedDispositions(); return $this->staffingStatus;
return $this->availableDispositions === $confirmedDispositions->count();
} }
public function isUnstaffed(): bool public function setStaffingStatus(?string $staffingStatus): static
{ {
return 0 === $this->dispositions->count(); $this->staffingStatus = $staffingStatus;
}
public function isPartlyStaffed(): bool return $this;
{
$confirmedDispositions = $this->getConfirmedDispositions();
return $this->availableDispositions > $confirmedDispositions->count()
&& 0 < $confirmedDispositions->count();
}
public function isStaffing(): bool
{
return $this->availableDispositions > $this->dispositions->count()
&& 0 < $this->applications->count();
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@ trait SoftDeletableEntity
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $deletedAt = null; private ?\DateTimeImmutable $deletedAt = null;
public function getDeletedAt(): \DateTimeImmutable public function getDeletedAt(): ?\DateTimeImmutable
{ {
return $this->deletedAt; return $this->deletedAt;
} }
+2 -2
View File
@@ -65,10 +65,10 @@ class AssignmentFilterType extends AbstractType
; ;
if ($this->security->isGranted('ROLE_ADMINISTRATIVE')) { if ($this->security->isGranted('ROLE_ADMINISTRATIVE')) {
$builder->add('status', ChoiceType::class, [ $builder->add('status', MultiselectType::class, [
'label' => 'Status', 'label' => 'Status',
'required' => false, 'required' => false,
'placeholder' => 'nicht filtern', 'empty_label' => 'nicht filtern',
'choices' => [ 'choices' => [
'voll besetzt' => Assignment::STATUS_STAFFED, 'voll besetzt' => Assignment::STATUS_STAFFED,
'teilweise besetzt' => Assignment::STATUS_PARTLY_STAFFED, 'teilweise besetzt' => Assignment::STATUS_PARTLY_STAFFED,
+3 -3
View File
@@ -17,7 +17,7 @@ class AssignmentFilterDto extends AbstractFilterDto
protected ?JobProfile $jobProfile = null; protected ?JobProfile $jobProfile = null;
protected ?string $hotel = null; protected ?string $hotel = null;
protected ?int $duration = null; protected ?int $duration = null;
protected ?string $status = null; protected array $status = [];
protected bool $includePast = false; protected bool $includePast = false;
public function getDateFrom(): ?\DateTimeImmutable public function getDateFrom(): ?\DateTimeImmutable
@@ -80,12 +80,12 @@ class AssignmentFilterDto extends AbstractFilterDto
return $this; return $this;
} }
public function getStatus(): ?string public function getStatus(): array
{ {
return $this->status; return $this->status;
} }
public function setStatus(?string $status): static public function setStatus(array $status): static
{ {
$this->status = $status; $this->status = $status;
+3 -20
View File
@@ -40,7 +40,6 @@ class AssignmentRepository extends ServiceEntityRepository
->leftJoin('assignment.applications', 'application') ->leftJoin('assignment.applications', 'application')
->leftJoin('assignment.dispositions', 'disposition') ->leftJoin('assignment.dispositions', 'disposition')
->leftJoin('disposition.teamer', 'teamer') ->leftJoin('disposition.teamer', 'teamer')
->groupBy('assignment')
; ;
$this->applyFilterSettings($filterDto, $qb); $this->applyFilterSettings($filterDto, $qb);
@@ -117,27 +116,11 @@ class AssignmentRepository extends ServiceEntityRepository
; ;
} }
if (null !== $status = $filterDto->getStatus()) { if (0 < count($filterDto->getStatus())) {
switch ($status) {
case Assignment::STATUS_STAFFED:
$qb->having('assignment.availableDispositions = COUNT(disposition.id)');
break;
case Assignment::STATUS_PARTLY_STAFFED:
$qb $qb
->having('COUNT(disposition.id) > 0') ->andWhere($qb->expr()->in('assignment.staffingStatus', ':status'))
->andHaving('assignment.availableDispositions > COUNT(disposition.id)') ->setParameter('status', $filterDto->getStatus())
; ;
break;
case Assignment::STATUS_STAFFING:
$qb
->having('COUNT(disposition.id) = 0')
->andHaving('COUNT(application.id) > 0')
;
break;
case Assignment::STATUS_UNSTAFFED:
$qb->having('COUNT(disposition.id) = 0');
break;
}
} }
if (null !== $dateFrom = $filterDto->getDateFrom()) { if (null !== $dateFrom = $filterDto->getDateFrom()) {
@@ -68,15 +68,15 @@
{% if assignment.status == 'draft' %} {% if assignment.status == 'draft' %}
{{ icon('edit', 'w-4 h-4') }} {{ icon('edit', 'w-4 h-4') }}
{% else %} {% else %}
{% if assignment.staffed %} {% if assignment.staffingStatus == constant('App\\Entity\\Assignment::STATUS_STAFFED') %}
<svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-green-500"> <svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-green-500">
<circle cx="3" cy="3" r="2" /> <circle cx="3" cy="3" r="2" />
</svg> </svg>
{% elseif assignment.partlyStaffed %} {% elseif assignment.staffingStatus == constant('App\\Entity\\Assignment::STATUS_PARTLY_STAFFED') %}
<svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-yellow-500"> <svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-yellow-500">
<circle cx="3" cy="3" r="2" /> <circle cx="3" cy="3" r="2" />
</svg> </svg>
{% elseif assignment.staffing %} {% elseif assignment.staffingStatus == constant('App\\Entity\\Assignment::STATUS_STAFFING') %}
<svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-blue-500"> <svg viewBox="0 0 5 5" class="h-4 w-4 fill-current text-blue-500">
<circle cx="3" cy="3" r="2" /> <circle cx="3" cy="3" r="2" />
</svg> </svg>
@@ -128,7 +128,7 @@
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_ADMIN') %}
<td> <td>
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}" class="flex items-center space-x-2"> <a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}" class="flex items-center space-x-2">
<span>{{ assignment.validApplications|length }}</span> <span>{{ assignment.applications|length }}</span>
</a> </a>
</td> </td>
{% endif %} {% endif %}