diff --git a/app/Livewire/Users.php b/app/Livewire/Users.php index aeb5c8f..50e1198 100644 --- a/app/Livewire/Users.php +++ b/app/Livewire/Users.php @@ -6,6 +6,7 @@ use App\Livewire\Concerns\ResolvesCustomer; use App\Models\Customer; use App\Models\Seat; use App\Provisioning\Jobs\SyncSeatToNextcloud; +use App\Services\Mail\GuestMailConfig; use Illuminate\Database\UniqueConstraintViolationException; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\RateLimiter; @@ -226,6 +227,28 @@ class Users extends Component return; } + // Die Einladung verschickt NEXTCLOUD, nicht CluPilot — nur so entsteht + // das Passwort dort, wo niemand sonst es zu sehen bekommt. Ohne + // eingerichteten Mailversand kommt dort aber nichts heraus, und + // `occ user:add --generate-password --email` GELINGT trotzdem: + // Nextcloud legt das Konto an, versucht die Willkommensmail, + // protokolliert intern einen Fehler und beendet mit 0. Der Auftrag + // meldete Erfolg, die Zeile sprang auf „Eingeladen", die Meldung + // versprach einen Link — und niemand bekam etwas. + // + // Am Tag der Auslieferung ist das kein Randfall, sondern der + // Normalzustand: der Mailserver dieses Produkts entsteht gerade erst, + // und das Postfach steht bis dahin leer. + // + // VOR dem Ratelimit, damit ein abgewiesener Versuch kein Kontingent + // verbraucht: sonst hätte der Inhaber seine Versuche aufgebraucht, + // bevor überhaupt einer hinausgehen konnte. + if (! $this->mailversandBereit($customer)) { + $this->dispatch('notify', message: __('users.invite_no_mail')); + + return; + } + if (($warten = $this->rateLimited($customer, $seat)) !== null) { $this->dispatch('notify', message: __('users.too_many_invites', ['minutes' => $warten])); @@ -550,6 +573,36 @@ class Users extends Component $this->revoke($uuid); } + /** + * Ob eine Einladung dieses Kunden überhaupt ankommen könnte. + * + * Ein reiner Blick in die Datenbank — Servereinstellung und + * Versandkonto — kein Tunnel, keine Warteschlange. Deshalb darf er auf der + * Seite stehen und nicht erst im Auftrag. + * + * OHNE Instanz gibt es hier nichts zu entscheiden: dann scheitert der + * Auftrag ohnehin an `no_instance`, und die Zeile sagt das im Klartext. + * Diese Wache gilt dem anderen Fall — die Cloud LÄUFT, nur der Versand + * fehlt. Genau dort meldet occ Erfolg, und niemand bekommt eine Mail. + * + * Dieselbe Auswahl wie im Auftrag (SyncSeatToNextcloud): die Instanz, in + * der die Einladung tatsächlich landen würde. + * + * Der Bereitstellungsschritt bleibt davon unberührt. Dort gilt weiter + * „nicht eingerichtet ist etwas anderes als kaputt", und eine bezahlte + * Bestellung läuft durch — eine Cloud ohne Mailversand funktioniert + * vollständig. Was sie nicht kann, ist eine Einladung verschicken, und nur + * darum geht es hier. + */ + private function mailversandBereit(Customer $customer): bool + { + $instance = $customer->instances() + ->whereIn('status', ['active', 'cancellation_scheduled']) + ->latest('id')->first(); + + return $instance === null || GuestMailConfig::for($instance)->available(); + } + private function usedSeats(Customer $customer): int { return $customer->seats()->where('status', '!=', 'revoked')->count(); @@ -583,6 +636,9 @@ class Users extends Component 'used' => $customer ? $this->usedSeats($customer) : 0, 'limit' => $customer ? $this->seatLimit($customer) : 0, 'roles' => Seat::ROLES, + // Der Hinweis steht dort, wo der Inhaber ihn liest, bevor er + // drückt. Der Knopf bleibt trotzdem stehen — siehe das Blade. + 'versandBereit' => $customer === null || $this->mailversandBereit($customer), ]); } } diff --git a/lang/de/users.php b/lang/de/users.php index 047369d..473aa0f 100644 --- a/lang/de/users.php +++ b/lang/de/users.php @@ -90,6 +90,12 @@ return [ 'added' => 'Benutzer angelegt. Die Einladung verschicken Sie mit „Einladen".', 'reinstated' => 'Zugang wieder aufgenommen. Die Einladung verschicken Sie mit „Einladen".', 'invite_sent' => 'Einladung verschickt. Der Mitarbeiter bekommt einen Link, an dem er sein Passwort selbst setzt.', + // Die Einladung verschickt die Nextcloud des Kunden selbst — ohne + // eingerichteten Mailversand kommt dort nichts heraus, und `occ user:add` + // meldet trotzdem Erfolg. Der Satz sagt deshalb beides: dass nichts + // verschickt wurde, und woran es liegt. + 'mail_not_ready' => 'Einladungen sind noch nicht möglich: Der Mailversand für Ihre Cloud ist noch nicht eingerichtet. Die Einladung verschickt Ihre Cloud selbst — ohne Versand bekäme niemand eine Mail. Bitte wenden Sie sich an den Support. Anlegen können Sie Ihre Leute schon jetzt.', + 'invite_no_mail' => 'Es wurde nichts verschickt: Der Mailversand für Ihre Cloud ist noch nicht eingerichtet. Bitte wenden Sie sich an den Support.', 'too_many_invites' => 'Zu viele Einladungen — in :minutes Minuten wieder möglich.', 'revoked' => 'Benutzer entfernt.', 'resent' => 'Einladung erneut gesendet.', diff --git a/lang/en/users.php b/lang/en/users.php index 32afafb..8dc5ee1 100644 --- a/lang/en/users.php +++ b/lang/en/users.php @@ -89,6 +89,12 @@ return [ 'added' => 'User added. Send the invitation with "Invite".', 'reinstated' => 'Access taken back on. Send the invitation with "Invite".', 'invite_sent' => 'Invitation sent. They get a link to set their own password.', + // The invitation is sent by the customer's own Nextcloud — with no mail + // delivery configured, nothing leaves it, and `occ user:add` reports + // success all the same. The sentence therefore says both: that nothing + // was sent, and why. + 'mail_not_ready' => 'Invitations are not possible yet: mail delivery for your cloud is not set up. The invitation is sent by your own cloud — without delivery, nobody would receive anything. Please contact support. You can already add your people.', + 'invite_no_mail' => 'Nothing was sent: mail delivery for your cloud is not set up yet. Please contact support.', 'too_many_invites' => 'Too many invitations — possible again in :minutes minutes.', 'revoked' => 'User removed.', 'resent' => 'Invitation resent.', diff --git a/resources/views/livewire/users.blade.php b/resources/views/livewire/users.blade.php index dfddfc8..ce56378 100644 --- a/resources/views/livewire/users.blade.php +++ b/resources/views/livewire/users.blade.php @@ -11,9 +11,9 @@ - {{-- Anlegen. Ausdruecklich NICHT einladen: hier entsteht nur die Zeile, + {{-- Anlegen. Ausdrücklich NICHT einladen: hier entsteht nur die Zeile, die Einladung verschickt der Knopf in der Tabelle. Ein Inhaber soll - sein Team vorbereiten koennen, ohne dass jemand eine Mail bekommt. --}} + sein Team vorbereiten können, ohne dass jemand eine Mail bekommt. --}}