Legacy-API-Paritaet S1+S2: connektar-Flow verhaltensgleich + KI-Schalter

Eigene Compat-Controller (Api/Legacy) fuer die 5 real genutzten
/service-Endpoints (Prod-Log-Beleg): Company-/Kontakt-Create (liefen
410!), PM-Create, Bild-Upload, published-only-GET — verschachtelte
Alt-Params, 201+Location+{}, Alt-Fehlerformat, Hit-Zaehlung,
Legacy-Land-Mapping.

- Anlegen = Einreichen (Alt-Verhalten); Sofort-Publish via neuer
  Permission api:immediate-publication (WS-7: connektar zuweisen)
- Admin-Schalter 'KI-Pruefung Legacy-API' (app_settings-Tabelle,
  Toggle auf /admin/press-releases): an = review + KI-Routing
- Admin-PM-Liste: KI-Spalte (Ampel-Badge) + manueller Pruef-Button
  ohne Routing fuer ungepruefte PMs
- Mails bei API-Einlieferern an Pressekontakt, sonst Firmen-Mail,
  Fallback Einlieferer (User-Entscheidung 10.07.)
- Tests: LegacyServiceParityTest (connektar-Flow end-to-end);
  Suite 1182 gruen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-10 14:09:40 +00:00
parent 5a352425d7
commit 6a45ebf607
15 changed files with 906 additions and 24 deletions

View file

@ -1,6 +1,10 @@
<?php
use App\Enums\Portal;
use App\Http\Controllers\Api\Legacy\LegacyPressReleaseController;
use App\Jobs\ClassifyPressRelease;
use App\Jobs\ScorePressRelease;
use App\Models\AppSetting;
use App\Enums\PressReleaseStatus;
use App\Models\Category;
use App\Models\Company;
@ -137,6 +141,43 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
$this->resetPage();
}
/**
* Admin-Schalter (Entscheidung 10.07.2026): KI-Prüfung für Legacy-API-
* Einlieferungen. Aus = Alt-Verhalten Sofort-Publish (connektar).
*/
public bool $legacyApiAiReview = false;
public function mount(): void
{
$this->legacyApiAiReview = AppSetting::getBool(LegacyPressReleaseController::SETTING_AI_REVIEW);
}
public function updatedLegacyApiAiReview(bool $value): void
{
AppSetting::setValue(LegacyPressReleaseController::SETTING_AI_REVIEW, $value);
Flux::toast(
$value
? __('KI-Prüfung für Legacy-API-Einlieferungen aktiviert.')
: __('Legacy-API veröffentlicht wieder sofort (Alt-Verhalten).'),
variant: 'success',
);
}
/**
* Manuelle KI-Prüfung aus der Liste — bewusst ohne Routing: bewertet
* und protokolliert, ändert aber nie den Status (Admin entscheidet).
*/
public function runAiCheck(int $id): void
{
$pr = $this->findAdminPR($id);
ClassifyPressRelease::dispatch($pr->id, route: false)->onQueue('classification');
ScorePressRelease::dispatch($pr->id)->onQueue('classification');
Flux::toast(__('KI-Prüfung angestoßen — Ergebnis erscheint nach dem nächsten Queue-Lauf.'), variant: 'success');
}
public function resetFilters(): void
{
$this->search = '';
@ -468,6 +509,13 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
@endforeach
</flux:select>
{{-- Entscheidung 10.07.2026: KI-Prüfung für Legacy-API-Einlieferungen;
aus = Alt-Verhalten (Sofort-Publish für berechtigte Bestandskunden). --}}
<flux:field variant="inline" class="whitespace-nowrap">
<flux:switch wire:model.live="legacyApiAiReview" />
<flux:label>{{ __('KI-Prüfung Legacy-API') }}</flux:label>
</flux:field>
<flux:select wire:model.live="portalFilter" class="w-full">
<option value="all">{{ __('Alle Portale') }}</option>
@foreach ($portalOptions as $p)
@ -809,6 +857,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
:direction="$sortDir" wire:click="sort('created_at')">{{ __('Datum') }}</flux:table.column>
<flux:table.column class="w-[80px]" sortable :sorted="$sortBy === 'hits'" :direction="$sortDir"
wire:click="sort('hits')">{{ __('Hits') }}</flux:table.column>
<flux:table.column class="w-[120px]">{{ __('KI-Prüfung') }}</flux:table.column>
<flux:table.column class="w-[90px]">{{ __('Aktionen') }}</flux:table.column>
</flux:table.columns>
@ -945,6 +994,24 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilungen')] class exten
class="font-mono text-[12px] text-[color:var(--color-ink-2)] tabular-nums">{{ number_format($pr->hits) }}</span>
</flux:table.cell>
<flux:table.cell>
@if ($pr->classification)
@php
$kiBadgeClass = match ($pr->classification) {
\App\Enums\PressReleaseClassification::Green => 'published',
\App\Enums\PressReleaseClassification::Yellow => 'review',
\App\Enums\PressReleaseClassification::Red => 'rejected',
};
@endphp
<span @class(['badge', $kiBadgeClass])>{{ $pr->classification->label() }}</span>
@else
<flux:button size="sm" variant="filled" icon="sparkles" wire:click="runAiCheck({{ $pr->id }})"
wire:loading.attr="disabled" title="{{ __('KI-Prüfung starten (ohne Statusänderung)') }}">
{{ __('Prüfen') }}
</flux:button>
@endif
</flux:table.cell>
<flux:table.cell>
<div class="flex items-center gap-1">
<flux:button size="sm" variant="filled" icon="eye"