feat: exclusion groups for additional services

This commit is contained in:
Björn Fromme
2026-08-04 10:24:29 +02:00
parent c31d086356
commit 331c88c38e
11 changed files with 457 additions and 17 deletions
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace App\Service;
use App\Entity\Groups\AdditionalService;
/**
* Resolves mutually exclusive additional services.
*
* Services sharing an exclusion group interact as soon as at least one of the two involved
* services is flagged as exclusive: selecting the exclusive one clears all other members of
* the group, selecting any other member clears the exclusive ones. Members that are not
* exclusive can be combined freely.
*
* Conflicts are resolved in favour of the newest selection, determined by diffing the
* submitted ids against the previously stored ones.
*/
class AdditionalServiceExclusionResolver
{
/**
* @param AdditionalService[] $availableServices
* @param array<array-key, int> $submittedIds keys are preserved, grouped radios submit under their group name
* @param array<array-key, int> $previousIds
*
* @return array<array-key, int>
*/
public function resolve(array $availableServices, array $submittedIds, array $previousIds): array
{
$rules = $this->buildRules($availableServices);
if ([] === $rules) {
return $submittedIds;
}
$added = array_values(array_diff($submittedIds, $previousIds));
foreach ($added as $addedId) {
if (!isset($rules[$addedId])) {
continue;
}
$submittedIds = array_filter(
$submittedIds,
fn(int $id) => !$this->conflicts($rules, $addedId, $id),
);
}
return $submittedIds;
}
/**
* @param array<int, array{group: string, exclusive: bool}> $rules
*/
private function conflicts(array $rules, int $a, int $b): bool
{
if ($a === $b || !isset($rules[$b])) {
return false;
}
if ($rules[$a]['group'] !== $rules[$b]['group']) {
return false;
}
return $rules[$a]['exclusive'] || $rules[$b]['exclusive'];
}
/**
* @param AdditionalService[] $availableServices
*
* @return array<int, array{group: string, exclusive: bool}>
*/
private function buildRules(array $availableServices): array
{
$rules = [];
foreach ($availableServices as $service) {
$group = $service->getExclusionGroup();
$id = $service->getId();
if (null === $id || null === $group || '' === $group) {
continue;
}
$rules[$id] = ['group' => $group, 'exclusive' => $service->isExclusive()];
}
return $rules;
}
}