admin = User::factory()->admin()->create(); $this->manager = User::factory()->manager()->create(); $this->assistant = User::factory()->assistantHead()->create(); $this->catalogItem = CommonCatalogItem::factory()->create([ 'article' => 'ГЗ-001', 'calculator_name' => 'Игровой комплекс', ]); } public function test_authorized_roles_can_view_year_independent_order_schedule(): void { $visible = ProductionOrder::factory()->create([ 'order_number' => 'ГЗ-2026', 'order_year' => 2026, 'manager_id' => $this->manager->id, ]); ProductionOrder::factory()->create([ 'order_number' => 'ГЗ-2024', 'order_year' => 2024, 'manager_id' => $this->manager->id, ]); $this->get(route('schedule.orders'))->assertRedirect(route('login')); $this->actingAs($this->manager) ->withSession(['year' => 2020]) ->get(route('schedule.orders')) ->assertOk() ->assertViewIs('production_orders.index') ->assertSee($visible->order_number) ->assertSee('ГЗ-2024'); $driver = User::factory()->driver()->create(); $this->actingAs($driver)->get(route('schedule.orders'))->assertOk(); } public function test_admin_can_create_order_with_items_and_calendar_shipment_date(): void { ProductionCalendarDay::factory()->create([ 'date' => '2026-09-07', 'is_working_day' => false, 'name' => 'Перенесённый выходной', ]); $secondCatalogItem = CommonCatalogItem::factory()->create([ 'article' => 'ГЗ-002', 'calculator_name' => 'Качели', ]); $response = $this->actingAs($this->admin)->post( route('schedule.orders.store'), $this->validData([ 'order_number' => 'ЗАК-100', 'payment_date' => '2026-09-04', 'supply_working_days' => 1, 'items' => [ [ 'common_catalog_item_id' => $this->catalogItem->id, 'order_item_number' => 'МАФ-100/1', ], [ 'common_catalog_item_id' => $secondCatalogItem->id, 'order_item_number' => 'МАФ-100/2', 'factory_number' => 'ЗН-002', 'manufacture_date' => '2026-09-03', ], ], ]), ); $order = ProductionOrder::query()->where('order_number', 'ЗАК-100')->firstOrFail(); $response->assertRedirect(); $this->assertSame('2026-09-08', $order->contract_shipment_date->toDateString()); $this->assertSame($this->admin->id, $order->created_by); $this->assertSame(2, $order->items()->count()); $this->assertDatabaseHas('production_order_items', [ 'production_order_id' => $order->id, 'factory_number' => 'ЗН-002', 'manufacture_date' => '2026-09-03', ]); } public function test_order_number_is_unique_within_year(): void { ProductionOrder::factory()->create([ 'order_number' => 'ДУБЛЬ-1', 'order_year' => 2026, 'manager_id' => $this->manager->id, ]); $this->actingAs($this->admin) ->post(route('schedule.orders.store'), $this->validData([ 'order_number' => 'ДУБЛЬ-1', 'order_year' => 2026, ])) ->assertSessionHasErrors('order_number'); $this->actingAs($this->admin) ->post(route('schedule.orders.store'), $this->validData([ 'order_number' => 'ДУБЛЬ-1', 'order_year' => 2027, ])) ->assertSessionHasNoErrors(); $this->assertDatabaseHas('production_orders', [ 'order_number' => 'ДУБЛЬ-1', 'order_year' => 2027, ]); } public function test_manager_cannot_create_or_update_production_order(): void { $order = ProductionOrder::factory()->create(['manager_id' => $this->manager->id]); $this->actingAs($this->manager) ->post(route('schedule.orders.store'), $this->validData()) ->assertForbidden(); $this->actingAs($this->manager) ->put(route('schedule.orders.update', $order), ['note' => 'Подмена']) ->assertForbidden(); } public function test_assistant_can_update_only_allowed_order_and_item_fields(): void { $otherCatalogItem = CommonCatalogItem::factory()->create(); $order = ProductionOrder::factory()->create([ 'order_number' => 'ЗАК-OLD', 'customer_name' => 'Исходный заказчик', 'object_address' => 'Старый адрес', 'status' => ProductionOrderStatus::Placed, 'manager_id' => $this->manager->id, ]); $item = ProductionOrderItem::factory()->create([ 'production_order_id' => $order->id, 'common_catalog_item_id' => $this->catalogItem->id, 'order_item_number' => 'МАФ-OLD', ]); $this->actingAs($this->assistant) ->put(route('schedule.orders.update', $order), [ 'order_number' => 'ЗАК-FORGED', 'customer_name' => 'Подменённый заказчик', 'object_address' => 'Новый адрес', 'status' => ProductionOrderStatus::Closed->value, 'note' => 'Уточнённое примечание', 'items' => [[ 'id' => $item->id, 'common_catalog_item_id' => $otherCatalogItem->id, 'order_item_number' => 'МАФ-FORGED', 'factory_number' => 'ЗН-NEW', 'manufacture_date' => '2026-09-02', ]], ]) ->assertRedirect(); $order->refresh(); $item->refresh(); $this->assertSame('ЗАК-OLD', $order->order_number); $this->assertSame('Исходный заказчик', $order->customer_name); $this->assertSame('Новый адрес', $order->object_address); $this->assertSame(ProductionOrderStatus::Placed, $order->status); $this->assertSame('Уточнённое примечание', $order->note); $this->assertSame($this->catalogItem->id, $item->common_catalog_item_id); $this->assertSame('МАФ-OLD', $item->order_item_number); $this->assertSame('ЗН-NEW', $item->factory_number); $this->assertSame('2026-09-02', $item->manufacture_date?->toDateString()); } public function test_order_card_displays_separate_catalog_items(): void { $order = ProductionOrder::factory()->create(['manager_id' => $this->manager->id]); ProductionOrderItem::factory()->create([ 'production_order_id' => $order->id, 'common_catalog_item_id' => $this->catalogItem->id, 'order_item_number' => 'МАФ-КАРТОЧКА-1', ]); $this->actingAs($this->manager) ->get(route('schedule.orders.show', $order)) ->assertOk() ->assertSee('ГЗ-001') ->assertSee('Игровой комплекс') ->assertSee('МАФ-КАРТОЧКА-1'); } public function test_status_filter_uses_labels_and_filters_by_enum_value(): void { ProductionOrder::factory()->create([ 'order_number' => 'ГЗ-РАЗМЕЩЕН', 'status' => ProductionOrderStatus::Placed, 'manager_id' => $this->manager->id, ]); ProductionOrder::factory()->create([ 'order_number' => 'ГЗ-СКЛАД', 'status' => ProductionOrderStatus::InStock, 'manager_id' => $this->manager->id, ]); $this->actingAs($this->manager) ->getJson(route('getFilters', [ 'table' => 'schedule_orders', 'column' => 'status_name', ])) ->assertOk() ->assertJsonFragment(['Размещен']) ->assertJsonFragment(['На складе']); $this->actingAs($this->manager) ->get(route('schedule.orders', [ 'filters' => ['status_name' => 'На складе'], ])) ->assertOk() ->assertSee('ГЗ-СКЛАД') ->assertDontSee('ГЗ-РАЗМЕЩЕН'); } public function test_admin_can_search_common_catalog_when_adding_order_items(): void { $matchingByName = CommonCatalogItem::factory()->create([ 'article' => 'SEARCH-002', 'calculator_name' => 'Особенная карусель', ]); CommonCatalogItem::factory()->create([ 'article' => 'OTHER-003', 'calculator_name' => 'Другая позиция', ]); $deleted = CommonCatalogItem::factory()->create([ 'article' => 'SEARCH-DELETED', 'calculator_name' => 'Удалённая карусель', ]); $deleted->delete(); $this->actingAs($this->admin) ->getJson(route('schedule.orders.catalog-search', ['q' => 'карусель'])) ->assertOk() ->assertJsonFragment([ 'id' => $matchingByName->id, 'label' => 'SEARCH-002 — Особенная карусель', ]) ->assertJsonMissing(['id' => $deleted->id]); $this->actingAs($this->admin) ->getJson(route('schedule.orders.catalog-search', ['q' => 'ГЗ-001'])) ->assertOk() ->assertJsonFragment(['id' => $this->catalogItem->id]); $this->actingAs($this->manager) ->getJson(route('schedule.orders.catalog-search', ['q' => 'карусель'])) ->assertForbidden(); } public function test_create_form_loads_catalog_items_only_through_search(): void { $unrelated = CommonCatalogItem::factory()->create([ 'article' => 'НЕ-ЗАГРУЖАТЬ', 'calculator_name' => 'Тяжёлая позиция каталога', ]); $this->actingAs($this->admin) ->get(route('schedule.orders.create')) ->assertOk() ->assertSee('Добавить МАФ из общего каталога') ->assertSee('production-order-catalog-search', false) ->assertDontSee($unrelated->article); } /** @return array */ private function validData(array $overrides = []): array { return array_replace_recursive([ 'order_number' => 'ЗАК-001', 'order_year' => 2026, 'customer_name' => 'ООО Заказчик', 'object_address' => 'г. Москва, ул. Строителей, 1', 'invoice_number' => 'СЧ-001', 'invoice_date' => '2026-09-01', 'contract_number' => 'Д-001', 'contract_date' => '2026-08-31', 'payment_date' => '2026-09-01', 'supply_working_days' => 10, 'application_shipment_date' => '2026-09-20', 'status' => ProductionOrderStatus::Placed->value, 'execution_type' => ProductionOrderExecutionType::Delivery->value, 'manager_id' => $this->manager->id, 'note' => 'Тестовый заказ', 'items' => [[ 'common_catalog_item_id' => $this->catalogItem->id, 'order_item_number' => 'МАФ-001', 'factory_number' => null, 'manufacture_date' => null, ]], ], $overrides); } }