fix: Menue Transaktionen und Stammdaten added to trasaction details

This commit is contained in:
2025-11-20 11:08:23 +01:00
parent 7d085f417c
commit 1505975ebb
4 changed files with 758 additions and 4 deletions
@@ -11,7 +11,8 @@ use Livewire\Attributes\Title;
use Livewire\Volt\Component;
use Livewire\WithPagination;
new #[Layout('components.layouts.app'), Title('Unternehmensauskunft Transaktionen')] class extends Component {
new #[Layout('components.layouts.app'), Title('Unternehmensauskunft Transaktionen')] class extends Component
{
use WithPagination;
public Company $company;
@@ -152,7 +153,7 @@ new #[Layout('components.layouts.app'), Title('Unternehmensauskunft Transaktione
->selectRaw('status, COUNT(*) as total, COALESCE(SUM(amount), 0) as volume')
->groupBy('status')
->get()
->mapWithKeys(fn($row) => [
->mapWithKeys(fn ($row) => [
$row->status => [
'count' => (int) $row->total,
'amount' => (float) $row->volume,
@@ -258,11 +259,448 @@ new #[Layout('components.layouts.app'), Title('Unternehmensauskunft Transaktione
return $items;
}
#[Computed]
public function transactionDetailStructure(): array
{
return [
[
'level' => 1,
'title' => 'Transaktion',
'sections' => [
[
'title' => 'Analyse Transaktionsbetrag',
'description' => null,
'fields' => [
[
'label' => 'Plausibilität Betrag',
'key' => 'tranx_TX_AMOUNT',
'prompt_id' => '51',
],
[
'label' => 'Plausibilität Transaktionszeitpunkt',
'key' => 'tranx_seasonality',
'prompt_id' => '59',
],
[
'label' => 'Plausibilität Transaktionsbetrag umsatzbezogen',
'key' => 'tranx_revenueimpact',
'prompt_id' => '67',
],
[
'label' => 'Plausibilität Transaktionsbetrag ergebnisbezogen',
'key' => 'tranx_profitimpact',
'prompt_id' => '68',
],
[
'label' => 'Plausibilität Transaktionsbetrag Branchüblichkeit',
'key' => 'tranx_turnoverimpact',
'prompt_id' => '69',
],
],
],
[
'title' => 'Analyse Transaktionszweck & außerbetragliche Parameter',
'description' => null,
'fields' => [
[
'label' => 'Verwendungszweck',
'key' => 'tranx_TX_PURPOSE',
'prompt_id' => '53',
],
[
'label' => 'Plausibilität Zahlungszweck',
'key' => 'tranx_PURPOSEbase',
'prompt_id' => '57',
],
[
'label' => 'Plausibilität Geschäftsbeziehung',
'key' => 'tranx_businesslogic',
'prompt_id' => '62',
],
[
'label' => 'Plausibilität Transaktionsbetrag Branchenadäquanz',
'key' => 'tranx_plausibilty',
'prompt_id' => '64',
],
[
'label' => 'Bewertung',
'key' => 'tranx_report',
'prompt_id' => '56',
],
[
'label' => 'Plausibilität Transaktionsumfang',
'key' => 'tranx_performanceperiod',
'prompt_id' => '58',
],
[
'label' => 'Auffälligkeiten Transaktion',
'key' => 'tranx_pattern2',
'prompt_id' => '80',
],
[
'label' => 'Plausibilität Transaktion Marktüblichkeit',
'key' => 'tranx_contracttenor',
'prompt_id' => '83',
],
[
'label' => 'Transaktion Namensabgleich',
'key' => 'tranx_mismatch',
'prompt_id' => '85',
],
[
'label' => 'Titel fehlt',
'key' => 'tranx_outliers',
'prompt_id' => '65',
],
[
'label' => 'Plausibilität Instruktion Transaktion',
'key' => 'tranx_weekday',
'prompt_id' => '95',
],
[
'label' => 'Prüfung letztendliche wirtschaftliche Eigentümer',
'key' => 'tranx_holdingobfuscation',
'prompt_id' => '96',
],
],
],
[
'title' => 'Anzeige historischer Transaktionen mit Sender und/oder Empfänger aus der Transaktion inklusive der dokumentierten Auffälligkeiten zu AML pro Datensatz',
'description' => null,
'fields' => [
[
'label' => 'Transaktionshistorie',
'key' => 'tranx_historical',
'prompt_id' => '52',
],
[
'label' => 'Smurfing',
'key' => 'tranx_duplicate',
'prompt_id' => '79',
],
[
'label' => 'Historische Transaktionen',
'key' => 'tranx_patterns',
'prompt_id' => '66',
],
[
'label' => 'Bankverbindung',
'key' => 'tranx_newbankaccount',
'prompt_id' => '93',
],
],
],
[
'title' => 'Zusätzliche Feststellungen (KYC/EDD)',
'description' => null,
'fields' => [
[
'label' => 'Gegenpartei',
'key' => 'tranx_counterpartyassessment',
'prompt_id' => '54',
],
],
],
[
'title' => 'Export controls/trade restrictions',
'description' => null,
'fields' => [
[
'label' => 'Sanktionen (Entity)',
'key' => 'entity_corporate_exportcontrol',
'prompt_id' => '81',
],
[
'label' => 'Sanktionen (Counterparty)',
'key' => 'counterparty_corporate_exportcontrol',
'prompt_id' => '81',
],
],
],
],
],
[
'level' => 2,
'title' => 'Stammdaten',
'sections' => [
[
'title' => 'Rechtlicher Name (aktuell) / Firma',
'description' => null,
'fields' => [
[
'label' => 'Name',
'key' => 'entity_corporate_name',
'prompt_id' => '9',
],
[
'label' => 'Name der Gegenpartei',
'key' => 'counterparty_corporate_name',
'prompt_id' => '9',
],
[
'label' => 'Warnmitteilung',
'key' => 'entity_corporate_warnings',
'prompt_id' => '42',
],
[
'label' => 'Warnmitteilung der Gegenpartei',
'key' => 'counterparty_corporate_warnings',
'prompt_id' => '42',
],
],
],
[
'title' => 'Rechtsform',
'description' => null,
'fields' => [
[
'label' => 'Rechtsform',
'key' => 'entity_corporate_form',
'prompt_id' => '10',
],
[
'label' => 'Rechtsform d. Gegenpartei',
'key' => 'counterparty_corporate_form',
'prompt_id' => '10',
],
],
],
[
'title' => 'Registergericht & Nummer',
'description' => null,
'fields' => [
[
'label' => 'Registernummer',
'key' => 'entity_corporate_forum',
'prompt_id' => '11',
],
[
'label' => 'Registernummer d. Gegenpartei',
'key' => 'counterparty_corporate_forum',
'prompt_id' => '11',
],
],
],
[
'title' => 'Sitz / eingetragene Adresse',
'description' => null,
'fields' => [
[
'label' => 'Sitzadresse',
'key' => 'entity_corporate_HQ',
'prompt_id' => '12',
],
[
'label' => 'Sitzadresse d. Gegenpartei',
'key' => 'counterparty_corporate_HQ',
'prompt_id' => '12',
],
[
'label' => 'Korruption',
'key' => 'entity_corporate_corruptionexposure',
'prompt_id' => '48',
],
[
'label' => 'Korruption d. Gegenpartei',
'key' => 'counterparty_corporate_corruptionexposure',
'prompt_id' => '48',
],
[
'label' => 'Länderrisiko',
'key' => 'entity_corporate_CTYexposure',
'prompt_id' => '50',
],
[
'label' => 'Länderrisiko d. Gegenpartei',
'key' => 'counterparty_corporate_CTYexposure',
'prompt_id' => '50',
],
[
'label' => 'Korruption Land',
'key' => 'entity_corruption_country',
'prompt_id' => '74',
],
[
'label' => 'Korruption Land d. Gegenpartei',
'key' => 'counterparty_corruption_country',
'prompt_id' => '74',
],
[
'label' => 'Risikoländer',
'key' => 'entity_country_risk',
'prompt_id' => '86',
],
[
'label' => 'Risikoländer d. Gegenpartei',
'key' => 'counterparty_country_risk',
'prompt_id' => '86',
],
],
],
[
'title' => 'Handelsname/Marke',
'description' => null,
'fields' => [
[
'label' => 'Handelsnamen',
'key' => 'entity_corporate_brands',
'prompt_id' => '34',
],
[
'label' => 'Handelsnamen d. Gegenpartei',
'key' => 'counterparty_corporate_brands',
'prompt_id' => '34',
],
[
'label' => 'Warnmitteilungen',
'key' => 'entity_corporate_warnings',
'prompt_id' => '42',
],
[
'label' => 'Warnmitteilungen d. Gegenpartei',
'key' => 'counterparty_corporate_warnings',
'prompt_id' => '42',
],
],
],
[
'title' => 'Summary Geschäftsmodell',
'description' => null,
'fields' => [
[
'label' => 'Geschäft',
'key' => 'entity_corporate_purpose',
'prompt_id' => '3',
],
[
'label' => 'Geschäft d. Gegenpartei',
'key' => 'counterparty_corporate_purpose',
'prompt_id' => '3',
],
],
],
[
'title' => 'Brief History of the company',
'description' => null,
'fields' => [
[
'label' => 'Historie',
'key' => 'entity_corporate_history',
'prompt_id' => '2',
],
[
'label' => 'Historie d. Gegenpartei',
'key' => 'counterparty_corporate_history',
'prompt_id' => '2',
],
],
],
[
'title' => 'IBAN/BIC der Firma (alle, Listenform)',
'description' => null,
'fields' => [
[
'label' => 'IBAN / BIC',
'key' => 'entity_corporate_IBAN',
'prompt_id' => '37',
],
[
'label' => 'IBAN / BIC d. Gegenpartei',
'key' => 'counterparty_corporate_IBAN',
'prompt_id' => '37',
],
],
],
[
'title' => 'Mitarbeiteranzahl',
'description' => null,
'fields' => [
[
'label' => 'Mitarbeiteranzahl',
'key' => 'entity_corporate_employeecount',
'prompt_id' => '22',
],
[
'label' => 'Mitarbeiteranzahl d. Gegenpartei',
'key' => 'counterparty_corporate_employeecount',
'prompt_id' => '22',
],
],
],
[
'title' => 'LEI (Legal Entity Identification Number)',
'description' => null,
'fields' => [
[
'label' => 'LEI',
'key' => 'entity_corporate_LEI',
'prompt_id' => '20',
],
[
'label' => 'LEI d. Gegenpartei',
'key' => 'counterparty_corporate_LEI',
'prompt_id' => '20',
],
],
],
[
'title' => 'USt-IdNr. (VAT)',
'description' => null,
'fields' => [
[
'label' => 'Umsatzsteuer-Identifikationsnummer',
'key' => 'entity_corporate_taxID',
'prompt_id' => '19',
],
[
'label' => 'Umsatzsteuer-Identifikationsnummer d. Gegenpartei',
'key' => 'counterparty_corporate_taxID',
'prompt_id' => '19',
],
],
],
[
'title' => 'Offizielle Website',
'description' => null,
'fields' => [
[
'label' => 'Website',
'key' => 'entity_corporate_website',
'prompt_id' => '35',
],
[
'label' => 'Website d. Gegenpartei',
'key' => 'counterparty_corporate_website',
'prompt_id' => '35',
],
],
],
[
'title' => 'Domain(s) & WHOIS',
'description' => null,
'fields' => [
[
'label' => 'Domains',
'key' => 'entity_corporate_domain',
'prompt_id' => '36',
],
[
'label' => 'Domains d. Gegenpartei',
'key' => 'counterparty_corporate_domain',
'prompt_id' => '36',
],
],
],
],
],
];
}
protected function filteredQuery(): Builder
{
return $this->baseQuery()
->when($this->status !== 'all', fn(Builder $query) => $query->where('status', $this->status))
->when($this->channel !== 'all', fn(Builder $query) => $query->where('channel', $this->channel));
->when($this->status !== 'all', fn (Builder $query) => $query->where('status', $this->status))
->when($this->channel !== 'all', fn (Builder $query) => $query->where('channel', $this->channel));
}
protected function baseQuery(): Builder
@@ -284,6 +722,7 @@ $counterpartyHistory = $this->counterpartyHistory;
$recentAlerts = $this->recentAlerts;
$actionChecklist = $this->actionChecklist;
$checklistIntro = __('Fokusbereiche für diesen Fall');
$detailStructure = $this->transactionDetailStructure;
$formatCurrency = static fn (float $value): string => number_format($value, 2, ',', '.') . ' €';
$formatCurrencyCompact = static function (float $value): string {
@@ -776,6 +1215,187 @@ Transaction::STATUS_CLEARED => __('Keine Auffälligkeiten. Dokumentieren und arc
</ul>
</div>
@endif
@if (! empty($detailStructure))
<div class="mt-8 rounded-3xl border border-slate-200/70 bg-white/95 p-6 shadow-sm dark:border-slate-700/70 dark:bg-slate-900/90">
<div class="flex items-center justify-between gap-4 border-b border-slate-200/70 pb-4 dark:border-slate-700/60">
<h4 class="text-sm font-semibold uppercase tracking-wide text-slate-600 dark:text-slate-200">
{{ __('Strukturierte Transaktionsanalyse') }}
</h4>
<span class="inline-flex items-center rounded-full border border-indigo-200/70 bg-indigo-500/10 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-indigo-700 dark:border-indigo-500/40 dark:bg-indigo-500/20 dark:text-indigo-100">
{{ __('Detailfelder') }}
</span>
</div>
<div class="mt-6 space-y-4">
@foreach ($detailStructure as $mainCategoryIndex => $mainCategory)
{{-- Ebene 0: Hauptthema (z.B. "Transaktion") --}}
<div
x-data="{ openMain: false }"
class="rounded-2xl border-2 border-indigo-200/70 bg-gradient-to-br from-indigo-50/70 to-slate-50/70 transition dark:border-indigo-700/70 dark:from-indigo-900/20 dark:to-slate-800/70"
>
<button
@click="openMain = !openMain"
type="button"
class="flex w-full items-center justify-between gap-4 p-6 text-left transition hover:bg-indigo-100/50 dark:hover:bg-indigo-900/30"
>
<div class="flex-1 space-y-2">
<div class="flex items-center gap-3">
<span class="inline-flex items-center rounded-full border border-indigo-400/70 bg-indigo-500/20 px-3 py-1 text-xs font-bold uppercase tracking-wide text-indigo-700 dark:border-indigo-500/60 dark:bg-indigo-500/30 dark:text-indigo-200">
{{ __('Hauptthema') }} {{ $mainCategoryIndex + 1 }}
</span>
<h4 class="text-lg font-bold text-slate-900 dark:text-white">
{{ $mainCategory['title'] }}
</h4>
</div>
<p class="text-xs text-slate-500 dark:text-slate-400">
{{ count($mainCategory['sections']) }} {{ __('Sektionen') }}
</p>
</div>
<div class="flex items-center gap-2">
<flux:icon
name="chevron-down"
class="h-6 w-6 text-indigo-600 transition-transform duration-200 dark:text-indigo-400"
::class="{ 'rotate-180': openMain }"
/>
</div>
</button>
<div
x-show="openMain"
x-collapse
class="border-t-2 border-indigo-200/70 dark:border-indigo-700/60"
>
<div class="space-y-3 p-4">
{{-- Ebene 1: Sektionen --}}
@foreach ($mainCategory['sections'] as $sectionIndex => $section)
<div
x-data="{ open: false }"
class="rounded-2xl border border-slate-200/70 bg-white/90 transition dark:border-slate-700/70 dark:bg-slate-800/90"
>
<button
@click="open = !open"
type="button"
class="flex w-full items-center justify-between gap-4 p-5 text-left transition hover:bg-slate-100/70 dark:hover:bg-slate-700/60"
>
<div class="flex-1 space-y-1">
<div class="flex items-center gap-3">
<span class="inline-flex items-center rounded-full border border-slate-300/70 bg-slate-100/60 px-2.5 py-0.5 text-[11px] font-semibold uppercase tracking-wide text-slate-600 dark:border-slate-600/60 dark:bg-slate-800/70 dark:text-slate-200">
{{ __('Sektion') }} {{ $sectionIndex + 1 }}
</span>
<h5 class="text-base font-semibold text-slate-900 dark:text-white">
{{ $section['title'] }}
</h5>
</div>
@if ($section['description'])
<p class="text-sm text-slate-500 dark:text-slate-400">
{{ $section['description'] }}
</p>
@endif
<p class="text-xs text-slate-400 dark:text-slate-500">
{{ count($section['fields']) }} {{ __('Felder') }}
</p>
</div>
<div class="flex items-center gap-2">
<flux:icon
name="chevron-down"
class="h-5 w-5 text-slate-400 transition-transform duration-200 dark:text-slate-500"
::class="{ 'rotate-180': open }"
/>
</div>
</button>
<div
x-show="open"
x-collapse
class="border-t border-slate-200/70 dark:border-slate-700/60"
>
<div class="grid gap-3 p-5 md:grid-cols-2">
@foreach ($section['fields'] as $field)
<div class="rounded-xl border border-slate-200/60 bg-white/80 p-4 shadow-sm dark:border-slate-700/60 dark:bg-slate-900/70">
<div class="flex items-start justify-between gap-3">
<div class="flex-1 space-y-2">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
{{ $field['label'] }}
</p>
<div class="space-y-1">
@php
$fieldValue = data_get($selected, $field['key']);
$answer = null;
$sources = null;
if (is_array($fieldValue) || is_object($fieldValue)) {
$fieldArray = json_decode(json_encode($fieldValue), true);
$answer = $fieldArray['answer'] ?? null;
$sources = $fieldArray['sources'] ?? null;
} else {
$answer = $fieldValue;
}
$hasValue = $answer !== null && $answer !== '' || ($sources !== null && !empty($sources));
@endphp
@if ($hasValue)
@if ($answer)
<div class="rounded-lg bg-slate-50/80 p-3 dark:bg-slate-800/80">
<p class="text-sm font-medium leading-relaxed text-slate-900 dark:text-slate-100" style="white-space: pre-wrap; word-break: break-word;">{{ $answer }}</p>
</div>
@endif
@if ($sources && is_array($sources) && !empty($sources))
<div class="mt-2 space-y-1">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">{{ __('Quellen') }}:</p>
<div class="flex flex-wrap gap-2">
@foreach ($sources as $source)
<a
href="{{ is_string($source) ? $source : '#' }}"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-1 rounded-md border border-indigo-200/70 bg-indigo-50/80 px-2 py-1 text-xs font-medium text-indigo-700 transition hover:bg-indigo-100/80 dark:border-indigo-500/40 dark:bg-indigo-500/10 dark:text-indigo-200 dark:hover:bg-indigo-500/20"
>
<flux:icon name="link" class="h-3 w-3" />
{{ is_string($source) ? (strlen($source) > 40 ? substr($source, 0, 40) . '...' : $source) : __('Quelle') }}
</a>
@endforeach
</div>
</div>
@endif
@else
<p class="text-sm italic text-slate-400 dark:text-slate-500">
{{ __('Nicht verfügbar') }}
</p>
@endif
<p class="text-xs text-slate-400 dark:text-slate-500">
{{ __('Feld') }}: {{ $field['key'] }}
</p>
</div>
</div>
<span class="inline-flex items-center rounded-md border border-slate-200/70 bg-slate-50/80 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-slate-500 dark:border-slate-600/60 dark:bg-slate-800/70 dark:text-slate-400">
ID {{ $field['prompt_id'] }}
</span>
</div>
</div>
@endforeach
</div>
</div>
</div>
@endforeach
</div>
</div>
</div>
@endforeach
</div>
<div class="mt-6 rounded-2xl border border-dashed border-slate-300/60 bg-slate-50/70 p-4 text-xs text-slate-600 dark:border-slate-600/60 dark:bg-slate-800/70 dark:text-slate-200">
<p class="font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
{{ __('Hinweis zur Datenstruktur') }}
</p>
<p class="mt-2 leading-relaxed">
{{ __('Diese Felder basieren auf der strukturierten Analyse-Konfiguration. Fehlende Werte können durch erweiterte Datenquellen oder manuelle Eingaben ergänzt werden.') }}
</p>
</div>
</div>
@endif
</div>
<div class="space-y-6">