user(); $profile = $user->profile; $this->name = (string) $user->name; $this->language = $user->language ?? 'de'; $this->salutationKey = (string) ($profile?->salutation_key ?? 'none'); $this->firstName = (string) ($profile?->first_name ?? ''); $this->lastName = (string) ($profile?->last_name ?? ''); $this->title = (string) ($profile?->title ?? ''); $this->phone = (string) ($profile?->phone ?? ''); $this->address = (string) ($profile?->address ?? ''); $this->countryCode = (string) ($profile?->country_code ?? 'DE'); $this->backlinkUrl = (string) ($profile?->backlink_url ?? ''); $this->showStats = (bool) ($profile?->show_stats ?? false); $this->disableFooterCode = (bool) ($profile?->disable_footer_code ?? false); $this->taxIdNumber = (string) ($profile?->tax_id_number ?? ''); $billingAddress = $user->billingAddress; $this->billingSalutationKey = (string) ($billingAddress?->salutation_key ?? 'none'); $this->billingCompany = (string) ($billingAddress?->company ?? ''); $this->billingFirstName = (string) ($billingAddress?->first_name ?? ''); $this->billingLastName = (string) ($billingAddress?->last_name ?? ''); $this->billingAddress1 = (string) ($billingAddress?->address1 ?? ''); $this->billingAddress2 = (string) ($billingAddress?->address2 ?? ''); $this->billingPostalCode = (string) ($billingAddress?->postal_code ?? ''); $this->billingCity = (string) ($billingAddress?->city ?? ''); $this->billingCountryCode = (string) ($billingAddress?->country_code ?? 'DE'); // Bestandsdaten vor der Feld-Trennung: `name` war eine freie // Empfängerzeile — einmalig in Vor-/Nachname aufteilen. if (blank($this->billingFirstName) && blank($this->billingLastName) && filled($billingAddress?->name)) { $parts = preg_split('/\s+/u', trim((string) $billingAddress->name)) ?: []; $this->billingLastName = (string) array_pop($parts); $this->billingFirstName = implode(' ', $parts); } if (filled($this->taxIdNumber)) { $this->refreshVatCheck(); } } /** * Persönliche Daten als Rechnungsempfänger übernehmen — löst die von * Kevin angemerkte Doppel-Eingabe auf, ohne die Datensätze zu koppeln. */ public function copyProfileToBilling(): void { $this->billingSalutationKey = $this->salutationKey; $this->billingFirstName = $this->firstName; $this->billingLastName = $this->lastName; if (filled($this->countryCode)) { $this->billingCountryCode = $this->countryCode; } } public function updatedTaxIdNumber(): void { $this->refreshVatCheck(); } public function updatedBillingCountryCode(): void { $this->refreshVatCheck(); } private function refreshVatCheck(): void { if (blank($this->taxIdNumber)) { $this->vatCheckStatus = null; $this->vatCheckMessage = null; return; } $result = app(VatIdValidationService::class)->check($this->taxIdNumber, $this->billingCountryCode); $this->vatCheckStatus = $result['status']->value; $this->vatCheckMessage = $result['message']; } public function saveProfile(): void { $rules = [ 'name' => ['required', 'string', 'max:120'], 'language' => ['required', Rule::in(['de', 'en'])], 'salutationKey' => ['required', Rule::in(array_keys((array) config('salutations.items', [])))], 'firstName' => ['nullable', 'string', 'max:80'], 'lastName' => ['nullable', 'string', 'max:80'], 'title' => ['nullable', 'string', 'max:80'], 'phone' => ['nullable', 'string', 'max:40'], 'address' => ['nullable', 'string', 'max:1000'], 'countryCode' => ['nullable', 'string', 'size:2', Rule::in(array_keys((array) config('countries.items', [])))], 'backlinkUrl' => ['nullable', 'url', 'max:255'], 'taxIdNumber' => ['nullable', 'string', 'max:255'], 'billingSalutationKey' => ['required', Rule::in(array_keys((array) config('salutations.items', [])))], 'billingCompany' => ['nullable', 'string', 'max:255'], 'billingFirstName' => ['nullable', 'string', 'max:80'], 'billingLastName' => ['nullable', 'string', 'max:80'], 'billingAddress1' => ['nullable', 'string', 'max:255'], 'billingAddress2' => ['nullable', 'string', 'max:255'], 'billingPostalCode' => ['nullable', 'string', 'max:20'], 'billingCity' => ['nullable', 'string', 'max:120'], 'billingCountryCode' => ['nullable', 'string', 'size:2', Rule::in(array_keys((array) config('countries.items', [])))], ]; // Sobald irgendein Rechnungsfeld gefüllt ist, werden die // Pflichtfelder einzeln eingefordert — die Meldung erscheint genau // einmal unter dem jeweils fehlenden Feld (vorher: eine generische // Sammelmeldung zusätzlich zur Feldmeldung). if ($this->billingHasInput()) { $rules['billingLastName'] = ['required', 'string', 'max:80']; $rules['billingAddress1'] = ['required', 'string', 'max:255']; $rules['billingPostalCode'] = ['required', 'string', 'max:20']; $rules['billingCity'] = ['required', 'string', 'max:120']; $rules['billingCountryCode'] = ['required', 'string', 'size:2', Rule::in(array_keys((array) config('countries.items', [])))]; } $validated = $this->validate($rules, attributes: [ 'billingLastName' => __('Nachname (Rechnung)'), 'billingAddress1' => __('Straße und Hausnummer'), 'billingPostalCode' => __('PLZ'), 'billingCity' => __('Ort'), 'billingCountryCode' => __('Land'), ]); // USt-ID: hartes Format-Gate; die Online-Bestätigung (eVatR) bleibt // ein Hinweis und blockiert das Speichern nicht. if (filled($validated['taxIdNumber'])) { $this->refreshVatCheck(); if ($this->vatCheckStatus === VatIdCheckStatus::FormatInvalid->value) { $this->addError('taxIdNumber', (string) $this->vatCheckMessage); return; } } /** @var User $user */ $user = auth()->user(); $user->forceFill([ 'name' => $validated['name'], 'language' => $validated['language'], ])->save(); $user->profile()->updateOrCreate( ['user_id' => $user->id], [ 'salutation_key' => $validated['salutationKey'], 'first_name' => $validated['firstName'] ?: null, 'last_name' => $validated['lastName'] ?: null, 'title' => $validated['title'] ?: null, 'phone' => $validated['phone'] ?: null, 'address' => $validated['address'] ?: null, 'country_code' => $validated['countryCode'] ?: null, 'backlink_url' => $validated['backlinkUrl'] ?: null, 'show_stats' => $this->showStats, 'disable_footer_code' => $this->disableFooterCode, 'tax_id_number' => $validated['taxIdNumber'] ?: null, ] ); if (! $this->billingHasInput()) { $user->billingAddress()->delete(); } else { $user->billingAddress()->updateOrCreate( ['user_id' => $user->id], [ 'salutation_key' => $validated['billingSalutationKey'] !== 'none' ? $validated['billingSalutationKey'] : null, 'company' => $validated['billingCompany'] ?: null, 'first_name' => $validated['billingFirstName'] ?: null, 'last_name' => $validated['billingLastName'] ?: null, // Zusammengesetzte Empfängerzeile für Rechnungs-Snapshots. 'name' => trim($validated['billingFirstName'].' '.$validated['billingLastName']), 'address1' => $validated['billingAddress1'], 'address2' => $validated['billingAddress2'] ?: null, 'postal_code' => $validated['billingPostalCode'], 'city' => $validated['billingCity'], 'country_code' => $validated['billingCountryCode'], // USt-ID auch an der Rechnungsadresse pflegen — sie wird // pro Rechnung eingefroren und bestimmt die Steuer // (EU-Befreiung nur mit gültiger USt-ID). 'vat_id' => $validated['taxIdNumber'] ?: null, ], ); } session()->flash('profile-status', __('Profil gespeichert.')); } public function with(): array { $user = auth()->user(); return [ 'user' => $user, 'salutations' => collect((array) config('salutations.items', [])) ->map(fn (array $labels) => $labels[$user->language] ?? $labels['de'] ?? '') ->all(), 'countries' => (array) config('countries.items', []), 'billingComplete' => $this->billingIsComplete(), ]; } public function billingHasInput(): bool { return filled($this->billingCompany) || filled($this->billingFirstName) || filled($this->billingLastName) || filled($this->billingAddress1) || filled($this->billingAddress2) || filled($this->billingPostalCode) || filled($this->billingCity); } public function billingIsComplete(): bool { return filled($this->billingLastName) && filled($this->billingAddress1) && filled($this->billingPostalCode) && filled($this->billingCity) && filled($this->billingCountryCode); } }; ?>
{{ __('Persönliche Daten, Rechnungsadresse und Konto-Einstellungen. Firmendaten liegen separat in der Firmenverwaltung.') }}