NotificationService.php 26 KB

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