123 lines
4.5 KiB
PHP
123 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Service\Assignment;
|
|
|
|
use App\Entity\Assignment;
|
|
use App\Model\TimelineItem;
|
|
use Carbon\CarbonPeriod;
|
|
use function Symfony\Component\String\b;
|
|
|
|
/**
|
|
* Service for processing assignments into timeline visualization data.
|
|
* Organizes assignments and dispositions by hotel in a timeline format,
|
|
* handling overlapping dates by distributing items across multiple rows.
|
|
*/
|
|
class TimelineService
|
|
{
|
|
/**
|
|
* Preprocesses assignments into a timeline structure organized by hotel.
|
|
* Creates timeline items for both vacant assignment slots and actual dispositions,
|
|
* distributing them across multiple rows to avoid date overlaps.
|
|
*
|
|
* @param Assignment[] $assignments Array of assignments to process
|
|
* @return array Multi-dimensional array: [hotelCode][rowIndex][itemKey] => TimelineItem
|
|
*/
|
|
public function preprocess(array $assignments): array
|
|
{
|
|
$rows = [];
|
|
|
|
foreach ($assignments as $assignment) {
|
|
// exclude called off assignments
|
|
// TODO: refactor filtering to do this on database level
|
|
if (Assignment::STATUS_CALLED_OFF === $assignment->getStatus()) {
|
|
continue;
|
|
}
|
|
$destination = $assignment->getDestination();
|
|
$hotelCode = $destination->getHotelCodeNormalized();
|
|
|
|
// add first row for current hotel unless existing
|
|
if (false === isset($rows[$hotelCode])) {
|
|
$rows[$hotelCode] = [[]];
|
|
}
|
|
|
|
$dispositionsCount = $assignment->getDispositions()->count();
|
|
$dispositionsAvailable = $assignment->getAvailableDispositions();
|
|
|
|
// Handle assignments with no dispositions - create items for all available slots
|
|
if (0 === $dispositionsCount) {
|
|
$item = TimelineItem::fromAssignment($assignment);
|
|
for ($i = 1; $i <= $assignment->getAvailableDispositions(); $i++) {
|
|
$this->addItem($rows[$hotelCode], $item);
|
|
}
|
|
} else {
|
|
// Create items for remaining vacant slots if any
|
|
if ($dispositionsCount < $dispositionsAvailable) {
|
|
$item = TimelineItem::fromAssignment($assignment);
|
|
for ($i = 1; $i <= $dispositionsAvailable - $dispositionsCount; $i++) {
|
|
$this->addItem($rows[$hotelCode], $item);
|
|
}
|
|
}
|
|
// Create items for actual dispositions
|
|
foreach ($assignment->getDispositions() as $disposition) {
|
|
$item = TimelineItem::fromDisposition($disposition);
|
|
$this->addItem($rows[$hotelCode], $item);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* Adds a timeline item to the appropriate row, ensuring no date overlaps.
|
|
* Finds the first available row where the item doesn't overlap with existing items,
|
|
* or creates a new row if necessary.
|
|
*
|
|
* @param array $rows Reference to the rows array for a specific hotel
|
|
* @param TimelineItem $item The timeline item to add
|
|
*/
|
|
private function addItem(array &$rows, TimelineItem $item): void
|
|
{
|
|
$rowIndex = 0;
|
|
$fitsRow = false;
|
|
|
|
// Find first row where item doesn't overlap with existing items
|
|
foreach ($rows as $index => $row) {
|
|
$fitsRow = true;
|
|
foreach ($row as $entry) {
|
|
/** @var TimelineItem $entry */
|
|
// Check for date overlap: item overlaps if its end >= entry start AND item start <= entry end
|
|
if ($entry->getDateTo() >= $item->getDateFrom() && $entry->getDateFrom() <= $item->getDateTo()) {
|
|
$fitsRow = false;
|
|
}
|
|
}
|
|
$rowIndex = $index;
|
|
if (true === $fitsRow) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If no existing row fits, create a new row
|
|
if (false === $fitsRow) {
|
|
++$rowIndex;
|
|
}
|
|
|
|
$rows[$rowIndex][$item->getKey()] = $item;
|
|
}
|
|
|
|
/**
|
|
* Creates a time window for timeline visualization.
|
|
* Returns a period from 6 weeks ago to 6 months in the future,
|
|
* providing context for both recent and upcoming assignments.
|
|
*
|
|
* @return CarbonPeriod The active time window for timeline display
|
|
*/
|
|
public function getActiveWindow(): CarbonPeriod
|
|
{
|
|
$dateFrom = (new \DateTimeImmutable())->modify('-6 weeks');
|
|
$dateTo = (new \DateTimeImmutable())->modify('+6 months');
|
|
|
|
return CarbonPeriod::create($dateFrom, $dateTo);
|
|
}
|
|
}
|