Explorar el Código

changed visibility scopes for brigadier

Alexander Musikhin hace 1 mes
padre
commit
305f5a9f09

+ 13 - 2
app/Http/Controllers/OrderController.php

@@ -104,7 +104,8 @@ class OrderController extends Controller
         $this->setSortAndOrderBy($model, $request);
 
         if(hasRole('brigadier')) {
-            $q->where('brigadier_id', auth()->id());
+            $q->where('brigadier_id', auth()->id())
+                ->whereIn('order_status_id', Order::visibleStatusIdsForBrigadier());
         }
 
         if(hasRole(Role::WAREHOUSE_HEAD)) {
@@ -272,6 +273,15 @@ class OrderController extends Controller
             }
         }
 
+        if (hasRole('brigadier') && $this->data['order']) {
+            $canView = (int)$this->data['order']->brigadier_id === (int)auth()->id()
+                && in_array((int)$this->data['order']->order_status_id, Order::visibleStatusIdsForBrigadier(), true);
+
+            if (!$canView) {
+                abort(403);
+            }
+        }
+
         $this->data['previous_url'] = $this->resolvePreviousUrl(
             $request,
             'previous_url_orders',
@@ -667,7 +677,8 @@ class OrderController extends Controller
             $this->setSortAndOrderBy($model, $filterRequest);
 
             if (hasRole('brigadier')) {
-                $q->where('brigadier_id', auth()->id());
+                $q->where('brigadier_id', auth()->id())
+                    ->whereIn('order_status_id', Order::visibleStatusIdsForBrigadier());
             }
 
             if (hasRole(Role::WAREHOUSE_HEAD)) {

+ 9 - 3
app/Http/Controllers/ReclamationController.php

@@ -80,7 +80,8 @@ class ReclamationController extends Controller
         $this->setSortAndOrderBy($model, $request);
 
         if (hasRole(Role::BRIGADIER)) {
-            $q->where('brigadier_id', auth()->id());
+            $q->where('brigadier_id', auth()->id())
+                ->whereIn('status_id', Reclamation::visibleStatusIdsForBrigadier());
         }
 
         $this->applyStableSorting($q);
@@ -557,8 +558,13 @@ class ReclamationController extends Controller
 
     private function ensureCanViewReclamation(Reclamation $reclamation): void
     {
-        if (hasRole(Role::BRIGADIER) && (int)$reclamation->brigadier_id !== (int)auth()->id()) {
-            abort(403);
+        if (hasRole(Role::BRIGADIER)) {
+            $canView = (int)$reclamation->brigadier_id === (int)auth()->id()
+                && in_array((int)$reclamation->status_id, Reclamation::visibleStatusIdsForBrigadier(), true);
+
+            if (!$canView) {
+                abort(403);
+            }
         }
     }
 

+ 63 - 1
app/Http/Controllers/ScheduleController.php

@@ -18,6 +18,8 @@ use App\Models\Role;
 use App\Models\Schedule;
 use App\Models\User;
 use App\Services\NotificationService;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Collection;
 use Illuminate\Http\Request;
 use Illuminate\Support\Carbon;
 
@@ -33,7 +35,11 @@ class ScheduleController extends Controller
     {
         $this->data['districts'] = District::query()->get()->pluck('name', 'id');
         $this->data['areas'] = Area::query()->get()->pluck('name', 'id');
-        $this->data['brigadiers'] = User::query()->where('role', Role::BRIGADIER)->get()->pluck('name', 'id');
+        $this->data['brigadiers'] = User::query()
+            ->where('role', Role::BRIGADIER)
+            ->when(hasRole(Role::BRIGADIER), fn (Builder $query) => $query->whereKey(auth()->id()))
+            ->get()
+            ->pluck('name', 'id');
 
         $this->data['scheduleYear'] = (int)$request->get('year', date('Y'));
         if ($this->data['scheduleYear'] < 2000 || $this->data['scheduleYear'] > 2100) {
@@ -62,8 +68,10 @@ class ScheduleController extends Controller
         }
         $result = Schedule::query()
             ->whereBetween('installation_date', [$weekDates['mon'], $weekDates['sun']])
+            ->when(hasRole(Role::BRIGADIER), fn (Builder $query) => $query->where('brigadier_id', auth()->id()))
             ->with(['brigadier', 'district', 'area'])
             ->get();
+        $result = $this->filterSchedulesForCurrentUser($result);
         $this->data['scheduleStatusMap'] = $this->buildScheduleStatusMap($result);
 
         foreach ($result as $schedule) {
@@ -104,8 +112,10 @@ class ScheduleController extends Controller
 
         $monthSchedules = Schedule::query()
             ->whereBetween('installation_date', [$monthStart->toDateString(), $monthEnd->toDateString()])
+            ->when(hasRole(Role::BRIGADIER), fn (Builder $query) => $query->where('brigadier_id', auth()->id()))
             ->with(['brigadier'])
             ->get();
+        $monthSchedules = $this->filterSchedulesForCurrentUser($monthSchedules);
 
         $monthScheduleColors = [];
         $monthBrigadierLegend = [];
@@ -345,6 +355,58 @@ class ScheduleController extends Controller
 
     }
 
+    private function filterSchedulesForCurrentUser(Collection $schedules): Collection
+    {
+        if (!hasRole(Role::BRIGADIER)) {
+            return $schedules;
+        }
+
+        $brigadierSchedules = $schedules
+            ->where('brigadier_id', auth()->id())
+            ->values();
+
+        if ($brigadierSchedules->isEmpty()) {
+            return $brigadierSchedules;
+        }
+
+        $visibleOrderIds = Order::query()
+            ->withoutGlobalScope(\App\Models\Scopes\YearScope::class)
+            ->whereIn('id', $brigadierSchedules->pluck('order_id')->filter()->unique()->all())
+            ->where('brigadier_id', auth()->id())
+            ->whereIn('order_status_id', Order::visibleStatusIdsForBrigadier())
+            ->pluck('id')
+            ->all();
+
+        $reclamationIds = $brigadierSchedules
+            ->filter(fn (Schedule $schedule) => $schedule->source === 'Рекламации')
+            ->map(fn (Schedule $schedule) => $this->extractReclamationId((string)$schedule->address_code))
+            ->filter()
+            ->unique()
+            ->values();
+
+        $visibleReclamationIds = Reclamation::query()
+            ->withoutGlobalScope(\App\Models\Scopes\YearScope::class)
+            ->whereIn('id', $reclamationIds->all())
+            ->where('brigadier_id', auth()->id())
+            ->whereIn('status_id', Reclamation::visibleStatusIdsForBrigadier())
+            ->pluck('id')
+            ->all();
+
+        return $brigadierSchedules
+            ->filter(function (Schedule $schedule) use ($visibleOrderIds, $visibleReclamationIds) {
+                if ($schedule->source === 'Площадки') {
+                    return in_array((int)$schedule->order_id, $visibleOrderIds, true);
+                }
+
+                if ($schedule->source === 'Рекламации') {
+                    return in_array($this->extractReclamationId((string)$schedule->address_code), $visibleReclamationIds, true);
+                }
+
+                return true;
+            })
+            ->values();
+    }
+
     private function buildScheduleStatusMap($schedules): array
     {
         $statusMap = [];

+ 12 - 1
app/Models/Order.php

@@ -69,6 +69,10 @@ class Order extends Model
         self::STATUS_PROBLEM => 'danger',
     ];
 
+    public const BRIGADIER_HIDDEN_STATUS_IDS = [
+        self::STATUS_HANDED_OVER,
+    ];
+
 
     // set year attribute to current selected year
     protected static function boot(): void
@@ -169,6 +173,14 @@ class Order extends Model
         return $this->hasMany(Reclamation::class);
     }
 
+    public static function visibleStatusIdsForBrigadier(): array
+    {
+        return array_values(array_diff(
+            array_keys(self::STATUS_NAMES),
+            self::BRIGADIER_HIDDEN_STATUS_IDS,
+        ));
+    }
+
 
 
     public function getNeeds(): array
@@ -301,4 +313,3 @@ class Order extends Model
     }
 
 }
-

+ 12 - 0
app/Models/Reclamation.php

@@ -39,6 +39,13 @@ class Reclamation extends Model
         self::STATUS_CLOSED_NO_PAY => 'Закрыта, не оплачивается',
     ];
 
