Jelajahi Sumber

feat(support): adiciona ticket para franchisee

ebagabee 3 minggu lalu
induk
melakukan
cce3c8a530

+ 88 - 6
app/Http/Controllers/SupportTicketController.php

@@ -15,19 +15,70 @@ public function __construct(
 
     public function index(): JsonResponse
     {
-        $items = $this->service->getAll();
-        return $this->successResponse(payload: SupportTicketResource::collection($items));
+        $user = auth()->user();
+        $query = \App\Models\SupportTicket::query()
+            ->with(['applicantUnit', 'targetUnit'])
+            ->orderBy('created_at', 'desc');
+
+        if (!$this->isMatriz($user)) {
+            $unitId = $user->units()->first()?->id;
+            $query->visibleToUnit($unitId);
+        }
+
+        return $this->successResponse(
+            payload: SupportTicketResource::collection($query->get())
+        );
     }
 
     public function store(SupportTicketRequest $request): JsonResponse
     {
         $data = $request->validated();
-        $data['applicant_user_id']   = auth()->id();
-        $data['responsable_user_id'] = auth()->id();
-        $data['status']              = 'in_progress';
+        $user = auth()->user();
+        $isMatriz = $this->isMatriz($user);
+
+        $data['origin'] = $isMatriz ? 'matriz' : 'unit';
+        $data['applicant_user_id'] = $user->id;
+        $data['responsable_user_id'] = $user->id;
+        $data['applicant_unit_id'] = $isMatriz ? null : $user->units()->first()?->id;
+        $data['status'] = 'in_progress';
+
+        // Broadcast: Matriz para todas as unidades
+        if ($isMatriz && ($data['scope'] ?? null) === 'all') {
+            $batchId = (string) \Illuminate\Support\Str::uuid();
+            $unitIds = \App\Models\Unit::query()->pluck('id');
+            $created = [];
+            foreach ($unitIds as $unitId) {
+                $created[] = $this->service->create(array_merge($data, [
+                    'target_unit_id' => $unitId,
+                    'batch_id' => $batchId,
+                ]));
+            }
+            return $this->successResponse(
+                payload: SupportTicketResource::collection($created),
+                message: __('messages.created'),
+                code: 201
+            );
+        }
+
+        // Resolução de target_unit_id por cenário
+        if ($isMatriz) {
+            if (($data['scope'] ?? null) === 'internal') {
+                $data['target_unit_id'] = null;
+            }
+            // scope='specific' → target_unit_id já veio do request
+        } else {
+            // Franchisee
+            $data['target_unit_id'] = (($data['scope'] ?? null) === 'internal')
+                ? $user->units()->first()?->id
+                : null; // 'specific' do Franchisee = "para Matriz"
+        }
 
         $item = $this->service->create($data);
-        return $this->successResponse(payload: new SupportTicketResource($item), message: __('messages.created'), code: 201);
+        return $this->successResponse(
+            payload: new SupportTicketResource($item),
+            message: __('messages.created'),
+            code: 201
+        );
     }
 
     public function show(int $id): JsonResponse
@@ -38,13 +89,44 @@ public function show(int $id): JsonResponse
 
     public function update(SupportTicketRequest $request, int $id): JsonResponse
     {
+        $user = auth()->user();
+        $item = $this->service->findById($id);
+
+        if (!$this->canManage($user, $item)) {
+            return $this->errorResponse(message: __('messages.unauthorized'), code: 403);
+        }
+
         $item = $this->service->update($id, $request->validated());
         return $this->successResponse(payload: new SupportTicketResource($item), message: __('messages.updated'));
     }
 
     public function destroy(int $id): JsonResponse
     {
+        $user = auth()->user();
+        $item = $this->service->findById($id);
+
+        if (!$this->canManage($user, $item)) {
+            return $this->errorResponse(message: __('messages.unauthorized'), code: 403);
+        }
+
         $this->service->delete($id);
         return $this->successResponse(message: __('messages.deleted'), code: 204);
     }
+
+    private function isMatriz(\App\Models\User $user): bool
+    {
+        return $user->user_type === 'ADMIN';
+    }
+
+    private function canManage(\App\Models\User $user, \App\Models\SupportTicket $ticket): bool
+    {
+        if ($this->isMatriz($user)) {
+            return true;
+        }
+
+        // Franchisee só pode gerenciar tickets internos que ela mesma criou
+        return $ticket->origin === 'unit'
+            && $ticket->scope === 'internal'
+            && $ticket->applicant_unit_id === $user->units()->first()?->id;
+    }
 }

+ 1 - 1
app/Http/Requests/SupportTicketRequest.php

