NotificationService.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. <?php
  2. namespace App\Services;
  3. use App\Events\SendPersistentNotificationEvent;
  4. use App\Helpers\DateHelper;
  5. use App\Jobs\SendUserNotificationChannelJob;
  6. use App\Models\ChatMessage;
  7. use App\Models\NotificationDeliveryLog;
  8. use App\Models\Order;
  9. use App\Models\Reclamation;
  10. use App\Models\Schedule;
  11. use App\Models\User;
  12. use App\Models\UserNotification;
  13. use App\Models\UserNotificationSetting;
  14. use App\Notifications\FireBaseNotification;
  15. use Illuminate\Database\Eloquent\Collection;
  16. use Illuminate\Support\Facades\Mail;
  17. use Illuminate\Support\Str;
  18. class NotificationService
  19. {
  20. public function notifyChatMessage(ChatMessage $chatMessage, array $recipientIds = [], bool $forceBrowserNotification = false): void
  21. {
  22. $chatMessage->loadMissing([
  23. 'user',
  24. 'targetUser',
  25. 'order.user',
  26. 'order.brigadier',
  27. 'reclamation.order',
  28. 'reclamation.user',
  29. 'reclamation.brigadier',
  30. ]);
  31. $type = $chatMessage->order_id ? UserNotification::TYPE_PLATFORM : UserNotification::TYPE_RECLAMATION;
  32. $sourceKey = $chatMessage->order_id ? 'platform' : 'reclamation';
  33. $title = $chatMessage->order_id ? 'Чат площадки' : 'Чат рекламации';
  34. [$message, $messageHtml, $payload] = $this->buildChatNotificationContent($chatMessage, $type);
  35. foreach ($this->chatRecipients($chatMessage, $recipientIds) as $user) {
  36. $settings = $this->settingsForUser($user->id);
  37. if (!$forceBrowserNotification && !$settings->isSectionEnabled('chat_settings')) {
  38. continue;
  39. }
  40. $channels = $settings->getChannelsForKey('chat_settings', $sourceKey);
  41. if (!$forceBrowserNotification && empty($channels)) {
  42. continue;
  43. }
  44. $notification = $this->createInAppNotification(
  45. $user,
  46. $type,
  47. UserNotification::EVENT_CHAT_MESSAGE,
  48. $title,
  49. $message,
  50. $messageHtml,
  51. $payload,
  52. );
  53. $this->dispatchDeliveryJobs($notification, [
  54. NotificationDeliveryLog::CHANNEL_BROWSER => $forceBrowserNotification || !empty($channels['browser']),
  55. NotificationDeliveryLog::CHANNEL_PUSH => !empty($channels['push']),
  56. NotificationDeliveryLog::CHANNEL_EMAIL => !empty($channels['email']),
  57. ]);
  58. }
  59. }
  60. public function notifyOrderCreated(Order $order, ?User $author = null): void
  61. {
  62. $statusName = $order->orderStatus?->name ?? (Order::STATUS_NAMES[$order->order_status_id] ?? '-');
  63. $authorSuffix = $author ? sprintf(' Изменил %s.', $author->name) : '';
  64. $authorSuffixHtml = $author ? sprintf(' Изменил %s.', e($author->name)) : '';
  65. $this->notifyOrderEvent(
  66. $order,
  67. UserNotification::EVENT_CREATED,
  68. 'Площадки',
  69. sprintf('Добавлена новая площадка %s.', $order->object_address) . $authorSuffix,
  70. sprintf(
  71. 'Добавлена новая площадка <a href="%s">%s</a>.',
  72. route('order.show', ['order' => $order->id, 'sync_year' => 1]),
  73. e($order->object_address)
  74. ) . $authorSuffixHtml,
  75. $statusName,
  76. );
  77. }
  78. public function notifyOrderStatusChanged(Order $order, ?User $author = null): void
  79. {
  80. $statusName = $order->orderStatus?->name ?? (Order::STATUS_NAMES[$order->order_status_id] ?? '-');
  81. $authorSuffix = $author ? sprintf(' Изменил %s.', $author->name) : '';
  82. $authorSuffixHtml = $author ? sprintf(' Изменил %s.', e($author->name)) : '';
  83. $this->notifyOrderEvent(
  84. $order,
  85. UserNotification::EVENT_STATUS_CHANGED,
  86. 'Площадки',
  87. sprintf('Статус площадки %s изменен на %s.', $order->object_address, $statusName) . $authorSuffix,
  88. sprintf(
  89. 'Статус площадки <a href="%s">%s</a> изменен на %s.',
  90. route('order.show', ['order' => $order->id, 'sync_year' => 1]),
  91. e($order->object_address),
  92. e($statusName)
  93. ) . $authorSuffixHtml,
  94. $statusName,
  95. );
  96. }
  97. public function notifyReclamationCreated(Reclamation $reclamation, ?User $author = null): void
  98. {
  99. $order = $reclamation->order;
  100. if (!$order) {
  101. return;
  102. }
  103. $authorSuffix = $author ? sprintf(' Изменил %s.', $author->name) : '';
  104. $authorSuffixHtml = $author ? sprintf(' Изменил %s.', e($author->name)) : '';
  105. $message = sprintf(
  106. 'Добавлена новая рекламация по адресу %s #%d.',
  107. $order->object_address,
  108. $reclamation->id,
  109. ) . $authorSuffix;
  110. $messageHtml = sprintf(
  111. 'Добавлена новая рекламация по адресу <a href="%s">%s</a> <a href="%s">#%d</a>.',
  112. route('order.show', ['order' => $order->id, 'sync_year' => 1]),
  113. e($order->object_address),
  114. route('reclamations.show', ['reclamation' => $reclamation->id]),
  115. $reclamation->id,
  116. ) . $authorSuffixHtml;
  117. $this->notifyReclamationEvent(
  118. $reclamation,
  119. UserNotification::EVENT_CREATED,
  120. 'Рекламации',
  121. $message,
  122. $messageHtml,
  123. (int)$reclamation->status_id,
  124. );
  125. }
  126. public function notifyReclamationStatusChanged(Reclamation $reclamation, ?User $author = null): void
  127. {
  128. $order = $reclamation->order;
  129. if (!$order) {
  130. return;
  131. }
  132. $statusName = $reclamation->status?->name ?? (Reclamation::STATUS_NAMES[$reclamation->status_id] ?? '-');
  133. $authorSuffix = $author ? sprintf(' Изменил %s.', $author->name) : '';
  134. $authorSuffixHtml = $author ? sprintf(' Изменил %s.', e($author->name)) : '';
  135. $message = sprintf(
  136. 'Статус рекламации %s #%d изменен на %s.',
  137. $order->object_address,
  138. $reclamation->id,
  139. $statusName,
  140. ) . $authorSuffix;
  141. $messageHtml = sprintf(
  142. 'Статус рекламации по адресу <a href="%s">%s</a> <a href="%s">#%d</a> изменен на %s.',
  143. route('order.show', ['order' => $order->id, 'sync_year' => 1]),
  144. e($order->object_address),
  145. route('reclamations.show', ['reclamation' => $reclamation->id]),
  146. $reclamation->id,
  147. e($statusName),
  148. ) . $authorSuffixHtml;
  149. $this->notifyReclamationEvent(
  150. $reclamation,
  151. UserNotification::EVENT_STATUS_CHANGED,
  152. 'Рекламации',
  153. $message,
  154. $messageHtml,
  155. (int)$reclamation->status_id,
  156. );
  157. }
  158. public function notifyScheduleAdded(Schedule $schedule, ?User $author = null): void
  159. {
  160. $sourceKey = $this->sourceToSettingKey((string)$schedule->source);
  161. if (!$sourceKey) {
  162. return;
  163. }
  164. $brigadierName = $schedule->brigadier?->name ?? '-';
  165. $date = DateHelper::getHumanDate((string)$schedule->installation_date, true);
  166. $authorSuffix = $author ? sprintf(' Изменил %s.', $author->name) : '';
  167. $authorSuffixHtml = $author ? sprintf(' Изменил %s.', e($author->name)) : '';
  168. if ((string)$schedule->source === 'Рекламации') {
  169. $reclamationId = $this->extractReclamationId((string)$schedule->address_code);
  170. $message = sprintf(
  171. 'Рекламация №%s по адресу %s добавлена в график на %s, Бригадир %s.',
  172. $reclamationId ?? '—',
  173. $schedule->object_address,
  174. $date,
  175. $brigadierName,
  176. ) . $authorSuffix;
  177. $reclamationLink = $reclamationId
  178. ? route('reclamations.show', ['reclamation' => $reclamationId])
  179. : route('schedule.index');
  180. $orderLink = $schedule->order_id
  181. ? route('order.show', ['order' => $schedule->order_id, 'sync_year' => 1])
  182. : null;
  183. $addressHtml = $orderLink
  184. ? sprintf('<a href="%s">%s</a>', $orderLink, e($schedule->object_address))
  185. : e($schedule->object_address);
  186. $messageHtml = sprintf(
  187. '<a href="%s">Рекламация №%s</a> по адресу %s добавлена в график на %s, Бригадир %s.',
  188. $reclamationLink,
  189. $reclamationId ?? '—',
  190. $addressHtml,
  191. e($date),
  192. e($brigadierName),
  193. ) . $authorSuffixHtml;
  194. } else {
  195. $message = sprintf(
  196. '%s добавлено в график монтажей на %s, Бригадир %s.',
  197. $schedule->object_address,
  198. $date,
  199. $brigadierName,
  200. ) . $authorSuffix;
  201. $orderLink = $schedule->order_id
  202. ? route('order.show', ['order' => $schedule->order_id, 'sync_year' => 1])
  203. : route('schedule.index');
  204. $messageHtml = sprintf(
  205. '<a href="%s">%s</a> добавлено в график монтажей на %s, Бригадир %s.',
  206. $orderLink,
  207. e($schedule->object_address),
  208. e($date),
  209. e($brigadierName),
  210. ) . $authorSuffixHtml;
  211. }
  212. $users = $this->scheduleRecipients($schedule);
  213. foreach ($users as $user) {
  214. $settings = $this->settingsForUser($user->id);
  215. if (!$settings->isSectionEnabled('schedule_settings')) {
  216. continue;
  217. }
  218. $channels = $settings->getChannelsForKey('schedule_settings', $sourceKey);
  219. if (empty($channels)) {
  220. continue;
  221. }
  222. $notification = $this->createInAppNotification(
  223. $user,
  224. UserNotification::TYPE_SCHEDULE,
  225. UserNotification::EVENT_SCHEDULE_ADDED,
  226. 'График монтажей',
  227. $message,
  228. $messageHtml,
  229. [
  230. 'schedule_id' => $schedule->id,
  231. 'source' => $schedule->source,
  232. ],
  233. );
  234. $this->dispatchDeliveryJobs($notification, [
  235. NotificationDeliveryLog::CHANNEL_BROWSER => !empty($channels['browser']),
  236. NotificationDeliveryLog::CHANNEL_PUSH => !empty($channels['push']),
  237. NotificationDeliveryLog::CHANNEL_EMAIL => !empty($channels['email']),
  238. ]);
  239. }
  240. }
  241. public function deliverChannel(int $userNotificationId, string $channel, int $attempt): void
  242. {
  243. $notification = UserNotification::query()->with('user')->find($userNotificationId);
  244. if (!$notification || !$notification->user) {
  245. return;
  246. }
  247. try {
  248. if ($channel === NotificationDeliveryLog::CHANNEL_BROWSER) {
  249. event(new SendPersistentNotificationEvent($notification->user_id, [
  250. 'id' => $notification->id,
  251. 'type' => $notification->type,
  252. 'title' => $notification->title,
  253. 'message' => $notification->message,
  254. 'message_html' => $notification->message_html,
  255. 'created_at' => $notification->created_at?->toDateTimeString(),
  256. ]));
  257. $this->createLog($notification, $channel, NotificationDeliveryLog::STATUS_SENT, $attempt, null);
  258. return;
  259. }
  260. if ($channel === NotificationDeliveryLog::CHANNEL_PUSH) {
  261. if (!$notification->user->token_fcm) {
  262. $this->createLog($notification, $channel, NotificationDeliveryLog::STATUS_SKIPPED, $attempt, 'Отсутствует token_fcm');
  263. return;
  264. }
  265. $notification->user->notify(new FireBaseNotification($notification->title, Str::limit(strip_tags($notification->message), 200)));
  266. $this->createLog($notification, $channel, NotificationDeliveryLog::STATUS_SENT, $attempt, null);
  267. return;
  268. }
  269. if ($channel === NotificationDeliveryLog::CHANNEL_EMAIL) {
  270. $email = $notification->user->notification_email;
  271. if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
  272. $this->createLog($notification, $channel, NotificationDeliveryLog::STATUS_SKIPPED, $attempt, 'Отсутствует валидный notification_email');
  273. return;
  274. }
  275. Mail::html($notification->message_html, function ($message) use ($email, $notification) {
  276. $message->to($email)
  277. ->subject($notification->title);
  278. });
  279. $this->createLog($notification, $channel, NotificationDeliveryLog::STATUS_SENT, $attempt, null);
  280. return;
  281. }
  282. $this->createLog($notification, $channel, NotificationDeliveryLog::STATUS_SKIPPED, $attempt, 'Неизвестный канал');
  283. } catch (\Throwable $exception) {
  284. $this->createLog($notification, $channel, NotificationDeliveryLog::STATUS_FAILED, $attempt, $exception->getMessage());
  285. throw $exception;
  286. }
  287. }
  288. public function markDeadLetter(int $userNotificationId, string $channel, int $attempt, ?string $error = null): void
  289. {
  290. $notification = UserNotification::query()->find($userNotificationId);
  291. if (!$notification) {
  292. return;
  293. }
  294. $this->createLog($notification, $channel, NotificationDeliveryLog::STATUS_DEAD_LETTER, $attempt, $error);
  295. }
  296. private function notifyOrderEvent(
  297. Order $order,
  298. string $event,
  299. string $title,
  300. string $message,
  301. string $messageHtml,
  302. string $statusName,
  303. ): void {
  304. $users = $this->orderRecipients($order);
  305. $statusId = (int) $order->order_status_id;
  306. foreach ($users as $user) {
  307. $settings = $this->settingsForUser($user->id);
  308. if (!$settings->isSectionEnabled('order_settings')) {
  309. continue;
  310. }
  311. $channels = $settings->getChannelsForKey('order_settings', $statusId);
  312. if (empty($channels)) {
  313. continue;
  314. }
  315. $notification = $this->createInAppNotification(
  316. $user,
  317. UserNotification::TYPE_PLATFORM,
  318. $event,
  319. $title,
  320. $message,
  321. $messageHtml,
  322. [
  323. 'order_id' => $order->id,
  324. 'status' => $statusName,
  325. ],
  326. );
  327. $this->dispatchDeliveryJobs($notification, [
  328. NotificationDeliveryLog::CHANNEL_BROWSER => !empty($channels['browser']),
  329. NotificationDeliveryLog::CHANNEL_PUSH => !empty($channels['push']),
  330. NotificationDeliveryLog::CHANNEL_EMAIL => !empty($channels['email']),
  331. ]);
  332. }
  333. }
  334. private function notifyReclamationEvent(
  335. Reclamation $reclamation,
  336. string $event,
  337. string $title,
  338. string $message,
  339. string $messageHtml,
  340. int $statusId,
  341. ): void {
  342. $users = $this->reclamationRecipients($reclamation);
  343. foreach ($users as $user) {
  344. $settings = $this->settingsForUser($user->id);
  345. if (!$settings->isSectionEnabled('reclamation_settings')) {
  346. continue;
  347. }
  348. $channels = $settings->getChannelsForKey('reclamation_settings', $statusId);
  349. if (empty($channels)) {
  350. continue;
  351. }
  352. $notification = $this->createInAppNotification(
  353. $user,
  354. UserNotification::TYPE_RECLAMATION,
  355. $event,
  356. $title,
  357. $message,
  358. $messageHtml,
  359. [
  360. 'reclamation_id' => $reclamation->id,
  361. 'status_id' => $statusId,
  362. ],
  363. );
  364. $this->dispatchDeliveryJobs($notification, [
  365. NotificationDeliveryLog::CHANNEL_BROWSER => !empty($channels['browser']),
  366. NotificationDeliveryLog::CHANNEL_PUSH => !empty($channels['push']),
  367. NotificationDeliveryLog::CHANNEL_EMAIL => !empty($channels['email']),
  368. ]);
  369. }
  370. }
  371. private function dispatchDeliveryJobs(UserNotification $notification, array $channels): void
  372. {
  373. foreach ($channels as $channel => $enabled) {
  374. if (!$enabled) {
  375. continue;
  376. }
  377. SendUserNotificationChannelJob::dispatch($notification->id, $channel);
  378. }
  379. }
  380. private function createInAppNotification(
  381. User $user,
  382. string $type,
  383. string $event,
  384. string $title,
  385. string $message,
  386. string $messageHtml,
  387. array $payload,
  388. ): UserNotification {
  389. $notification = UserNotification::query()->create([
  390. 'user_id' => $user->id,
  391. 'type' => $type,
  392. 'event' => $event,
  393. 'title' => $title,
  394. 'message' => $message,
  395. 'message_html' => $messageHtml,
  396. 'data' => $payload,
  397. ]);
  398. $this->createLog($notification, NotificationDeliveryLog::CHANNEL_IN_APP, NotificationDeliveryLog::STATUS_SENT, 1, null);
  399. return $notification;
  400. }
  401. private function createLog(
  402. UserNotification $notification,
  403. string $channel,
  404. string $status,
  405. int $attempt,
  406. ?string $error,
  407. ): void {
  408. NotificationDeliveryLog::query()->create([
  409. 'user_notification_id' => $notification->id,
  410. 'user_id' => $notification->user_id,
  411. 'channel' => $channel,
  412. 'status' => $status,
  413. 'attempt' => $attempt,
  414. 'message' => $notification->message,
  415. 'error' => $error,
  416. ]);
  417. }
  418. private function settingsForUser(int $userId): UserNotificationSetting
  419. {
  420. return UserNotificationSetting::query()->firstOrCreate(
  421. ['user_id' => $userId],
  422. UserNotificationSetting::defaultsForUser($userId),
  423. );
  424. }
  425. private function orderRecipients(Order $order): Collection
  426. {
  427. $query = User::query()
  428. ->withAnyPermission(['orders.scope.admin', 'orders.scope.warehouse_head']);
  429. if ($order->user_id) {
  430. $query->orWhere('id', $order->user_id);
  431. }
  432. return $query->distinct()->get();
  433. }
  434. private function reclamationRecipients(Reclamation $reclamation): Collection
  435. {
  436. $query = User::query()
  437. ->withAnyPermission(['reclamations.scope.admin', 'reclamations.scope.warehouse_head']);
  438. $managerId = $reclamation->user_id;
  439. if ($managerId) {
  440. $query->orWhere('id', $managerId);
  441. }
  442. return $query->distinct()->get();
  443. }
  444. private function scheduleRecipients(Schedule $schedule): Collection
  445. {
  446. $query = User::query()
  447. ->withAnyPermission(['schedule.scope.admin', 'orders.scope.warehouse_head', 'reclamations.scope.warehouse_head']);
  448. if ($schedule->brigadier_id) {
  449. $query->orWhere('id', $schedule->brigadier_id);
  450. }
  451. $managerId = null;
  452. if ((string)$schedule->source === 'Площадки' && $schedule->order_id) {
  453. $managerId = Order::query()
  454. ->withoutGlobalScope(\App\Models\Scopes\YearScope::class)
  455. ->where('id', $schedule->order_id)
  456. ->value('user_id');
  457. }
  458. if ((string)$schedule->source === 'Рекламации') {
  459. $reclamationId = $this->extractReclamationId((string)$schedule->address_code);
  460. if ($reclamationId) {
  461. $reclamation = Reclamation::query()
  462. ->withoutGlobalScope(\App\Models\Scopes\YearScope::class)
  463. ->with('order')
  464. ->find($reclamationId);
  465. $managerId = $reclamation?->order?->user_id ?: $reclamation?->user_id;
  466. }
  467. }
  468. if ($managerId) {
  469. $query->orWhere('id', $managerId);
  470. }
  471. return $query->distinct()->get();
  472. }
  473. private function extractReclamationId(string $addressCode): ?int
  474. {
  475. if (preg_match('/^РЕКЛ-(\d+)$/u', $addressCode, $matches)) {
  476. return (int)$matches[1];
  477. }
  478. return null;
  479. }
  480. private function sourceToSettingKey(string $source): ?string
  481. {
  482. return match ($source) {
  483. 'Площадки' => 'platform',
  484. 'Рекламации' => 'reclamation',
  485. default => null,
  486. };
  487. }
  488. private function chatRecipients(ChatMessage $chatMessage, array $recipientIds = []): Collection
  489. {
  490. $recipientIds = array_values(array_unique(array_map(static fn ($id) => (int) $id, $recipientIds)));
  491. $recipientIds = array_values(array_diff($recipientIds, [(int) $chatMessage->user_id]));
  492. if (!empty($recipientIds)) {
  493. return User::query()
  494. ->whereIn('id', $recipientIds)
  495. ->get();
  496. }
  497. if ($chatMessage->notification_type === ChatMessage::NOTIFICATION_USER) {
  498. if (!$chatMessage->target_user_id || (int) $chatMessage->target_user_id === (int) $chatMessage->user_id) {
  499. return new Collection();
  500. }
  501. return User::query()
  502. ->where('id', $chatMessage->target_user_id)
  503. ->get();
  504. }
  505. if (!in_array($chatMessage->notification_type, [
  506. ChatMessage::NOTIFICATION_ALL,
  507. ChatMessage::NOTIFICATION_RESPONSIBLES,
  508. ], true)) {
  509. return new Collection();
  510. }
  511. $recipientIds = [];
  512. if ($chatMessage->order) {
  513. $recipientIds = $chatMessage->notification_type === ChatMessage::NOTIFICATION_ALL
  514. ? $this->allChatRecipientIds()
  515. : $this->chatResponsibleRecipientIdsForOrder($chatMessage->order);
  516. }
  517. if ($chatMessage->reclamation) {
  518. $recipientIds = $chatMessage->notification_type === ChatMessage::NOTIFICATION_ALL
  519. ? $this->allChatRecipientIds()
  520. : $this->chatResponsibleRecipientIdsForReclamation($chatMessage->reclamation);
  521. }
  522. $recipientIds = array_values(array_unique(array_filter($recipientIds)));
  523. $recipientIds = array_values(array_diff($recipientIds, [(int) $chatMessage->user_id]));
  524. if (empty($recipientIds)) {
  525. return new Collection();
  526. }
  527. return User::query()
  528. ->whereIn('id', $recipientIds)
  529. ->get();
  530. }
  531. private function allChatRecipientIds(): array
  532. {
  533. return User::query()
  534. ->pluck('id')
  535. ->map(static fn ($id) => (int) $id)
  536. ->all();
  537. }
  538. private function chatResponsibleRecipientIdsForOrder(Order $order): array
  539. {
  540. $adminIds = User::query()
  541. ->withPermission('orders.scope.admin')
  542. ->pluck('id')
  543. ->map(static fn ($id) => (int) $id)
  544. ->all();
  545. return array_merge($adminIds, [
  546. $order->user_id ? (int) $order->user_id : null,
  547. $order->brigadier_id ? (int) $order->brigadier_id : null,
  548. ]);
  549. }
  550. private function chatResponsibleRecipientIdsForReclamation(Reclamation $reclamation): array
  551. {
  552. $adminIds = User::query()
  553. ->withPermission('reclamations.scope.admin')
  554. ->pluck('id')
  555. ->map(static fn ($id) => (int) $id)
  556. ->all();
  557. return array_merge($adminIds, [
  558. $reclamation->user_id ? (int) $reclamation->user_id : null,
  559. $reclamation->brigadier_id ? (int) $reclamation->brigadier_id : null,
  560. ]);
  561. }
  562. private function buildChatNotificationContent(ChatMessage $chatMessage, string $type): array
  563. {
  564. $senderName = $chatMessage->user?->name ?? 'Пользователь';
  565. $text = trim((string) $chatMessage->message);
  566. $text = $text !== '' ? Str::limit($text, 200) : 'Вложение';
  567. if ($type === UserNotification::TYPE_PLATFORM) {
  568. $order = $chatMessage->order;
  569. $address = $order?->object_address ?? '-';
  570. $orderUrl = $order ? route('order.show', ['order' => $order->id, 'sync_year' => 1]) : route('order.index');
  571. $message = sprintf('Новое сообщение в чате площадки %s от %s: %s', $address, $senderName, $text);
  572. $messageHtml = sprintf(
  573. 'Новое сообщение в <a href="%s">чате площадки %s</a> от %s: %s',
  574. $orderUrl,
  575. e($address),
  576. e($senderName),
  577. e($text)
  578. );
  579. return [$message, $messageHtml, [
  580. 'chat_message_id' => $chatMessage->id,
  581. 'order_id' => $order?->id,
  582. ]];
  583. }
  584. $reclamation = $chatMessage->reclamation;
  585. $address = $reclamation?->order?->object_address ?? '-';
  586. $reclamationUrl = $reclamation
  587. ? route('reclamations.show', ['reclamation' => $reclamation->id, 'sync_year' => 1])
  588. : route('reclamations.index');
  589. $reclamationNumber = $reclamation?->id ? ('#' . $reclamation->id) : '#-';
  590. $message = sprintf('Новое сообщение в чате рекламации %s по адресу %s от %s: %s', $reclamationNumber, $address, $senderName, $text);
  591. $messageHtml = sprintf(
  592. 'Новое сообщение в <a href="%s">чате рекламации %s</a> по адресу %s от %s: %s',
  593. $reclamationUrl,
  594. e($reclamationNumber),
  595. e($address),
  596. e($senderName),
  597. e($text)
  598. );
  599. return [$message, $messageHtml, [
  600. 'chat_message_id' => $chatMessage->id,
  601. 'reclamation_id' => $reclamation?->id,
  602. ]];
  603. }
  604. }