, bcc: array} */ public function previewTemplateEmail(string $uuid, int $templateId): array { $customer = $this->resolveCustomer($uuid); $template = $this->sendableTemplate($templateId); $config = $template->mailConfig(); return [ ...$this->renderForCustomer($template, $customer), 'cc' => $config['cc'] ?? [], 'bcc' => $config['bcc'] ?? [], ]; } public function wrapWithLayout(string $uuid, string $contentHtml): string { $customer = $this->resolveCustomer($uuid); return MailPlaceholders::fill(Template::layoutBody($contentHtml), [ ...MailGlobals::values(), ...MailPlaceholders::recipientValues($customer, $customer->email), ]); } private function sendableTemplate(int $templateId): Template { $template = Template::query()->whereKey($templateId)->where('status', 1)->first(); abort_if(!$template, 422, 'Template không tồn tại hoặc đang bị tắt.'); abort_if($template->format === 'json', 422, 'Không thể gửi template JSON (hoá đơn).'); return $template; } /** * @return array{subject: string, html: string} */ private function renderForCustomer(Template $template, Customer $customer): array { $values = collect($template->variables()) ->map(fn ($definition) => $definition['example'] ?? '') ->merge(MailGlobals::values()) ->merge(MailPlaceholders::recipientValues($customer, $customer->email)) ->all(); try { return $this->renderer->render($template, $values); } catch (InvalidTemplateVariableException $e) { abort(422, $e->getMessage()); } } /** * Reset về mật khẩu ngẫu nhiên và gửi mail cho khách hàng. */ public function resetPassword(string $uuid): void { $customer = $this->resolveCustomer($uuid); $this->setPasswordAndNotify($customer, str()->random(10)); } /** * Đặt mật khẩu do admin nhập và gửi mail cho khách hàng, có ghi log. */ public function sendPasswordEmail(string $uuid, string $password, string $clientIp): void { $customer = $this->resolveCustomer($uuid); $this->setPasswordAndNotify($customer, $password); Log::create([ 'agent' => 'admin', 'client_ip' => $clientIp, 'description' => sprintf('Admin gửi lại mật khẩu mới cho customer %s', $customer->email), 'customer_id' => $customer->id, ]); } private function setPasswordAndNotify(Customer $customer, string $password): void { DB::transaction(function () use ($customer, $password) { $this->customerRepository->update($customer->id, ['password' => Hash::make($password)]); $mail = new PasswordResetByAdmin([ 'fullname' => $customer->fullname, 'email' => $customer->email, 'password' => $password, ]); Mail::to($customer->email)->send($mail); MailLog::create([ 'subject' => $mail->envelope()->subject, 'to' => $customer->email, 'customer_id' => $customer->id, 'message' => $mail->render(), ]); }); } /** * Gửi email tự do (hoặc theo template) cho khách hàng, có ghi log. */ public function sendCustomEmail(string $uuid, array $data, User $admin, string $clientIp): void { $customer = $this->resolveCustomer($uuid); abort_if(!AutoEmailGate::mailEnabled(), 422, 'Hệ thống đang tắt gửi email (Mẫu email → Cài đặt → Bật gửi email).'); $template = empty($data['template_id']) ? null : $this->sendableTemplate((int) $data['template_id']); abort_if( $template?->isMarketing() && !$customer->acceptsMarketingEmail(), 422, 'Khách hàng đã tắt nhận email marketing, không thể gửi mẫu email marketing.', ); $subject = trim((string) ($data['subject'] ?? '')); $message = trim((string) ($data['message'] ?? '')); if ($template && ($subject === '' || $message === '')) { $rendered = $this->renderForCustomer($template, $customer); $subject = $subject ?: $rendered['subject']; $message = $message ?: $rendered['html']; } abort_if($subject === '' || $message === '', 422, 'Subject và content không được để trống.'); $to = EmailRecipients::parse($data['to'] ?? null) ?: [$customer->email]; $cc = EmailRecipients::parse($data['cc'] ?? null); $bcc = EmailRecipients::parse($data['bcc'] ?? null); abort_if($to === [], 422, 'Không có địa chỉ email nhận hợp lệ.'); $files = MailAttachments::store($data['attachments'] ?? [], "customer-emails/{$customer->id}"); $mailer = Mail::to($to); if ($cc !== []) { $mailer->cc($cc); } if ($bcc !== []) { $mailer->bcc($bcc); } $mailer->send($template ? new GenericTemplateMail($subject, $message, array_diff_key($template->mailConfig(), array_flip(['cc', 'bcc'])), storedFiles: $files) : new ManualCustomerEmail($subject, $message, wrapLayout: (bool) ($data['with_layout'] ?? true), storedFiles: $files)); MailLog::create([ 'template_id' => $template->id ?? 0, 'subject' => $subject, 'from_address' => $template?->mailConfig()['from_email'] ?? config('mail.from.address'), 'to' => implode(', ', $to), 'cc' => implode(', ', $cc), 'bcc' => implode(', ', $bcc), 'customer_id' => $customer->id, 'sent_by' => $admin->id, 'message' => $message, ]); Log::create([ 'agent' => 'admin', 'client_ip' => $clientIp, 'description' => sprintf( '%s gửi email cho customer %s%s', $admin->username ?? $admin->email ?? 'Admin', $customer->email, $template ? sprintf(' bằng template #%s', $template->id) : '', ), 'customer_id' => $customer->id, ]); } }