seed(RbacSeeder::class); $this->adminUser = $this->createUserWithRole(Role::ADMIN); $this->managerUser = $this->createUserWithRole(Role::MANAGER); $this->brigadierUser = $this->createUserWithRole(Role::BRIGADIER); $this->assistantHeadUser = $this->createUserWithRole(Role::ASSISTANT_HEAD); } private function createUserWithRole(string $roleSlug): User { return User::factory()->create([ 'role' => $roleSlug, 'role_id' => Role::query()->where('slug', $roleSlug)->value('id'), ]); } // ==================== Guest redirects ==================== public function test_guest_cannot_access_product_sku_index(): void { $response = $this->get(route('product_sku.index')); $response->assertRedirect(route('login')); } public function test_guest_cannot_access_product_sku_show(): void { $sku = ProductSKU::factory()->create(); $response = $this->get(route('product_sku.show', $sku)); $response->assertRedirect(route('login')); } public function test_guest_cannot_update_product_sku(): void { $sku = ProductSKU::factory()->create(); $response = $this->post(route('product_sku.update', $sku), []); $response->assertRedirect(route('login')); } // ==================== Authorization ==================== public function test_brigadier_cannot_access_product_sku_index(): void { $response = $this->actingAs($this->brigadierUser) ->get(route('product_sku.index')); $response->assertStatus(403); } public function test_manager_cannot_export_mafs(): void { $response = $this->actingAs($this->managerUser) ->post(route('mafs.export')); $response->assertStatus(403); } public function test_manager_cannot_export_maf_registry(): void { $response = $this->actingAs($this->managerUser) ->post(route('mafs.registry'), ['upd_number' => 'UPD-001']); $response->assertStatus(403); } // ==================== Index ==================== public function test_admin_can_access_product_sku_index(): void { $response = $this->actingAs($this->adminUser) ->get(route('product_sku.index')); $response->assertStatus(200); $response->assertViewIs('products_sku.index'); } public function test_manager_can_access_product_sku_index(): void { $response = $this->actingAs($this->managerUser) ->get(route('product_sku.index')); $response->assertStatus(200); $response->assertViewIs('products_sku.index'); } public function test_maf_filter_options_include_empty_marker_for_factory_number(): void { ProductSKU::factory()->create(['factory_number' => null]); ProductSKU::factory()->create(['factory_number' => ' ']); ProductSKU::factory()->create(['factory_number' => 'FN-123456']); $response = $this->actingAs($this->adminUser) ->getJson(route('getFilters', [ 'table' => 'product_sku', 'column' => 'factory_number', ])); $response->assertOk(); $response->assertJsonFragment(['-пусто-']); $response->assertJsonFragment(['FN-123456']); } public function test_maf_empty_filter_matches_null_blank_and_null_date_values(): void { $orderWithNullFactory = Order::factory()->create(['object_address' => 'ул. Пустая фабрика']); $orderWithBlankFactory = Order::factory()->create(['object_address' => 'ул. Пробелы фабрика']); $orderWithNullDate = Order::factory()->create(['object_address' => 'ул. Пустая дата']); $orderWithBlankStatement = Order::factory()->create(['object_address' => 'ул. Пустая ведомость']); $orderWithFilledValue = Order::factory()->create(['object_address' => 'ул. Заполненная']); $product = Product::factory()->create(); ProductSKU::factory()->create([ 'order_id' => $orderWithNullFactory->id, 'product_id' => $product->id, 'factory_number' => null, 'manufacture_date' => '2026-04-01', 'statement_number' => 'STAT-1', ]); ProductSKU::factory()->create([ 'order_id' => $orderWithBlankFactory->id, 'product_id' => $product->id, 'factory_number' => ' ', 'manufacture_date' => '2026-04-02', 'statement_number' => 'STAT-2', ]); ProductSKU::factory()->create([ 'order_id' => $orderWithNullDate->id, 'product_id' => $product->id, 'factory_number' => 'FN-000001', 'manufacture_date' => null, 'statement_number' => 'STAT-3', ]); ProductSKU::factory()->create([ 'order_id' => $orderWithBlankStatement->id, 'product_id' => $product->id, 'factory_number' => 'FN-000002', 'manufacture_date' => '2026-04-03', 'statement_number' => ' ', ]); ProductSKU::factory()->create([ 'order_id' => $orderWithFilledValue->id, 'product_id' => $product->id, 'factory_number' => 'FN-999999', 'manufacture_date' => '2026-04-04', 'statement_number' => 'STAT-9', ]); $factoryResponse = $this->actingAs($this->adminUser) ->get(route('product_sku.index', [ 'filters' => ['factory_number' => '-пусто-'], ])); $factoryResponse->assertOk(); $factoryAddresses = $factoryResponse->viewData('products_sku')->pluck('object_address')->all(); $this->assertContains('ул. Пустая фабрика', $factoryAddresses); $this->assertContains('ул. Пробелы фабрика', $factoryAddresses); $this->assertNotContains('ул. Заполненная', $factoryAddresses); $dateResponse = $this->actingAs($this->adminUser) ->get(route('product_sku.index', [ 'filters' => ['manufacture_date' => '-пусто-'], ])); $dateResponse->assertOk(); $dateAddresses = $dateResponse->viewData('products_sku')->pluck('object_address')->all(); $this->assertContains('ул. Пустая дата', $dateAddresses); $this->assertNotContains('ул. Заполненная', $dateAddresses); $statementResponse = $this->actingAs($this->adminUser) ->get(route('product_sku.index', [ 'filters' => ['statement_number' => '-пусто-'], ])); $statementResponse->assertOk(); $statementAddresses = $statementResponse->viewData('products_sku')->pluck('object_address')->all(); $this->assertContains('ул. Пустая ведомость', $statementAddresses); $this->assertNotContains('ул. Заполненная', $statementAddresses); } // ==================== Show ==================== public function test_admin_can_view_product_sku(): void { $sku = ProductSKU::factory()->create(); $response = $this->actingAs($this->adminUser) ->get(route('product_sku.show', $sku)); $response->assertStatus(200); $response->assertViewIs('products_sku.edit'); } public function test_manager_can_view_product_sku(): void { $sku = ProductSKU::factory()->create(); $response = $this->actingAs($this->managerUser) ->get(route('product_sku.show', $sku)); $response->assertStatus(200); $response->assertViewIs('products_sku.edit'); } public function test_product_sku_show_uses_nav_context_back_url(): void { $sku = ProductSKU::factory()->create(); $reclamation = \App\Models\Reclamation::factory()->create(); $indexResponse = $this->actingAs($this->adminUser) ->get(route('reclamations.index')); $nav = $indexResponse->viewData('nav'); $this->actingAs($this->adminUser) ->get(route('reclamations.show', [ 'reclamation' => $reclamation, 'nav' => $nav, ])) ->assertOk(); $response = $this->actingAs($this->adminUser) ->get(route('product_sku.show', [ 'product_sku' => $sku, 'nav' => $nav, ])); $response->assertOk(); $response->assertViewHas('nav', $nav); $response->assertViewHas('back_url', route('reclamations.show', [ 'reclamation' => $reclamation, 'nav' => $nav, ])); } // ==================== Update ==================== public function test_admin_can_update_product_sku(): void { $product = Product::factory()->create(); $order = Order::factory()->create(); $sku = ProductSKU::factory()->create([ 'product_id' => $product->id, 'order_id' => $order->id, ]); $response = $this->actingAs($this->adminUser) ->post(route('product_sku.update', $sku), [ 'product_id' => $product->id, 'order_id' => $order->id, 'status' => 'shipped', 'factory_number' => 'FN-999999', 'comment' => 'Updated by admin', ]); $response->assertRedirect(); $this->assertDatabaseHas('products_sku', [ 'id' => $sku->id, 'factory_number' => 'FN-999999', 'comment' => 'Updated by admin', ]); } public function test_manager_can_update_product_sku(): void { $product = Product::factory()->create(); $order = Order::factory()->create(); $sku = ProductSKU::factory()->create([ 'product_id' => $product->id, 'order_id' => $order->id, ]); $response = $this->actingAs($this->managerUser) ->post(route('product_sku.update', $sku), [ 'product_id' => $product->id, 'order_id' => $order->id, 'status' => 'needs', 'factory_number' => 'FN-777777', ]); $response->assertRedirect(); } public function test_update_product_sku_redirects_to_parent_url_from_nav_context(): void { $product = Product::factory()->create(); $order = Order::factory()->create(); $sku = ProductSKU::factory()->create([ 'product_id' => $product->id, 'order_id' => $order->id, ]); $reclamation = \App\Models\Reclamation::factory()->create(); $nav = 'product-sku-nav-token'; $response = $this->actingAs($this->adminUser) ->withSession([ 'navigation' => [ $nav => [ 'updated_at' => time(), 'stack' => [ route('reclamations.index'), route('reclamations.show', $reclamation), route('product_sku.show', $sku), ], ], ], ]) ->post(route('product_sku.update', $sku), [ 'nav' => $nav, 'product_id' => $product->id, 'order_id' => $order->id, 'status' => 'shipped', 'factory_number' => 'FN-NAV-123', 'comment' => 'Updated through nav', ]); $response->assertRedirect(route('reclamations.show', [ 'reclamation' => $reclamation, 'nav' => 'product-sku-nav-token', ])); } public function test_admin_can_inline_update_product_sku_field(): void { $sku = ProductSKU::factory()->create(['rfid' => 'OLD-RFID']); $response = $this->actingAs($this->adminUser) ->postJson(route('product_sku.inline-update', $sku), [ 'field' => 'rfid', 'value' => 'NEW-RFID', ]); $response->assertOk() ->assertJson([ 'field' => 'rfid', 'value' => 'NEW-RFID', ]); $this->assertDatabaseHas('products_sku', [ 'id' => $sku->id, 'rfid' => 'NEW-RFID', ]); } public function test_assistant_head_can_inline_update_product_sku_date_field(): void { $sku = ProductSKU::factory()->create(['manufacture_date' => null]); $response = $this->actingAs($this->assistantHeadUser) ->postJson(route('product_sku.inline-update', $sku), [ 'field' => 'manufacture_date', 'value' => '2026-05-19', ]); $response->assertOk() ->assertJson([ 'field' => 'manufacture_date', 'value' => '2026-05-19', ]); $this->assertDatabaseHas('products_sku', [ 'id' => $sku->id, 'manufacture_date' => '2026-05-19', ]); } public function test_user_with_maf_field_update_permission_can_inline_update_product_sku_field(): void { $role = Role::query()->create([ 'slug' => 'maf_inline_editor', 'name' => 'Редактор МАФ в таблице', 'is_system' => false, 'is_active' => true, 'sort' => 100, ]); $permission = Permission::query()->where('slug', 'maf.fields.statement_number.update')->firstOrFail(); $role->permissions()->sync([ $permission->id => ['effect' => 'allow'], ]); $user = User::factory()->create([ 'role' => $role->slug, 'role_id' => $role->id, ]); $sku = ProductSKU::factory()->create(['statement_number' => 'OLD-STAT']); $response = $this->actingAs($user) ->postJson(route('product_sku.inline-update', $sku), [ 'field' => 'statement_number', 'value' => 'NEW-STAT', ]); $response->assertOk() ->assertJson([ 'field' => 'statement_number', 'value' => 'NEW-STAT', ]); $this->assertDatabaseHas('products_sku', [ 'id' => $sku->id, 'statement_number' => 'NEW-STAT', ]); } public function test_manager_cannot_inline_update_product_sku_field(): void { $sku = ProductSKU::factory()->create(['upd_number' => 'UPD-OLD']); $response = $this->actingAs($this->managerUser) ->postJson(route('product_sku.inline-update', $sku), [ 'field' => 'upd_number', 'value' => 'UPD-NEW', ]); $response->assertForbidden(); $this->assertDatabaseHas('products_sku', [ 'id' => $sku->id, 'upd_number' => 'UPD-OLD', ]); } public function test_inline_update_rejects_unapproved_product_sku_field(): void { $sku = ProductSKU::factory()->create(['comment' => 'Old comment']); $response = $this->actingAs($this->adminUser) ->postJson(route('product_sku.inline-update', $sku), [ 'field' => 'comment', 'value' => 'New comment', ]); $response->assertUnprocessable(); $this->assertDatabaseHas('products_sku', [ 'id' => $sku->id, 'comment' => 'Old comment', ]); } public function test_user_with_passport_upload_permission_can_upload_product_sku_passport(): void { Storage::fake('public'); $role = Role::query()->create([ 'slug' => 'maf_passport_loader', 'name' => 'Загрузка паспортов МАФ', 'is_system' => false, 'is_active' => true, 'sort' => 100, ]); $permission = Permission::query()->where('slug', 'maf.passports.upload')->firstOrFail(); $role->permissions()->sync([ $permission->id => ['effect' => 'allow'], ]); $user = User::factory()->create([ 'role' => $role->slug, 'role_id' => $role->id, ]); $sku = ProductSKU::factory()->create(['passport_id' => null]); $response = $this->actingAs($user) ->from(route('order.show', $sku->order)) ->post(route('product-sku.upload-passport', $sku), [ 'passport' => UploadedFile::fake()->create('passport.pdf', 10, 'application/pdf'), ]); $response->assertRedirect(route('order.show', $sku->order)); $this->assertNotNull($sku->fresh()->passport_id); } public function test_user_without_passport_upload_permission_cannot_upload_product_sku_passport(): void { Storage::fake('public'); $sku = ProductSKU::factory()->create(['passport_id' => null]); $response = $this->actingAs($this->brigadierUser) ->post(route('product-sku.upload-passport', $sku), [ 'passport' => UploadedFile::fake()->create('passport.pdf', 10, 'application/pdf'), ]); $response->assertForbidden(); $this->assertNull($sku->fresh()->passport_id); } // ==================== Export ==================== public function test_admin_can_export_mafs(): void { Bus::fake(); $response = $this->actingAs($this->adminUser) ->post(route('mafs.export')); $response->assertRedirect(route('product_sku.index')); $response->assertSessionHas('success'); Bus::assertDispatched(ExportMafJob::class); } public function test_admin_can_export_maf_registry(): void { Bus::fake(); $response = $this->actingAs($this->adminUser) ->post(route('mafs.registry'), ['upd_number' => 'UPD-001']); $response->assertRedirect(route('product_sku.index')); $response->assertSessionHas('success'); Bus::assertDispatched(ExportMafRegistryJob::class); } public function test_assistant_head_can_export_maf_registry(): void { Bus::fake(); $response = $this->actingAs($this->assistantHeadUser) ->post(route('mafs.registry'), ['upd_number' => 'UPD-002']); $response->assertRedirect(route('product_sku.index')); $response->assertSessionHas('success'); Bus::assertDispatched(ExportMafRegistryJob::class); } }