@@ -14,7 +14,7 @@ public function rules(): array
             'title'       => ($isUpdate ? 'sometimes' : 'required') . '|string|max:255',
             'severity'    => ($isUpdate ? 'sometimes' : 'required') . '|string|in:alta,normal,baixa',
             'scope'       => ($isUpdate ? 'sometimes' : 'required') . '|string|in:all,internal,specific',
-            'unit_id'     => 'nullable|integer|exists:units,id',
+            'target_unit_id' => 'nullable|integer|exists:units,id',
             'sector'      => 'nullable|string|max:255',
             'description' => 'nullable|string',
             'status'      => 'sometimes|string|in:in_progress,resolved,unresolved',

+ 8 - 4
app/Http/Resources/SupportTicketResource.php

@@ -2,7 +2,6 @@
 
 namespace App\Http\Resources;
 
-use Carbon\Carbon;
 use Illuminate\Http\Request;
 use Illuminate\Http\Resources\Json\JsonResource;
 use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
@@ -18,13 +17,18 @@ public function toArray(Request $request): array
             'description'         => $this->description,
             'severity'            => $this->severity,
             'scope'               => $this->scope,
-            'unit_id'             => $this->unit_id,
             'sector'              => $this->sector,
             'status'              => $this->status,
+            'origin'              => $this->origin,
             'applicant_user_id'   => $this->applicant_user_id,
             'responsable_user_id' => $this->responsable_user_id,
-            'created_at'          => Carbon::parse($this->created_at)->format('d/m/Y H:i'),
-            'updated_at'          => Carbon::parse($this->updated_at)->format('d/m/Y H:i'),
+            'applicant_unit_id'   => $this->applicant_unit_id,
+            'target_unit_id'      => $this->target_unit_id,
+            'applicant_unit_name' => $this->whenLoaded('applicantUnit', fn() => $this->applicantUnit?->fantasy_name),
+            'target_unit_name'    => $this->whenLoaded('targetUnit', fn() => $this->targetUnit?->fantasy_name),
+            'batch_id'            => $this->batch_id,
+            'created_at'          => $this->created_at?->format('d/m/Y H:i'),
+            'updated_at'          => $this->updated_at?->format('d/m/Y H:i'),
         ];
     }
 

+ 26 - 2
app/Models/SupportTicket.php

@@ -53,10 +53,34 @@ class SupportTicket extends Model
 
     // Relationships
 
-    // Business Logic Methods
+    public function applicantUnit()
+    {
+        return $this->belongsTo(\App\Models\Unit::class, 'applicant_unit_id');
+    }
 
-    // Custom Finders
+    public function targetUnit()
+    {
+        return $this->belongsTo(\App\Models\Unit::class, 'target_unit_id');
+    }
+
+    public function applicantUser()
+    {
+        return $this->belongsTo(\App\Models\User::class, 'applicant_user_id');
+    }
+
+    public function responsableUser()
+    {
+        return $this->belongsTo(\App\Models\User::class, 'responsable_user_id');
+    }
 
     // Query Scopes
 
+    public function scopeVisibleToUnit($query, int $unitId)
+    {
+        return $query->where(function ($q) use ($unitId) {
+            $q->where('target_unit_id', $unitId)
+              ->orWhere('applicant_unit_id', $unitId);
+        });
+    }
+
 }

+ 34 - 0
database/migrations/2026_05_19_120000_alter_support_tickets_add_origin_targets_batch.php

@@ -0,0 +1,34 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Support\Facades\DB;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('support_tickets', function (Blueprint $table) {
+            $table->enum('origin', ['matriz', 'unit'])->default('matriz')->after('id');
+            $table->unsignedBigInteger('applicant_unit_id')->nullable()->after('origin');
+            $table->unsignedBigInteger('target_unit_id')->nullable()->after('applicant_unit_id');
+            $table->uuid('batch_id')->nullable()->index()->after('target_unit_id');
+
+            $table->foreign('applicant_unit_id')->references('id')->on('units')->nullOnDelete();
+            $table->foreign('target_unit_id')->references('id')->on('units')->nullOnDelete();
+        });
+
+        // Backfill: tickets legacy assumem origem matriz e target = unit_id antigo
+        DB::statement("UPDATE support_tickets SET origin = 'matriz', target_unit_id = unit_id WHERE origin IS NULL OR target_unit_id IS NULL");
+    }
+
+    public function down(): void
+    {
+        Schema::table('support_tickets', function (Blueprint $table) {
+            $table->dropForeign(['applicant_unit_id']);
+            $table->dropForeign(['target_unit_id']);
+            $table->dropColumn(['origin', 'applicant_unit_id', 'target_unit_id', 'batch_id']);
+        });
+    }
+};