+    public const BRIGADIER_VISIBLE_STATUS_IDS = [
+        self::STATUS_NEW,
+        self::STATUS_WAIT,
+        self::STATUS_IN_WORK,
+        self::STATUS_SUBSCRIBE_ACT,
+    ];
+
     protected $fillable = [
         'order_id',
         'user_id',
@@ -149,4 +156,9 @@ class Reclamation extends Model
         return $this->hasMany(Shortage::class)->where('status', Shortage::STATUS_OPEN);
     }
 
+    public static function visibleStatusIdsForBrigadier(): array
+    {
+        return self::BRIGADIER_VISIBLE_STATUS_IDS;
+    }
+
 }

+ 29 - 18
resources/views/schedule/index.blade.php

@@ -100,19 +100,20 @@
             </thead>
             <tbody>
             @foreach($schedules as $dow => $schs)
-                <tr>
-                    <td rowspan="{{ ($schs) ? count($schs) : '1' }}"
-                        class="vertical">{{ \App\Helpers\DateHelper::getHumanDayOfWeek($dow) }}
-                        @if(hasRole('admin'))
-                            <i class="bi bi-calendar-plus text-primary ms-2 createSchedule"
-                               title="Новая запись" data-schedule-date="{{ $dow }}"></i>
-                        @endif
-                    </td>
-                    <td rowspan="{{ ($schs) ? count($schs) : '1' }}"
-                        class="vertical">{{ \App\Helpers\DateHelper::getHumanDate($dow) }}</td>
-                    @if($schs)
-                        @foreach($schs as $schedule)
-                            {!! (!$loop->first) ? '<tr>':'' !!}
+                @if($schs)
+                    @foreach($schs as $schedule)
+                        <tr>
+                            @if($loop->first)
+                                <td rowspan="{{ count($schs) }}"
+                                    class="vertical">{{ \App\Helpers\DateHelper::getHumanDayOfWeek($dow) }}
+                                    @if(hasRole('admin'))
+                                        <i class="bi bi-calendar-plus text-primary ms-2 createSchedule"
+                                           title="Новая запись" data-schedule-date="{{ $dow }}"></i>
+                                    @endif
+                                </td>
+                                <td rowspan="{{ count($schs) }}"
+                                    class="vertical">{{ \App\Helpers\DateHelper::getHumanDate($dow) }}</td>
+                            @endif
                             <td style="background: {{ $schedule->brigadier->color }}"
                                 class="align-middle code-{{ $schedule->id }}">
                                 @php
