feat: order and format the pickup fields

This commit is contained in:
fromme committed 2026-09-27 18:05:12 +02:00
1 parent 8da9af46f5
commit 392e67448e
9 files changed
+746 -21

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace App\Service\Common;
use App\Entity\Destination;
use App\Service\Assignment\PickupDepartureOrder;
/**
* The boarding points of a destination, as something a form can offer.
*
* Every caller keys its choices on the busProId rather than on the label, because the
* label is not unique: six destinations have two boarding points in the same town, and one
* of them has both at 03:15. Keying on the label, as all three pickup fields used to, made
* the second one unreachable - it never reached the form and could not be picked.
*
* Two orders, because the two audiences read the list differently. Disposition planners
* read a route and want it chronological; a teamer is looking up their own town in a list
* and wants it alphabetical.
*/
final class PickupOptions
{
/**
* @return list<array{busProId: int, label: string, departure: ?string, street: ?string}>
*/
public static function inDepartureOrder(Destination $destination): array
{
return self::rows(PickupDepartureOrder::sort($destination->getPickups()));
}
/**
* Alphabetical by town, with the street folded into the label where a town repeats -
* otherwise the teamer is offered the same word twice with nothing to choose between.
*
* @return list<array{busProId: int, label: string, departure: ?string, street: ?string}>
*/
public static function byTown(Destination $destination): array
{
$options = self::rows($destination->getPickups());
$occurrences = array_count_values(array_column($options, 'label'));
foreach ($options as $index => $option) {
if ($occurrences[$option['label']] < 2) {
continue;
}
$options[$index]['label'] = match (true) {
null !== $option['street'] => sprintf('%s, %s', $option['label'], $option['street']),
null !== $option['departure'] => sprintf('%s, %s Uhr', $option['label'], $option['departure']),
default => sprintf('%s (Zustieg %d)', $option['label'], $option['busProId']),
};
}
usort($options, static fn (array $a, array $b): int => $a['label'] <=> $b['label']);
return $options;
}
/**
* @param array<int, array<string, mixed>> $pickups
*
* @return list<array{busProId: int, label: string, departure: ?string, street: ?string}>
*/
private static function rows(array $pickups): array
{
$options = [];
foreach ($pickups as $pickup) {
$busProId = (int) ($pickup['busProId'] ?? 0);
if (0 === $busProId) {
continue;
}
$city = trim((string) ($pickup['city'] ?? ''));
$street = trim((string) ($pickup['street'] ?? ''));
$options[] = [
'busProId' => $busProId,
// a boarding point missing from the base data has no address, but it is
// still a real departure and has to stay selectable. None exist today; they
// arrive with MyE&P's travel export, which reports the ones whose master
// data it could not resolve rather than dropping them.
'label' => '' === $city ? 'Zustieg '.$busProId : $city,
'departure' => PickupDepartureOrder::clock($pickup),
'street' => '' === $street ? null : $street,
];
}
return $options;
}
}