Просмотр исходного кода

Add brigadier chat notifications and admin deletion

Alexander Musikhin 3 недель назад
Родитель
Сommit
457c96f845

+ 96 - 3
app/Http/Controllers/ChatMessageController.php

@@ -11,6 +11,7 @@ use App\Services\FileService;
 use App\Services\NotificationService;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;
 use Throwable;
 
 class ChatMessageController extends Controller
@@ -23,6 +24,14 @@ class ChatMessageController extends Controller
     ): RedirectResponse {
         $this->ensureCanViewOrder($order);
 
+        if ($this->isDeleteRequest($request)) {
+            $chatMessage = $this->resolveMessageForDeletion($request);
+            $this->ensureAdminCanDeleteMessage($chatMessage, $order, null);
+            $this->deleteMessage($chatMessage);
+
+            return redirect()->back()->with(['success' => 'Сообщение удалено.']);
+        }
+
         return $this->storeMessage(
             $request,
             $fileService,
@@ -41,6 +50,14 @@ class ChatMessageController extends Controller
     ): RedirectResponse {
         $this->ensureCanViewReclamation($reclamation);
 
+        if ($this->isDeleteRequest($request)) {
+            $chatMessage = $this->resolveMessageForDeletion($request);
+            $this->ensureAdminCanDeleteMessage($chatMessage, null, $reclamation);
+            $this->deleteMessage($chatMessage);
+
+            return redirect()->back()->with(['success' => 'Сообщение удалено.']);
+        }
+
         return $this->storeMessage(
             $request,
             $fileService,
@@ -51,6 +68,22 @@ class ChatMessageController extends Controller
         );
     }
 
+    public function destroyForOrder(Order $order, ChatMessage $chatMessage): RedirectResponse
+    {
+        $this->ensureAdminCanDeleteMessage($chatMessage, $order, null);
+        $this->deleteMessage($chatMessage);
+
+        return redirect()->back()->with(['success' => 'Сообщение удалено.']);
+    }
+
+    public function destroyForReclamation(Reclamation $reclamation, ChatMessage $chatMessage): RedirectResponse
+    {
+        $this->ensureAdminCanDeleteMessage($chatMessage, null, $reclamation);
+        $this->deleteMessage($chatMessage);
+
+        return redirect()->back()->with(['success' => 'Сообщение удалено.']);
+    }
+
     private function storeMessage(
         Request $request,
         FileService $fileService,
@@ -60,6 +93,7 @@ class ChatMessageController extends Controller
         string $filePath,
     ): RedirectResponse {
         $isPrivileged = hasRole(Role::ADMIN . ',' . Role::MANAGER);
+        $isBrigadier = hasRole(Role::BRIGADIER);
 
         $validated = $request->validate([
             'message' => 'nullable|string',
@@ -72,7 +106,9 @@ class ChatMessageController extends Controller
         ]);
 
         $notificationType = $validated['notification_type'] ?? ChatMessage::NOTIFICATION_NONE;
-        if (!$isPrivileged) {
+        if ($isBrigadier) {
+            $notificationType = ChatMessage::NOTIFICATION_RESPONSIBLES;
+        } elseif (!$isPrivileged) {
             $notificationType = ChatMessage::NOTIFICATION_NONE;
         }
 
@@ -85,7 +121,9 @@ class ChatMessageController extends Controller
 
         $recipientIds = [];
         $targetUserId = null;
-        if ($notificationType === ChatMessage::NOTIFICATION_USER) {
+        if ($isBrigadier) {
+            $recipientIds = $this->chatBrigadierRecipientIds($order, $reclamation, (int) auth()->id());
+        } elseif ($notificationType === ChatMessage::NOTIFICATION_USER) {
             $targetUserId = (int) ($validated['target_user_id'] ?? 0);
             if ($targetUserId < 1) {
                 return redirect()->back()->with(['danger' => 'Нужно выбрать пользователя для уведомления.']);
@@ -148,7 +186,7 @@ class ChatMessageController extends Controller
                     'reclamation.order',
                     'reclamation.user',
                     'reclamation.brigadier',
-                ]), $recipientIds);
+                ]), $recipientIds, $isBrigadier);
             }
         } catch (Throwable $exception) {
             report($exception);
@@ -159,6 +197,45 @@ class ChatMessageController extends Controller
         return redirect()->back()->with(['success' => 'Сообщение отправлено.']);
     }
 
