diff --git a/app/Http/Controllers/VehicleController.php b/app/Http/Controllers/VehicleController.php new file mode 100644 index 0000000..17ef39e --- /dev/null +++ b/app/Http/Controllers/VehicleController.php @@ -0,0 +1,131 @@ +user()->hasRole('pracownik')) { + $query->where('user_id', $request->user()->id); + } + + $search = trim((string) $request->query('search', '')); + if ($search !== '') { + $query->where(function ($query) use ($search) { + $query->where('brand', 'like', "%{$search}%") + ->orWhere('model', 'like', "%{$search}%") + ->orWhere('registration_number', 'like', "%{$search}%") + ->orWhere('insurance_provider', 'like', "%{$search}%") + ->orWhere('status', 'like', "%{$search}%") + ->orWhereHas('user', function ($userQuery) use ($search) { + $userQuery->where('name', 'like', "%{$search}%") + ->orWhere('surname', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%"); + }); + }); + } + + $vehicles = $query->orderBy('brand')->orderBy('registration_number')->paginate(15)->withQueryString(); + $users = User::orderBy('name')->orderBy('surname')->get(); + + return view('flota', compact('vehicles', 'users')); + } + + public function create(Request $request): View + { + $this->authorizeVehicleManagement($request); + + $users = User::orderBy('name')->orderBy('surname')->get(); + + return view('flota.create', compact('users')); + } + + public function store(Request $request) + { + $this->authorizeVehicleManagement($request); + + $validated = $request->validate([ + 'brand' => ['required', 'string', 'max:255'], + 'model' => ['nullable', 'string', 'max:255'], + 'registration_number' => ['required', 'string', 'max:50', 'unique:vehicles,registration_number'], + 'user_id' => ['required', 'integer', 'exists:users,id'], + 'year' => ['required', 'integer', 'min:1900', 'max:2100'], + 'purchase_date' => ['required', 'date'], + 'oc_expiry_date' => ['required', 'date'], + 'oil_change_km' => ['required', 'integer', 'min:0'], + 'insurance_provider' => ['required', 'string', 'max:255'], + 'technical_inspection_date' => ['required', 'date'], + 'mileage_km' => ['required', 'integer', 'min:0'], + 'status' => ['required', 'string', 'in:aktywny,serwis,używany,sprzedany'], + ]); + + Vehicle::create($validated); + + return redirect()->route('flota')->with('status', 'Pojazd został dodany.'); + } + + public function edit(Request $request, Vehicle $vehicle): View + { + $this->authorizeVehicleManagement($request); + $this->authorizeVehicleAccess($request, $vehicle); + + $users = User::orderBy('name')->orderBy('surname')->get(); + + return view('flota.edit', compact('vehicle', 'users')); + } + + public function update(Request $request, Vehicle $vehicle) + { + $this->authorizeVehicleManagement($request); + $this->authorizeVehicleAccess($request, $vehicle); + + $validated = $request->validate([ + 'brand' => ['required', 'string', 'max:255'], + 'model' => ['nullable', 'string', 'max:255'], + 'registration_number' => ['required', 'string', 'max:50', 'unique:vehicles,registration_number,' . $vehicle->id], + 'user_id' => ['required', 'integer', 'exists:users,id'], + 'year' => ['required', 'integer', 'min:1900', 'max:2100'], + 'purchase_date' => ['required', 'date'], + 'oc_expiry_date' => ['required', 'date'], + 'oil_change_km' => ['required', 'integer', 'min:0'], + 'insurance_provider' => ['required', 'string', 'max:255'], + 'technical_inspection_date' => ['required', 'date'], + 'mileage_km' => ['required', 'integer', 'min:0'], + 'status' => ['required', 'string', 'in:aktywny,serwis,używany,sprzedany'], + ]); + + $vehicle->update($validated); + + return redirect()->route('flota')->with('status', 'Pojazd został zaktualizowany.'); + } + + public function destroy(Request $request, Vehicle $vehicle) + { + $this->authorizeVehicleManagement($request); + $this->authorizeVehicleAccess($request, $vehicle); + + $vehicle->delete(); + + return redirect()->route('flota')->with('status', 'Pojazd został usunięty.'); + } + + private function authorizeVehicleManagement(Request $request): void + { + abort_unless($request->user()->can('flota_access'), 403); + } + + private function authorizeVehicleAccess(Request $request, Vehicle $vehicle): void + { + if ($request->user()->hasRole('pracownik') && $vehicle->user_id !== $request->user()->id) { + abort(403); + } + } +} diff --git a/app/Models/Vehicle.php b/app/Models/Vehicle.php new file mode 100644 index 0000000..10cf919 --- /dev/null +++ b/app/Models/Vehicle.php @@ -0,0 +1,44 @@ + 'date', + 'oc_expiry_date' => 'date', + 'technical_inspection_date' => 'date', + 'year' => 'integer', + 'oil_change_km' => 'integer', + 'mileage_km' => 'integer', + ]; + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/database/migrations/2026_08_30_190641_create_vehicles_table.php b/database/migrations/2026_08_30_190641_create_vehicles_table.php new file mode 100644 index 0000000..aa4d758 --- /dev/null +++ b/database/migrations/2026_08_30_190641_create_vehicles_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('brand'); + $table->string('model')->nullable(); + $table->string('registration_number')->unique(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->unsignedSmallInteger('year'); + $table->date('purchase_date'); + $table->date('oc_expiry_date'); + $table->unsignedInteger('oil_change_km'); + $table->string('insurance_provider'); + $table->date('technical_inspection_date'); + $table->unsignedInteger('mileage_km'); + $table->string('status'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('vehicles'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 938673c..4b14c2b 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -31,6 +31,7 @@ class DatabaseSeeder extends Seeder $this->call([ AdvanceTypeSeeder::class, + VehicleSeeder::class, ]); // 2. Tworzenie ról (Roles) diff --git a/database/seeders/VehicleSeeder.php b/database/seeders/VehicleSeeder.php new file mode 100644 index 0000000..27d40a4 --- /dev/null +++ b/database/seeders/VehicleSeeder.php @@ -0,0 +1,126 @@ +orderBy('name')->orderBy('surname')->get(); + + if ($users->isEmpty()) { + return; + } + + $sampleVehicles = [ + [ + 'brand' => 'Toyota', + 'model' => 'Corolla', + 'registration_number' => 'DW1234A', + 'user_id' => $users->first()->id, + 'year' => 2021, + 'purchase_date' => '2021-05-12', + 'oc_expiry_date' => '2027-05-12', + 'oil_change_km' => 12000, + 'insurance_provider' => 'PZU', + 'technical_inspection_date' => '2026-12-01', + 'mileage_km' => 95000, + 'status' => 'używany', + ], + [ + 'brand' => 'Ford', + 'model' => 'Transit', + 'registration_number' => 'WA9876Z', + 'user_id' => $users->get(1)->id ?? $users->first()->id, + 'year' => 2019, + 'purchase_date' => '2019-07-20', + 'oc_expiry_date' => '2027-07-20', + 'oil_change_km' => 8000, + 'insurance_provider' => 'Warta', + 'technical_inspection_date' => '2026-09-15', + 'mileage_km' => 175000, + 'status' => 'aktywny', + ], + [ + 'brand' => 'BMW', + 'model' => 'X5', + 'registration_number' => 'PO1111A', + 'user_id' => $users->last()->id, + 'year' => 2022, + 'purchase_date' => '2022-02-18', + 'oc_expiry_date' => '2028-02-18', + 'oil_change_km' => 15000, + 'insurance_provider' => 'PZU', + 'technical_inspection_date' => '2027-02-11', + 'mileage_km' => 64000, + 'status' => 'serwis', + ], + [ + 'brand' => 'Mercedes', + 'model' => 'Vito', + 'registration_number' => 'GD2044K', + 'user_id' => $users->first()->id, + 'year' => 2020, + 'purchase_date' => '2020-11-04', + 'oc_expiry_date' => '2027-11-04', + 'oil_change_km' => 10000, + 'insurance_provider' => 'PZU', + 'technical_inspection_date' => '2026-10-20', + 'mileage_km' => 118500, + 'status' => 'aktywny', + ], + [ + 'brand' => 'Volkswagen', + 'model' => 'Crafter', + 'registration_number' => 'LU5567N', + 'user_id' => $users->get(1)->id ?? $users->first()->id, + 'year' => 2018, + 'purchase_date' => '2018-06-15', + 'oc_expiry_date' => '2026-12-15', + 'oil_change_km' => 9000, + 'insurance_provider' => 'Warta', + 'technical_inspection_date' => '2026-08-30', + 'mileage_km' => 214000, + 'status' => 'serwis', + ], + [ + 'brand' => 'Skoda', + 'model' => 'Superb', + 'registration_number' => 'TR9912R', + 'user_id' => $users->last()->id, + 'year' => 2023, + 'purchase_date' => '2023-03-22', + 'oc_expiry_date' => '2028-03-22', + 'oil_change_km' => 18000, + 'insurance_provider' => 'PZU', + 'technical_inspection_date' => '2027-04-12', + 'mileage_km' => 42000, + 'status' => 'używany', + ], + [ + 'brand' => 'Renault', + 'model' => 'Traffic', + 'registration_number' => 'WK3345P', + 'user_id' => $users->first()->id, + 'year' => 2017, + 'purchase_date' => '2017-09-01', + 'oc_expiry_date' => '2026-09-01', + 'oil_change_km' => 11000, + 'insurance_provider' => 'Hestia', + 'technical_inspection_date' => '2026-11-05', + 'mileage_km' => 188400, + 'status' => 'sprzedany', + ], + ]; + + foreach ($sampleVehicles as $vehicle) { + Vehicle::firstOrCreate([ + 'registration_number' => $vehicle['registration_number'], + ], $vehicle); + } + } +} diff --git a/resources/views/flota.blade.php b/resources/views/flota.blade.php index 3964d15..a6340f0 100644 --- a/resources/views/flota.blade.php +++ b/resources/views/flota.blade.php @@ -5,11 +5,113 @@ + @php + $showActions = !auth()->user()->hasRole('pracownik'); + @endphp +
-
+
- Tu będzie Flota +
+

Lista pojazdów

+ + @if(!auth()->user()->hasRole('pracownik')) + + + Dodaj pojazd + + @endif +
+ +
+
+
+ +
+ + + + @if(request('search')) + + Wyczyść + + @endif +
+
+ + @if(session('status')) +
+ {{ session('status') }} +
+ @endif + +
+
+ + + + + + + + + + + + + + + + @if($showActions) + + @endif + + + + @forelse($vehicles as $vehicle) + + + + + + + + + + + + + + @if($showActions) + + @endif + + @empty + + + + @endforelse + +
MarkaModelNr rejestracyjnyWłaścicielRokZakupOC doUbezpieczycielPrzeglądOlejPrzebiegStatusAkcje
{{ $vehicle->brand }}{{ $vehicle->model ?? '-' }}{{ $vehicle->registration_number }}{{ $vehicle->user ? $vehicle->user->name.' '.$vehicle->user->surname : '-' }}{{ $vehicle->year }}{{ $vehicle->purchase_date?->format('d.m.Y') ?? '-' }}{{ $vehicle->oc_expiry_date?->format('d.m.Y') ?? '-' }}{{ $vehicle->insurance_provider ?? '-' }}{{ $vehicle->technical_inspection_date?->format('d.m.Y') ?? '-' }}{{ number_format($vehicle->oil_change_km, 0, ',', ' ') }} km{{ number_format($vehicle->mileage_km, 0, ',', ' ') }} km + + {{ $vehicle->status }} + + + Edytuj +
+ @csrf + @method('DELETE') + +
+
Brak pojazdów do wyświetlenia.
+
+
+ {{ $vehicles->links() }} +
+
+
- \ No newline at end of file + diff --git a/resources/views/flota/create.blade.php b/resources/views/flota/create.blade.php new file mode 100644 index 0000000..784bf0b --- /dev/null +++ b/resources/views/flota/create.blade.php @@ -0,0 +1,93 @@ + + +

+ {{ __('Dodaj pojazd') }} +

+
+ +
+
+
+
+ @csrf + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ Anuluj + +
+
+
+
+
+
diff --git a/resources/views/flota/edit.blade.php b/resources/views/flota/edit.blade.php new file mode 100644 index 0000000..13933b0 --- /dev/null +++ b/resources/views/flota/edit.blade.php @@ -0,0 +1,94 @@ + + +

+ {{ __('Edytuj pojazd') }} +

+
+ +
+
+
+
+ @csrf + @method('PUT') + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ Anuluj + +
+
+
+
+
+
diff --git a/routes/web.php b/routes/web.php index 68e0c9c..458260b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -5,6 +5,7 @@ use App\Http\Controllers\AdvanceController; use App\Http\Controllers\ProfileController; use App\Http\Controllers\SettingsController; use App\Http\Controllers\UserController; +use App\Http\Controllers\VehicleController; use Illuminate\Support\Facades\Route; Route::get('/', function () { @@ -50,7 +51,12 @@ Route::middleware(['auth', 'verified'])->group(function () { // Moduł Flota Route::middleware(['can:flota_access'])->group(function () { - Route::view('/flota', 'flota')->name('flota'); + Route::get('/flota', [VehicleController::class, 'index'])->name('flota'); + Route::get('/flota/create', [VehicleController::class, 'create'])->name('flota.create'); + Route::post('/flota', [VehicleController::class, 'store'])->name('flota.store'); + Route::get('/flota/{vehicle}/edit', [VehicleController::class, 'edit'])->name('flota.edit'); + Route::put('/flota/{vehicle}', [VehicleController::class, 'update'])->name('flota.update'); + Route::delete('/flota/{vehicle}', [VehicleController::class, 'destroy'])->name('flota.destroy'); }); // Moduł Zaliczki diff --git a/tests/Feature/VehicleTest.php b/tests/Feature/VehicleTest.php new file mode 100644 index 0000000..498ff37 --- /dev/null +++ b/tests/Feature/VehicleTest.php @@ -0,0 +1,131 @@ + 'flota_access']); +}); + +function createFleetUser(string $roleName = 'pracownik'): User +{ + $user = User::factory()->create(); + $role = Role::firstOrCreate(['name' => $roleName]); + $role->givePermissionTo('flota_access'); + $user->assignRole($role); + + return $user; +} + +test('manager sees all vehicles and employee sees only assigned vehicles', function () { + $manager = createFleetUser('kierownik'); + $employee = createFleetUser('pracownik'); + + $assignedVehicle = Vehicle::create([ + 'brand' => 'Toyota', + 'registration_number' => 'DW1234A', + 'user_id' => $employee->id, + 'year' => 2020, + 'purchase_date' => '2020-06-01', + 'oc_expiry_date' => '2027-06-01', + 'oil_change_km' => 10000, + 'insurance_provider' => 'Warta', + 'technical_inspection_date' => '2026-09-15', + 'mileage_km' => 120000, + 'status' => 'używany', + ]); + + $otherVehicle = Vehicle::create([ + 'brand' => 'Ford', + 'registration_number' => 'WA9876Z', + 'user_id' => $manager->id, + 'year' => 2018, + 'purchase_date' => '2018-03-01', + 'oc_expiry_date' => '2027-03-01', + 'oil_change_km' => 8000, + 'insurance_provider' => 'PZU', + 'technical_inspection_date' => '2026-10-01', + 'mileage_km' => 180000, + 'status' => 'używany', + ]); + + $managerResponse = $this->actingAs($manager)->get(route('flota')); + $managerResponse->assertOk(); + $managerResponse->assertSee('Toyota'); + $managerResponse->assertSee('Ford'); + + $employeeResponse = $this->actingAs($employee)->get(route('flota')); + $employeeResponse->assertOk(); + $employeeResponse->assertSee('Toyota'); + $employeeResponse->assertDontSee('Ford'); + + $this->assertDatabaseHas('vehicles', ['id' => $assignedVehicle->id, 'brand' => 'Toyota']); + $this->assertDatabaseHas('vehicles', ['id' => $otherVehicle->id, 'brand' => 'Ford']); +}); + +test('manager can create and edit a vehicle', function () { + $manager = createFleetUser('kierownik'); + $user = User::factory()->create(); + + $createResponse = $this->actingAs($manager)->post(route('flota.store'), [ + 'brand' => 'BMW', + 'registration_number' => 'PO1111A', + 'user_id' => $user->id, + 'year' => 2022, + 'purchase_date' => '2022-02-10', + 'oc_expiry_date' => '2027-02-10', + 'oil_change_km' => 15000, + 'insurance_provider' => 'PZU', + 'technical_inspection_date' => '2026-11-20', + 'mileage_km' => 50000, + 'status' => 'używany', + ]); + + $createResponse->assertRedirect(route('flota')); + $this->assertDatabaseHas('vehicles', ['registration_number' => 'PO1111A', 'brand' => 'BMW']); + + $vehicle = Vehicle::where('registration_number', 'PO1111A')->first(); + + $editResponse = $this->actingAs($manager)->put(route('flota.update', $vehicle), [ + 'brand' => 'BMW', + 'registration_number' => 'PO1111A', + 'user_id' => $user->id, + 'year' => 2023, + 'purchase_date' => '2022-02-10', + 'oc_expiry_date' => '2028-02-10', + 'oil_change_km' => 15000, + 'insurance_provider' => 'Warta', + 'technical_inspection_date' => '2027-11-20', + 'mileage_km' => 60000, + 'status' => 'sprzedany', + ]); + + $editResponse->assertRedirect(route('flota')); + $this->assertDatabaseHas('vehicles', ['id' => $vehicle->id, 'year' => 2023, 'status' => 'sprzedany']); +}); + +test('manager can delete a vehicle', function () { + $manager = createFleetUser('kierownik'); + $user = User::factory()->create(); + + $vehicle = Vehicle::create([ + 'brand' => 'Audi', + 'registration_number' => 'KR9999X', + 'user_id' => $user->id, + 'year' => 2019, + 'purchase_date' => '2019-01-01', + 'oc_expiry_date' => '2027-01-01', + 'oil_change_km' => 12000, + 'insurance_provider' => 'Warta', + 'technical_inspection_date' => '2026-12-01', + 'mileage_km' => 99000, + 'status' => 'używany', + ]); + + $response = $this->actingAs($manager)->delete(route('flota.destroy', $vehicle)); + + $response->assertRedirect(route('flota')); + $this->assertDatabaseMissing('vehicles', ['id' => $vehicle->id]); +});