authorize('customers.manage'); $this->customer = Customer::query()->where('uuid', $uuid)->firstOrFail(); if (! in_array($this->tab, self::TABS, true)) { $this->tab = self::TABS[0]; } } /** * Put a template into the fields, filled in for THIS customer. * * Inserted, never sent: the placeholders come out filled and the last two * sentences are still the operator's to write. What goes out is what they * saw and edited. */ public function useTemplate(string $uuid): void { $this->authorize('customers.manage'); $template = MailTemplate::query()->where('uuid', $uuid)->where('active', true)->first(); if ($template === null) { return; } $filled = app(MailTemplateRenderer::class)->render( $template, $this->customer, Auth::guard('operator')->user(), ); // The subject is only taken when the operator has not written one — a // template that overwrites what somebody typed is a template that eats // work. The body is replaced, because that is what "use this template" // means and the field is empty in the case that matters. if (trim($this->subject) === '') { $this->subject = $filled['subject']; } $this->body = $filled['body']; $this->tab = 'compose'; } /** Start an answer to one request: subject prefilled, the request remembered. */ public function answer(string $uuid): void { $this->authorize('customers.manage'); $request = $this->customer->supportRequests()->where('uuid', $uuid)->first(); if ($request === null) { return; } $this->answering = $request->uuid; $this->subject = __('customer_message.re', ['subject' => $request->subject]); $this->body = ''; $this->tab = 'compose'; } public function cancelAnswer(): void { $this->answering = null; $this->subject = ''; $this->body = ''; } /** * Send it, record it, and close the request it answers. * * The register row is written here rather than left to the MessageSent * listener alone, because this is the one mail whose BODY is worth keeping: * an operator typed it, and "what exactly did I write to them in March" is a * question the mail server's log cannot answer either. */ public function send(): void { $this->authorize('customers.manage'); $data = $this->validate(); Mail::to($this->customer->email)->send( new OperatorMessageMail($this->customer, $data['subject'], $data['body']) ); SentMail::query() ->where('customer_id', $this->customer->id) ->where('to', $this->customer->email) ->latest('id') ->limit(1) ->update(['from_operator' => true, 'body' => $data['body']]); if ($this->answering !== null) { $this->customer->supportRequests() ->where('uuid', $this->answering) ->update(['status' => 'answered', 'answered_at' => now()]); } $this->cancelAnswer(); $this->dispatch('notify', message: __('customer_message.sent')); } public function render() { $this->authorize('customers.manage'); return view('livewire.admin.customer-detail', [ 'tabs' => self::TABS, 'subscription' => $this->subscription(), 'instance' => Instance::query() ->where('customer_id', $this->customer->id) ->latest('id') ->first(), 'orders' => Order::query() ->where('customer_id', $this->customer->id) ->latest('id') ->limit(20) ->get(), 'invoices' => Invoice::query() ->where('customer_id', $this->customer->id) ->orderByDesc('issued_on') ->orderByDesc('id') ->limit(20) ->get(), 'requests' => $this->customer->supportRequests()->latest('id')->get(), 'inbound' => InboundMail::query() ->where('customer_id', $this->customer->id) ->latest('received_at') ->limit(30) ->get(), 'mails' => SentMail::query() ->where('customer_id', $this->customer->id) ->latest('sent_at') ->limit(30) ->get(), 'templates' => MailTemplate::query()->usable()->get(), // Counted for the tab bar, so an operator sees where the unread // things are before opening anything. 'openRequests' => $this->customer->supportRequests() ->whereNotIn('status', SupportRequest::CLOSED) ->count(), ]); } /** The contract that is being billed, or the most recent one there was. */ private function subscription(): ?Subscription { return Subscription::query() ->where('customer_id', $this->customer->id) ->with('addons') ->orderByRaw("status = 'active' DESC") ->latest('id') ->first(); } }