feat: remove obsolete date range override
This commit is contained in:
+2
-3
@@ -204,9 +204,8 @@ pinned in tests without a database.
|
|||||||
|
|
||||||
### A period means the season, not when the row was written
|
### A period means the season, not when the row was written
|
||||||
|
|
||||||
`StatisticsDateBasis::SEASON` (the default) filters on the assignment's dates, falling back to
|
`StatisticsDateBasis::SEASON` (the default) filters on the destination's date range through
|
||||||
the destination's — the translation of `Assignment::getEffectivePeriod()` into DQL, which
|
`src/Repository/Filter/SeasonPeriodFilter.php`, which is shared with
|
||||||
lives in `src/Repository/Filter/SeasonPeriodFilter.php` and is shared with
|
|
||||||
`DispositionRepository`. This matches every other statistics screen and
|
`DispositionRepository`. This matches every other statistics screen and
|
||||||
`StatisticsFilterType`. Change what a season means there, once, not per screen.
|
`StatisticsFilterType`. Change what a season means there, once, not per screen.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drops the per-assignment date range override (assignment.date_from / date_to).
|
||||||
|
*
|
||||||
|
* The columns were never writable from the UI and hold no data; every consumer now reads the
|
||||||
|
* destination's dates directly. See .ddev/plans/remove-assignment-date-override.md.
|
||||||
|
*/
|
||||||
|
final class Version20260831144511 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Drop the unused per-assignment date range override columns';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE assignment DROP date_from, DROP date_to');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE assignment ADD date_from DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', ADD date_to DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\'');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ class IndexController extends AbstractController
|
|||||||
$request->query->getInt('page', 1),
|
$request->query->getInt('page', 1),
|
||||||
$request->query->getInt('limit', 50),
|
$request->query->getInt('limit', 50),
|
||||||
[
|
[
|
||||||
'defaultSortFieldName' => 'assignment.dateFrom',
|
'defaultSortFieldName' => 'destination.dateFrom',
|
||||||
'defaultSortDirection' => 'asc',
|
'defaultSortDirection' => 'asc',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class IndexController extends AbstractController
|
|||||||
$request->query->getInt('page', 1),
|
$request->query->getInt('page', 1),
|
||||||
9,
|
9,
|
||||||
[
|
[
|
||||||
'defaultSortFieldName' => 'assignment.dateFrom',
|
'defaultSortFieldName' => 'destination.dateFrom',
|
||||||
'defaultSortDirection' => 'asc',
|
'defaultSortDirection' => 'asc',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class IndexController extends AbstractController
|
|||||||
$request->query->getInt('page', 1),
|
$request->query->getInt('page', 1),
|
||||||
9,
|
9,
|
||||||
[
|
[
|
||||||
'defaultSortFieldName' => 'assignment.dateFrom',
|
'defaultSortFieldName' => 'destination.dateFrom',
|
||||||
'defaultSortDirection' => 'asc',
|
'defaultSortDirection' => 'asc',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ class IcsController extends AbstractController
|
|||||||
$filename = sprintf(
|
$filename = sprintf(
|
||||||
'Einteilung_%s_%s-%s.ics',
|
'Einteilung_%s_%s-%s.ics',
|
||||||
$jobProfile->getName(),
|
$jobProfile->getName(),
|
||||||
$assignment->getEffectivePeriod()->start->format('d.m.Y'),
|
$destination->getDateFrom()->format('d.m.Y'),
|
||||||
$assignment->getEffectivePeriod()->end->format('d.m.Y')
|
$destination->getDateTo()->format('d.m.Y')
|
||||||
);
|
);
|
||||||
$slugger = new AsciiSlugger();
|
$slugger = new AsciiSlugger();
|
||||||
$filename = $slugger->slug($filename);
|
$filename = $slugger->slug($filename);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class RecentController extends AbstractController
|
|||||||
$request->query->getInt('page', 1),
|
$request->query->getInt('page', 1),
|
||||||
9,
|
9,
|
||||||
[
|
[
|
||||||
'defaultSortFieldName' => 'assignment.dateFrom',
|
'defaultSortFieldName' => 'destination.dateFrom',
|
||||||
'defaultSortDirection' => 'asc',
|
'defaultSortDirection' => 'asc',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class UpcomingController extends AbstractController
|
|||||||
$request->query->getInt('page', 1),
|
$request->query->getInt('page', 1),
|
||||||
9,
|
9,
|
||||||
[
|
[
|
||||||
'defaultSortFieldName' => 'assignment.dateFrom',
|
'defaultSortFieldName' => 'destination.dateFrom',
|
||||||
'defaultSortDirection' => 'asc',
|
'defaultSortDirection' => 'asc',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use App\Entity\Traits\BlameableEntity;
|
|||||||
use App\Entity\Traits\SoftDeletableEntity;
|
use App\Entity\Traits\SoftDeletableEntity;
|
||||||
use App\Entity\Traits\TimestampableEntity;
|
use App\Entity\Traits\TimestampableEntity;
|
||||||
use App\Repository\AssignmentRepository;
|
use App\Repository\AssignmentRepository;
|
||||||
use Carbon\CarbonPeriodImmutable;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -64,13 +63,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
private bool $skipFormalities = false;
|
private bool $skipFormalities = false;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
|
||||||
private ?\DateTimeImmutable $dateFrom = null;
|
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
|
||||||
#[Assert\GreaterThan(propertyPath: 'dateFrom', message: 'Das Enddatum muss nach dem Beginndatum liegen')]
|
|
||||||
private ?\DateTimeImmutable $dateTo = null;
|
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
#[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)]
|
||||||
private ?\DateTimeImmutable $pickupDate = null;
|
private ?\DateTimeImmutable $pickupDate = null;
|
||||||
|
|
||||||
@@ -133,8 +125,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
->setAvailableDispositions($assignment->getAvailableDispositions())
|
->setAvailableDispositions($assignment->getAvailableDispositions())
|
||||||
->setShowAvailableDispositions($assignment->isShowAvailableDispositions())
|
->setShowAvailableDispositions($assignment->isShowAvailableDispositions())
|
||||||
->setSkipFormalities($assignment->isSkipFormalities())
|
->setSkipFormalities($assignment->isSkipFormalities())
|
||||||
->setDateFrom($assignment->getDateFrom())
|
|
||||||
->setDateTo($assignment->getDateTo())
|
|
||||||
->setPickupDate($assignment->getPickupDate())
|
->setPickupDate($assignment->getPickupDate())
|
||||||
->setPickup($assignment->getPickup())
|
->setPickup($assignment->getPickup())
|
||||||
->setBenefits($assignment->getBenefits())
|
->setBenefits($assignment->getBenefits())
|
||||||
@@ -266,66 +256,6 @@ class Assignment implements BlameableEntityInterface, TimestampableEntityInterfa
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDateFrom(): ?\DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->dateFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDateFrom(?\DateTimeImmutable $dateFrom): static
|
|
||||||
{
|
|
||||||
$this->dateFrom = $dateFrom;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDateTo(): ?\DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->dateTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDateTo(?\DateTimeImmutable $dateTo): static
|
|
||||||
{
|
|
||||||
$this->dateTo = $dateTo;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Immutable on purpose: a plain CarbonPeriod hands out mutable Carbon instances, so a caller
|
|
||||||
* deriving one date from another - `$end = $period->getEndDate(); $end->addDays(14)` - moves
|
|
||||||
* the original as well and silently collapses its own period. CarbonPeriodImmutable yields
|
|
||||||
* CarbonImmutable from start, end and getEndDate(), which makes that mistake impossible.
|
|
||||||
*/
|
|
||||||
public function getEffectivePeriod(): ?CarbonPeriodImmutable
|
|
||||||
{
|
|
||||||
if (null !== $destination = $this->getDestination()) {
|
|
||||||
$dateFrom = $this->getDateFrom() ?? $destination->getDateFrom();
|
|
||||||
$dateTo = $this->getDateTo() ?? $destination->getDateTo();
|
|
||||||
|
|
||||||
return new CarbonPeriodImmutable($dateFrom, $dateTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEffectiveDateFrom(): ?\DateTimeImmutable
|
|
||||||
{
|
|
||||||
if (null !== $effectivePeriod = $this->getEffectivePeriod()) {
|
|
||||||
return $effectivePeriod->start->toDateTimeImmutable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEffectiveDateTo(): ?\DateTimeImmutable
|
|
||||||
{
|
|
||||||
if (null !== $effectivePeriod = $this->getEffectivePeriod()) {
|
|
||||||
return $effectivePeriod->end->toDateTimeImmutable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEffectivePickupDate(): ?\DateTimeImmutable
|
public function getEffectivePickupDate(): ?\DateTimeImmutable
|
||||||
{
|
{
|
||||||
if (null !== $this->pickupDate) {
|
if (null !== $this->pickupDate) {
|
||||||
|
|||||||
@@ -246,9 +246,8 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
|
|||||||
}
|
}
|
||||||
|
|
||||||
$assignment = $this->getAssignment();
|
$assignment = $this->getAssignment();
|
||||||
$assignmentPeriod = $assignment->getEffectivePeriod();
|
|
||||||
$dueDateFrom = $this->getCreatedAt()->modify(sprintf('+%d days', $deadlineDays));
|
$dueDateFrom = $this->getCreatedAt()->modify(sprintf('+%d days', $deadlineDays));
|
||||||
$dueDateTo = $assignmentPeriod->end->modify('-1 day');
|
$dueDateTo = $assignment->getDestination()->getDateTo()->modify('-1 day');
|
||||||
|
|
||||||
// A short-notice assignment can end before the normal $deadlineDays window would even
|
// A short-notice assignment can end before the normal $deadlineDays window would even
|
||||||
// start. Left uncapped, dueDateFrom >= dueDateTo makes isStarted() and isEnded() equal
|
// start. Left uncapped, dueDateFrom >= dueDateTo makes isStarted() and isEnded() equal
|
||||||
@@ -297,9 +296,8 @@ class Disposition implements BlameableEntityInterface, TimestampableEntityInterf
|
|||||||
}
|
}
|
||||||
|
|
||||||
$assignment = $this->getAssignment();
|
$assignment = $this->getAssignment();
|
||||||
$assignmentPeriod = $assignment->getEffectivePeriod();
|
$dueDateFrom = $assignment->getDestination()->getDateTo();
|
||||||
$dueDateFrom = $assignmentPeriod->getEndDate();
|
$dueDateTo = $dueDateFrom->modify(sprintf('+%d days', $deadlineDays));
|
||||||
$dueDateTo = $dueDateFrom->addDays($deadlineDays);
|
|
||||||
|
|
||||||
$period = CarbonPeriodImmutable::create($dueDateFrom, $dueDateTo);
|
$period = CarbonPeriodImmutable::create($dueDateFrom, $dueDateTo);
|
||||||
|
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ class Feedback implements BlameableEntityInterface, TimestampableEntityInterface
|
|||||||
|
|
||||||
$instance
|
$instance
|
||||||
->setAssignment($assignment)
|
->setAssignment($assignment)
|
||||||
->setAssignmentDateFrom($assignment->getEffectiveDateFrom())
|
->setAssignmentDateFrom($destination->getDateFrom())
|
||||||
->setAssignmentDateTo($assignment->getEffectiveDateTo())
|
->setAssignmentDateTo($destination->getDateTo())
|
||||||
->setDestinationName($destination->getProduct())
|
->setDestinationName($destination->getProduct())
|
||||||
->setHotel($destination->getHotel())
|
->setHotel($destination->getHotel())
|
||||||
->setHotelCode($destination->getHotelCode())
|
->setHotelCode($destination->getHotelCode())
|
||||||
|
|||||||
@@ -75,14 +75,13 @@ class DispositionWorkflowGuardSubscriber implements EventSubscriberInterface
|
|||||||
/** @var Disposition $disposition */
|
/** @var Disposition $disposition */
|
||||||
$disposition = $event->getSubject();
|
$disposition = $event->getSubject();
|
||||||
$assignment = $disposition->getAssignment();
|
$assignment = $disposition->getAssignment();
|
||||||
$assignmentPeriod = $assignment->getEffectivePeriod();
|
|
||||||
$today = new \DateTimeImmutable();
|
$today = new \DateTimeImmutable();
|
||||||
|
|
||||||
$dueDateFrom = $disposition
|
$dueDateFrom = $disposition
|
||||||
->getCreatedAt()
|
->getCreatedAt()
|
||||||
->modify(sprintf('+%d days', $this->contractUploadDeadlineDays))
|
->modify(sprintf('+%d days', $this->contractUploadDeadlineDays))
|
||||||
;
|
;
|
||||||
$dueDateTo = $assignmentPeriod->end->modify('-1 day');
|
$dueDateTo = $assignment->getDestination()->getDateTo()->modify('-1 day');
|
||||||
|
|
||||||
// Block upload if deadline has passed
|
// Block upload if deadline has passed
|
||||||
if ($dueDateTo < $today) {
|
if ($dueDateTo < $today) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Entity\Upload;
|
|||||||
use App\Event\ApplicationDeletedEvent;
|
use App\Event\ApplicationDeletedEvent;
|
||||||
use App\Event\DocumentConfirmedEvent;
|
use App\Event\DocumentConfirmedEvent;
|
||||||
use App\Repository\ApplicationRepository;
|
use App\Repository\ApplicationRepository;
|
||||||
|
use Carbon\CarbonPeriodImmutable;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||||
@@ -37,10 +38,12 @@ class InvalidateApplicationsListener
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $period = $disposition->getAssignment()->getEffectivePeriod()) {
|
if (null === $destination = $disposition->getAssignment()->getDestination()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$period = new CarbonPeriodImmutable($destination->getDateFrom(), $destination->getDateTo());
|
||||||
|
|
||||||
$applications = $this->applicationRepository->findOverlappingForTeamer($teamer, $period);
|
$applications = $this->applicationRepository->findOverlappingForTeamer($teamer, $period);
|
||||||
|
|
||||||
if (0 === count($applications)) {
|
if (0 === count($applications)) {
|
||||||
@@ -55,8 +58,8 @@ class InvalidateApplicationsListener
|
|||||||
'teamer' => $teamer->getUuid(),
|
'teamer' => $teamer->getUuid(),
|
||||||
'teamer_name' => (string) $teamer,
|
'teamer_name' => (string) $teamer,
|
||||||
'destination' => $assignment->getDestination()->getHotelCode(),
|
'destination' => $assignment->getDestination()->getHotelCode(),
|
||||||
'date_from' => $assignment->getEffectiveDateFrom()->format('Y-m-d'),
|
'date_from' => $assignment->getDestination()->getDateFrom()->format('Y-m-d'),
|
||||||
'date_to' => $assignment->getEffectiveDateTo()->format('Y-m-d'),
|
'date_to' => $assignment->getDestination()->getDateTo()->format('Y-m-d'),
|
||||||
]);
|
]);
|
||||||
$this->entityManager->remove($application);
|
$this->entityManager->remove($application);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,14 +79,6 @@ class AssignmentType extends AbstractType
|
|||||||
'rows' => 3,
|
'rows' => 3,
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
// ->add('dateFrom', DatepickerType::class, [
|
|
||||||
// 'label' => 'abweichender Einsatztermin von',
|
|
||||||
// 'required' => false,
|
|
||||||
// ])
|
|
||||||
// ->add('dateTo', DatepickerType::class, [
|
|
||||||
// 'label' => 'abweichender Einsatztermin bis',
|
|
||||||
// 'required' => false,
|
|
||||||
// ])
|
|
||||||
// ->add('pickupDate', DatepickerType::class, [
|
// ->add('pickupDate', DatepickerType::class, [
|
||||||
// 'label' => 'abweichender Zustieg bei Busbegleitung',
|
// 'label' => 'abweichender Zustieg bei Busbegleitung',
|
||||||
// 'required' => false,
|
// 'required' => false,
|
||||||
|
|||||||
@@ -163,12 +163,10 @@ class ApplicationRepository extends ServiceEntityRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds all applications of a teamer whose effective assignment period overlaps the given period.
|
* Finds all applications of a teamer whose assignment period overlaps the given period.
|
||||||
*
|
*
|
||||||
* The effective period of an assignment is its own date range, falling back to the date range of
|
* The assignment period is the date range of its destination. Boundaries count as an
|
||||||
* its destination whenever the assignment does not override it - the query mirrors
|
* overlap: a teamer cannot end one assignment on the same day another one begins.
|
||||||
* {@see Assignment::getEffectivePeriod()}. Boundaries count as an overlap: a teamer cannot end one
|
|
||||||
* assignment on the same day another one begins.
|
|
||||||
*
|
*
|
||||||
* @return array<Application>
|
* @return array<Application>
|
||||||
*/
|
*/
|
||||||
@@ -193,26 +191,8 @@ class ApplicationRepository extends ServiceEntityRepository
|
|||||||
$qb->expr()->neq('assignment.status', ':assignment_called_off'),
|
$qb->expr()->neq('assignment.status', ':assignment_called_off'),
|
||||||
$qb->expr()->isNull('assignment.deletedAt'),
|
$qb->expr()->isNull('assignment.deletedAt'),
|
||||||
$qb->expr()->isNull('destination.deletedAt'),
|
$qb->expr()->isNull('destination.deletedAt'),
|
||||||
$qb->expr()->orX(
|
$qb->expr()->lte('destination.dateFrom', ':dateTo'),
|
||||||
$qb->expr()->andX(
|
$qb->expr()->gte('destination.dateTo', ':dateFrom')
|
||||||
$qb->expr()->isNull('assignment.dateFrom'),
|
|
||||||
$qb->expr()->lte('destination.dateFrom', ':dateTo')
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateFrom'),
|
|
||||||
$qb->expr()->lte('assignment.dateFrom', ':dateTo')
|
|
||||||
)
|
|
||||||
),
|
|
||||||
$qb->expr()->orX(
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->gte('destination.dateTo', ':dateFrom')
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->gte('assignment.dateTo', ':dateFrom')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
))
|
))
|
||||||
->setParameter('teamer', $teamer)
|
->setParameter('teamer', $teamer)
|
||||||
->setParameter('application_rejected', Application::STATUS_REJECTED)
|
->setParameter('application_rejected', Application::STATUS_REJECTED)
|
||||||
|
|||||||
@@ -73,13 +73,7 @@ class AssignmentRepository extends ServiceEntityRepository
|
|||||||
->innerJoin('assignment.destination', 'destination')
|
->innerJoin('assignment.destination', 'destination')
|
||||||
->innerJoin('assignment.jobProfile', 'job_profile')
|
->innerJoin('assignment.jobProfile', 'job_profile')
|
||||||
->where($qb->expr()->andX(
|
->where($qb->expr()->andX(
|
||||||
$qb->expr()->orX(
|
$qb->expr()->gt('destination.dateFrom', ':now'),
|
||||||
$qb->expr()->gt('destination.dateFrom', ':now'),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateFrom'),
|
|
||||||
$qb->expr()->gt('assignment.dateFrom', ':now')
|
|
||||||
)
|
|
||||||
),
|
|
||||||
$qb->expr()->isNull('assignment.deletedAt'),
|
$qb->expr()->isNull('assignment.deletedAt'),
|
||||||
$qb->expr()->notIn('assignment.status', ':status'),
|
$qb->expr()->notIn('assignment.status', ':status'),
|
||||||
$qb->expr()->in('assignment.id', $availableAssignmentsIds)
|
$qb->expr()->in('assignment.id', $availableAssignmentsIds)
|
||||||
@@ -399,13 +393,7 @@ class AssignmentRepository extends ServiceEntityRepository
|
|||||||
)
|
)
|
||||||
->innerJoin('assignment.destination', 'destination')
|
->innerJoin('assignment.destination', 'destination')
|
||||||
->where($qb->expr()->andX(
|
->where($qb->expr()->andX(
|
||||||
$qb->expr()->orX(
|
$qb->expr()->gt('destination.dateFrom', ':now'),
|
||||||
$qb->expr()->gt('destination.dateFrom', ':now'),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateFrom'),
|
|
||||||
$qb->expr()->gt('assignment.dateFrom', ':now')
|
|
||||||
)
|
|
||||||
),
|
|
||||||
$qb->expr()->notIn('assignment.status', ':status'),
|
$qb->expr()->notIn('assignment.status', ':status'),
|
||||||
$qb->expr()->isNull('assignment.deletedAt')
|
$qb->expr()->isNull('assignment.deletedAt')
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
|||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\Query\Expr\Comparison;
|
use Doctrine\ORM\Query\Expr\Comparison;
|
||||||
use Doctrine\ORM\Query\Expr\Join;
|
use Doctrine\ORM\Query\Expr\Join;
|
||||||
use Doctrine\ORM\Query\Expr\Orx;
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
@@ -43,16 +42,7 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
$qb->expr()->eq('disposition.teamer', ':teamer'),
|
$qb->expr()->eq('disposition.teamer', ':teamer'),
|
||||||
$qb->expr()->neq('assignment.status', ':assignmentStatus'),
|
$qb->expr()->neq('assignment.status', ':assignmentStatus'),
|
||||||
$qb->expr()->neq('disposition.status', ':dispositionStatus'),
|
$qb->expr()->neq('disposition.status', ':dispositionStatus'),
|
||||||
$qb->expr()->orX(
|
$qb->expr()->gte('destination.dateTo', ':date')
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->gte('destination.dateTo', ':date')
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->gte('assignment.dateTo', ':date')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
))
|
))
|
||||||
->setParameter('teamer', $teamer)
|
->setParameter('teamer', $teamer)
|
||||||
->setParameter('assignmentStatus', Assignment::STATUS_CALLED_OFF)
|
->setParameter('assignmentStatus', Assignment::STATUS_CALLED_OFF)
|
||||||
@@ -85,16 +75,7 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
$qb->expr()->eq('disposition.teamer', ':teamer'),
|
$qb->expr()->eq('disposition.teamer', ':teamer'),
|
||||||
$qb->expr()->neq('assignment.status', ':assignmentStatus'),
|
$qb->expr()->neq('assignment.status', ':assignmentStatus'),
|
||||||
$qb->expr()->neq('disposition.status', ':dispositionStatus'),
|
$qb->expr()->neq('disposition.status', ':dispositionStatus'),
|
||||||
$qb->expr()->orX(
|
$qb->expr()->lte('destination.dateTo', ':date')
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->lte('destination.dateTo', ':date')
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->lte('assignment.dateTo', ':date')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
))
|
))
|
||||||
->setParameter('teamer', $teamer)
|
->setParameter('teamer', $teamer)
|
||||||
->setParameter('assignmentStatus', Assignment::STATUS_CALLED_OFF)
|
->setParameter('assignmentStatus', Assignment::STATUS_CALLED_OFF)
|
||||||
@@ -145,12 +126,12 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The effective start date of every non-called-off disposition of a teamer, one row
|
* The start date of every non-called-off disposition of a teamer, one row each, for the
|
||||||
* each, for the per-season tally in the info modal.
|
* per-season tally in the info modal.
|
||||||
*
|
*
|
||||||
* Same teamer/called-off filtering as findRecentDispositionsByTeamer(), but unbounded
|
* Same teamer/called-off filtering as findRecentDispositionsByTeamer(), but unbounded
|
||||||
* and stripped to the one date it needs: assignment.dateFrom falling back to the
|
* and stripped to the one date it needs: the destination's start, the season convention
|
||||||
* destination's, the season convention shared with SeasonPeriodFilter.
|
* shared with SeasonPeriodFilter.
|
||||||
*
|
*
|
||||||
* @return array<int, array{seasonDate: string}>
|
* @return array<int, array{seasonDate: string}>
|
||||||
*/
|
*/
|
||||||
@@ -159,7 +140,7 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
$qb = $this->createQueryBuilder('disposition');
|
$qb = $this->createQueryBuilder('disposition');
|
||||||
|
|
||||||
return $qb
|
return $qb
|
||||||
->select('COALESCE(assignment.dateFrom, destination.dateFrom) AS seasonDate')
|
->select('destination.dateFrom AS seasonDate')
|
||||||
->innerJoin('disposition.assignment', 'assignment')
|
->innerJoin('disposition.assignment', 'assignment')
|
||||||
->innerJoin('assignment.destination', 'destination')
|
->innerJoin('assignment.destination', 'destination')
|
||||||
->where($qb->expr()->andX(
|
->where($qb->expr()->andX(
|
||||||
@@ -242,13 +223,7 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
->leftJoin('disposition.feedback', 'feedback')
|
->leftJoin('disposition.feedback', 'feedback')
|
||||||
->where($qb->expr()->andX(
|
->where($qb->expr()->andX(
|
||||||
$qb->expr()->notIn('assignment.status', ':status'),
|
$qb->expr()->notIn('assignment.status', ':status'),
|
||||||
$qb->expr()->orX(
|
$qb->expr()->lte('destination.dateTo', ':dateTo'),
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->lte('assignment.dateTo', ':dateTo')
|
|
||||||
),
|
|
||||||
$qb->expr()->lte('destination.dateTo', ':dateTo')
|
|
||||||
),
|
|
||||||
$qb->expr()->isNull('feedback'),
|
$qb->expr()->isNull('feedback'),
|
||||||
$this->excludesSkipFormalities($qb)
|
$this->excludesSkipFormalities($qb)
|
||||||
))
|
))
|
||||||
@@ -302,13 +277,7 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
->innerJoin('assignment.destination', 'destination')
|
->innerJoin('assignment.destination', 'destination')
|
||||||
->where($qb->expr()->andX(
|
->where($qb->expr()->andX(
|
||||||
$qb->expr()->in('disposition.status', ':status'),
|
$qb->expr()->in('disposition.status', ':status'),
|
||||||
$qb->expr()->orX(
|
$qb->expr()->lte('destination.dateTo', ':dateTo')
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->lte('assignment.dateTo', ':dateTo')
|
|
||||||
),
|
|
||||||
$qb->expr()->lte('destination.dateTo', ':dateTo')
|
|
||||||
)
|
|
||||||
))
|
))
|
||||||
->setParameter('status', [
|
->setParameter('status', [
|
||||||
Disposition::STATUS_NEW,
|
Disposition::STATUS_NEW,
|
||||||
@@ -529,10 +498,10 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
$normalDay = $qb->expr()->andX(
|
$normalDay = $qb->expr()->andX(
|
||||||
$qb->expr()->gte('disposition.createdAt', ':dayStart'),
|
$qb->expr()->gte('disposition.createdAt', ':dayStart'),
|
||||||
$qb->expr()->lt('disposition.createdAt', ':dayEnd'),
|
$qb->expr()->lt('disposition.createdAt', ':dayEnd'),
|
||||||
$this->effectiveDateToGreaterThan($qb, ':tomorrow'),
|
$qb->expr()->gt('destination.dateTo', ':tomorrow'),
|
||||||
);
|
);
|
||||||
$shortNoticeDay = $qb->expr()->andX(
|
$shortNoticeDay = $qb->expr()->andX(
|
||||||
$this->effectiveDateToEquals($qb, ':tomorrow'),
|
$qb->expr()->eq('destination.dateTo', ':tomorrow'),
|
||||||
$qb->expr()->gte('disposition.createdAt', ':dayStart'),
|
$qb->expr()->gte('disposition.createdAt', ':dayStart'),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -599,7 +568,7 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
// in CronCommand, so a skip-formalities placement is still `confirmed` here and
|
// in CronCommand, so a skip-formalities placement is still `confirmed` here and
|
||||||
// would slip past the status filter on the day after the assignment ends.
|
// would slip past the status filter on the day after the assignment ends.
|
||||||
$this->excludesSkipFormalities($qb),
|
$this->excludesSkipFormalities($qb),
|
||||||
$this->effectiveDateToEquals($qb, ':endDate'),
|
$qb->expr()->eq('destination.dateTo', ':endDate'),
|
||||||
))
|
))
|
||||||
->setParameter('documentType', Upload::TYPE_INVOICE)
|
->setParameter('documentType', Upload::TYPE_INVOICE)
|
||||||
->setParameter('dispositionStatus', [Disposition::STATUS_CALLED_OFF, Disposition::STATUS_COMPLETED])
|
->setParameter('dispositionStatus', [Disposition::STATUS_CALLED_OFF, Disposition::STATUS_COMPLETED])
|
||||||
@@ -611,30 +580,6 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares the assignment's end date against $parameter, falling back to the destination's
|
|
||||||
* when the assignment does not override it.
|
|
||||||
*
|
|
||||||
* The fallback is guarded on both sides - without the isNull() on the second branch an
|
|
||||||
* assignment that moves the end date into the future would still match on the
|
|
||||||
* destination's date. Kept in one place because the per-assignment date override is due to
|
|
||||||
* be removed (docs/remove-assignment-date-override.md), and this then collapses to a plain
|
|
||||||
* comparison on destination.dateTo.
|
|
||||||
*/
|
|
||||||
private function effectiveDateToEquals(QueryBuilder $qb, string $parameter): Orx
|
|
||||||
{
|
|
||||||
return $qb->expr()->orX(
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->eq('assignment.dateTo', $parameter)
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->eq('destination.dateTo', $parameter)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restricts a query to assignments that still run the contract, invoice and feedback process.
|
* Restricts a query to assignments that still run the contract, invoice and feedback process.
|
||||||
*
|
*
|
||||||
@@ -653,23 +598,6 @@ class DispositionRepository extends ServiceEntityRepository
|
|||||||
return $qb->expr()->eq('assignment.skipFormalities', ':skipFormalities');
|
return $qb->expr()->eq('assignment.skipFormalities', ':skipFormalities');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Same fallback as effectiveDateToEquals(), but for "later than $parameter".
|
|
||||||
*/
|
|
||||||
private function effectiveDateToGreaterThan(QueryBuilder $qb, string $parameter): Orx
|
|
||||||
{
|
|
||||||
return $qb->expr()->orX(
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->gt('assignment.dateTo', $parameter)
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->gt('destination.dateTo', $parameter)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispositions whose contract upload period has run out, for the admin dashboard.
|
* Dispositions whose contract upload period has run out, for the admin dashboard.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,15 +5,14 @@ namespace App\Repository\Filter;
|
|||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Narrows a query to the season an assignment runs in.
|
* Narrows a query to the season an assignment runs in, which is the date range of its
|
||||||
|
* destination.
|
||||||
*
|
*
|
||||||
* The season is assignment.dateFrom/dateTo falling back to the destination's, mirroring
|
* Kept in one place because every statistics screen means the same thing by "a season":
|
||||||
* Assignment::getEffectivePeriod(), which DQL cannot call. Every statistics screen means
|
* the expression carried by hand in each repository is one more chance for it to come to
|
||||||
* this by a date range, so it lives in one place: three repositories carrying the same
|
* mean something slightly different on one screen than on the one beside it.
|
||||||
* expression by hand is three chances for "a season" to come to mean something slightly
|
|
||||||
* different on one screen than on the one beside it.
|
|
||||||
*
|
*
|
||||||
* Both aliases must already be joined by the caller - this only adds the conditions.
|
* The destination alias must already be joined by the caller - this only adds the conditions.
|
||||||
*/
|
*/
|
||||||
final class SeasonPeriodFilter
|
final class SeasonPeriodFilter
|
||||||
{
|
{
|
||||||
@@ -21,37 +20,18 @@ final class SeasonPeriodFilter
|
|||||||
QueryBuilder $qb,
|
QueryBuilder $qb,
|
||||||
?\DateTimeImmutable $dateFrom,
|
?\DateTimeImmutable $dateFrom,
|
||||||
?\DateTimeImmutable $dateTo,
|
?\DateTimeImmutable $dateTo,
|
||||||
string $assignmentAlias = 'assignment',
|
|
||||||
string $destinationAlias = 'destination',
|
string $destinationAlias = 'destination',
|
||||||
): void {
|
): void {
|
||||||
if (null !== $dateFrom) {
|
if (null !== $dateFrom) {
|
||||||
$qb
|
$qb
|
||||||
->andWhere($qb->expr()->orX(
|
->andWhere($qb->expr()->gte($destinationAlias.'.dateFrom', ':dateFrom'))
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull($assignmentAlias.'.dateFrom'),
|
|
||||||
$qb->expr()->gte($assignmentAlias.'.dateFrom', ':dateFrom')
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNull($assignmentAlias.'.dateFrom'),
|
|
||||||
$qb->expr()->gte($destinationAlias.'.dateFrom', ':dateFrom')
|
|
||||||
)
|
|
||||||
))
|
|
||||||
->setParameter('dateFrom', $dateFrom)
|
->setParameter('dateFrom', $dateFrom)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null !== $dateTo) {
|
if (null !== $dateTo) {
|
||||||
$qb
|
$qb
|
||||||
->andWhere($qb->expr()->orX(
|
->andWhere($qb->expr()->lte($destinationAlias.'.dateTo', ':dateTo'))
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull($assignmentAlias.'.dateTo'),
|
|
||||||
$qb->expr()->lte($assignmentAlias.'.dateTo', ':dateTo')
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNull($assignmentAlias.'.dateTo'),
|
|
||||||
$qb->expr()->lte($destinationAlias.'.dateTo', ':dateTo')
|
|
||||||
)
|
|
||||||
))
|
|
||||||
->setParameter('dateTo', $dateTo)
|
->setParameter('dateTo', $dateTo)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ class IcsGenerator
|
|||||||
$label = sprintf('E&P: Einteilung als %s', $assignment->getJobProfile()->getName());
|
$label = sprintf('E&P: Einteilung als %s', $assignment->getJobProfile()->getName());
|
||||||
$address = sprintf("%s\n%s", $assignment->getDestination()->getProduct(), $assignment->getDestination()->getHotel());
|
$address = sprintf("%s\n%s", $assignment->getDestination()->getProduct(), $assignment->getDestination()->getHotel());
|
||||||
|
|
||||||
|
$destination = $assignment->getDestination();
|
||||||
|
|
||||||
$event = Event::create($label)
|
$event = Event::create($label)
|
||||||
->startsAt($assignment->getEffectivePeriod()->start)
|
->startsAt($destination->getDateFrom())
|
||||||
->endsAt($assignment->getEffectivePeriod()->end->modify('+1 day'))
|
->endsAt($destination->getDateTo()->modify('+1 day'))
|
||||||
->address($address)
|
->address($address)
|
||||||
->description($label)
|
->description($label)
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ class ContractRenderer extends AbstractPdfRenderer
|
|||||||
$pdf->Text(21, 104.5, $pdf->encodeString($assignment->getJobProfile()->getName()));
|
$pdf->Text(21, 104.5, $pdf->encodeString($assignment->getJobProfile()->getName()));
|
||||||
|
|
||||||
// Period
|
// Period
|
||||||
$dateFrom = $assignment->getEffectivePeriod()->start->format('d.m.Y');
|
$dateFrom = $destination->getDateFrom()->format('d.m.Y');
|
||||||
$dateTo = $assignment->getEffectivePeriod()->end->format('d.m.Y');
|
$dateTo = $destination->getDateTo()->format('d.m.Y');
|
||||||
$period = sprintf('%s - %s', $dateFrom, $dateTo);
|
$period = sprintf('%s - %s', $dateFrom, $dateTo);
|
||||||
$pdf->Text(57, 113, $period);
|
$pdf->Text(57, 113, $period);
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class InvoiceRenderer extends AbstractPdfRenderer
|
|||||||
|
|
||||||
// Invoice number
|
// Invoice number
|
||||||
$pdf->SetFont('Arial', '', 12);
|
$pdf->SetFont('Arial', '', 12);
|
||||||
$date = $assignment->getEffectivePeriod()->start->format('d/m/Y');
|
$date = $assignment->getDestination()->getDateFrom()->format('d/m/Y');
|
||||||
$suffix = $assignment->getId();
|
$suffix = $assignment->getId();
|
||||||
$number = sprintf('%s-%05d', $date, $suffix);
|
$number = sprintf('%s-%05d', $date, $suffix);
|
||||||
$pdf->Text(85, 116, $number);
|
$pdf->Text(85, 116, $number);
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ class ApplicationValidator extends ConstraintValidator
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null === $period = $application->getAssignment()->getEffectivePeriod()) {
|
if (null === $destination = $application->getAssignment()->getDestination()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$applicationDateFrom = $period->start->toDateTimeImmutable();
|
$applicationDateFrom = $destination->getDateFrom();
|
||||||
$applicationDateTo = $period->end->toDateTimeImmutable();
|
$applicationDateTo = $destination->getDateTo();
|
||||||
|
|
||||||
$qb = $this
|
$qb = $this
|
||||||
->dispositionRepository
|
->dispositionRepository
|
||||||
@@ -62,26 +62,8 @@ class ApplicationValidator extends ConstraintValidator
|
|||||||
$qb->expr()->neq('assignment.status', ':assignment_called_off'),
|
$qb->expr()->neq('assignment.status', ':assignment_called_off'),
|
||||||
$qb->expr()->isNull('assignment.deletedAt'),
|
$qb->expr()->isNull('assignment.deletedAt'),
|
||||||
$qb->expr()->isNull('destination.deletedAt'),
|
$qb->expr()->isNull('destination.deletedAt'),
|
||||||
$qb->expr()->orX(
|
$qb->expr()->lte('destination.dateFrom', ':application_date_to'),
|
||||||
$qb->expr()->andX(
|
$qb->expr()->gte('destination.dateTo', ':application_date_from')
|
||||||
$qb->expr()->isNull('assignment.dateFrom'),
|
|
||||||
$qb->expr()->lte('destination.dateFrom', ':application_date_to')
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateFrom'),
|
|
||||||
$qb->expr()->lte('assignment.dateFrom', ':application_date_to')
|
|
||||||
)
|
|
||||||
),
|
|
||||||
$qb->expr()->orX(
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->gte('destination.dateTo', ':application_date_from')
|
|
||||||
),
|
|
||||||
$qb->expr()->andX(
|
|
||||||
$qb->expr()->isNotNull('assignment.dateTo'),
|
|
||||||
$qb->expr()->gte('assignment.dateTo', ':application_date_from')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
))
|
))
|
||||||
->setParameter('teamer', $teamer)
|
->setParameter('teamer', $teamer)
|
||||||
->setParameter('disposition_called_off', Disposition::STATUS_CALLED_OFF)
|
->setParameter('disposition_called_off', Disposition::STATUS_CALLED_OFF)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
Einsatztermin
|
Einsatztermin
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
||||||
{{ assignment.effectivePeriod.start|date('d.m.Y') }} - {{ assignment.effectivePeriod.end|date('d.m.Y') }}
|
{{ assignment.destination.dateFrom|date('d.m.Y') }} - {{ assignment.destination.dateTo|date('d.m.Y') }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
{% if assignment.pickup %}
|
{% if assignment.pickup %}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
Einsatztermin
|
Einsatztermin
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
|
||||||
{{ assignment.effectivePeriod.start|date('d.m.Y') }} - {{ assignment.effectivePeriod.end|date('d.m.Y') }}
|
{{ assignment.destination.dateFrom|date('d.m.Y') }} - {{ assignment.destination.dateTo|date('d.m.Y') }}
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
<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">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<span class="whitespace-nowrap">{{ disposition.teamer.fullName(true) }}</span>
|
<span class="whitespace-nowrap">{{ disposition.teamer.fullName(true) }}</span>
|
||||||
{% include '_partials/_teamer_deleted_badge.html.twig' with { 'teamer': disposition.teamer } %}
|
{% include '_partials/_teamer_deleted_badge.html.twig' with { 'teamer': disposition.teamer } %}
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
|
|||||||
@@ -67,8 +67,8 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}">
|
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}">
|
||||||
{{ assignment.effectivePeriod.start|date('d.m.Y') }} -
|
{{ assignment.destination.dateFrom|date('d.m.Y') }} -
|
||||||
{{ assignment.effectivePeriod.end|date('d.m.Y') }}
|
{{ assignment.destination.dateTo|date('d.m.Y') }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
{{ icon('hand', 'w-4 h-4 shrink-0') }}
|
{{ icon('hand', 'w-4 h-4 shrink-0') }}
|
||||||
<span class="whitespace-nowrap">{{ application.teamer.fullName(true) }}</span>
|
<span class="whitespace-nowrap">{{ application.teamer.fullName(true) }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
hx-swap="beforeend">
|
hx-swap="beforeend">
|
||||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}"
|
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}"
|
||||||
class="flex items-center space-x-1">
|
class="flex items-center space-x-1">
|
||||||
{{ icon('calendar', 'w-4 h-4 shrink-0') }}
|
{{ icon('calendar', 'w-4 h-4 shrink-0') }}
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.jobProfile.name }}</span>
|
<span class="whitespace-nowrap">{{ assignment.jobProfile.name }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
@@ -286,7 +286,7 @@
|
|||||||
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}"
|
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}"
|
||||||
class="flex items-center space-x-1">
|
class="flex items-center space-x-1">
|
||||||
{{ icon('calendar', 'w-4 h-4 shrink-0') }}
|
{{ icon('calendar', 'w-4 h-4 shrink-0') }}
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.jobProfile.name }}</span>
|
<span class="whitespace-nowrap">{{ assignment.jobProfile.name }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
|
|||||||
@@ -96,9 +96,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}">
|
<a href="{{ path('app_administrative_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}">
|
||||||
{{ assignment.effectivePeriod.start|date('d.m.Y') }} -
|
{{ assignment.destination.dateFrom|date('d.m.Y') }} -
|
||||||
<br>
|
<br>
|
||||||
{{ assignment.effectivePeriod.end|date('d.m.Y') }}
|
{{ assignment.destination.dateTo|date('d.m.Y') }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
{% for disposition in pagination %}
|
{% for disposition in pagination %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{{ disposition.assignment.effectivePeriod.start|date('d.m.Y') }} - {{ disposition.assignment.effectivePeriod.end|date('d.m.Y') }}
|
{{ disposition.assignment.destination.dateFrom|date('d.m.Y') }} - {{ disposition.assignment.destination.dateTo|date('d.m.Y') }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{ disposition.assignment.destination.product }}
|
{{ disposition.assignment.destination.product }}
|
||||||
|
|||||||
@@ -63,9 +63,9 @@
|
|||||||
<td>
|
<td>
|
||||||
<a href="{{ path('app_administrative_feedback_provide', { 'uuid': disposition.uuid }) }}"
|
<a href="{{ path('app_administrative_feedback_provide', { 'uuid': disposition.uuid }) }}"
|
||||||
title="Feedback abgeben">
|
title="Feedback abgeben">
|
||||||
{{ assignment.effectivePeriod.start|date('d.m.Y') }} -
|
{{ assignment.destination.dateFrom|date('d.m.Y') }} -
|
||||||
<br>
|
<br>
|
||||||
{{ assignment.effectivePeriod.end|date('d.m.Y') }}
|
{{ assignment.destination.dateTo|date('d.m.Y') }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
{{ icon('info', 'w-4 h-4') }}
|
{{ icon('info', 'w-4 h-4') }}
|
||||||
</button>
|
</button>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
hx-swap="beforeend">
|
hx-swap="beforeend">
|
||||||
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
{{ icon('download', 'w-4 h-4 shrink-0') }}
|
||||||
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
<span>{{ document.owner.teamer.fullName(true) }}</span>
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-start space-x-2">
|
<div class="flex items-start space-x-2">
|
||||||
{{ icon('calendar', 'w-5 h-5 shrink-0') }}
|
{{ icon('calendar', 'w-5 h-5 shrink-0') }}
|
||||||
<span class="flex-1">{{ assignment.effectivePeriod.start|date('d.m.Y') }} - {{ assignment.effectivePeriod.end|date('d.m.Y') }}</span>
|
<span class="flex-1">{{ assignment.destination.dateFrom|date('d.m.Y') }} - {{ assignment.destination.dateTo|date('d.m.Y') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-start space-x-2">
|
<div class="flex items-start space-x-2">
|
||||||
{{ icon('profile', 'w-5 h-5 shrink-0') }}
|
{{ icon('profile', 'w-5 h-5 shrink-0') }}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-start space-x-2">
|
<div class="flex items-start space-x-2">
|
||||||
{{ icon('calendar', 'w-5 h-5 shrink-0') }}
|
{{ icon('calendar', 'w-5 h-5 shrink-0') }}
|
||||||
<span class="flex-1">{{ assignment.effectivePeriod.start|date('d.m.Y') }} - {{ assignment.effectivePeriod.end|date('d.m.Y') }}</span>
|
<span class="flex-1">{{ assignment.destination.dateFrom|date('d.m.Y') }} - {{ assignment.destination.dateTo|date('d.m.Y') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-start space-x-2">
|
<div class="flex items-start space-x-2">
|
||||||
{{ icon('profile', 'w-5 h-5 shrink-0') }}
|
{{ icon('profile', 'w-5 h-5 shrink-0') }}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-start space-x-2">
|
<div class="flex items-start space-x-2">
|
||||||
{{ icon('calendar', 'w-5 h-5 shrink-0') }}
|
{{ icon('calendar', 'w-5 h-5 shrink-0') }}
|
||||||
<span class="flex-1">{{ assignment.effectivePeriod.start|date('d.m.Y') }} - {{ assignment.effectivePeriod.end|date('d.m.Y') }}</span>
|
<span class="flex-1">{{ assignment.destination.dateFrom|date('d.m.Y') }} - {{ assignment.destination.dateTo|date('d.m.Y') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-start space-x-2">
|
<div class="flex items-start space-x-2">
|
||||||
{{ icon('profile', 'w-5 h-5 shrink-0') }}
|
{{ icon('profile', 'w-5 h-5 shrink-0') }}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-start space-x-2">
|
<div class="flex items-start space-x-2">
|
||||||
{{ icon('calendar', 'w-5 h-5 shrink-0') }}
|
{{ icon('calendar', 'w-5 h-5 shrink-0') }}
|
||||||
<span class="flex-1">{{ assignment.effectivePeriod.start|date('d.m.Y') }} - {{ assignment.effectivePeriod.end|date('d.m.Y') }}</span>
|
<span class="flex-1">{{ assignment.destination.dateFrom|date('d.m.Y') }} - {{ assignment.destination.dateTo|date('d.m.Y') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-start space-x-2">
|
<div class="flex items-start space-x-2">
|
||||||
{{ icon('profile', 'w-5 h-5 shrink-0') }}
|
{{ icon('profile', 'w-5 h-5 shrink-0') }}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
{{ icon('hand', 'w-4 h-4 shrink-0') }}
|
{{ icon('hand', 'w-4 h-4 shrink-0') }}
|
||||||
<div class="flex items-center space-x-1 truncate">
|
<div class="flex items-center space-x-1 truncate">
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
{{ icon('alert', 'w-4 h-4 shrink-0') }}
|
{{ icon('alert', 'w-4 h-4 shrink-0') }}
|
||||||
<div class="flex items-center space-x-1 truncate">
|
<div class="flex items-center space-x-1 truncate">
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
{{ icon('check', 'w-4 h-4 shrink-0') }}
|
{{ icon('check', 'w-4 h-4 shrink-0') }}
|
||||||
<div class="flex items-center space-x-1 truncate">
|
<div class="flex items-center space-x-1 truncate">
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
<a href="{{ path('app_teamer_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}">
|
<a href="{{ path('app_teamer_assignment_detail', { 'uuid': assignment.uuid, 'r': return_url() }) }}">
|
||||||
<div class="flex items-center space-x-1 truncate">
|
<div class="flex items-center space-x-1 truncate">
|
||||||
{{ icon('calendar', 'w-4 h-4 shrink-0') }}
|
{{ icon('calendar', 'w-4 h-4 shrink-0') }}
|
||||||
<span class="whitespace-nowrap">{{ assignment.effectivePeriod.start|date('d.m.y') }} - {{ assignment.effectivePeriod.end|date('d.m.y') }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.dateFrom|date('d.m.y') }} - {{ assignment.destination.dateTo|date('d.m.y') }}</span>
|
||||||
{% include '_partials/_dot.html.twig' %}
|
{% include '_partials/_dot.html.twig' %}
|
||||||
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
<span class="whitespace-nowrap">{{ assignment.destination.hotel }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class InvalidateApplicationsListenerTest extends TestCase
|
|||||||
$this->assertSame([], $this->removedApplications);
|
$this->assertSame([], $this->removedApplications);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIgnoresAssignmentsWithoutEffectivePeriod(): void
|
public function testIgnoresAssignmentsWithoutDestination(): void
|
||||||
{
|
{
|
||||||
$document = $this->createContract(null, $this->createTeamer());
|
$document = $this->createContract(null, $this->createTeamer());
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ class InvalidateApplicationsListenerTest extends TestCase
|
|||||||
$this->assertSame([], $this->dispatchedEvents);
|
$this->assertSame([], $this->dispatchedEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testQueriesOverlappingApplicationsForTheTeamerAndTheEffectivePeriod(): void
|
public function testQueriesOverlappingApplicationsForTheTeamerAndTheDestinationPeriod(): void
|
||||||
{
|
{
|
||||||
$teamer = $this->createTeamer();
|
$teamer = $this->createTeamer();
|
||||||
$period = new CarbonPeriodImmutable('2025-01-10', '2025-01-20');
|
$period = new CarbonPeriodImmutable('2025-01-10', '2025-01-20');
|
||||||
@@ -134,7 +134,13 @@ class InvalidateApplicationsListenerTest extends TestCase
|
|||||||
$this->applicationRepository
|
$this->applicationRepository
|
||||||
->expects($this->once())
|
->expects($this->once())
|
||||||
->method('findOverlappingForTeamer')
|
->method('findOverlappingForTeamer')
|
||||||
->with($teamer, $period)
|
->with(
|
||||||
|
$this->identicalTo($teamer),
|
||||||
|
$this->callback(static function (CarbonPeriodImmutable $actual) use ($period): bool {
|
||||||
|
return $actual->getStartDate()->equalTo($period->getStartDate())
|
||||||
|
&& $actual->getEndDate()->equalTo($period->getEndDate());
|
||||||
|
})
|
||||||
|
)
|
||||||
->willReturn([])
|
->willReturn([])
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -194,7 +200,15 @@ class InvalidateApplicationsListenerTest extends TestCase
|
|||||||
private function createContract(?CarbonPeriodImmutable $period, Teamer $teamer): Upload&MockObject
|
private function createContract(?CarbonPeriodImmutable $period, Teamer $teamer): Upload&MockObject
|
||||||
{
|
{
|
||||||
$assignment = $this->createMock(Assignment::class);
|
$assignment = $this->createMock(Assignment::class);
|
||||||
$assignment->method('getEffectivePeriod')->willReturn($period);
|
|
||||||
|
if (null === $period) {
|
||||||
|
$assignment->method('getDestination')->willReturn(null);
|
||||||
|
} else {
|
||||||
|
$destination = $this->createMock(Destination::class);
|
||||||
|
$destination->method('getDateFrom')->willReturn($period->getStartDate()->toDateTimeImmutable());
|
||||||
|
$destination->method('getDateTo')->willReturn($period->getEndDate()->toDateTimeImmutable());
|
||||||
|
$assignment->method('getDestination')->willReturn($destination);
|
||||||
|
}
|
||||||
|
|
||||||
$disposition = $this->createMock(Disposition::class);
|
$disposition = $this->createMock(Disposition::class);
|
||||||
$disposition->method('getAssignment')->willReturn($assignment);
|
$disposition->method('getAssignment')->willReturn($assignment);
|
||||||
@@ -211,12 +225,12 @@ class InvalidateApplicationsListenerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$destination = $this->createMock(Destination::class);
|
$destination = $this->createMock(Destination::class);
|
||||||
$destination->method('getHotelCode')->willReturn('TST');
|
$destination->method('getHotelCode')->willReturn('TST');
|
||||||
|
$destination->method('getDateFrom')->willReturn(new \DateTimeImmutable('2025-01-12'));
|
||||||
|
$destination->method('getDateTo')->willReturn(new \DateTimeImmutable('2025-01-18'));
|
||||||
|
|
||||||
$assignment = $this->createMock(Assignment::class);
|
$assignment = $this->createMock(Assignment::class);
|
||||||
$assignment->method('getDestination')->willReturn($destination);
|
$assignment->method('getDestination')->willReturn($destination);
|
||||||
$assignment->method('getUuid')->willReturn('assignment-uuid');
|
$assignment->method('getUuid')->willReturn('assignment-uuid');
|
||||||
$assignment->method('getEffectiveDateFrom')->willReturn(new \DateTimeImmutable('2025-01-12'));
|
|
||||||
$assignment->method('getEffectiveDateTo')->willReturn(new \DateTimeImmutable('2025-01-18'));
|
|
||||||
|
|
||||||
$application = $this->createMock(Application::class);
|
$application = $this->createMock(Application::class);
|
||||||
$application->method('getUuid')->willReturn('application-uuid');
|
$application->method('getUuid')->willReturn('application-uuid');
|
||||||
|
|||||||
@@ -20,21 +20,13 @@ use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|||||||
*/
|
*/
|
||||||
class ApplicationRepositoryTest extends KernelTestCase
|
class ApplicationRepositoryTest extends KernelTestCase
|
||||||
{
|
{
|
||||||
public function testOverlappingQueryUsesInclusiveBoundariesAndEffectiveDates(): void
|
public function testOverlappingQueryUsesInclusiveBoundariesOnTheDestinationDates(): void
|
||||||
{
|
{
|
||||||
$dql = $this->createOverlappingQuery()->getDQL();
|
$dql = $this->createOverlappingQuery()->getDQL();
|
||||||
|
|
||||||
// an assignment overrides the date range of its destination, so both have to be considered
|
// the assignment period is the date range of its destination
|
||||||
$this->assertStringContainsString(
|
$this->assertStringContainsString('destination.dateFrom <= :dateTo', $dql);
|
||||||
'(assignment.dateFrom IS NULL AND destination.dateFrom <= :dateTo)'
|
$this->assertStringContainsString('destination.dateTo >= :dateFrom', $dql);
|
||||||
.' OR (assignment.dateFrom IS NOT NULL AND assignment.dateFrom <= :dateTo)',
|
|
||||||
$dql
|
|
||||||
);
|
|
||||||
$this->assertStringContainsString(
|
|
||||||
'(assignment.dateTo IS NULL AND destination.dateTo >= :dateFrom)'
|
|
||||||
.' OR (assignment.dateTo IS NOT NULL AND assignment.dateTo >= :dateFrom)',
|
|
||||||
$dql
|
|
||||||
);
|
|
||||||
|
|
||||||
// strict comparisons would let assignments that touch on a boundary pass and could never
|
// strict comparisons would let assignments that touch on a boundary pass and could never
|
||||||
// match a single day assignment at all
|
// match a single day assignment at all
|
||||||
|
|||||||
@@ -93,14 +93,8 @@ class DispositionRepositoryTest extends KernelTestCase
|
|||||||
{
|
{
|
||||||
$dql = $this->contractQuery()->getDQL();
|
$dql = $this->contractQuery()->getDQL();
|
||||||
|
|
||||||
$this->assertStringContainsString(
|
$this->assertStringContainsString('destination.dateTo > :tomorrow', $dql);
|
||||||
'assignment.dateTo IS NOT NULL AND assignment.dateTo > :tomorrow',
|
$this->assertStringContainsString('destination.dateTo = :tomorrow', $dql);
|
||||||
$dql
|
|
||||||
);
|
|
||||||
$this->assertStringContainsString(
|
|
||||||
'assignment.dateTo IS NOT NULL AND assignment.dateTo = :tomorrow',
|
|
||||||
$dql
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testContractReminderBindsTomorrowAsADateAndNotATimestamp(): void
|
public function testContractReminderBindsTomorrowAsADateAndNotATimestamp(): void
|
||||||
@@ -114,8 +108,8 @@ class DispositionRepositoryTest extends KernelTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The bug that killed this query: destination.dateTo and assignment.dateTo are DATE columns,
|
* The bug that killed this query: destination.dateTo is a DATE column, and Doctrine binds a
|
||||||
* and Doctrine binds a DateTimeImmutable as 'Y-m-d H:i:s', which a DATE never equals.
|
* DateTimeImmutable as 'Y-m-d H:i:s', which a DATE never equals.
|
||||||
*/
|
*/
|
||||||
public function testInvoiceReminderBindsADateAndNotATimestamp(): void
|
public function testInvoiceReminderBindsADateAndNotATimestamp(): void
|
||||||
{
|
{
|
||||||
@@ -129,21 +123,13 @@ class DispositionRepositoryTest extends KernelTestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Without the IS NULL guard on the second branch, an assignment that moves the end date into
|
* The reminder fires for placements whose destination ends on exactly $endDate.
|
||||||
* the future still matches on its destination's date.
|
|
||||||
*/
|
*/
|
||||||
public function testInvoiceReminderGuardsTheDestinationFallback(): void
|
public function testInvoiceReminderComparesTheDestinationEndDate(): void
|
||||||
{
|
{
|
||||||
$dql = $this->invoiceQuery()->getDQL();
|
$dql = $this->invoiceQuery()->getDQL();
|
||||||
|
|
||||||
$this->assertStringContainsString(
|
$this->assertStringContainsString('destination.dateTo = :endDate', $dql);
|
||||||
'assignment.dateTo IS NOT NULL AND assignment.dateTo = :endDate',
|
|
||||||
$dql
|
|
||||||
);
|
|
||||||
$this->assertStringContainsString(
|
|
||||||
'assignment.dateTo IS NULL AND destination.dateTo = :endDate',
|
|
||||||
$dql
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInvoiceReminderOnlyJoinsTheInvoiceAndSkipsFinishedDispositions(): void
|
public function testInvoiceReminderOnlyJoinsTheInvoiceAndSkipsFinishedDispositions(): void
|
||||||
|
|||||||
@@ -24,25 +24,21 @@ class StatisticsEventRepositoryTest extends KernelTestCase
|
|||||||
{
|
{
|
||||||
$dql = $this->query(dateFrom: new \DateTimeImmutable('2026-07-01'))->getDQL();
|
$dql = $this->query(dateFrom: new \DateTimeImmutable('2026-07-01'))->getDQL();
|
||||||
|
|
||||||
$this->assertStringContainsString('assignment.dateFrom >= :dateFrom', $dql);
|
$this->assertStringContainsString('destination.dateFrom >= :dateFrom', $dql);
|
||||||
$this->assertStringNotContainsString('event.occurredAt', $dql);
|
$this->assertStringNotContainsString('event.occurredAt', $dql);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The season start is assignment.dateFrom falling back to the destination's, per
|
* The season is the destination's date range, the convention shared with SeasonPeriodFilter.
|
||||||
* Assignment::getEffectivePeriod(). Losing the fallback would silently drop every
|
|
||||||
* assignment that inherits its dates.
|
|
||||||
*/
|
*/
|
||||||
public function testTheSeasonFallsBackToTheDestinationDates(): void
|
public function testTheSeasonIsTheDestinationDateRange(): void
|
||||||
{
|
{
|
||||||
$dql = $this->query(
|
$dql = $this->query(
|
||||||
dateFrom: new \DateTimeImmutable('2026-07-01'),
|
dateFrom: new \DateTimeImmutable('2026-07-01'),
|
||||||
dateTo: new \DateTimeImmutable('2027-06-30'),
|
dateTo: new \DateTimeImmutable('2027-06-30'),
|
||||||
)->getDQL();
|
)->getDQL();
|
||||||
|
|
||||||
$this->assertStringContainsString('assignment.dateFrom IS NULL', $dql);
|
|
||||||
$this->assertStringContainsString('destination.dateFrom >= :dateFrom', $dql);
|
$this->assertStringContainsString('destination.dateFrom >= :dateFrom', $dql);
|
||||||
$this->assertStringContainsString('assignment.dateTo IS NULL', $dql);
|
|
||||||
$this->assertStringContainsString('destination.dateTo <= :dateTo', $dql);
|
$this->assertStringContainsString('destination.dateTo <= :dateTo', $dql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ use App\Service\Pdf\InvoiceApprover;
|
|||||||
use App\Service\Pdf\InvoiceRenderer;
|
use App\Service\Pdf\InvoiceRenderer;
|
||||||
use App\Service\Pdf\Pdf;
|
use App\Service\Pdf\Pdf;
|
||||||
use App\Service\Upload\UploadHandler;
|
use App\Service\Upload\UploadHandler;
|
||||||
use Carbon\CarbonPeriodImmutable;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
@@ -84,7 +83,6 @@ class InvoiceRendererTest extends WebTestCase
|
|||||||
;
|
;
|
||||||
|
|
||||||
$assignment = $this->createMock(Assignment::class);
|
$assignment = $this->createMock(Assignment::class);
|
||||||
$assignment->method('getEffectivePeriod')->willReturn(CarbonPeriodImmutable::create('2025-02-01', '2025-02-08'));
|
|
||||||
$assignment->method('getId')->willReturn(1234);
|
$assignment->method('getId')->willReturn(1234);
|
||||||
$assignment->method('getDestination')->willReturn($destination);
|
$assignment->method('getDestination')->willReturn($destination);
|
||||||
$assignment->method('getJobProfile')->willReturn($jobProfile);
|
$assignment->method('getJobProfile')->willReturn($jobProfile);
|
||||||
|
|||||||
Reference in New Issue
Block a user