adminUser = User::factory()->create(['role' => Role::ADMIN]); $this->managerUser = User::factory()->create(['role' => Role::MANAGER]); $this->brigadierUser = User::factory()->create(['role' => Role::BRIGADIER]); } // ==================== Authentication ==================== public function test_guest_cannot_access_schedule_index(): void { $response = $this->get(route('schedule.index')); $response->assertRedirect(route('login')); } public function test_guest_cannot_create_schedule_from_order(): void { $response = $this->post(route('schedule.create-from-order'), []); $response->assertRedirect(route('login')); } public function test_guest_cannot_update_schedule(): void { $response = $this->post(route('schedule.update'), []); $response->assertRedirect(route('login')); } public function test_guest_cannot_delete_schedule(): void { $brigadier = User::factory()->create(['role' => Role::BRIGADIER]); $schedule = Schedule::factory()->create(['brigadier_id' => $brigadier->id]); $response = $this->delete(route('schedule.delete', $schedule)); $response->assertRedirect(route('login')); } // ==================== Authorization ==================== public function test_manager_cannot_create_schedule_from_order(): void { $response = $this->actingAs($this->managerUser) ->post(route('schedule.create-from-order'), []); $response->assertStatus(403); } public function test_manager_cannot_update_schedule(): void { $response = $this->actingAs($this->managerUser) ->post(route('schedule.update'), []); $response->assertStatus(403); } public function test_manager_cannot_delete_schedule(): void { $brigadier = User::factory()->create(['role' => Role::BRIGADIER]); $schedule = Schedule::factory()->create(['brigadier_id' => $brigadier->id]); $response = $this->actingAs($this->managerUser) ->delete(route('schedule.delete', $schedule)); $response->assertStatus(403); } // ==================== Index ==================== public function test_admin_can_access_schedule_index(): void { $response = $this->actingAs($this->adminUser) ->get(route('schedule.index')); $response->assertStatus(200); $response->assertViewIs('schedule.index'); } public function test_brigadier_can_access_schedule_index(): void { $response = $this->actingAs($this->brigadierUser) ->get(route('schedule.index')); $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 { Bus::fake(); $district = District::query()->first(); $area = Area::query()->first(); $brigadier = User::factory()->create(['role' => Role::BRIGADIER]); $response = $this->actingAs($this->adminUser) ->post(route('schedule.update'), [ 'installation_date' => '2026-03-15', 'address_code' => 'TEST-001', 'object_address' => 'ул. Тестовая, 1', 'object_type' => 'Площадка', 'mafs' => 'МАФ-001 - 2', 'mafs_count' => 2, 'district_id' => $district->id, 'area_id' => $area->id, 'brigadier_id' => $brigadier->id, 'comment' => 'Тестовый комментарий', ]); $response->assertRedirect(); $this->assertDatabaseHas('schedules', [ 'address_code' => 'TEST-001', 'object_address' => 'ул. Тестовая, 1', 'manual' => true, ]); } public function test_admin_can_update_existing_schedule(): void { Bus::fake(); $district = District::query()->first(); $area = Area::query()->first(); $brigadier = User::factory()->create(['role' => Role::BRIGADIER]); $schedule = Schedule::factory()->create([ 'installation_date' => '2026-03-10', 'object_address' => 'Старый адрес', 'brigadier_id' => $brigadier->id, ]); $response = $this->actingAs($this->adminUser) ->post(route('schedule.update'), [ 'id' => $schedule->id, 'installation_date' => '2026-03-20', 'address_code' => $schedule->address_code ?? 'UPD-001', 'object_address' => 'Новый адрес', 'object_type' => 'Площадка', 'mafs' => 'МАФ-002 - 1', 'mafs_count' => 1, 'district_id' => $district->id, 'area_id' => $area->id, 'brigadier_id' => $brigadier->id, 'comment' => '', ]); $response->assertRedirect(); $this->assertDatabaseHas('schedules', [ 'id' => $schedule->id, 'object_address' => 'Новый адрес', 'installation_date' => '2026-03-20', ]); } public function test_cannot_create_schedule_from_order_when_order_has_unlinked_maf(): void { $product = Product::factory()->create(); $order = Order::factory()->readyToMount()->withBrigadier($this->brigadierUser)->create([ 'installation_date' => '2026-04-22', 'install_days' => 2, ]); ProductSKU::factory()->create([ 'order_id' => $order->id, 'product_id' => $product->id, 'maf_order_id' => null, ]); $response = $this->actingAs($this->adminUser) ->post(route('schedule.create-from-order'), [ 'order_id' => $order->id, 'delete_old_records' => '1', ]); $response->assertRedirect(route('order.show', $order->id)); $response->assertSessionHas('danger', function ($messages) { return in_array('МАФ не привязан к заказу', (array) $messages, true); }); $this->assertDatabaseCount('schedules', 0); } public function test_cannot_create_schedule_from_order_when_maf_order_number_is_empty(): void { $product = Product::factory()->create(); $order = Order::factory()->readyToMount()->withBrigadier($this->brigadierUser)->create([ 'installation_date' => '2026-04-22', 'install_days' => 2, ]); $mafOrder = MafOrder::factory()->create([ 'product_id' => $product->id, 'order_number' => '', ]); ProductSKU::factory()->create([ 'order_id' => $order->id, 'product_id' => $product->id, 'maf_order_id' => $mafOrder->id, ]); Schedule::factory()->create([ 'order_id' => $order->id, 'source' => 'Площадки', 'manual' => false, 'brigadier_id' => $this->brigadierUser->id, 'object_address' => $order->object_address, 'installation_date' => '2026-04-20', ]); $response = $this->actingAs($this->adminUser) ->post(route('schedule.create-from-order'), [ 'order_id' => $order->id, 'delete_old_records' => '1', ]); $response->assertRedirect(route('order.show', $order->id)); $response->assertSessionHas('danger', function ($messages) { return in_array('МАФ не привязан к заказу', (array) $messages, true); }); $this->assertDatabaseCount('schedules', 1); } public function test_can_create_schedule_from_order_when_all_mafs_are_linked_to_order_numbers(): void { $product = Product::factory()->create(); $order = Order::factory()->readyToMount()->withBrigadier($this->brigadierUser)->create([ 'installation_date' => '2026-04-22', 'install_days' => 2, ]); $mafOrder = MafOrder::factory()->create([ 'product_id' => $product->id, 'order_number' => 'MO-2026-1', ]); ProductSKU::factory()->create([ 'order_id' => $order->id, 'product_id' => $product->id, 'maf_order_id' => $mafOrder->id, ]); $response = $this->actingAs($this->adminUser) ->post(route('schedule.create-from-order'), [ 'order_id' => $order->id, 'comment' => '', ]); $response->assertRedirect(route('schedule.index')); $this->assertDatabaseCount('schedules', 2); $this->assertDatabaseHas('schedules', [ 'order_id' => $order->id, 'source' => 'Площадки', 'manual' => false, ]); } // ==================== Delete ==================== public function test_admin_can_delete_schedule(): void { $brigadier = User::factory()->create(['role' => Role::BRIGADIER]); $schedule = Schedule::factory()->create(['brigadier_id' => $brigadier->id]); $response = $this->actingAs($this->adminUser) ->delete(route('schedule.delete', $schedule)); $response->assertRedirect(); $this->assertDatabaseMissing('schedules', ['id' => $schedule->id]); } // ==================== Export ==================== public function test_admin_can_export_schedule(): void { Bus::fake(); $response = $this->actingAs($this->adminUser) ->post(route('schedule.export'), [ 'start_date' => '2026-03-01', 'end_date' => '2026-03-31', 'week' => '10', 'year' => 2026, ]); $response->assertRedirect(route('schedule.index', ['week' => 10, 'year' => 2026])); $response->assertSessionHas('success'); } }