'integer', 'metadata' => 'array', 'builder_data' => 'array', ]; protected static function booted(): void { static::saved(fn (self $template) => $template->forgetCache()); static::deleted(fn (self $template) => $template->forgetCache()); } public function forgetCache(): void { if ($this->scenario) { Cache::forget(self::CACHE_PREFIX . $this->scenario); } if ($this->key) { Cache::forget(self::CACHE_PREFIX . $this->key); } } /** Danh sách variable hợp lệ khai báo trong metadata.variables, key theo variable key. */ public function variables(): array { return collect($this->metadata['variables'] ?? [])->keyBy('key')->all(); } /** Mail sender config khai báo trong metadata.mail (from_email, from_name, reply_to, reply_to_name, cc, bcc). */ public function mailConfig(): array { return $this->metadata['mail'] ?? []; } public const SCENARIOS = [ 'layout' => 'Bố cục Header/Footer (mặc định)', 'vm_credentials' => 'Khởi tạo dịch vụ', 'renewal_reminder' => 'Nhắc gia hạn', 'news_notification' => 'Thông báo tin tức', 'invoice_pdf' => 'Hoá đơn điện tử', 'vm_suspended' => 'VPS bị tạm ngưng', 'domain_order_received' => 'Đơn đăng ký tên miền đã ghi nhận', 'hosting_account_created' => 'Hosting đã khởi tạo', 'manual_deposit_status' => 'Trạng thái nạp tiền thủ công', 'order_payment_success' => 'Thanh toán đơn hàng thành công', 'password_reset_admin' => 'Admin cấp lại mật khẩu', 'service_renewal_reminder'=> 'Nhắc gia hạn dịch vụ', 'snapshot_action' => 'Thao tác Snapshot VPS', 'ticket_created' => 'Ticket hỗ trợ mới', 'verify_account_welcome' => 'Mã xác thực tài khoản', 'vm_creation_failed' => 'Khởi tạo VPS thất bại', 'vm_password_changed' => 'Đổi mật khẩu VPS', 'vm_rebuild_failed' => 'Rebuild VPS thất bại', 'domain_order_approved' => 'Tên miền được duyệt', 'domain_order_rejected' => 'Tên miền bị từ chối', 'order_created' => 'Xác nhận đơn hàng', 'admin_order_notification' => '[Admin] Đơn hàng mới', 'admin_domain_order_new' => '[Admin] Đơn đăng ký tên miền mới', 'admin_hosting_account_created' => '[Admin] Hosting account mới', 'admin_manual_deposit_pending' => '[Admin] Yêu cầu nạp tiền chờ duyệt', 'admin_ticket_notification' => '[Admin] Ticket hỗ trợ mới', 'admin_trial_registration_request' => '[Admin] Yêu cầu dùng thử VPS', 'customer_deposit_success' => 'Nạp tiền thành công', 'customer_welcome' => 'Chào mừng khách hàng mới', 'vm_port_change' => 'Đổi port Remote VPS', 'vm_password_reset_success' => 'Đặt lại mật khẩu VPS thành công', 'customer_signup_verify' => 'Xác thực đăng ký tài khoản', 'hosting_information' => 'Thông tin truy cập Hosting', 'customer_password_reset' => 'Khôi phục mật khẩu khách hàng', 'customer_password_reset_link' => 'Link đặt lại mật khẩu khách hàng', 'service_expiry' => 'Dịch vụ sắp hết hạn', 'customer_payment' => 'Thông báo thanh toán khách hàng', 'invoice_payment_reminder' => 'Nhắc thanh toán hoá đơn', 'invoice_notification' => 'Thông báo hoá đơn / gia hạn cước', ]; public function invoiceTemplate() { return $this->belongsTo(self::class, 'invoice_template_id'); } /** * Wrap $contentHtml with the admin-editable "layout" template (scenario=layout), * replacing its {{$content}} placeholder. * * Không còn fallback về blade: mọi email đều render từ tbl.template. Thiếu layout thì * dùng khung header/footer chuẩn trong code thay vì gửi ra nội dung trần. */ public static function renderWithLayout(?string $subject, string $contentHtml): string { $layout = self::where('scenario', 'layout')->where('status', 1)->first(); $body = $layout && $layout->message ? str_replace('{{$content}}', $contentHtml, $layout->message) : EmailTemplateShell::wrap(EmailTemplateShell::body($contentHtml)); return EmailTemplateShell::document($subject, $body); } public function type() { return $this->belongsTo(\App\Models\Type::class); } }