From eebeb02b916ff92650644756946a7eef4a6d0465 Mon Sep 17 00:00:00 2001 From: Arya Dwi Putra Date: Mon, 1 Dec 2025 08:45:24 +0700 Subject: [PATCH] Add CRUD UI/controllers for master data --- .../Master/AssetCategoryController.php | 64 +++++++ .../Master/AssetClassController.php | 59 +++++++ .../Master/AssetLocationController.php | 64 +++++++ .../Master/AssetStatusController.php | 59 +++++++ .../Master/AssetUserController.php | 68 ++++++++ .../Master/DepartmentController.php | 59 +++++++ .../Master/PersonInChargeController.php | 62 +++++++ .../Controllers/Master/UnitController.php | 59 +++++++ .../Controllers/Master/WarrantyController.php | 59 +++++++ app/Models/AssetUser.php | 5 + .../layouts/components/main-sidebar.blade.php | 27 +++ resources/views/masters/simple.blade.php | 161 ++++++++++++++++++ resources/views/masters/simple_form.blade.php | 26 +++ routes/web.php | 19 +++ tests/Feature/MasterDataTest.php | 40 +++++ 15 files changed, 831 insertions(+) create mode 100644 app/Http/Controllers/Master/AssetCategoryController.php create mode 100644 app/Http/Controllers/Master/AssetClassController.php create mode 100644 app/Http/Controllers/Master/AssetLocationController.php create mode 100644 app/Http/Controllers/Master/AssetStatusController.php create mode 100644 app/Http/Controllers/Master/AssetUserController.php create mode 100644 app/Http/Controllers/Master/DepartmentController.php create mode 100644 app/Http/Controllers/Master/PersonInChargeController.php create mode 100644 app/Http/Controllers/Master/UnitController.php create mode 100644 app/Http/Controllers/Master/WarrantyController.php create mode 100644 resources/views/masters/simple.blade.php create mode 100644 resources/views/masters/simple_form.blade.php create mode 100644 tests/Feature/MasterDataTest.php diff --git a/app/Http/Controllers/Master/AssetCategoryController.php b/app/Http/Controllers/Master/AssetCategoryController.php new file mode 100644 index 0000000..55fd35a --- /dev/null +++ b/app/Http/Controllers/Master/AssetCategoryController.php @@ -0,0 +1,64 @@ +get()->pluck('name', 'id')->toArray(); + + return view('masters.simple', [ + 'title' => 'Kategori Aset', + 'routeName' => 'asset-categories', + 'items' => AssetCategory::orderBy('name')->get(), + 'fields' => [ + ['name' => 'name', 'label' => 'Nama', 'required' => true], + ['name' => 'code', 'label' => 'Kode', 'required' => true], + ['name' => 'parent_id', 'label' => 'Parent', 'type' => 'select', 'options' => $options], + ['name' => 'description', 'label' => 'Deskripsi', 'type' => 'textarea'], + ], + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:asset_categories,code'], + 'parent_id' => ['nullable', 'uuid', 'exists:asset_categories,id'], + 'description' => ['nullable', 'string'], + ]); + + AssetCategory::create($data); + + return back()->with('success', 'Kategori aset berhasil ditambahkan.'); + } + + public function update(Request $request, AssetCategory $asset_category): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:asset_categories,code,'.$asset_category->id], + 'parent_id' => ['nullable', 'uuid', 'exists:asset_categories,id'], + 'description' => ['nullable', 'string'], + ]); + + $asset_category->update($data); + + return back()->with('success', 'Kategori aset berhasil diperbarui.'); + } + + public function destroy(AssetCategory $asset_category): RedirectResponse + { + $asset_category->delete(); + + return back()->with('success', 'Kategori aset berhasil dihapus.'); + } +} diff --git a/app/Http/Controllers/Master/AssetClassController.php b/app/Http/Controllers/Master/AssetClassController.php new file mode 100644 index 0000000..ea94cb1 --- /dev/null +++ b/app/Http/Controllers/Master/AssetClassController.php @@ -0,0 +1,59 @@ + 'Kelas Aset', + 'routeName' => 'asset-classes', + 'items' => AssetClass::orderBy('name')->get(), + 'fields' => [ + ['name' => 'name', 'label' => 'Nama', 'required' => true], + ['name' => 'code', 'label' => 'Kode', 'required' => true], + ['name' => 'description', 'label' => 'Deskripsi', 'type' => 'textarea'], + ], + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:asset_classes,code'], + 'description' => ['nullable', 'string'], + ]); + + AssetClass::create($data); + + return back()->with('success', 'Kelas aset berhasil ditambahkan.'); + } + + public function update(Request $request, AssetClass $asset_class): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:asset_classes,code,'.$asset_class->id], + 'description' => ['nullable', 'string'], + ]); + + $asset_class->update($data); + + return back()->with('success', 'Kelas aset berhasil diperbarui.'); + } + + public function destroy(AssetClass $asset_class): RedirectResponse + { + $asset_class->delete(); + + return back()->with('success', 'Kelas aset berhasil dihapus.'); + } +} diff --git a/app/Http/Controllers/Master/AssetLocationController.php b/app/Http/Controllers/Master/AssetLocationController.php new file mode 100644 index 0000000..082d8dd --- /dev/null +++ b/app/Http/Controllers/Master/AssetLocationController.php @@ -0,0 +1,64 @@ +get()->pluck('name', 'id')->toArray(); + + return view('masters.simple', [ + 'title' => 'Lokasi Aset', + 'routeName' => 'asset-locations', + 'items' => AssetLocation::orderBy('name')->get(), + 'fields' => [ + ['name' => 'name', 'label' => 'Nama', 'required' => true], + ['name' => 'code', 'label' => 'Kode', 'required' => true], + ['name' => 'parent_id', 'label' => 'Parent', 'type' => 'select', 'options' => $options], + ['name' => 'description', 'label' => 'Deskripsi', 'type' => 'textarea'], + ], + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:asset_locations,code'], + 'parent_id' => ['nullable', 'uuid', 'exists:asset_locations,id'], + 'description' => ['nullable', 'string'], + ]); + + AssetLocation::create($data); + + return back()->with('success', 'Lokasi aset berhasil ditambahkan.'); + } + + public function update(Request $request, AssetLocation $asset_location): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:asset_locations,code,'.$asset_location->id], + 'parent_id' => ['nullable', 'uuid', 'exists:asset_locations,id'], + 'description' => ['nullable', 'string'], + ]); + + $asset_location->update($data); + + return back()->with('success', 'Lokasi aset berhasil diperbarui.'); + } + + public function destroy(AssetLocation $asset_location): RedirectResponse + { + $asset_location->delete(); + + return back()->with('success', 'Lokasi aset berhasil dihapus.'); + } +} diff --git a/app/Http/Controllers/Master/AssetStatusController.php b/app/Http/Controllers/Master/AssetStatusController.php new file mode 100644 index 0000000..be57987 --- /dev/null +++ b/app/Http/Controllers/Master/AssetStatusController.php @@ -0,0 +1,59 @@ + 'Status Aset', + 'routeName' => 'asset-statuses', + 'items' => AssetStatus::orderBy('name')->get(), + 'fields' => [ + ['name' => 'name', 'label' => 'Nama', 'required' => true], + ['name' => 'code', 'label' => 'Kode', 'required' => true], + ['name' => 'description', 'label' => 'Deskripsi', 'type' => 'textarea'], + ], + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:asset_statuses,code'], + 'description' => ['nullable', 'string'], + ]); + + AssetStatus::create($data); + + return back()->with('success', 'Status aset berhasil ditambahkan.'); + } + + public function update(Request $request, AssetStatus $asset_status): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:asset_statuses,code,'.$asset_status->id], + 'description' => ['nullable', 'string'], + ]); + + $asset_status->update($data); + + return back()->with('success', 'Status aset berhasil diperbarui.'); + } + + public function destroy(AssetStatus $asset_status): RedirectResponse + { + $asset_status->delete(); + + return back()->with('success', 'Status aset berhasil dihapus.'); + } +} diff --git a/app/Http/Controllers/Master/AssetUserController.php b/app/Http/Controllers/Master/AssetUserController.php new file mode 100644 index 0000000..6e3d3f4 --- /dev/null +++ b/app/Http/Controllers/Master/AssetUserController.php @@ -0,0 +1,68 @@ +get()->pluck('name', 'id')->toArray(); + + return view('masters.simple', [ + 'title' => 'Pengguna Aset', + 'routeName' => 'asset-users', + 'items' => AssetUser::with('department')->orderBy('name')->get(), + 'fields' => [ + ['name' => 'name', 'label' => 'Nama', 'required' => true], + ['name' => 'email', 'label' => 'Email'], + ['name' => 'phone', 'label' => 'Telepon'], + ['name' => 'department_id', 'label' => 'Departemen', 'type' => 'select', 'options' => $departments], + ['name' => 'notes', 'label' => 'Catatan', 'type' => 'textarea'], + ], + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['nullable', 'email', 'max:255'], + 'phone' => ['nullable', 'string', 'max:30'], + 'department_id' => ['nullable', 'uuid', 'exists:departments,id'], + 'notes' => ['nullable', 'string'], + ]); + + AssetUser::create($data); + + return back()->with('success', 'Pengguna aset berhasil ditambahkan.'); + } + + public function update(Request $request, AssetUser $asset_user): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['nullable', 'email', 'max:255'], + 'phone' => ['nullable', 'string', 'max:30'], + 'department_id' => ['nullable', 'uuid', 'exists:departments,id'], + 'notes' => ['nullable', 'string'], + ]); + + $asset_user->update($data); + + return back()->with('success', 'Pengguna aset berhasil diperbarui.'); + } + + public function destroy(AssetUser $asset_user): RedirectResponse + { + $asset_user->delete(); + + return back()->with('success', 'Pengguna aset berhasil dihapus.'); + } +} diff --git a/app/Http/Controllers/Master/DepartmentController.php b/app/Http/Controllers/Master/DepartmentController.php new file mode 100644 index 0000000..b6c9628 --- /dev/null +++ b/app/Http/Controllers/Master/DepartmentController.php @@ -0,0 +1,59 @@ + 'Departemen', + 'routeName' => 'departments', + 'items' => Department::orderBy('name')->get(), + 'fields' => [ + ['name' => 'name', 'label' => 'Nama', 'required' => true], + ['name' => 'code', 'label' => 'Kode', 'required' => true], + ['name' => 'description', 'label' => 'Deskripsi', 'type' => 'textarea'], + ], + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:departments,code'], + 'description' => ['nullable', 'string'], + ]); + + Department::create($data); + + return back()->with('success', 'Departemen berhasil ditambahkan.'); + } + + public function update(Request $request, Department $department): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'code' => ['required', 'string', 'max:50', 'unique:departments,code,'.$department->id], + 'description' => ['nullable', 'string'], + ]); + + $department->update($data); + + return back()->with('success', 'Departemen berhasil diperbarui.'); + } + + public function destroy(Department $department): RedirectResponse + { + $department->delete(); + + return back()->with('success', 'Departemen berhasil dihapus.'); + } +} diff --git a/app/Http/Controllers/Master/PersonInChargeController.php b/app/Http/Controllers/Master/PersonInChargeController.php new file mode 100644 index 0000000..a4ccc35 --- /dev/null +++ b/app/Http/Controllers/Master/PersonInChargeController.php @@ -0,0 +1,62 @@ + 'Penanggung Jawab', + 'routeName' => 'people-in-charge', + 'items' => PersonInCharge::orderBy('name')->get(), + 'fields' => [ + ['name' => 'name', 'label' => 'Nama', 'required' => true], + ['name' => 'email', 'label' => 'Email'], + ['name' => 'phone', 'label' => 'Telepon'], + ['name' => 'notes', 'label' => 'Catatan', 'type' => 'textarea'], + ], + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['nullable', 'email', 'max:255'], + 'phone' => ['nullable', 'string', 'max:30'], + 'notes' => ['nullable', 'string'], + ]); + + PersonInCharge::create($data); + + return back()->with('success', 'Penanggung jawab berhasil ditambahkan.'); + } + + public function update(Request $request, PersonInCharge $people_in_charge): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['nullable', 'email', 'max:255'], + 'phone' => ['nullable', 'string', 'max:30'], + 'notes' => ['nullable', 'string'], + ]); + + $people_in_charge->update($data); + + return back()->with('success', 'Penanggung jawab berhasil diperbarui.'); + } + + public function destroy(PersonInCharge $people_in_charge): RedirectResponse + { + $people_in_charge->delete(); + + return back()->with('success', 'Penanggung jawab berhasil dihapus.'); + } +} diff --git a/app/Http/Controllers/Master/UnitController.php b/app/Http/Controllers/Master/UnitController.php new file mode 100644 index 0000000..9a4e666 --- /dev/null +++ b/app/Http/Controllers/Master/UnitController.php @@ -0,0 +1,59 @@ + 'Satuan Unit', + 'routeName' => 'units', + 'items' => Unit::orderBy('name')->get(), + 'fields' => [ + ['name' => 'name', 'label' => 'Nama', 'required' => true], + ['name' => 'symbol', 'label' => 'Simbol', 'required' => true], + ['name' => 'description', 'label' => 'Deskripsi', 'type' => 'textarea'], + ], + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'symbol' => ['required', 'string', 'max:20', 'unique:units,symbol'], + 'description' => ['nullable', 'string'], + ]); + + Unit::create($data); + + return back()->with('success', 'Satuan berhasil ditambahkan.'); + } + + public function update(Request $request, Unit $unit): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'symbol' => ['required', 'string', 'max:20', 'unique:units,symbol,'.$unit->id], + 'description' => ['nullable', 'string'], + ]); + + $unit->update($data); + + return back()->with('success', 'Satuan berhasil diperbarui.'); + } + + public function destroy(Unit $unit): RedirectResponse + { + $unit->delete(); + + return back()->with('success', 'Satuan berhasil dihapus.'); + } +} diff --git a/app/Http/Controllers/Master/WarrantyController.php b/app/Http/Controllers/Master/WarrantyController.php new file mode 100644 index 0000000..ab3ba7c --- /dev/null +++ b/app/Http/Controllers/Master/WarrantyController.php @@ -0,0 +1,59 @@ + 'Masa Garansi', + 'routeName' => 'warranties', + 'items' => Warranty::orderBy('duration_months')->get(), + 'fields' => [ + ['name' => 'name', 'label' => 'Nama', 'required' => true], + ['name' => 'duration_months', 'label' => 'Durasi (bulan)', 'type' => 'number', 'required' => true], + ['name' => 'notes', 'label' => 'Catatan', 'type' => 'textarea'], + ], + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'duration_months' => ['required', 'integer', 'min:1'], + 'notes' => ['nullable', 'string'], + ]); + + Warranty::create($data); + + return back()->with('success', 'Garansi berhasil ditambahkan.'); + } + + public function update(Request $request, Warranty $warranty): RedirectResponse + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'duration_months' => ['required', 'integer', 'min:1'], + 'notes' => ['nullable', 'string'], + ]); + + $warranty->update($data); + + return back()->with('success', 'Garansi berhasil diperbarui.'); + } + + public function destroy(Warranty $warranty): RedirectResponse + { + $warranty->delete(); + + return back()->with('success', 'Garansi berhasil dihapus.'); + } +} diff --git a/app/Models/AssetUser.php b/app/Models/AssetUser.php index 51477a4..8fa93f3 100644 --- a/app/Models/AssetUser.php +++ b/app/Models/AssetUser.php @@ -17,4 +17,9 @@ class AssetUser extends Model 'department_id', 'notes', ]; + + public function department() + { + return $this->belongsTo(Department::class); + } } diff --git a/resources/views/layouts/components/main-sidebar.blade.php b/resources/views/layouts/components/main-sidebar.blade.php index fc01f70..d9ac843 100644 --- a/resources/views/layouts/components/main-sidebar.blade.php +++ b/resources/views/layouts/components/main-sidebar.blade.php @@ -85,6 +85,33 @@ + +
  • Master Data
  • + + +
  • + + + + + + + Master Data + + + +
  • +
  • Web Apps
  • diff --git a/resources/views/masters/simple.blade.php b/resources/views/masters/simple.blade.php new file mode 100644 index 0000000..cc6113b --- /dev/null +++ b/resources/views/masters/simple.blade.php @@ -0,0 +1,161 @@ +@extends('layouts.master') + +@section('content') +
    +
    +

    {{ $title }}

    + @if(!empty($subtitle)) +

    {{ $subtitle }}

    + @endif +
    + +
    + +
    +
    +
    {{ $title }}
    +
    +
    + @if(session('success')) + + @endif + + @if($errors->any()) +
    +
      + @foreach($errors->all() as $error) +
    • {{ $error }}
    • + @endforeach +
    +
    + @endif + +
    + + + + @foreach($fields as $field) + + @endforeach + + + + + @forelse($items as $item) + + @foreach($fields as $field) + @php + $value = data_get($item, $field['name']); + if (($field['type'] ?? '') === 'select' && isset($field['options'][$value])) { + $value = $field['options'][$value]; + } + @endphp + + @endforeach + + + @empty + + + + @endforelse + +
    {{ $field['label'] }}Aksi
    {{ $value ?: '-' }} + +
    + @csrf + @method('DELETE') + +
    +
    Belum ada data.
    +
    +
    +
    + + {{-- Modal create --}} + + + {{-- Modal edit --}} + +@endsection + +@section('scripts') + +@endsection diff --git a/resources/views/masters/simple_form.blade.php b/resources/views/masters/simple_form.blade.php new file mode 100644 index 0000000..ee415ad --- /dev/null +++ b/resources/views/masters/simple_form.blade.php @@ -0,0 +1,26 @@ +
    +@foreach($fields as $field) + @php + $value = $values[$field['name']] ?? ''; + $required = $field['required'] ?? false; + $type = $field['type'] ?? 'text'; + @endphp +
    + + @if($type === 'textarea') + + @elseif($type === 'select') + + @elseif($type === 'number') + + @else + + @endif +
    +@endforeach +
    diff --git a/routes/web.php b/routes/web.php index 9f64ff4..9f4ed7a 100644 --- a/routes/web.php +++ b/routes/web.php @@ -7,6 +7,15 @@ use App\Http\Controllers\SystemSettingController; use App\Http\Controllers\RoleController; use App\Http\Controllers\PermissionController; use App\Http\Controllers\UserManagementController; +use App\Http\Controllers\Master\AssetStatusController; +use App\Http\Controllers\Master\AssetClassController; +use App\Http\Controllers\Master\UnitController; +use App\Http\Controllers\Master\DepartmentController; +use App\Http\Controllers\Master\PersonInChargeController; +use App\Http\Controllers\Master\AssetUserController; +use App\Http\Controllers\Master\AssetCategoryController; +use App\Http\Controllers\Master\AssetLocationController; +use App\Http\Controllers\Master\WarrantyController; use App\Http\Controllers\Controller; Route::get('/', function () { @@ -26,3 +35,13 @@ Route::resource('settings', SystemSettingController::class)->only(['index', 'sto Route::resource('roles', RoleController::class)->only(['index', 'store', 'update']); Route::resource('permissions', PermissionController::class)->only(['index', 'store', 'update']); Route::resource('users', UserManagementController::class)->only(['index', 'store', 'update']); + +Route::resource('asset-statuses', AssetStatusController::class)->only(['index', 'store', 'update', 'destroy']); +Route::resource('asset-classes', AssetClassController::class)->only(['index', 'store', 'update', 'destroy']); +Route::resource('units', UnitController::class)->only(['index', 'store', 'update', 'destroy']); +Route::resource('departments', DepartmentController::class)->only(['index', 'store', 'update', 'destroy']); +Route::resource('people-in-charge', PersonInChargeController::class)->only(['index', 'store', 'update', 'destroy']); +Route::resource('asset-users', AssetUserController::class)->only(['index', 'store', 'update', 'destroy']); +Route::resource('asset-categories', AssetCategoryController::class)->only(['index', 'store', 'update', 'destroy']); +Route::resource('asset-locations', AssetLocationController::class)->only(['index', 'store', 'update', 'destroy']); +Route::resource('warranties', WarrantyController::class)->only(['index', 'store', 'update', 'destroy']); diff --git a/tests/Feature/MasterDataTest.php b/tests/Feature/MasterDataTest.php new file mode 100644 index 0000000..501fb42 --- /dev/null +++ b/tests/Feature/MasterDataTest.php @@ -0,0 +1,40 @@ +post(route('asset-statuses.store'), [ + 'name' => 'Uji', + 'code' => 'TEST', + 'description' => 'Status uji', + ]); + + $response->assertRedirect(); + $this->assertDatabaseHas('asset_statuses', ['code' => 'TEST']); + } + + public function test_can_create_asset_user_with_department(): void + { + $dept = Department::create(['name' => 'QA', 'code' => 'QA', 'description' => 'Quality Assurance']); + + $response = $this->post(route('asset-users.store'), [ + 'name' => 'User QA', + 'email' => 'qa@example.com', + 'phone' => '0800000', + 'department_id' => $dept->id, + 'notes' => 'test', + ]); + + $response->assertRedirect(); + $this->assertDatabaseHas('asset_users', ['email' => 'qa@example.com', 'department_id' => $dept->id]); + } +}