@@ -182,11 +183,21 @@
                                     </div>
                                 </td>
                             @endif
-                </tr>
-            @endforeach
-            @endif
-            {!! (!$schs) ? '</tr>': '' !!}
-
+                        </tr>
+                    @endforeach
+                @else
+                    <tr>
+                        <td rowspan="1"
+                            class="vertical">{{ \App\Helpers\DateHelper::getHumanDayOfWeek($dow) }}
+                            @if(hasRole('admin'))
+                                <i class="bi bi-calendar-plus text-primary ms-2 createSchedule"
+                                   title="Новая запись" data-schedule-date="{{ $dow }}"></i>
+                            @endif
+                        </td>
+                        <td rowspan="1"
+                            class="vertical">{{ \App\Helpers\DateHelper::getHumanDate($dow) }}</td>
+                    </tr>
+                @endif
             @endforeach
 
             </tbody>

+ 35 - 0
tests/Feature/OrderControllerTest.php

@@ -90,6 +90,28 @@ class OrderControllerTest extends TestCase
         $response->assertDontSee($otherOrder->object_address);
     }
 
+    public function test_brigadier_does_not_see_handed_over_orders_in_index(): void
+    {
+        $visibleOrder = Order::factory()->create([
+            'brigadier_id' => $this->brigadierUser->id,
+            'order_status_id' => Order::STATUS_IN_MOUNT,
+            'object_address' => 'ул. Видимая, д. 11',
+        ]);
+
+        $hiddenOrder = Order::factory()->create([
+            'brigadier_id' => $this->brigadierUser->id,
+            'order_status_id' => Order::STATUS_HANDED_OVER,
+            'object_address' => 'ул. Сданная, д. 12',
+        ]);
+
+        $response = $this->actingAs($this->brigadierUser)
+            ->get(route('order.index'));
+
+        $response->assertStatus(200);
+        $response->assertSee($visibleOrder->object_address);
+        $response->assertDontSee($hiddenOrder->object_address);
+    }
+
     // ==================== Create ====================
 
     public function test_can_view_create_order_form(): void
