api: neue Firmen dem bestehenden Newsroom zuordnen (Roadmap 1a.4)

connektar legt je Meldung eine Firma an. ApiNewsroomAssignment prüft vor
dem Anlegen, ob unter derselben Firmen-E-Mail schon dieselbe Firma steht
(Namensregel der Klassifizierung), und gibt dann deren ID zurück — der
Alt-Vertrag (201 + Location) bleibt. Nie-veröffentlicht-Adressen werden nie
zugeordnet, abweichende Namen bekommen wie bisher eine neue Firma. Liegt
die Firma auf dem anderen Portal, wird sie both; bei fremd belegtem Slug
bleibt es beim alten Verhalten.

LegacyPressReleaseController: both ist kein Ziel-Portal für eine Meldung —
dann entscheidet das Portal des API-Nutzers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-15 10:26:13 +00:00
parent 4303266841
commit 8dad652d67
8 changed files with 367 additions and 34 deletions

View file

@ -96,7 +96,7 @@ class CompanyMergeService
$targetPortal = $this->targetPortal($survivor, $portals);
if ($targetPortal === Portal::Both && $survivor->portal !== Portal::Both
&& $this->slugTakenElsewhere($survivor, $candidates)) {
&& $survivor->slugTakenOnOtherPortals($candidates->modelKeys())) {
$blocked[] = $survivor->slug;
continue;
@ -294,33 +294,6 @@ class CompanyMergeService
return $survivor->portal;
}
/**
* Trägt eine fremde, lebende Firma auf einem anderen Portal denselben
* Slug? Dann fände die öffentliche Auflösung (`portal IN (eigenes, both)`)
* nach dem Wechsel auf `both` zwei Firmen. Die eigenen Gruppenmitglieder
* zählen nicht — die sind danach soft-deleted.
*
* @param Collection<int, Company> $group
*/
private function slugTakenElsewhere(Company $survivor, Collection $group): bool
{
// Die anderen Portale ausdrücklich aufzählen statt `portal != ?`:
// Nur so greift der Unique-Index (portal, slug) — der Unterschied ist
// 24 ms gegen 0 ms je Adresse, bei vierzehntausend Adressen sechs
// Minuten gegen eine Sekunde.
$otherPortals = array_values(array_diff(
array_map(fn (Portal $portal): string => $portal->value, Portal::cases()),
[$survivor->portal->value],
));
return Company::query()
->withoutGlobalScope(PortalScope::class)
->whereIn('portal', $otherPortals)
->where('slug', $survivor->slug)
->whereKeyNot($group->modelKeys())
->exists();
}
/**
* @param list<int> $companyIds
* @return Builder<PressRelease>