diff --git a/app/Livewire/Studio/Catalog/Bottle/Audit.php b/app/Livewire/Studio/Catalog/Bottle/Audit.php
index 9db237d..a4618d0 100644
--- a/app/Livewire/Studio/Catalog/Bottle/Audit.php
+++ b/app/Livewire/Studio/Catalog/Bottle/Audit.php
@@ -62,7 +62,7 @@ public function render(): View
{
return view('components.sections.ui.audit', [
'pageTitle' => 'Audit '.$this->label,
- 'module' => 'bottle',
+ 'backRoute' => route('studio.catalog.bottle.index'),
]);
}
}
diff --git a/app/Livewire/Studio/Catalog/Perfume/Audit.php b/app/Livewire/Studio/Catalog/Perfume/Audit.php
index 655a040..cf0106c 100644
--- a/app/Livewire/Studio/Catalog/Perfume/Audit.php
+++ b/app/Livewire/Studio/Catalog/Perfume/Audit.php
@@ -62,7 +62,7 @@ public function render(): View
{
return view('components.sections.ui.audit', [
'pageTitle' => 'Audit '.$this->label,
- 'module' => 'perfume',
+ 'backRoute' => route('studio.catalog.perfume.index'),
]);
}
}
diff --git a/app/Livewire/Studio/Catalog/Product/Audit.php b/app/Livewire/Studio/Catalog/Product/Audit.php
index ee6360c..b7905b3 100644
--- a/app/Livewire/Studio/Catalog/Product/Audit.php
+++ b/app/Livewire/Studio/Catalog/Product/Audit.php
@@ -62,7 +62,7 @@ public function render(): View
{
return view('components.sections.ui.audit', [
'pageTitle' => 'Audit '.$this->label,
- 'module' => 'product',
+ 'backRoute' => route('studio.catalog.product.index'),
]);
}
}
diff --git a/app/Livewire/Studio/Master/Warehouse/Audit/Bottle.php b/app/Livewire/Studio/Master/Warehouse/Audit/Bottle.php
new file mode 100644
index 0000000..186b017
--- /dev/null
+++ b/app/Livewire/Studio/Master/Warehouse/Audit/Bottle.php
@@ -0,0 +1,68 @@
+label = $bottle->name.' di '.$warehouse->name;
+
+ $this->activityLogs = Activity::where('subject_type', get_class($bottle))
+ ->where('subject_id', $bottle->id)
+ ->where('properties->warehouse_id', $warehouse->id)
+ ->latest()
+ ->get()
+ ->map(function (Activity $activity) {
+ $status = match ($activity->event) {
+ 'increase' => AuditStockStatus::INCREASED,
+ 'decrease' => AuditStockStatus::DECREASED,
+ 'created' => AuditStockStatus::INCREASED,
+ 'updated' => AuditStockStatus::UPDATED,
+ 'deleted' => AuditStockStatus::DELETED,
+ default => AuditStockStatus::tryFrom($activity->event),
+ };
+
+ $color = $status?->color() ?? 'gray';
+ $label = $status?->label() ?? ucfirst($activity->event);
+
+ $classes = match ($color) {
+ 'emerald' => 'bg-emerald-200 text-emerald-800 dark:bg-emerald-800 dark:text-emerald-200',
+ 'orange' => 'bg-orange-200 text-orange-800 dark:bg-orange-800 dark:text-orange-200',
+ 'yellow' => 'bg-yellow-200 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-200',
+ 'red' => 'bg-red-200 text-red-800 dark:bg-red-800 dark:text-red-200',
+ default => 'bg-gray-200 text-gray-800 dark:bg-gray-800 dark:text-gray-200',
+ };
+
+ $activity->event = [
+ 'label' => $label,
+ 'color' => $color,
+ 'classes' => $classes,
+ ];
+
+ return $activity;
+ });
+ }
+
+ public function render(): View
+ {
+ return view('components.sections.ui.audit', [
+ 'pageTitle' => 'Audit '.$this->label,
+ 'backRoute' => route('studio.master.warehouse.show', ['warehouse' => request()->route('warehouse')]),
+ ]);
+ }
+}
diff --git a/app/Livewire/Studio/Master/Warehouse/Audit/Perfume.php b/app/Livewire/Studio/Master/Warehouse/Audit/Perfume.php
new file mode 100644
index 0000000..f4e08b1
--- /dev/null
+++ b/app/Livewire/Studio/Master/Warehouse/Audit/Perfume.php
@@ -0,0 +1,68 @@
+label = $perfume->name.' di '.$warehouse->name;
+
+ $this->activityLogs = Activity::where('subject_type', get_class($perfume))
+ ->where('subject_id', $perfume->id)
+ ->where('properties->warehouse_id', $warehouse->id)
+ ->latest()
+ ->get()
+ ->map(function (Activity $activity) {
+ $status = match ($activity->event) {
+ 'increase' => AuditStockStatus::INCREASED,
+ 'decrease' => AuditStockStatus::DECREASED,
+ 'created' => AuditStockStatus::INCREASED,
+ 'updated' => AuditStockStatus::UPDATED,
+ 'deleted' => AuditStockStatus::DELETED,
+ default => AuditStockStatus::tryFrom($activity->event),
+ };
+
+ $color = $status?->color() ?? 'gray';
+ $label = $status?->label() ?? ucfirst($activity->event);
+
+ $classes = match ($color) {
+ 'emerald' => 'bg-emerald-200 text-emerald-800 dark:bg-emerald-800 dark:text-emerald-200',
+ 'orange' => 'bg-orange-200 text-orange-800 dark:bg-orange-800 dark:text-orange-200',
+ 'yellow' => 'bg-yellow-200 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-200',
+ 'red' => 'bg-red-200 text-red-800 dark:bg-red-800 dark:text-red-200',
+ default => 'bg-gray-200 text-gray-800 dark:bg-gray-800 dark:text-gray-200',
+ };
+
+ $activity->event = [
+ 'label' => $label,
+ 'color' => $color,
+ 'classes' => $classes,
+ ];
+
+ return $activity;
+ });
+ }
+
+ public function render(): View
+ {
+ return view('components.sections.ui.audit', [
+ 'pageTitle' => 'Audit '.$this->label,
+ 'backRoute' => route('studio.master.warehouse.show', ['warehouse' => request()->route('warehouse')]),
+ ]);
+ }
+}
diff --git a/app/Livewire/Studio/Master/Warehouse/Audit/Product.php b/app/Livewire/Studio/Master/Warehouse/Audit/Product.php
new file mode 100644
index 0000000..878feb2
--- /dev/null
+++ b/app/Livewire/Studio/Master/Warehouse/Audit/Product.php
@@ -0,0 +1,68 @@
+label = $product->name.' di '.$warehouse->name;
+
+ $this->activityLogs = Activity::where('subject_type', get_class($product))
+ ->where('subject_id', $product->id)
+ ->where('properties->warehouse_id', $warehouse->id)
+ ->latest()
+ ->get()
+ ->map(function (Activity $activity) {
+ $status = match ($activity->event) {
+ 'increase' => AuditStockStatus::INCREASED,
+ 'decrease' => AuditStockStatus::DECREASED,
+ 'created' => AuditStockStatus::INCREASED,
+ 'updated' => AuditStockStatus::UPDATED,
+ 'deleted' => AuditStockStatus::DELETED,
+ default => AuditStockStatus::tryFrom($activity->event),
+ };
+
+ $color = $status?->color() ?? 'gray';
+ $label = $status?->label() ?? ucfirst($activity->event);
+
+ $classes = match ($color) {
+ 'emerald' => 'bg-emerald-200 text-emerald-800 dark:bg-emerald-800 dark:text-emerald-200',
+ 'orange' => 'bg-orange-200 text-orange-800 dark:bg-orange-800 dark:text-orange-200',
+ 'yellow' => 'bg-yellow-200 text-yellow-800 dark:bg-yellow-800 dark:text-yellow-200',
+ 'red' => 'bg-red-200 text-red-800 dark:bg-red-800 dark:text-red-200',
+ default => 'bg-gray-200 text-gray-800 dark:bg-gray-800 dark:text-gray-200',
+ };
+
+ $activity->event = [
+ 'label' => $label,
+ 'color' => $color,
+ 'classes' => $classes,
+ ];
+
+ return $activity;
+ });
+ }
+
+ public function render(): View
+ {
+ return view('components.sections.ui.audit', [
+ 'pageTitle' => 'Audit '.$this->label,
+ 'backRoute' => route('studio.master.warehouse.show', ['warehouse' => request()->route('warehouse')]),
+ ]);
+ }
+}
diff --git a/app/Livewire/Studio/Master/Warehouse/Show.php b/app/Livewire/Studio/Master/Warehouse/Show.php
index 5d9125e..f4496b1 100644
--- a/app/Livewire/Studio/Master/Warehouse/Show.php
+++ b/app/Livewire/Studio/Master/Warehouse/Show.php
@@ -15,12 +15,16 @@ class Show extends Component
public Warehouse $warehouse;
+ public string $search = '';
+
+ public array $type = [];
+
public function mount(Warehouse $warehouse): void
{
$this->warehouse = $warehouse;
}
- public function getStatsProperty()
+ public function getStatsProperty(): array
{
$perfumesCount = $this->warehouse->perfumes()->count();
$productsCount = $this->warehouse->products()->count();
@@ -50,33 +54,69 @@ public function getStatsProperty()
public function getItemsProperty()
{
$warehouse = $this->warehouse;
+ $search = $this->search;
+ $typeFilter = $this->type;
- $perfumes = $warehouse->perfumes()->get()->map(function ($item) {
- return [
- 'id' => 'perfume_'.$item->id,
- 'type' => 'Parfum',
- 'name' => $item->name,
- 'stock' => $item->pivot->stock ?? 0,
- ];
- });
+ $perfumes = collect();
+ $products = collect();
+ $bottles = collect();
- $products = $warehouse->products()->get()->map(function ($item) {
- return [
- 'id' => 'product_'.$item->id,
- 'type' => 'Produk',
- 'name' => $item->name,
- 'stock' => $item->pivot->stock ?? 0,
- ];
- });
+ // Load perfumes if no type filter or 'perfume' is selected
+ if (empty($typeFilter) || in_array('perfume', $typeFilter)) {
+ $perfumes = $warehouse->perfumes()
+ ->when(! empty($search), function ($query) use ($search) {
+ return $query->where('name', 'like', '%'.$search.'%');
+ })
+ ->get()
+ ->map(function ($item) {
+ return [
+ 'id' => 'perfume_'.$item->id,
+ 'hash_id' => $item->hash,
+ 'type' => 'Parfum',
+ 'type_key' => 'perfume',
+ 'name' => $item->name,
+ 'stock' => $item->pivot->stock ?? 0,
+ ];
+ });
+ }
- $bottles = $warehouse->bottles()->get()->map(function ($item) {
- return [
- 'id' => 'bottle_'.$item->id,
- 'type' => 'Botol',
- 'name' => $item->name,
- 'stock' => $item->pivot->stock ?? 0,
- ];
- });
+ // Load products if no type filter or 'product' is selected
+ if (empty($typeFilter) || in_array('product', $typeFilter)) {
+ $products = $warehouse->products()
+ ->when(! empty($search), function ($query) use ($search) {
+ return $query->where('name', 'like', '%'.$search.'%');
+ })
+ ->get()
+ ->map(function ($item) {
+ return [
+ 'id' => 'product_'.$item->id,
+ 'hash_id' => $item->hash,
+ 'type' => 'Produk',
+ 'type_key' => 'product',
+ 'name' => $item->name,
+ 'stock' => $item->pivot->stock ?? 0,
+ ];
+ });
+ }
+
+ // Load bottles if no type filter or 'bottle' is selected
+ if (empty($typeFilter) || in_array('bottle', $typeFilter)) {
+ $bottles = $warehouse->bottles()
+ ->when(! empty($search), function ($query) use ($search) {
+ return $query->where('name', 'like', '%'.$search.'%');
+ })
+ ->get()
+ ->map(function ($item) {
+ return [
+ 'id' => 'bottle_'.$item->id,
+ 'hash_id' => $item->hash,
+ 'type' => 'Botol',
+ 'type_key' => 'bottle',
+ 'name' => $item->name,
+ 'stock' => $item->pivot->stock ?? 0,
+ ];
+ });
+ }
return $perfumes->concat($products)->concat($bottles);
}
diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php
index b57e43e..3010a99 100644
--- a/database/seeders/RolePermissionSeeder.php
+++ b/database/seeders/RolePermissionSeeder.php
@@ -194,6 +194,7 @@ public function run(): void
'show warehouse',
'update warehouse',
'delete warehouse',
+ 'audit stock warehouse',
];
@@ -314,6 +315,7 @@ public function run(): void
'show warehouse',
'update warehouse',
'delete warehouse',
+ 'audit stock warehouse',
'view stock history',
'show stock history',
@@ -462,6 +464,7 @@ public function run(): void
'show warehouse',
'update warehouse',
'delete warehouse',
+ 'audit stock warehouse',
'view stock history',
'show stock history',
diff --git a/docs/WAREHOUSE_AUDIT_FEATURE.md b/docs/WAREHOUSE_AUDIT_FEATURE.md
new file mode 100644
index 0000000..0d0bd0d
--- /dev/null
+++ b/docs/WAREHOUSE_AUDIT_FEATURE.md
@@ -0,0 +1,57 @@
+# Fitur Audit Gudang
+
+## Deskripsi
+Fitur audit gudang memungkinkan pengguna untuk melihat riwayat perubahan stok untuk setiap item (parfum, produk, botol) di gudang tertentu. Fitur ini mirip dengan fitur audit stock di outlet, tetapi disesuaikan untuk konteks gudang.
+
+## File yang Dibuat
+
+### 1. Livewire Components
+- `app/Livewire/Studio/Master/Warehouse/Audit/Perfume.php` - Component untuk audit parfum di gudang
+- `app/Livewire/Studio/Master/Warehouse/Audit/Product.php` - Component untuk audit produk di gudang
+- `app/Livewire/Studio/Master/Warehouse/Audit/Bottle.php` - Component untuk audit botol di gudang
+
+### 2. View Template
+- `resources/views/components/sections/ui/warehouse-audit.blade.php` - Template view untuk menampilkan audit log
+
+## File yang Dimodifikasi
+
+### 1. Routes
+- `routes/pages/studio.php`
+ - Menambahkan import untuk 3 audit components
+ - Menambahkan 3 route baru untuk audit:
+ - `studio.master.warehouse.perfume.audit`
+ - `studio.master.warehouse.product.audit`
+ - `studio.master.warehouse.bottle.audit`
+
+### 2. Warehouse Show Component
+- `app/Livewire/Studio/Master/Warehouse/Show.php`
+ - Mengupdate method `getItemsProperty()` untuk menambahkan `hash_id` dan `type_key` pada setiap item
+
+### 3. Warehouse Show View
+- `resources/views/livewire/studio/master/warehouse/show.blade.php`
+ - Menambahkan kolom "Aksi" pada tabel
+ - Menambahkan tombol "Audit" untuk setiap item yang mengarah ke halaman audit
+
+## Cara Penggunaan
+
+1. Buka halaman detail gudang (`/studio/master/warehouses/{warehouse}/show`)
+2. Pada tabel daftar item, klik tombol "Audit" pada item yang ingin dilihat riwayatnya
+3. Halaman audit akan menampilkan:
+ - Log aktivitas stok (increase, decrease, updated, deleted)
+ - Informasi lokasi dan tipe lokasi
+ - Stok awal, perubahan, dan stok akhir
+ - User yang melakukan perubahan
+ - Deskripsi aktivitas
+ - Timestamp
+
+## Permissions
+Fitur ini menggunakan permission yang sama dengan audit outlet:
+- `can:audit perfume`
+- `can:audit product`
+- `can:audit bottle`
+
+## Catatan Teknis
+- Activity log menggunakan Spatie Activity Log package
+- Filter berdasarkan `properties->warehouse_id` untuk memastikan hanya menampilkan log yang relevan dengan gudang tertentu
+- Menggunakan `AuditStockStatus` enum untuk status perubahan stok
+- View menggunakan Flux UI components
diff --git a/resources/views/components/sections/ui/audit.blade.php b/resources/views/components/sections/ui/audit.blade.php
index f53f682..25016cd 100644
--- a/resources/views/components/sections/ui/audit.blade.php
+++ b/resources/views/components/sections/ui/audit.blade.php
@@ -4,7 +4,7 @@