@@ -188,6 +210,19 @@ class OrderControllerTest extends TestCase
         $response->assertSee($order->object_address);
     }
 
+    public function test_brigadier_cannot_view_handed_over_order_details(): void
+    {
+        $order = Order::factory()->create([
+            'brigadier_id' => $this->brigadierUser->id,
+            'order_status_id' => Order::STATUS_HANDED_OVER,
+        ]);
+
+        $response = $this->actingAs($this->brigadierUser)
+            ->get(route('order.show', $order));
+
+        $response->assertStatus(403);
+    }
+
     // ==================== Edit ====================
 
     public function test_can_view_edit_order_form(): void

+ 44 - 0
tests/Feature/ReclamationControllerTest.php

@@ -26,6 +26,7 @@ class ReclamationControllerTest extends TestCase
 
     private User $adminUser;
     private User $managerUser;
+    private User $brigadierUser;
 
     protected function setUp(): void
     {
@@ -33,6 +34,7 @@ class ReclamationControllerTest extends TestCase
 
         $this->adminUser = User::factory()->create(['role' => Role::ADMIN]);
         $this->managerUser = User::factory()->create(['role' => Role::MANAGER]);
+        $this->brigadierUser = User::factory()->create(['role' => Role::BRIGADIER]);
     }
 
     // ==================== Authentication ====================
@@ -65,6 +67,35 @@ class ReclamationControllerTest extends TestCase
         $response->assertStatus(200);
     }
 
+    public function test_brigadier_sees_only_assigned_reclamations_with_allowed_statuses(): void
+    {
+        $visibleReclamation = Reclamation::factory()->create([
+            'brigadier_id' => $this->brigadierUser->id,
+            'status_id' => Reclamation::STATUS_IN_WORK,
+            'reason' => 'Видимая рекламация',
+        ]);
+
+        $hiddenByStatus = Reclamation::factory()->create([
+            'brigadier_id' => $this->brigadierUser->id,
+            'status_id' => Reclamation::STATUS_DONE,
+            'reason' => 'Скрытая по статусу',
+        ]);
+
+        $hiddenByBrigadier = Reclamation::factory()->create([
+            'brigadier_id' => User::factory()->create(['role' => Role::BRIGADIER])->id,
+            'status_id' => Reclamation::STATUS_IN_WORK,
+            'reason' => 'Скрытая по бригадиру',
+        ]);
+
+        $response = $this->actingAs($this->brigadierUser)
+            ->get(route('reclamations.index'));
+
+        $response->assertStatus(200);
+        $response->assertSee($visibleReclamation->reason);
+        $response->assertDontSee($hiddenByStatus->reason);
+        $response->assertDontSee($hiddenByBrigadier->reason);
+    }
+
     // ==================== Create ====================
 
     public function test_can_create_reclamation_for_order(): void
@@ -125,6 +156,19 @@ class ReclamationControllerTest extends TestCase
         $response->assertViewIs('reclamations.edit');
     }
 
+    public function test_brigadier_cannot_view_reclamation_details_with_hidden_status(): void
+    {
+        $reclamation = Reclamation::factory()->create([
+            'brigadier_id' => $this->brigadierUser->id,
+            'status_id' => Reclamation::STATUS_DONE,
+        ]);
+
+        $response = $this->actingAs($this->brigadierUser)
+            ->get(route('reclamations.show', $reclamation));
+
+        $response->assertStatus(403);
+    }
+
     // ==================== Update ====================
 
     public function test_can_update_reclamation(): void

+ 99 - 0
tests/Feature/ScheduleControllerTest.php

@@ -113,6 +113,105 @@ class ScheduleControllerTest extends TestCase
         $response->assertStatus(200);
     }
 
