tenantIdOrNull(); if (! $tenantId) { Log::warning('[PushNotificationService] No tenant_id provided'); return; } $channelId = $this->getChannelForEvent($event); // 해당 테넌트의 활성 토큰 조회 (global scope 무시) $query = PushDeviceToken::withoutGlobalScopes() ->forTenant($tenantId) ->active(); // 특정 사용자에게만 발송 if ($userId) { $query->forUser($userId); } $tokens = $query->pluck('token')->toArray(); if (empty($tokens)) { Log::info('[PushNotificationService] No active tokens found', [ 'tenant_id' => $tenantId, 'user_id' => $userId, 'event' => $event, ]); return; } // 이벤트 타입을 data에 추가 $data['event'] = $event; try { $result = $this->fcmSender->sendToMany( $tokens, $title, $body, $channelId, $data ); Log::info('[PushNotificationService] Push sent', [ 'tenant_id' => $tenantId, 'event' => $event, 'channel_id' => $channelId, 'token_count' => count($tokens), 'success_count' => $result->getSuccessCount(), 'failure_count' => $result->getFailureCount(), ]); } catch (\Exception $e) { Log::error('[PushNotificationService] Push failed', [ 'tenant_id' => $tenantId, 'event' => $event, 'error' => $e->getMessage(), ]); } } /** * 신규 거래처 등록 알림 */ public function notifyNewClient(int $clientId, string $clientName, ?int $tenantId = null): void { $this->sendByEvent( 'new_client', '신규 거래처 등록', "새로운 거래처 '{$clientName}'이(가) 등록되었습니다.", ['client_id' => $clientId, 'type' => 'client'], $tenantId ); } /** * 결제 알림 */ public function notifyPayment(int $paymentId, string $message, ?int $tenantId = null, ?int $userId = null): void { $this->sendByEvent( 'payment', '결제 알림', $message, ['payment_id' => $paymentId, 'type' => 'payment'], $tenantId, $userId ); } /** * 수주 알림 */ public function notifySalesOrder(int $orderId, string $message, ?int $tenantId = null): void { $this->sendByEvent( 'sales_order', '수주 알림', $message, ['order_id' => $orderId, 'type' => 'sales_order'], $tenantId ); } /** * 발주 알림 */ public function notifyPurchaseOrder(int $orderId, string $message, ?int $tenantId = null): void { $this->sendByEvent( 'purchase_order', '발주 알림', $message, ['order_id' => $orderId, 'type' => 'purchase_order'], $tenantId ); } /** * 계약 알림 */ public function notifyContract(int $contractId, string $message, ?int $tenantId = null): void { $this->sendByEvent( 'contract', '계약 알림', $message, ['contract_id' => $contractId, 'type' => 'contract'], $tenantId ); } /** * 일반 알림 */ public function notifyGeneral(string $title, string $body, array $data = [], ?int $tenantId = null, ?int $userId = null): void { $this->sendByEvent( 'general', $title, $body, $data, $tenantId, $userId ); } /** * 이벤트 타입에 따른 채널 ID 반환 */ private function getChannelForEvent(string $event): string { return match ($event) { 'new_client' => 'push_urgent', 'payment' => 'push_payment', 'sales_order' => 'push_sales_order', 'purchase_order' => 'push_purchase_order', 'contract' => 'push_contract', default => 'push_default', }; } }