+    private function ensureAdminCanDeleteMessage(
+        ChatMessage $chatMessage,
+        ?Order $order,
+        ?Reclamation $reclamation,
+    ): void {
+        abort_unless(hasRole(Role::ADMIN), 403);
+
+        if ($order && (int) $chatMessage->order_id !== (int) $order->id) {
+            abort(404);
+        }
+
+        if ($reclamation && (int) $chatMessage->reclamation_id !== (int) $reclamation->id) {
+            abort(404);
+        }
+    }
+
+    private function deleteMessage(ChatMessage $chatMessage): void
+    {
+        DB::transaction(function () use ($chatMessage): void {
+            $chatMessage->notifiedUsers()->detach();
+            $chatMessage->files()->detach();
+            $chatMessage->delete();
+        });
+    }
+
+    private function isDeleteRequest(Request $request): bool
+    {
+        return $request->boolean('delete_message');
+    }
+
+    private function resolveMessageForDeletion(Request $request): ChatMessage
+    {
+        $validated = $request->validate([
+            'chat_message_id' => 'required|integer|exists:chat_messages,id',
+        ]);
+
+        return ChatMessage::query()->findOrFail((int) $validated['chat_message_id']);
+    }
+
     private function ensureCanViewOrder(Order $order): void
     {
         if (hasRole(Role::BRIGADIER)) {
@@ -226,4 +303,20 @@ class ChatMessageController extends Controller
 
         return $adminIds;
     }
+
+    private function chatBrigadierRecipientIds(?Order $order, ?Reclamation $reclamation, int $senderId): array
+    {
+        $adminIds = User::query()
+            ->where('role', Role::ADMIN)
+            ->pluck('id')
+            ->map(static fn ($id) => (int) $id)
+            ->all();
+
+        $managerId = $order?->user_id ?: $reclamation?->user_id;
+
+        return array_values(array_unique(array_diff(array_filter([
+            ...$adminIds,
+            $managerId ? (int) $managerId : null,
+        ]), [$senderId])));
+    }
 }

+ 4 - 4
app/Services/NotificationService.php

