diff --git a/app/Http/Controllers/AssetController.php b/app/Http/Controllers/AssetController.php new file mode 100644 index 0000000..73174d7 --- /dev/null +++ b/app/Http/Controllers/AssetController.php @@ -0,0 +1,90 @@ +orderBy('created_at', 'desc') + ->get(); + + return view('assets.index', [ + 'assets' => $assets, + 'statuses' => AssetStatus::orderBy('name')->get(), + 'classes' => AssetClass::orderBy('name')->get(), + 'categories' => AssetCategory::orderBy('name')->get(), + 'units' => Unit::orderBy('name')->get(), + 'departments' => Department::orderBy('name')->get(), + 'peopleInCharge' => PersonInCharge::orderBy('name')->get(), + 'users' => AssetUser::orderBy('name')->get(), + 'locations' => AssetLocation::orderBy('name')->get(), + 'warranties' => Warranty::orderBy('duration_months')->get(), + ]); + } + + public function store(Request $request): RedirectResponse + { + $data = $this->validateRequest($request); + $asset = $this->assets->create($data); + + return back()->with('success', "Aset {$asset->code} berhasil ditambahkan."); + } + + public function update(Request $request, Asset $asset): RedirectResponse + { + $data = $this->validateRequest($request, $asset->id); + $this->assets->update($asset, $data); + + return back()->with('success', "Aset {$asset->code} berhasil diperbarui."); + } + + public function history(Asset $asset): View + { + $asset->load('histories'); + + return view('assets.history', compact('asset')); + } + + private function validateRequest(Request $request, ?string $assetId = null): array + { + return $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'serial_number' => ['nullable', 'string', 'max:255'], + 'description' => ['nullable', 'string'], + 'asset_status_id' => ['nullable', 'uuid', 'exists:asset_statuses,id'], + 'asset_class_id' => ['nullable', 'uuid', 'exists:asset_classes,id'], + 'asset_category_id' => ['nullable', 'uuid', 'exists:asset_categories,id'], + 'unit_id' => ['nullable', 'uuid', 'exists:units,id'], + 'department_id' => ['nullable', 'uuid', 'exists:departments,id'], + 'person_in_charge_id' => ['nullable', 'uuid', 'exists:people_in_charge,id'], + 'asset_user_id' => ['nullable', 'uuid', 'exists:asset_users,id'], + 'asset_location_id' => ['nullable', 'uuid', 'exists:asset_locations,id'], + 'warranty_id' => ['nullable', 'uuid', 'exists:warranties,id'], + 'purchase_date' => ['nullable', 'date'], + 'warranty_end' => ['nullable', 'date'], + 'cost' => ['nullable', 'numeric', 'min:0'], + 'metadata' => ['nullable', 'array'], + ]); + } +} diff --git a/app/Models/Asset.php b/app/Models/Asset.php new file mode 100644 index 0000000..b0fdba5 --- /dev/null +++ b/app/Models/Asset.php @@ -0,0 +1,90 @@ + 'date', + 'warranty_end' => 'date', + 'metadata' => 'array', + ]; + + public function status() + { + return $this->belongsTo(AssetStatus::class, 'asset_status_id'); + } + + public function class() + { + return $this->belongsTo(AssetClass::class, 'asset_class_id'); + } + + public function category() + { + return $this->belongsTo(AssetCategory::class, 'asset_category_id'); + } + + public function unit() + { + return $this->belongsTo(Unit::class); + } + + public function department() + { + return $this->belongsTo(Department::class); + } + + public function personInCharge() + { + return $this->belongsTo(PersonInCharge::class); + } + + public function user() + { + return $this->belongsTo(AssetUser::class, 'asset_user_id'); + } + + public function location() + { + return $this->belongsTo(AssetLocation::class, 'asset_location_id'); + } + + public function warranty() + { + return $this->belongsTo(Warranty::class); + } + + public function histories() + { + return $this->hasMany(AssetHistory::class); + } +} diff --git a/app/Models/AssetHistory.php b/app/Models/AssetHistory.php new file mode 100644 index 0000000..b5871b5 --- /dev/null +++ b/app/Models/AssetHistory.php @@ -0,0 +1,29 @@ + 'array', + ]; + + public function asset() + { + return $this->belongsTo(Asset::class); + } +} diff --git a/app/Models/PersonInCharge.php b/app/Models/PersonInCharge.php index 39924e3..16df19f 100644 --- a/app/Models/PersonInCharge.php +++ b/app/Models/PersonInCharge.php @@ -10,6 +10,8 @@ class PersonInCharge extends Model { use HasFactory, HasUuids; + protected $table = 'person_in_charges'; + protected $fillable = [ 'name', 'email', diff --git a/app/Services/Asset/AssetService.php b/app/Services/Asset/AssetService.php new file mode 100644 index 0000000..b79fb4b --- /dev/null +++ b/app/Services/Asset/AssetService.php @@ -0,0 +1,80 @@ +generateCode(); + $data['qr_token'] = $data['qr_token'] ?? Str::uuid()->toString(); + + // Hitung warranty_end jika ada warranty dan purchase_date + if (!empty($data['purchase_date']) && empty($data['warranty_end'])) { + $days = (int) $this->settings->get('asset.warranty_reminder_days', 0); + if ($days > 0) { + $data['warranty_end'] = now()->parse($data['purchase_date'])->addDays($days); + } + } + + $asset = Asset::create($data); + + $this->logHistory($asset, 'created', 'Aset dibuat', $data); + + return $asset; + }); + } + + /** + * Perbarui aset dan catat riwayat. + */ + public function update(Asset $asset, array $data): Asset + { + return DB::transaction(function () use ($asset, $data) { + $asset->update($data); + $this->logHistory($asset, 'updated', 'Aset diperbarui', $data); + + return $asset; + }); + } + + private function generateCode(): string + { + $prefix = $this->settings->get('asset.code_prefix', 'AST'); + $last = Asset::where('code', 'like', $prefix.'-%') + ->orderByDesc('code') + ->value('code'); + + $nextNumber = 1; + if ($last && preg_match('/\d+$/', $last, $m)) { + $nextNumber = ((int) $m[0]) + 1; + } + + return sprintf('%s-%05d', $prefix, $nextNumber); + } + + private function logHistory(Asset $asset, string $action, string $description, array $payload = []): void + { + AssetHistory::create([ + 'asset_id' => $asset->id, + 'action' => $action, + 'description' => $description, + 'changed_by' => auth()->id(), + 'payload' => $payload, + ]); + } +} diff --git a/database/migrations/2025_12_03_000000_create_master_tables.php b/database/migrations/2025_12_03_000000_create_master_tables.php index e30567d..a16c400 100644 --- a/database/migrations/2025_12_03_000000_create_master_tables.php +++ b/database/migrations/2025_12_03_000000_create_master_tables.php @@ -84,10 +84,46 @@ return new class extends Migration $table->string('notes')->nullable(); $table->timestamps(); }); + + Schema::create('assets', function (Blueprint $table) { + $table->uuid('id')->primary(); + $table->string('code')->unique(); + $table->string('name'); + $table->string('serial_number')->nullable(); + $table->text('description')->nullable(); + $table->foreignUuid('asset_status_id')->nullable()->constrained('asset_statuses')->nullOnDelete(); + $table->foreignUuid('asset_class_id')->nullable()->constrained('asset_classes')->nullOnDelete(); + $table->foreignUuid('asset_category_id')->nullable()->constrained('asset_categories')->nullOnDelete(); + $table->foreignUuid('unit_id')->nullable()->constrained('units')->nullOnDelete(); + $table->foreignUuid('department_id')->nullable()->constrained('departments')->nullOnDelete(); + $table->foreignUuid('person_in_charge_id')->nullable()->constrained('person_in_charges')->nullOnDelete(); + $table->foreignUuid('asset_user_id')->nullable()->constrained('asset_users')->nullOnDelete(); + $table->foreignUuid('asset_location_id')->nullable()->constrained('asset_locations')->nullOnDelete(); + $table->foreignUuid('warranty_id')->nullable()->constrained('warranties')->nullOnDelete(); + $table->date('purchase_date')->nullable(); + $table->date('warranty_end')->nullable(); + $table->decimal('cost', 15, 2)->nullable(); + $table->string('qr_token')->unique(); + $table->string('qr_path')->nullable(); + $table->json('metadata')->nullable(); + $table->timestamps(); + }); + + Schema::create('asset_histories', function (Blueprint $table) { + $table->uuid('id')->primary(); + $table->foreignUuid('asset_id')->constrained('assets')->cascadeOnDelete(); + $table->string('action'); + $table->text('description')->nullable(); + $table->uuid('changed_by')->nullable(); + $table->json('payload')->nullable(); + $table->timestamps(); + }); } public function down(): void { + Schema::dropIfExists('asset_histories'); + Schema::dropIfExists('assets'); Schema::dropIfExists('warranties'); Schema::dropIfExists('asset_locations'); Schema::dropIfExists('asset_categories'); diff --git a/resources/views/assets/history.blade.php b/resources/views/assets/history.blade.php new file mode 100644 index 0000000..82fbba5 --- /dev/null +++ b/resources/views/assets/history.blade.php @@ -0,0 +1,45 @@ +@extends('layouts.master') + +@section('content') +
+ +| Waktu | +Aksi | +Deskripsi | +Payload | +
|---|---|---|---|
| {{ $history->created_at->format('d/m/Y H:i') }} | +{{ $history->action }} | +{{ $history->description }} | +{{ json_encode($history->payload, JSON_PRETTY_PRINT) }} |
+
| Belum ada riwayat. | +|||
| Kode | +Nama | +Status | +Kategori | +Lokasi | +QR Token | +Aksi | +
|---|---|---|---|---|---|---|
| {{ $asset->code }} | +{{ $asset->name }} | +{{ $asset->status?->name ?: '-' }} | +{{ $asset->category?->name ?: '-' }} | +{{ $asset->location?->name ?: '-' }} | +{{ $asset->qr_token }} |
+
+
+
+ History
+
+ |
+
| Belum ada aset. | +||||||