ChatMessageController.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. <?php
  2. namespace App\Http\Controllers;
  3. use App\Models\ChatMessage;
  4. use App\Models\Order;
  5. use App\Models\Reclamation;
  6. use App\Models\Role;
  7. use App\Models\User;
  8. use App\Services\FileService;
  9. use App\Services\NotificationService;
  10. use Illuminate\Http\RedirectResponse;
  11. use Illuminate\Http\Request;
  12. use Throwable;
  13. class ChatMessageController extends Controller
  14. {
  15. public function storeForOrder(
  16. Request $request,
  17. Order $order,
  18. FileService $fileService,
  19. NotificationService $notificationService,
  20. ): RedirectResponse {
  21. $this->ensureCanViewOrder($order);
  22. return $this->storeMessage(
  23. $request,
  24. $fileService,
  25. $notificationService,
  26. $order,
  27. null,
  28. 'chat/orders/' . $order->id,
  29. );
  30. }
  31. public function storeForReclamation(
  32. Request $request,
  33. Reclamation $reclamation,
  34. FileService $fileService,
  35. NotificationService $notificationService,
  36. ): RedirectResponse {
  37. $this->ensureCanViewReclamation($reclamation);
  38. return $this->storeMessage(
  39. $request,
  40. $fileService,
  41. $notificationService,
  42. null,
  43. $reclamation,
  44. 'chat/reclamations/' . $reclamation->id,
  45. );
  46. }
  47. private function storeMessage(
  48. Request $request,
  49. FileService $fileService,
  50. NotificationService $notificationService,
  51. ?Order $order,
  52. ?Reclamation $reclamation,
  53. string $filePath,
  54. ): RedirectResponse {
  55. $isPrivileged = hasRole(Role::ADMIN . ',' . Role::MANAGER);
  56. $validated = $request->validate([
  57. 'message' => 'nullable|string',
  58. 'notification_type' => 'nullable|string|in:none,responsibles,all,user',
  59. 'target_user_id' => 'nullable|integer|exists:users,id,deleted_at,NULL',
  60. 'target_user_ids' => 'nullable|array',
  61. 'target_user_ids.*' => 'integer|exists:users,id,deleted_at,NULL',
  62. 'attachments' => 'nullable|array|max:5',
  63. 'attachments.*' => 'file|max:10240',
  64. ]);
  65. $notificationType = $validated['notification_type'] ?? ChatMessage::NOTIFICATION_NONE;
  66. if (!$isPrivileged) {
  67. $notificationType = ChatMessage::NOTIFICATION_NONE;
  68. }
  69. $messageText = trim((string) ($validated['message'] ?? ''));
  70. $attachments = $request->file('attachments', []);
  71. if ($messageText === '' && empty($attachments)) {
  72. return redirect()->back()->with(['danger' => 'Нужно указать сообщение или добавить файл.']);
  73. }
  74. $recipientIds = [];
  75. $targetUserId = null;
  76. if ($notificationType === ChatMessage::NOTIFICATION_USER) {
  77. $targetUserId = (int) ($validated['target_user_id'] ?? 0);
  78. if ($targetUserId < 1) {
  79. return redirect()->back()->with(['danger' => 'Нужно выбрать пользователя для уведомления.']);
  80. }
  81. $recipientIds = [$targetUserId];
  82. }
  83. if (in_array($notificationType, [
  84. ChatMessage::NOTIFICATION_RESPONSIBLES,
  85. ChatMessage::NOTIFICATION_ALL,
  86. ], true)) {
  87. $recipientIds = $this->resolveTargetUserIds(
  88. $notificationType,
  89. $validated['target_user_ids'] ?? [],
  90. $order,
  91. $reclamation,
  92. (int) auth()->id(),
  93. );
  94. if (empty($recipientIds)) {
  95. return redirect()->back()->with(['danger' => 'Нужно выбрать хотя бы одного получателя уведомления.']);
  96. }
  97. }
  98. if (!in_array($notificationType, [ChatMessage::NOTIFICATION_USER], true)) {
  99. $targetUserId = null;
  100. }
  101. try {
  102. $chatMessage = ChatMessage::query()->create([
  103. 'order_id' => $order?->id,
  104. 'reclamation_id' => $reclamation?->id,
  105. 'user_id' => (int) auth()->id(),
  106. 'target_user_id' => $targetUserId,
  107. 'notification_type' => $notificationType,
  108. 'message' => $messageText !== '' ? $messageText : null,
  109. ]);
  110. $files = [];
  111. foreach ($attachments as $attachment) {
  112. $files[] = $fileService->saveUploadedFile($filePath, $attachment);
  113. }
  114. if (!empty($files)) {
  115. $chatMessage->files()->syncWithoutDetaching(collect($files)->pluck('id')->all());
  116. }
  117. if ($notificationType !== ChatMessage::NOTIFICATION_NONE) {
  118. $notificationService->notifyChatMessage($chatMessage->fresh([
  119. 'user',
  120. 'targetUser',
  121. 'files',
  122. 'order.user',
  123. 'order.brigadier',
  124. 'reclamation.order',
  125. 'reclamation.user',
  126. 'reclamation.brigadier',
  127. ]), $recipientIds);
  128. }
  129. } catch (Throwable $exception) {
  130. report($exception);
  131. return redirect()->back()->with(['error' => 'Не удалось отправить сообщение в чат.']);
  132. }
  133. return redirect()->back()->with(['success' => 'Сообщение отправлено.']);
  134. }
  135. private function ensureCanViewOrder(Order $order): void
  136. {
  137. if (hasRole(Role::BRIGADIER)) {
  138. $canView = (int) $order->brigadier_id === (int) auth()->id()
  139. && in_array((int) $order->order_status_id, Order::visibleStatusIdsForBrigadier(), true);
  140. if (!$canView) {
  141. abort(403);
  142. }
  143. }
  144. }
  145. private function ensureCanViewReclamation(Reclamation $reclamation): void
  146. {
  147. if (hasRole(Role::BRIGADIER)) {
  148. $canView = (int) $reclamation->brigadier_id === (int) auth()->id()
  149. && in_array((int) $reclamation->status_id, Reclamation::visibleStatusIdsForBrigadier(), true);
  150. if (!$canView) {
  151. abort(403);
  152. }
  153. }
  154. }
  155. private function resolveTargetUserIds(
  156. string $notificationType,
  157. array $targetUserIds,
  158. ?Order $order,
  159. ?Reclamation $reclamation,
  160. int $senderId,
  161. ): array {
  162. $selectedIds = array_values(array_unique(array_map(static fn ($id) => (int) $id, $targetUserIds)));
  163. $allowedIds = match ($notificationType) {
  164. ChatMessage::NOTIFICATION_RESPONSIBLES => $this->chatResponsibleRecipientIds($order, $reclamation),
  165. ChatMessage::NOTIFICATION_ALL => User::query()->pluck('id')->map(static fn ($id) => (int) $id)->all(),
  166. default => [],
  167. };
  168. $selectedIds = array_values(array_intersect($selectedIds, $allowedIds));
  169. return array_values(array_diff($selectedIds, [$senderId]));
  170. }
  171. private function chatResponsibleRecipientIds(?Order $order, ?Reclamation $reclamation): array
  172. {
  173. $adminIds = User::query()
  174. ->where('role', Role::ADMIN)
  175. ->pluck('id')
  176. ->map(static fn ($id) => (int) $id)
  177. ->all();
  178. if ($order) {
  179. return array_values(array_unique(array_filter(array_merge($adminIds, [
  180. $order->user_id ? (int) $order->user_id : null,
  181. $order->brigadier_id ? (int) $order->brigadier_id : null,
  182. ]))));
  183. }
  184. if ($reclamation) {
  185. return array_values(array_unique(array_filter(array_merge($adminIds, [
  186. $reclamation->user_id ? (int) $reclamation->user_id : null,
  187. $reclamation->brigadier_id ? (int) $reclamation->brigadier_id : null,
  188. ]))));
  189. }
  190. return $adminIds;
  191. }
  192. }