@@ -21,7 +21,7 @@ use Illuminate\Support\Str;
 
 class NotificationService
 {
-    public function notifyChatMessage(ChatMessage $chatMessage, array $recipientIds = []): void
+    public function notifyChatMessage(ChatMessage $chatMessage, array $recipientIds = [], bool $forceBrowserNotification = false): void
     {
         $chatMessage->loadMissing([
             'user',
@@ -41,12 +41,12 @@ class NotificationService
 
         foreach ($this->chatRecipients($chatMessage, $recipientIds) as $user) {
             $settings = $this->settingsForUser($user->id);
-            if (!$settings->isSectionEnabled('chat_settings')) {
+            if (!$forceBrowserNotification && !$settings->isSectionEnabled('chat_settings')) {
                 continue;
             }
 
             $channels = $settings->getChannelsForKey('chat_settings', $sourceKey);
-            if (empty($channels)) {
+            if (!$forceBrowserNotification && empty($channels)) {
                 continue;
             }
 
@@ -61,7 +61,7 @@ class NotificationService
             );
 
             $this->dispatchDeliveryJobs($notification, [
-                NotificationDeliveryLog::CHANNEL_BROWSER => !empty($channels['browser']),
+                NotificationDeliveryLog::CHANNEL_BROWSER => $forceBrowserNotification || !empty($channels['browser']),
                 NotificationDeliveryLog::CHANNEL_PUSH => !empty($channels['push']),
                 NotificationDeliveryLog::CHANNEL_EMAIL => !empty($channels['email']),
             ]);

+ 14 - 1
resources/views/partials/chat.blade.php

@@ -10,6 +10,7 @@
     $title = $title ?? 'Чат';
     $submitLabel = $submitLabel ?? 'Отправить';
     $canSendNotifications = hasRole('admin,manager');
+    $canDeleteMessages = hasRole('admin') && !empty($action);
     $notificationValue = old('notification_type', \App\Models\ChatMessage::NOTIFICATION_NONE);
     $notificationEnabled = $canSendNotifications && $notificationValue !== \App\Models\ChatMessage::NOTIFICATION_NONE;
     $showAllUsers = $notificationValue === \App\Models\ChatMessage::NOTIFICATION_ALL;
@@ -50,7 +51,19 @@
                                     <span class="badge text-bg-light border">Уведомления: {{ $message->notifiedUsers->pluck('name')->join(', ') ?: 'получатели' }}</span>
                                 @endif
                             </div>
-                            <small class="text-muted">{{ $message->created_at?->format('d.m.Y H:i') }}</small>
+                            <div class="d-flex align-items-center gap-2">
+                                <small class="text-muted">{{ $message->created_at?->format('d.m.Y H:i') }}</small>
+                                @if($canDeleteMessages)
+                                    <form action="{{ $action }}" method="post" class="d-inline">
+                                        @csrf
+                                        <input type="hidden" name="delete_message" value="1">
+                                        <input type="hidden" name="chat_message_id" value="{{ $message->id }}">
+                                        <i class="bi bi-x-circle-fill fs-6 text-danger cursor-pointer"
+                                           onclick="customConfirm('Удалить сообщение?', function () { this.closest('form').submit(); }.bind(this), 'Подтверждение удаления')"
+                                           title="Удалить"></i>
+                                    </form>
+                                @endif
+                            </div>
                         </div>
 
                         @if(!empty($message->message))

+ 6 - 0
routes/web.php

@@ -215,6 +215,9 @@ Route::middleware('auth:web')->group(function () {
     Route::get('order/show/{order}', [OrderController::class, 'show'])->name('order.show');
     Route::post('order/{order}/upload-photo', [OrderController::class, 'uploadPhoto'])->name('order.upload-photo');
     Route::post('order/{order}/chat-messages', [ChatMessageController::class, 'storeForOrder'])->name('order.chat-messages.store');
+    Route::delete('order/{order}/chat-messages/{chatMessage}', [ChatMessageController::class, 'destroyForOrder'])
+        ->name('order.chat-messages.delete')
+        ->middleware('role:' . Role::ADMIN);
     Route::get('order/generate-photos-pack/{order}', [OrderController::class, 'generatePhotosPack'])->name('order.generate-photos-pack');
     Route::get('order/download-tech-docs/{order}', [OrderController::class, 'downloadTechDocs'])->name('order.download-tech-docs');
 
@@ -276,6 +279,9 @@ Route::middleware('auth:web')->group(function () {
     Route::get('reclamations', [ReclamationController::class, 'index'])->name('reclamations.index');
     Route::get('reclamations/show/{reclamation}', [ReclamationController::class, 'show'])->name('reclamations.show');
     Route::post('reclamations/{reclamation}/chat-messages', [ChatMessageController::class, 'storeForReclamation'])->name('reclamations.chat-messages.store');
+    Route::delete('reclamations/{reclamation}/chat-messages/{chatMessage}', [ChatMessageController::class, 'destroyForReclamation'])
+        ->name('reclamations.chat-messages.delete')
+        ->middleware('role:' . Role::ADMIN);
 
 
     Route::post('reclamations/{reclamation}/upload-photo-before', [ReclamationController::class, 'uploadPhotoBefore'])->name('reclamations.upload-photo-before');