admin = User::factory()->admin()->create(); $this->assistant = User::factory()->assistantHead()->create(); $this->manager = User::factory()->manager()->create(); $this->driver = User::factory()->driver()->create(); $this->brigadier = User::factory()->brigadier()->create(); $this->order = ProductionOrder::factory()->create(['manager_id' => $this->manager->id]); } public function test_admin_can_add_multiple_deliveries_and_installations_with_audit(): void { $this->actingAs($this->admin) ->post(route('schedule.orders.deliveries.store', $this->order), $this->deliveryData([ 'delivery_date' => '2026-09-10', ])) ->assertRedirect(); $this->actingAs($this->admin) ->post(route('schedule.orders.deliveries.store', $this->order), $this->deliveryData([ 'delivery_date' => '2026-09-12', 'driver_id' => $this->brigadier->id, ])) ->assertRedirect(); $this->actingAs($this->admin) ->post(route('schedule.orders.installations.store', $this->order), $this->installationData()) ->assertRedirect(); $this->assertSame(2, $this->order->deliveries()->count()); $this->assertSame(1, $this->order->installations()->count()); $installation = $this->order->installations()->firstOrFail(); $this->assertSame(3, $installation->schedules()->count()); $this->assertSame( ['2026-09-20', '2026-09-21', '2026-09-22'], $installation->schedules()->orderBy('installation_date')->pluck('installation_date')->all(), ); $this->assertDatabaseHas('production_order_activities', [ 'production_order_id' => $this->order->id, 'type' => ProductionOrderActivityType::DeliveryScheduled->value, 'user_id' => $this->admin->id, 'message' => 'Назначена доставка на 10.09.2026, водитель '.$this->driver->name.'.', ]); $this->assertDatabaseHas('production_order_activities', [ 'production_order_id' => $this->order->id, 'type' => ProductionOrderActivityType::InstallationScheduled->value, 'user_id' => $this->admin->id, 'message' => 'Назначен монтаж на 20.09.2026, бригадир '.$this->brigadier->name.'.', ]); $this->assertSame(3, $this->order->activities()->count()); } public function test_assistant_can_update_and_delete_planning_records_without_extra_audit(): void { $delivery = ProductionOrderDelivery::factory()->create([ 'production_order_id' => $this->order->id, 'driver_id' => $this->driver->id, 'delivery_date' => '2026-09-10', ]); $installation = ProductionOrderInstallation::factory()->create([ 'production_order_id' => $this->order->id, 'brigadier_id' => $this->brigadier->id, ]); $this->actingAs($this->assistant) ->put( route('schedule.orders.deliveries.update', [$this->order, $delivery]), $this->deliveryData(['delivery_date' => '2026-09-15']), ) ->assertRedirect(); $this->actingAs($this->assistant) ->put( route('schedule.orders.installations.update', [$this->order, $installation]), $this->installationData(['installation_days' => 5]), ) ->assertRedirect(); $this->assertDatabaseHas('production_order_deliveries', [ 'id' => $delivery->id, 'delivery_date' => '2026-09-15', 'updated_by' => $this->assistant->id, ]); $this->assertDatabaseHas('production_order_installations', [ 'id' => $installation->id, 'installation_days' => 5, 'updated_by' => $this->assistant->id, ]); $this->assertSame(5, $installation->schedules()->count()); $this->assertDatabaseHas('schedules', [ 'production_order_installation_id' => $installation->id, 'installation_date' => '2026-09-24', 'source' => \App\Models\Schedule::SOURCE_PRODUCTION_ORDER, 'brigadier_id' => $this->brigadier->id, ]); $this->assertSame(0, $this->order->activities()->count()); $this->actingAs($this->assistant) ->delete(route('schedule.orders.deliveries.destroy', [$this->order, $delivery])) ->assertRedirect(); $this->actingAs($this->assistant) ->delete(route('schedule.orders.installations.destroy', [$this->order, $installation])) ->assertRedirect(); $this->assertSoftDeleted('production_order_deliveries', ['id' => $delivery->id]); $this->assertSoftDeleted('production_order_installations', ['id' => $installation->id]); $this->assertDatabaseMissing('schedules', ['production_order_installation_id' => $installation->id]); } public function test_manager_cannot_manage_delivery_or_installation(): void { $this->actingAs($this->manager) ->post(route('schedule.orders.deliveries.store', $this->order), $this->deliveryData()) ->assertForbidden(); $this->actingAs($this->manager) ->post(route('schedule.orders.installations.store', $this->order), $this->installationData()) ->assertForbidden(); } public function test_delivery_and_installation_require_users_with_allowed_roles(): void { $wrongRole = User::factory()->create(['role' => Role::MANAGER]); $this->actingAs($this->admin) ->post(route('schedule.orders.deliveries.store', $this->order), $this->deliveryData([ 'driver_id' => $wrongRole->id, ])) ->assertSessionHasErrors('driver_id'); $this->actingAs($this->admin) ->post(route('schedule.orders.installations.store', $this->order), $this->installationData([ 'brigadier_id' => $this->driver->id, ])) ->assertSessionHasErrors('brigadier_id'); } public function test_planning_record_from_another_order_returns_not_found(): void { $otherOrder = ProductionOrder::factory()->create(['manager_id' => $this->manager->id]); $delivery = ProductionOrderDelivery::factory()->create([ 'production_order_id' => $otherOrder->id, 'driver_id' => $this->driver->id, ]); $this->actingAs($this->admin) ->put( route('schedule.orders.deliveries.update', [$this->order, $delivery]), $this->deliveryData(), ) ->assertNotFound(); } public function test_order_card_shows_planning_and_audit_to_read_only_role(): void { $delivery = ProductionOrderDelivery::factory()->create([ 'production_order_id' => $this->order->id, 'driver_id' => $this->driver->id, 'delivery_date' => '2026-09-18', ]); $this->order->activities()->create([ 'type' => ProductionOrderActivityType::DeliveryScheduled, 'user_id' => $this->admin->id, 'delivery_id' => $delivery->id, 'message' => 'Заказ добавлен в график доставок.', ]); $this->actingAs($this->manager) ->get(route('schedule.orders.show', $this->order)) ->assertOk() ->assertSee('18.09.2026') ->assertSee($this->driver->name) ->assertSee('Заказ добавлен в график доставок.') ->assertDontSee('Добавить доставку'); } public function test_status_change_is_the_only_order_field_update_written_to_audit(): void { $this->order->update(['note' => 'Обычное изменение']); $this->assertSame(0, $this->order->activities()->count()); $service = app(\App\Services\ProductionOrderService::class); $service->update( $this->order, ['status' => ProductionOrderStatus::InTransit->value], $this->admin, false, ); $this->assertDatabaseHas('production_order_activities', [ 'production_order_id' => $this->order->id, 'type' => ProductionOrderActivityType::StatusChanged->value, 'message' => 'Статус изменён на «В пути».', ]); $this->assertSame(1, $this->order->activities()->count()); } public function test_weekly_delivery_schedule_contains_only_selected_week(): void { ProductionOrderDelivery::factory()->create([ 'production_order_id' => $this->order->id, 'driver_id' => $this->driver->id, 'delivery_date' => '2026-09-10', 'delivery_time' => '10:30', 'carrier' => 'Нужный перевозчик', ]); ProductionOrderDelivery::factory()->create([ 'production_order_id' => $this->order->id, 'driver_id' => $this->driver->id, 'delivery_date' => '2026-09-20', 'carrier' => 'Перевозчик другой недели', ]); $this->actingAs($this->manager) ->withSession(['year' => 2020]) ->get(route('schedule.deliveries', ['week' => '2026-09-07'])) ->assertOk() ->assertViewIs('production_order_deliveries.index') ->assertSee('Нужный перевозчик') ->assertSee($this->order->order_number) ->assertSee($this->driver->name) ->assertDontSee('Перевозчик другой недели'); $this->actingAs($this->driver) ->get(route('schedule.deliveries', ['week' => '2026-09-07'])) ->assertOk(); } public function test_installation_is_visible_in_existing_schedule_and_managed_from_order_card(): void { $this->actingAs($this->admin) ->post(route('schedule.orders.installations.store', $this->order), $this->installationData([ 'installation_date' => '2026-09-15', 'installation_days' => 2, ])) ->assertRedirect(); $installation = $this->order->installations()->firstOrFail(); $schedule = $installation->schedules()->firstOrFail(); $this->actingAs($this->admin) ->get(route('schedule.index', ['tab' => 'week', 'year' => 2026, 'week' => 38])) ->assertOk() ->assertSee($this->order->order_number) ->assertSee($this->order->object_address) ->assertSee('Размещен') ->assertSee(route('schedule.orders.show', $this->order), false); $this->actingAs($this->admin) ->delete(route('schedule.delete', $schedule)) ->assertForbidden(); $this->assertDatabaseHas('schedules', ['id' => $schedule->id]); } /** @return array */ private function deliveryData(array $overrides = []): array { return array_replace([ 'delivery_date' => '2026-09-10', 'delivery_time' => '10:30', 'driver_id' => $this->driver->id, 'carrier' => 'ООО Перевозчик', 'request_number' => 'ЗД-100', 'request_date' => '2026-09-08', 'carrier_note' => 'Позвонить заранее', 'contact_position' => 'Прораб', 'contact_name' => 'Иван Иванов', 'contact_phone' => '+7 900 000-00-00', 'desired_time' => '11:00', 'access_system' => 'По пропуску', 'unloading_place' => 'Вторые ворота', 'advance_notice' => 'За один час', 'document_signing_method' => 'Оригинал на объекте', 'has_passports_and_certificates' => true, 'note' => 'Без опозданий', ], $overrides); } /** @return array */ private function installationData(array $overrides = []): array { return array_replace([ 'installation_date' => '2026-09-20', 'installation_days' => 3, 'brigadier_id' => $this->brigadier->id, 'note' => 'Первый этап', ], $overrides); } }