diff --git a/app/Enums/AuditStockStatus.php b/app/Enums/AuditStockStatus.php
new file mode 100644
index 0000000..5d065c2
--- /dev/null
+++ b/app/Enums/AuditStockStatus.php
@@ -0,0 +1,19 @@
+ 'emerald',
+ self::DECREASED => 'red',
+ self::UPDATED => 'yellow',
+ };
+ }
+}
diff --git a/app/Livewire/Datatable/Studio/Catalog/BottlesTable.php b/app/Livewire/Datatable/Studio/Catalog/BottlesTable.php
index 2eef52c..2a17f19 100644
--- a/app/Livewire/Datatable/Studio/Catalog/BottlesTable.php
+++ b/app/Livewire/Datatable/Studio/Catalog/BottlesTable.php
@@ -82,15 +82,25 @@ public function columns(): array
ArrayColumn::make('Outlet')
->data(
fn ($value, $row) => $row->outlets->map(fn ($outlet) => [
+ 'id' => $outlet->hash,
+ 'bottle_id' => $row->hash,
'name' => $outlet->name,
'stock' => currency($outlet->pivot->stock, ''),
])->toArray()
)
- ->outputFormat(fn ($index, $value) => Blade::render('
-
- {{ $value["name"] }}
- {{ $value["stock"] }}
-
', ['value' => $value]))
+ ->outputFormat(function ($index, $value) {
+ $route = route('studio.catalog.bottle.audit', [$value['id'], $value['bottle_id']]);
+
+ return Blade::render('
+
+ {{ $value["name"] }}
+ {{ $value["stock"] }}
+ ', [
+ 'value' => $value,
+ 'route' => $route,
+ ]);
+ })
->flexRow(['class' => 'gap-2 flex-wrap']),
Column::make('Aksi')
diff --git a/app/Livewire/Datatable/Studio/Catalog/PerfumesTable.php b/app/Livewire/Datatable/Studio/Catalog/PerfumesTable.php
index e552f28..9c82e23 100644
--- a/app/Livewire/Datatable/Studio/Catalog/PerfumesTable.php
+++ b/app/Livewire/Datatable/Studio/Catalog/PerfumesTable.php
@@ -85,15 +85,25 @@ public function columns(): array
ArrayColumn::make('Outlet')
->data(
fn ($value, $row) => $row->outlets->map(fn ($outlet) => [
+ 'id' => $outlet->hash,
+ 'perfume_id' => $row->hash,
'name' => $outlet->name,
'stock' => currency($outlet->pivot->stock, ''),
])->toArray()
)
- ->outputFormat(fn ($index, $value) => Blade::render('
-
- {{ $value["name"] }}
- {{ $value["stock"] }}
-
', ['value' => $value]))
+ ->outputFormat(function ($index, $value) {
+ $route = route('studio.catalog.perfume.audit', [$value['id'], $value['perfume_id']]);
+
+ return Blade::render('
+
+ {{ $value["name"] }}
+ {{ $value["stock"] }}
+ ', [
+ 'value' => $value,
+ 'route' => $route,
+ ]);
+ })
->flexRow(['class' => 'gap-2 flex-wrap']),
Column::make('Aksi')
diff --git a/app/Livewire/Datatable/Studio/Catalog/ProductsTable.php b/app/Livewire/Datatable/Studio/Catalog/ProductsTable.php
index 93bcf32..22e7778 100644
--- a/app/Livewire/Datatable/Studio/Catalog/ProductsTable.php
+++ b/app/Livewire/Datatable/Studio/Catalog/ProductsTable.php
@@ -74,15 +74,25 @@ public function columns(): array
ArrayColumn::make('Outlet')
->data(
fn ($value, $row) => $row->outlets->map(fn ($outlet) => [
+ 'id' => $outlet->hash,
+ 'product_id' => $row->hash,
'name' => $outlet->name,
'stock' => currency($outlet->pivot->stock, ''),
])->toArray()
)
- ->outputFormat(fn ($index, $value) => Blade::render('
-
- {{ $value["name"] }}
- {{ $value["stock"] }}
-
', ['value' => $value]))
+ ->outputFormat(function ($index, $value) {
+ $route = route('studio.catalog.product.audit', [$value['id'], $value['product_id']]);
+
+ return Blade::render('
+
+ {{ $value["name"] }}
+ {{ $value["stock"] }}
+ ', [
+ 'value' => $value,
+ 'route' => $route,
+ ]);
+ })
->flexRow(['class' => 'gap-2 flex-wrap']),
Column::make('Aksi')
diff --git a/app/Livewire/Studio/Catalog/Bottle/Audit.php b/app/Livewire/Studio/Catalog/Bottle/Audit.php
new file mode 100644
index 0000000..f4e15a4
--- /dev/null
+++ b/app/Livewire/Studio/Catalog/Bottle/Audit.php
@@ -0,0 +1,54 @@
+label = $bottle->name;
+
+ $this->activityLogs = Activity::where('subject_type', get_class($bottle))
+ ->where('subject_id', $bottle->id)
+ ->where('properties->outlet_id', $outlet->id)
+ ->latest()
+ ->get()
+ ->map(function (Activity $activity) use ($outlet) {
+
+ return [
+ 'created_at' => $activity->created_at,
+ 'outlet' => $outlet->name,
+ 'event' => [
+ 'label' => $activity->event,
+ 'color' => AuditStockStatus::from($activity->event)->color(),
+ ],
+ 'new_stock' => currency($activity->properties['new_stock']),
+ 'previous_stock' => currency($activity->properties['previous_stock']),
+ 'quantity_change' => currency($activity->properties['quantity_change']),
+ 'user' => $activity->causer?->profile?->full_name,
+ 'description' => $activity->description,
+ ];
+ })
+ ->toArray();
+ }
+
+ public function render()
+ {
+ return view('components.partials.audit', [
+ 'pageTitle' => 'Audit '.$this->label,
+ 'module' => 'bottle',
+ ]);
+ }
+}
diff --git a/app/Livewire/Studio/Catalog/Perfume/Audit.php b/app/Livewire/Studio/Catalog/Perfume/Audit.php
new file mode 100644
index 0000000..c2c1227
--- /dev/null
+++ b/app/Livewire/Studio/Catalog/Perfume/Audit.php
@@ -0,0 +1,54 @@
+label = $perfume->name;
+
+ $this->activityLogs = Activity::where('subject_type', get_class($perfume))
+ ->where('subject_id', $perfume->id)
+ ->where('properties->outlet_id', $outlet->id)
+ ->latest()
+ ->get()
+ ->map(function (Activity $activity) use ($outlet) {
+
+ return [
+ 'created_at' => $activity->created_at,
+ 'outlet' => $outlet->name,
+ 'event' => [
+ 'label' => $activity->event,
+ 'color' => AuditStockStatus::from($activity->event)->color(),
+ ],
+ 'new_stock' => currency($activity->properties['new_stock']),
+ 'previous_stock' => currency($activity->properties['previous_stock']),
+ 'quantity_change' => currency($activity->properties['quantity_change']),
+ 'user' => $activity->causer?->profile?->full_name,
+ 'description' => $activity->description,
+ ];
+ })
+ ->toArray();
+ }
+
+ public function render()
+ {
+ return view('components.partials.audit', [
+ 'pageTitle' => 'Audit '.$this->label,
+ 'module' => 'perfume',
+ ]);
+ }
+}
diff --git a/app/Livewire/Studio/Catalog/Product/Audit.php b/app/Livewire/Studio/Catalog/Product/Audit.php
new file mode 100644
index 0000000..4685c20
--- /dev/null
+++ b/app/Livewire/Studio/Catalog/Product/Audit.php
@@ -0,0 +1,54 @@
+label = $product->name;
+
+ $this->activityLogs = Activity::where('subject_type', get_class($product))
+ ->where('subject_id', $product->id)
+ ->where('properties->outlet_id', $outlet->id)
+ ->latest()
+ ->get()
+ ->map(function (Activity $activity) use ($outlet) {
+
+ return [
+ 'created_at' => $activity->created_at,
+ 'outlet' => $outlet->name,
+ 'event' => [
+ 'label' => $activity->event,
+ 'color' => AuditStockStatus::from($activity->event)->color(),
+ ],
+ 'new_stock' => currency($activity->properties['new_stock']),
+ 'previous_stock' => currency($activity->properties['previous_stock']),
+ 'quantity_change' => currency($activity->properties['quantity_change']),
+ 'user' => $activity->causer?->profile?->full_name,
+ 'description' => $activity->description,
+ ];
+ })
+ ->toArray();
+ }
+
+ public function render()
+ {
+ return view('components.partials.audit', [
+ 'pageTitle' => 'Audit '.$this->label,
+ 'module' => 'product',
+ ]);
+ }
+}
diff --git a/app/Livewire/Studio/Manage/Order/Create.php b/app/Livewire/Studio/Manage/Order/Create.php
index 93b4c2e..c986bf5 100644
--- a/app/Livewire/Studio/Manage/Order/Create.php
+++ b/app/Livewire/Studio/Manage/Order/Create.php
@@ -54,7 +54,7 @@ public function mount()
$this->perfumes = Perfume::orderBy('name')->pluck('name', 'id')->toArray();
- $this->bottles = Bottle::orderBy('name')->pluck('name', 'id')->toArray();
+ $this->bottles = Bottle::orderBy('size')->pluck('name', 'id')->toArray();
$this->products = Product::orderBy('name')->pluck('name', 'id')->toArray();
diff --git a/app/Livewire/Studio/Manage/Purchase/Create.php b/app/Livewire/Studio/Manage/Purchase/Create.php
index 8ab5d39..8db3060 100644
--- a/app/Livewire/Studio/Manage/Purchase/Create.php
+++ b/app/Livewire/Studio/Manage/Purchase/Create.php
@@ -4,7 +4,6 @@
use App\Livewire\Forms\Studio\Manage\PurchaseForm;
use App\Models\Bottle;
-use App\Models\Outlet;
use App\Models\Perfume;
use App\Models\Product;
use App\Traits\Purchase\WithAddItem;
@@ -38,13 +37,13 @@ class Create extends Component
public function mount()
{
- $this->outlets = Outlet::pluck('name', 'id')->toArray();
+ $this->outlets = auth()->user()->outlets->pluck('name', 'id')->toArray();
- $this->perfumes = Perfume::pluck('name', 'id')->toArray();
+ $this->perfumes = Perfume::orderBy('name')->pluck('name', 'id')->toArray();
- $this->products = Product::pluck('name', 'id')->toArray();
+ $this->bottles = Bottle::orderBy('size')->pluck('name', 'id')->toArray();
- $this->bottles = Bottle::pluck('name', 'id')->toArray();
+ $this->products = Product::orderBy('name')->pluck('name', 'id')->toArray();
$this->purchaseItems = $this->loadPurchaseItems();
diff --git a/app/Traits/Purchase/WithUpdateStock.php b/app/Traits/Purchase/WithUpdateStock.php
index b71b644..7be4a43 100644
--- a/app/Traits/Purchase/WithUpdateStock.php
+++ b/app/Traits/Purchase/WithUpdateStock.php
@@ -26,18 +26,34 @@ public function increaseOutletStock($outlet, $item)
return;
}
- $existing = $outlet->{$relation}()->where("{$relation}.id", $item->purchasable_id)->first();
+ $existing = $outlet->{$relation}()->withPivot('stock')->where("{$relation}.id", $item->purchasable_id)->first();
if ($existing) {
- $currentStock = $existing->pivot->stock ?? 0;
+ $previousStock = $existing->pivot->stock ?? 0;
$outlet->{$relation}()->updateExistingPivot($item->purchasable_id, [
- 'stock' => $currentStock + $quantity,
+ 'stock' => $previousStock + $quantity,
]);
} else {
+ $previousStock = 0;
$outlet->{$relation}()->attach($item->purchasable_id, [
'stock' => $quantity,
]);
}
+
+ activity('Belanja')
+ ->performedOn($item->purchasable)
+ ->causedBy(auth()->user())
+ ->withProperties([
+ 'purchasable_id' => $item->purchasable_id,
+ 'purchasable_type' => $item->purchasable_type,
+ 'outlet_id' => $outlet->id,
+ 'quantity_change' => $quantity,
+ 'previous_stock' => $previousStock,
+ 'new_stock' => $previousStock + $quantity,
+ 'purchase_id' => $item->purchase_id ?? null,
+ ])
+ ->event('Menambah')
+ ->log('Stok '.$item->purchasable->name.' di '.$outlet->name.' bertambah '.currency($quantity));
}
public function decreaseOutletStock($outlet, $item)
@@ -58,7 +74,7 @@ public function decreaseOutletStock($outlet, $item)
return;
}
- $existing = $outlet->{$relation}()->where("{$relation}.id", $item->purchasable_id)->first();
+ $existing = $outlet->{$relation}()->withPivot('stock')->where("{$relation}.id", $item->purchasable_id)->first();
if ($existing) {
$currentStock = $existing->pivot->stock ?? 0;
@@ -67,5 +83,20 @@ public function decreaseOutletStock($outlet, $item)
'stock' => $newStock,
]);
}
+
+ activity('Belanja')
+ ->performedOn($item->purchasable)
+ ->causedBy(auth()->user())
+ ->withProperties([
+ 'purchasable_id' => $item->purchasable_id,
+ 'purchasable_type' => $item->purchasable_type,
+ 'outlet_id' => $outlet->id,
+ 'quantity_change' => $quantity,
+ 'previous_stock' => $currentStock,
+ 'new_stock' => $newStock,
+ 'purchase_id' => $item->purchase_id,
+ ])
+ ->event('Mengurangi')
+ ->log('Stok '.$item->purchasable->name.' di '.$outlet->name.' berkurang '.$quantity);
}
}
diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php
index f8f99aa..fc4e1d8 100644
--- a/database/seeders/RolePermissionSeeder.php
+++ b/database/seeders/RolePermissionSeeder.php
@@ -73,16 +73,19 @@ public function run(): void
'create perfume',
'update perfume',
'delete perfume',
+ 'audit perfume',
'view product',
'create product',
'update product',
'delete product',
+ 'audit product',
'view bottle',
'create bottle',
'update bottle',
'delete bottle',
+ 'audit bottle',
'view expense',
'create expense',
@@ -153,14 +156,17 @@ public function run(): void
'create perfume',
'update perfume',
'delete perfume',
+ 'audit perfume',
'create product',
'update product',
'delete product',
+ 'audit product',
'create bottle',
'update bottle',
'delete bottle',
+ 'audit bottle',
]));
$admin->syncPermissions(array_diff($permissions, [
@@ -193,14 +199,17 @@ public function run(): void
'create perfume',
'update perfume',
'delete perfume',
+ 'audit perfume',
'create product',
'update product',
'delete product',
+ 'audit product',
'create bottle',
'update bottle',
'delete bottle',
+ 'audit bottle',
'view article',
'create article',
diff --git a/resources/views/components/partials/audit.blade.php b/resources/views/components/partials/audit.blade.php
new file mode 100644
index 0000000..e5acd7b
--- /dev/null
+++ b/resources/views/components/partials/audit.blade.php
@@ -0,0 +1,60 @@
+
+
+
+ {{ $pageTitle }}
+
+
+
+ Kembali
+
+
+
+
+
+ @forelse ($activityLogs as $log)
+
+
+
{{ $log['created_at'] }}
+
{{ $log['outlet'] }}
+
{{ $log['user'] }}
+
+ {{ $log['event']['label'] }}
+
+
+
+
+
+
+
Stok Sebelumnya: {{ $log['previous_stock'] }}
+
+
Perubahan: {{ $log['quantity_change'] }}
+
+
Total Stok: {{ $log['new_stock'] }}
+
+
+
+
+
+ {{ $log['description'] }}
+
+
+ @empty
+
+
+
+
+ Yahhh, sepertinya belum ada data yang tersedia.
+
+
+ @endforelse
+
+
+
+@assets
+
+@endassets
diff --git a/resources/views/livewire/studio/catalog/bottle/index.blade.php b/resources/views/livewire/studio/catalog/bottle/index.blade.php
index 62831b4..3d8ce7d 100644
--- a/resources/views/livewire/studio/catalog/bottle/index.blade.php
+++ b/resources/views/livewire/studio/catalog/bottle/index.blade.php
@@ -14,6 +14,14 @@ class="text-sm">
+
+ Informasi
+
+ Untuk melihat harga pembelian, silakan klik pada bagian harga. Untuk melakukan audit stok,
+ silakan klik pada nama outlet.
+
+
+
diff --git a/resources/views/livewire/studio/catalog/perfume/index.blade.php b/resources/views/livewire/studio/catalog/perfume/index.blade.php
index 2d77fb7..3c18889 100644
--- a/resources/views/livewire/studio/catalog/perfume/index.blade.php
+++ b/resources/views/livewire/studio/catalog/perfume/index.blade.php
@@ -14,6 +14,14 @@ class="text-sm">
+
+ Informasi
+
+ Untuk melihat harga pembelian, silakan klik pada bagian harga. Untuk melakukan audit stok,
+ silakan klik pada nama outlet.
+
+
+
diff --git a/resources/views/livewire/studio/catalog/product/index.blade.php b/resources/views/livewire/studio/catalog/product/index.blade.php
index 90c816f..1043cbd 100644
--- a/resources/views/livewire/studio/catalog/product/index.blade.php
+++ b/resources/views/livewire/studio/catalog/product/index.blade.php
@@ -14,6 +14,14 @@ class="text-sm">
+
+ Informasi
+
+ Untuk melihat harga pembelian, silakan klik pada bagian harga. Untuk melakukan audit stok,
+ silakan klik pada nama outlet.
+
+
+
diff --git a/resources/views/livewire/studio/manage/purchase/form.blade.php b/resources/views/livewire/studio/manage/purchase/form.blade.php
index e1e433f..6ef1b7e 100644
--- a/resources/views/livewire/studio/manage/purchase/form.blade.php
+++ b/resources/views/livewire/studio/manage/purchase/form.blade.php
@@ -11,6 +11,13 @@
+
+ Informasi
+
+ Tolong perhatikan data yang Anda masukkan, karena tidak dapat diubah setelah disimpan.
+
+
+
diff --git a/resources/views/livewire/studio/manage/purchase/index.blade.php b/resources/views/livewire/studio/manage/purchase/index.blade.php
index 1c5039d..af15cb3 100644
--- a/resources/views/livewire/studio/manage/purchase/index.blade.php
+++ b/resources/views/livewire/studio/manage/purchase/index.blade.php
@@ -3,7 +3,7 @@
{{ $pageTitle }}
- @can ('create purchase')
+ @can('create purchase')
@@ -14,6 +14,13 @@ class="text-sm">
+
+ Informasi
+
+ Menghapus data belanja otomatis akan menambah stok.
+
+
+
diff --git a/routes/pages/studio.php b/routes/pages/studio.php
index 83f0dad..1991d27 100644
--- a/routes/pages/studio.php
+++ b/routes/pages/studio.php
@@ -1,14 +1,17 @@
name('create')->middleware('can:create perfume');
Route::get('/{perfume}/edit', PerfumeEdit::class)->name('edit')->middleware('can:update perfume');
Route::delete('/{perfume}/delete', PerfumeCreate::class)->name('delete')->middleware('can:delete perfume');
+ Route::get('/{outlet}/{perfume}/audit', PerfumeAudit::class)->name('audit')->middleware('can:audit perfume');
});
Route::prefix('products')->name('product.')->group(function () {
@@ -115,6 +119,7 @@
Route::get('/create', ProductCreate::class)->name('create')->middleware('can:create product');
Route::get('/{product}/edit', ProductEdit::class)->name('edit')->middleware('can:update product');
Route::delete('/{product}/delete', ProductCreate::class)->name('delete')->middleware('can:delete product');
+ Route::get('/{outlet}/{product}/audit', ProductAudit::class)->name('audit')->middleware('can:audit product');
});
Route::prefix('bottles')->name('bottle.')->group(function () {
@@ -122,6 +127,7 @@
Route::get('/create', BottleCreate::class)->name('create')->middleware('can:create bottle');
Route::get('/{bottle}/edit', BottleEdit::class)->name('edit')->middleware('can:update bottle');
Route::delete('/{bottle}/delete', BottleCreate::class)->name('delete')->middleware('can:delete bottle');
+ Route::get('/{outlet}/{bottle}/audit', BottleAudit::class)->name('audit')->middleware('can:audit bottle');
});
});