diff --git a/app/Livewire/Datatable/Manage/PurchasesTable.php b/app/Livewire/Datatable/Manage/PurchasesTable.php
index 0e425ae..ae1e9f1 100644
--- a/app/Livewire/Datatable/Manage/PurchasesTable.php
+++ b/app/Livewire/Datatable/Manage/PurchasesTable.php
@@ -25,7 +25,7 @@ class PurchasesTable extends DataTableComponent
public function columns(): array
{
return [
- Column::make('Outlet', 'outlet.name')->searchable(),
+ Column::make('Gudang', 'warehouse.name')->searchable(),
Column::make('Nomor Invoice', 'invoice_number')->searchable(),
@@ -73,14 +73,13 @@ public function columns(): array
return $actions;
})
->html()
- ->hideIf(auth()->user()->cannot('update purchase')),
+ ->hideIf(auth()->user()->cannot('delete purchase')),
];
}
public function builder(): Builder
{
- return Purchase::select('purchases.id', 'outlet_id', 'invoice_number', 'purchase_date', 'total')
- ->with(['outlet', 'items', 'items.purchasable'])
- ->whereIn('outlet_id', auth()->user()->outlets->pluck('id')->toArray());
+ return Purchase::select('purchases.id', 'warehouse_id', 'invoice_number', 'purchase_date', 'total')
+ ->with(['warehouse', 'items', 'items.purchasable']);
}
}
diff --git a/app/Livewire/Forms/Studio/Manage/PurchaseForm.php b/app/Livewire/Forms/Studio/Manage/PurchaseForm.php
index 9d6ec00..fbb0d26 100644
--- a/app/Livewire/Forms/Studio/Manage/PurchaseForm.php
+++ b/app/Livewire/Forms/Studio/Manage/PurchaseForm.php
@@ -6,14 +6,14 @@
use App\Rules\UnsignedInteger;
use App\Traits\Media\WithMediaHandler;
use App\Traits\Purchase\WithPurchaseItems;
-use App\Traits\Purchase\WithUpdateStock;
+use App\Traits\Purchase\WithWarehouseStock;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Livewire\Form;
class PurchaseForm extends Form
{
- use WithMediaHandler, WithPurchaseItems, WithUpdateStock;
+ use WithMediaHandler, WithPurchaseItems, WithWarehouseStock;
public ?Purchase $purchase = null;
@@ -25,7 +25,7 @@ class PurchaseForm extends Form
public string $total = '';
- public string $outlet_id = '';
+ public string $warehouse_id = '';
public array $image = [];
@@ -51,7 +51,7 @@ public function rules(): array
'invoice_number' => ['nullable', 'string', 'max:50'],
'note' => ['nullable', 'string', 'max:100'],
'purchase_date' => ['required', 'date', 'before_or_equal:today'],
- 'outlet_id' => ['required', Rule::exists('outlets', 'id')],
+ 'warehouse_id' => ['required', Rule::exists('warehouses', 'id')],
'total' => ['required', new UnsignedInteger],
'image' => ['nullable', 'array', 'max:1'],
];
@@ -63,7 +63,7 @@ public function validationAttributes(): array
'invoice_number' => 'nomor invoice',
'note' => 'catatan',
'purchase_date' => 'tanggal belanja',
- 'outlet_id' => 'outlet',
+ 'warehouse_id' => 'gudang',
'image' => 'gambar',
];
}
@@ -75,7 +75,7 @@ public function setPurchase(Purchase $purchase): void
$this->invoice_number = $purchase->invoice_number;
$this->note = $purchase->note;
$this->purchase_date = $purchase->purchase_date;
- $this->outlet_id = $purchase->outlet_id;
+ $this->warehouse_id = $purchase->warehouse_id;
$this->total = $purchase->total;
$this->image = $this->mapMediaCollection($purchase->getMedia('image'));
@@ -101,7 +101,7 @@ public function store(): void
$purchase = Purchase::create($data);
- $purchase->load('outlet');
+ $purchase->load('warehouse');
$this->uploadMedia($this->image, $purchase, 'image');
@@ -113,36 +113,17 @@ public function store(): void
$item->update(['purchase_id' => $purchase->id]);
- $this->increaseOutletStock($purchase->outlet, $item);
+ $this->increaseWarehouseStock($purchase->warehouse, $item);
}
});
}
- public function update(): void
- {
- $this->validate();
-
- // Ensure total calculation integrity from existing items
- $calculatedTotal = $this->purchase->items()->sum(DB::raw('unit_price * quantity'));
- $this->total = formatCurrencyNumber($calculatedTotal);
-
- DB::transaction(function () use ($calculatedTotal) {
- $data = $this->prepareSavedData();
- $data['total'] = $calculatedTotal; // Enforce calculated total
-
- $this->purchase->update($data);
-
- $this->syncMedia($this->image, $this->purchase, 'image');
- $this->uploadMedia($this->image, $this->purchase, 'image');
- });
- }
-
private function prepareSavedData(): array
{
return [
'invoice_number' => $this->invoice_number,
'note' => $this->note,
- 'outlet_id' => $this->outlet_id,
+ 'warehouse_id' => $this->warehouse_id,
'purchase_date' => $this->purchase_date,
'total' => parseRupiahToInt($this->total),
];
diff --git a/app/Livewire/Studio/Manage/Purchase/Create.php b/app/Livewire/Studio/Manage/Purchase/Create.php
index 15eee93..6dfe3a5 100644
--- a/app/Livewire/Studio/Manage/Purchase/Create.php
+++ b/app/Livewire/Studio/Manage/Purchase/Create.php
@@ -6,6 +6,7 @@
use App\Models\Bottle;
use App\Models\Perfume;
use App\Models\Product;
+use App\Models\Warehouse as WarehouseModel;
use App\Traits\Authorization\WithAuthorization;
use App\Traits\Components\WithConfirmation;
use App\Traits\Components\WithToast;
@@ -26,7 +27,7 @@ class Create extends Component
public PurchaseForm $form;
- public array $outlets = [];
+ public array $warehouses = [];
public array $perfumes = [];
@@ -38,13 +39,13 @@ class Create extends Component
public function mount(): void
{
- $this->outlets = auth()->user()->outlets->pluck('name', 'id')->toArray();
+ $this->warehouses = WarehouseModel::orderBy('name')->pluck('name', 'id')->toArray();
- $this->perfumes = Perfume::whereHas('outlets', fn ($query) => $query->where('outlets.id', $this->form->outlet_id))->orderBy('name')->pluck('name', 'id')->toArray();
+ $this->perfumes = Perfume::orderBy('name')->pluck('name', 'id')->toArray();
- $this->bottles = Bottle::whereHas('outlets', fn ($query) => $query->where('outlets.id', $this->form->outlet_id))->orderBy('size')->pluck('name', 'id')->toArray();
+ $this->bottles = Bottle::orderBy('name')->pluck('name', 'id')->toArray();
- $this->products = Product::whereHas('outlets', fn ($query) => $query->where('outlets.id', $this->form->outlet_id))->orderBy('name')->pluck('name', 'id')->toArray();
+ $this->products = Product::orderBy('name')->pluck('name', 'id')->toArray();
$this->purchaseItems = $this->loadPurchaseItems();
@@ -68,13 +69,6 @@ public function save(): void
], navigate: true);
}
- public function updatedFormOutletId($value): void
- {
- $this->perfumes = Perfume::whereHas('outlets', fn ($query) => $query->where('outlets.id', $value))->orderBy('name')->pluck('name', 'id')->toArray();
- $this->bottles = Bottle::whereHas('outlets', fn ($query) => $query->where('outlets.id', $value))->orderBy('size')->pluck('name', 'id')->toArray();
- $this->products = Product::whereHas('outlets', fn ($query) => $query->where('outlets.id', $value))->orderBy('name')->pluck('name', 'id')->toArray();
- }
-
public function render(): View
{
return view('livewire.studio.manage.purchase.form', [
diff --git a/app/Livewire/Studio/Manage/Purchase/Index.php b/app/Livewire/Studio/Manage/Purchase/Index.php
index 651bf32..a7453bd 100644
--- a/app/Livewire/Studio/Manage/Purchase/Index.php
+++ b/app/Livewire/Studio/Manage/Purchase/Index.php
@@ -27,7 +27,7 @@ public function delete(Purchase $purchase): void
DB::transaction(function () use ($purchase) {
foreach ($purchase->items as $item) {
- $this->decreaseOutletStock($purchase->outlet, $item);
+ $this->decreaseWarehouseStock($purchase->warehouse, $item);
}
$purchase->delete();
diff --git a/app/Models/Purchase.php b/app/Models/Purchase.php
index 3d5f176..30e2259 100644
--- a/app/Models/Purchase.php
+++ b/app/Models/Purchase.php
@@ -38,9 +38,9 @@ public function yesterday(Builder $query): void
$query->whereDate('created_at', today()->subDay());
}
- public function outlet(): BelongsTo
+ public function warehouse(): BelongsTo
{
- return $this->belongsTo(Outlet::class);
+ return $this->belongsTo(Warehouse::class);
}
public function items(): HasMany
diff --git a/app/Traits/Purchase/WithUpdateStock.php b/app/Traits/Purchase/WithUpdateStock.php
index 56ab602..5200f56 100644
--- a/app/Traits/Purchase/WithUpdateStock.php
+++ b/app/Traits/Purchase/WithUpdateStock.php
@@ -9,7 +9,7 @@
trait WithUpdateStock
{
- public function increaseOutletStock($outlet, $item): void
+ public function increaseWarehouseStock($warehouse, $item): void
{
$quantity = $item->quantity;
@@ -28,7 +28,7 @@ public function increaseOutletStock($outlet, $item): void
}
// Lock the row to prevent race conditions
- $existing = $outlet->{$relation}()
+ $existing = $warehouse->{$relation}()
->where("{$relation}.id", $item->purchasable_id)
->lockForUpdate()
->withPivot('stock')
@@ -36,22 +36,22 @@ public function increaseOutletStock($outlet, $item): void
if ($existing) {
$previousStock = $existing->pivot->stock ?? 0;
- $outlet->{$relation}()->updateExistingPivot($item->purchasable_id, [
+ $warehouse->{$relation}()->updateExistingPivot($item->purchasable_id, [
'stock' => $previousStock + $quantity,
]);
} else {
$previousStock = 0;
- $outlet->{$relation}()->attach($item->purchasable_id, [
+ $warehouse->{$relation}()->attach($item->purchasable_id, [
'stock' => $quantity,
]);
}
- StockActivityLogService::log(
+ StockActivityLogService::logWarehouse(
logName: 'Belanja',
event: 'Menambah',
- description: 'Stok '.$item->purchasable->name.' di '.$outlet->name.' bertambah '.formatCurrencyNumber($quantity),
+ description: 'Stok '.$item->purchasable->name.' di '.$warehouse->name.' bertambah '.formatCurrencyNumber($quantity),
performedOn: $item->purchasable,
- outlet: $outlet,
+ warehouse: $warehouse,
quantityChange: $quantity,
previousStock: $previousStock,
newStock: $previousStock + $quantity,
@@ -63,7 +63,7 @@ public function increaseOutletStock($outlet, $item): void
);
}
- public function decreaseOutletStock($outlet, $item): void
+ public function decreaseWarehouseStock($warehouse, $item): void
{
$quantity = $item->quantity;
@@ -81,31 +81,32 @@ public function decreaseOutletStock($outlet, $item): void
return;
}
- $existing = $outlet->{$relation}()
+ $existing = $warehouse->{$relation}()
->where("{$relation}.id", $item->purchasable_id)
->lockForUpdate()
->withPivot('stock')
->first();
+ $currentStock = $existing?->pivot->stock ?? 0;
+ $newStock = max(0, $currentStock - $quantity);
+
if ($existing) {
- $currentStock = $existing->pivot->stock ?? 0;
- $newStock = max(0, $currentStock - $quantity);
- $outlet->{$relation}()->updateExistingPivot($item->purchasable_id, [
- 'stock' => $newStock,
- ]);
- } else {
- // Handle case where stock doesn't exist but we are decreasing?
- // Should not happen in normal flow, but good to handle.
- $currentStock = 0;
- $newStock = 0;
+ if ($newStock === 0) {
+ $warehouse->{$relation}()->detach($item->purchasable_id);
+ } else {
+ $warehouse->{$relation}()->updateExistingPivot(
+ $item->purchasable_id,
+ ['stock' => $newStock]
+ );
+ }
}
- StockActivityLogService::log(
+ StockActivityLogService::logWarehouse(
logName: 'Belanja',
event: 'Mengurangi',
- description: 'Stok '.$item->purchasable->name.' di '.$outlet->name.' berkurang '.$quantity,
+ description: 'Stok '.$item->purchasable->name.' di '.$warehouse->name.' berkurang '.$quantity,
performedOn: $item->purchasable,
- outlet: $outlet,
+ warehouse: $warehouse,
quantityChange: $quantity,
previousStock: $currentStock,
newStock: $newStock,
diff --git a/app/Traits/Purchase/WithWarehouseStock.php b/app/Traits/Purchase/WithWarehouseStock.php
new file mode 100644
index 0000000..4c798cb
--- /dev/null
+++ b/app/Traits/Purchase/WithWarehouseStock.php
@@ -0,0 +1,129 @@
+quantity;
+
+ switch ($item->purchasable_type) {
+ case Perfume::class:
+ $relation = 'perfumes';
+ break;
+ case Product::class:
+ $relation = 'products';
+ break;
+ case Bottle::class:
+ $relation = 'bottles';
+ break;
+ default:
+ return;
+ }
+
+ // Lock the row to prevent race conditions
+ $existing = $warehouse->{$relation}()
+ ->where("{$relation}.id", $item->purchasable_id)
+ ->lockForUpdate()
+ ->withPivot('stock')
+ ->first();
+
+ if ($existing) {
+ $previousStock = $existing->pivot->stock ?? 0;
+ $warehouse->{$relation}()->updateExistingPivot($item->purchasable_id, [
+ 'stock' => $previousStock + $quantity,
+ ]);
+ } else {
+ $previousStock = 0;
+ $warehouse->{$relation}()->attach($item->purchasable_id, [
+ 'stock' => $quantity,
+ ]);
+ }
+
+ $purchase = $item->purchase ?? \App\Models\Purchase::find($item->purchase_id);
+
+ if ($purchase) {
+ StockActivityLogService::purchase(
+ purchase: $purchase,
+ product: $item->purchasable,
+ quantity: $quantity,
+ previousStock: $previousStock,
+ newStock: $previousStock + $quantity
+ );
+ } else {
+ StockActivityLogService::logWarehouse(
+ logName: StockActivityLogService::LOG_PURCHASE,
+ event: 'increase',
+ description: 'Stok '.$item->purchasable->name.' di gudang '.$warehouse->name.' bertambah '.formatCurrencyNumber($quantity),
+ performedOn: $item->purchasable,
+ warehouse: $warehouse,
+ quantityChange: $quantity,
+ previousStock: $previousStock,
+ newStock: $previousStock + $quantity,
+ extraProperties: [
+ 'purchasable_id' => $item->purchasable_id,
+ 'purchasable_type' => $item->purchasable_type,
+ 'purchase_id' => $item->purchase_id ?? null,
+ ]
+ );
+ }
+ }
+
+ public function decreaseWarehouseStock($warehouse, $item): void
+ {
+ $quantity = $item->quantity;
+
+ switch ($item->purchasable_type) {
+ case Perfume::class:
+ $relation = 'perfumes';
+ break;
+ case Product::class:
+ $relation = 'products';
+ break;
+ case Bottle::class:
+ $relation = 'bottles';
+ break;
+ default:
+ return;
+ }
+
+ $existing = $warehouse->{$relation}()
+ ->where("{$relation}.id", $item->purchasable_id)
+ ->lockForUpdate()
+ ->withPivot('stock')
+ ->first();
+
+ if ($existing) {
+ $currentStock = $existing->pivot->stock ?? 0;
+ $newStock = max(0, $currentStock - $quantity);
+ $warehouse->{$relation}()->updateExistingPivot($item->purchasable_id, [
+ 'stock' => $newStock,
+ ]);
+ } else {
+ $currentStock = 0;
+ $newStock = 0;
+ }
+
+ StockActivityLogService::logWarehouse(
+ logName: StockActivityLogService::LOG_PURCHASE,
+ event: 'decrease',
+ description: 'Stok '.$item->purchasable->name.' di gudang '.$warehouse->name.' berkurang '.$quantity,
+ performedOn: $item->purchasable,
+ warehouse: $warehouse,
+ quantityChange: -1 * abs($quantity),
+ previousStock: $currentStock,
+ newStock: $newStock,
+ extraProperties: [
+ 'purchasable_id' => $item->purchasable_id,
+ 'purchasable_type' => $item->purchasable_type,
+ 'purchase_id' => $item->purchase_id,
+ ]
+ );
+ }
+}
diff --git a/database/migrations/2025_10_03_080030_create_purchases_table.php b/database/migrations/2025_12_28_080030_create_purchases_table.php
similarity index 91%
rename from database/migrations/2025_10_03_080030_create_purchases_table.php
rename to database/migrations/2025_12_28_080030_create_purchases_table.php
index 5b6ca14..34e2630 100644
--- a/database/migrations/2025_10_03_080030_create_purchases_table.php
+++ b/database/migrations/2025_12_28_080030_create_purchases_table.php
@@ -13,7 +13,7 @@ public function up(): void
{
Schema::create('purchases', function (Blueprint $table) {
$table->id();
- $table->foreignId('outlet_id')->constrained()->cascadeOnDelete();
+ $table->foreignId('warehouse_id')->constrained()->cascadeOnDelete();
$table->string('invoice_number', 50)->nullable();
$table->date('purchase_date');
$table->unsignedInteger('total');
diff --git a/database/migrations/2025_10_03_080031_create_purchase_items_table.php b/database/migrations/2025_12_28_080031_create_purchase_items_table.php
similarity index 100%
rename from database/migrations/2025_10_03_080031_create_purchase_items_table.php
rename to database/migrations/2025_12_28_080031_create_purchase_items_table.php
diff --git a/resources/views/livewire/studio/manage/purchase/form.blade.php b/resources/views/livewire/studio/manage/purchase/form.blade.php
index 9a77f46..506adc2 100644
--- a/resources/views/livewire/studio/manage/purchase/form.blade.php
+++ b/resources/views/livewire/studio/manage/purchase/form.blade.php
@@ -48,15 +48,15 @@
- Outlet *
-
- @foreach ($outlets as $key => $value)
+ Gudang *
+
+ @foreach ($warehouses as $key => $value)
{{ $value }}
@endforeach
-
+
diff --git a/resources/views/livewire/studio/manage/purchase/index.blade.php b/resources/views/livewire/studio/manage/purchase/index.blade.php
index 9d9613b..bd44c89 100644
--- a/resources/views/livewire/studio/manage/purchase/index.blade.php
+++ b/resources/views/livewire/studio/manage/purchase/index.blade.php
@@ -17,7 +17,7 @@ class="text-sm">
Informasi
- Menghapus data belanja otomatis akan menambah stok.
+ Menghapus data belanja otomatis menghapus stok dari gudang.