+    public function test_brigadier_sees_only_visible_platform_and_reclamation_schedules(): void
+    {
+        $otherBrigadier = User::factory()->create(['role' => Role::BRIGADIER]);
+
+        $visibleOrder = Order::factory()->create([
+            'brigadier_id' => $this->brigadierUser->id,
+            'order_status_id' => Order::STATUS_IN_MOUNT,
+            'object_address' => 'ул. Площадка видимая, д. 1',
+        ]);
+        $hiddenOrder = Order::factory()->create([
+            'brigadier_id' => $this->brigadierUser->id,
+            'order_status_id' => Order::STATUS_HANDED_OVER,
+            'object_address' => 'ул. Площадка скрытая, д. 2',
+        ]);
+        $visibleReclamation = Reclamation::factory()->create([
+            'order_id' => $visibleOrder->id,
+            'brigadier_id' => $this->brigadierUser->id,
+            'status_id' => Reclamation::STATUS_IN_WORK,
+            'reason' => 'Рекламация видимая',
+        ]);
+        $hiddenReclamation = Reclamation::factory()->create([
+            'order_id' => $hiddenOrder->id,
+            'brigadier_id' => $this->brigadierUser->id,
+            'status_id' => Reclamation::STATUS_DONE,
+            'reason' => 'Рекламация скрытая',
+        ]);
+        $foreignOrder = Order::factory()->create([
+            'brigadier_id' => $otherBrigadier->id,
+            'order_status_id' => Order::STATUS_IN_MOUNT,
+            'object_address' => 'ул. Чужая площадка, д. 3',
+        ]);
+        $foreignReclamation = Reclamation::factory()->create([
+            'order_id' => $foreignOrder->id,
+            'brigadier_id' => $otherBrigadier->id,
+            'status_id' => Reclamation::STATUS_IN_WORK,
+            'reason' => 'Чужая рекламация',
+        ]);
+
+        Schedule::factory()->create([
+            'source' => 'Площадки',
+            'order_id' => $visibleOrder->id,
+            'brigadier_id' => $this->brigadierUser->id,
+            'installation_date' => '2026-03-16',
+            'object_address' => $visibleOrder->object_address,
+        ]);
+        Schedule::factory()->create([
+            'source' => 'Площадки',
+            'order_id' => $hiddenOrder->id,
+            'brigadier_id' => $this->brigadierUser->id,
+            'installation_date' => '2026-03-16',
+            'object_address' => $hiddenOrder->object_address,
+        ]);
+        Schedule::factory()->create([
+            'source' => 'Рекламации',
+            'address_code' => 'РЕКЛ-' . $visibleReclamation->id,
+            'order_id' => $visibleOrder->id,
+            'brigadier_id' => $this->brigadierUser->id,
+            'installation_date' => '2026-03-17',
+            'object_address' => $visibleOrder->object_address,
+            'object_type' => $visibleReclamation->reason,
+        ]);
+        Schedule::factory()->create([
+            'source' => 'Рекламации',
+            'address_code' => 'РЕКЛ-' . $hiddenReclamation->id,
+            'order_id' => $hiddenOrder->id,
+            'brigadier_id' => $this->brigadierUser->id,
+            'installation_date' => '2026-03-18',
+            'object_address' => $hiddenOrder->object_address,
+            'object_type' => $hiddenReclamation->reason,
+        ]);
+        Schedule::factory()->create([
+            'source' => 'Площадки',
+            'order_id' => $foreignOrder->id,
+            'brigadier_id' => $otherBrigadier->id,
+            'installation_date' => '2026-03-19',
+            'object_address' => $foreignOrder->object_address,
+        ]);
+        Schedule::factory()->create([
+            'source' => 'Рекламации',
+            'address_code' => 'РЕКЛ-' . $foreignReclamation->id,
+            'order_id' => $foreignOrder->id,
+            'brigadier_id' => $otherBrigadier->id,
+            'installation_date' => '2026-03-20',
+            'object_address' => $foreignOrder->object_address,
+            'object_type' => $foreignReclamation->reason,
+        ]);
+
+        $response = $this->actingAs($this->brigadierUser)
+            ->get(route('schedule.index', ['year' => 2026, 'week' => 12]));
+
+        $response->assertStatus(200);
+        $response->assertSee($visibleOrder->object_address);
+        $response->assertSee($visibleReclamation->reason);
+        $response->assertDontSee($hiddenOrder->object_address);
+        $response->assertDontSee($hiddenReclamation->reason);
+        $response->assertDontSee($foreignOrder->object_address);
+        $response->assertDontSee($foreignReclamation->reason);
+    }
+
     // ==================== Update (create manual schedule) ====================
 
     public function test_admin_can_create_manual_schedule(): void