feat: Implement restock management, refactor warehouse views, and enhance stock activity logging for sales.

This commit is contained in:
Yoga Pangestu 2026-01-02 13:13:34 +07:00
parent 77d3d25cd9
commit cc917ac946
13 changed files with 546 additions and 144 deletions

View File

@ -76,6 +76,11 @@ public function approveApproval(StockOpname $stockOpname): void
} }
$itemable = $item->itemable; $itemable = $item->itemable;
if (! $itemable) {
continue;
}
$morphType = class_basename($itemable); $morphType = class_basename($itemable);
$relationMap = [ $relationMap = [
@ -90,9 +95,11 @@ public function approveApproval(StockOpname $stockOpname): void
$relationName = $relationMap[$morphType]; $relationName = $relationMap[$morphType];
$currentStock = $outlet->$relationName() $currentPivot = $outlet->$relationName()
->where($itemable->getTable().'.id', $itemable->id) ->where($itemable->getTable().'.id', $itemable->id)
->first()?->pivot?->stock ?? 0; ->first();
$currentStock = $currentPivot?->pivot?->stock ?? 0;
$quantity = $item->qty_physical; $quantity = $item->qty_physical;
@ -100,26 +107,13 @@ public function approveApproval(StockOpname $stockOpname): void
$itemable->id => ['stock' => $quantity], $itemable->id => ['stock' => $quantity],
]); ]);
StockActivityLogService::log( StockActivityLogService::stockOpname(
logName: 'Stock Opname', stockOpname: $stockOpname,
event: 'Mengubah', product: $itemable,
description: sprintf(
'Stok %s di %s diperbarui dari %s menjadi %s',
$itemable->name,
$outlet->name,
$currentStock,
$quantity
),
performedOn: $itemable,
outlet: $outlet,
quantityChange: $quantity - $currentStock, quantityChange: $quantity - $currentStock,
previousStock: $currentStock, previousStock: $currentStock,
newStock: $quantity, newStock: $quantity,
extraProperties: [ note: $stockOpname->note
// Preserving 'itemable_id' pointing to StockOpnameItem id as per original code overwriting
'itemable_id' => $item->id ?? null,
'itemable_type' => $item->itemable_type,
]
); );
} }
}); });

View File

@ -1,89 +0,0 @@
<?php
namespace App\Livewire\Studio\Master;
use App\Livewire\Forms\Studio\Master\WarehouseForm;
use App\Models\Warehouse as WarehouseModel;
use App\Traits\Authorization\WithAuthorization;
use App\Traits\Components\WithCloseModal;
use App\Traits\Components\WithConfirmation;
use App\Traits\Components\WithToast;
use App\Traits\Notification\WithSubscribeNotification;
use App\Traits\Utilities\WithUpdatedData;
use Flux\Flux;
use Illuminate\Contracts\View\View;
use Livewire\Attributes\On;
use Livewire\Attributes\Title;
use Livewire\Component;
#[Title('Gudang')]
class Warehouse extends Component
{
use WithAuthorization, WithCloseModal, WithConfirmation, WithSubscribeNotification, WithToast, WithUpdatedData;
public WarehouseForm $form;
public string $method = 'create';
public string $modalTitle = '';
public function create(): void
{
$this->canOrAbort('create warehouse');
$this->form->store();
$this->dispatch('refreshDatatable');
$this->toast('Gudang berhasil ditambahkan.');
Flux::modals()->close();
}
public function update(): void
{
$this->canOrAbort('update warehouse');
$this->form->update();
$this->dispatch('refreshDatatable');
$this->toast('Gudang berhasil diperbarui.');
Flux::modals()->close();
}
public function delete(WarehouseModel $warehouse): void
{
$this->canOrAbort('delete warehouse');
$this->form->warehouse = $warehouse;
$this->form->delete();
$this->dispatch('refreshDatatable');
$this->toast('Gudang berhasil dihapus.');
Flux::modals()->close();
}
#[On('modal:open')]
public function openModal(string $method, string $modalTitle, ?string $id = null): void
{
$this->resetValidation();
$this->resetErrorBag();
$this->method = $method;
$this->modalTitle = $modalTitle;
$id && $this->form->setWarehouse(WarehouseModel::byHashOrFail($id));
}
public function render(): View
{
return view('livewire.studio.master.warehouse', [
'pageTitle' => 'Gudang',
]);
}
}

View File

@ -40,6 +40,11 @@ public function outlets(): BelongsToMany
return $this->belongsToMany(Outlet::class)->withPivot('stock'); return $this->belongsToMany(Outlet::class)->withPivot('stock');
} }
public function warehouses(): BelongsToMany
{
return $this->belongsToMany(Warehouse::class)->withPivot('stock');
}
public function purchaseItems(): MorphMany public function purchaseItems(): MorphMany
{ {
return $this->morphMany(PurchaseItem::class, 'purchasable'); return $this->morphMany(PurchaseItem::class, 'purchasable');
@ -49,4 +54,9 @@ public function items(): MorphMany
{ {
return $this->morphMany(OrderItem::class, 'orderable'); return $this->morphMany(OrderItem::class, 'orderable');
} }
public function restockItems(): MorphMany
{
return $this->morphMany(RestockItem::class, 'restockable');
}
} }

View File

@ -99,4 +99,9 @@ public function stockOpname(): HasMany
{ {
return $this->hasMany(StockOpname::class); return $this->hasMany(StockOpname::class);
} }
public function restocks(): HasMany
{
return $this->hasMany(Restock::class);
}
} }

View File

@ -53,6 +53,11 @@ public function outlets(): BelongsToMany
return $this->belongsToMany(Outlet::class)->withPivot('stock'); return $this->belongsToMany(Outlet::class)->withPivot('stock');
} }
public function warehouses(): BelongsToMany
{
return $this->belongsToMany(Warehouse::class)->withPivot('stock');
}
public function purchaseItems(): MorphMany public function purchaseItems(): MorphMany
{ {
return $this->morphMany(PurchaseItem::class, 'purchasable'); return $this->morphMany(PurchaseItem::class, 'purchasable');
@ -62,4 +67,9 @@ public function items(): MorphMany
{ {
return $this->morphMany(OrderItem::class, 'orderable'); return $this->morphMany(OrderItem::class, 'orderable');
} }
public function restockItems(): MorphMany
{
return $this->morphMany(RestockItem::class, 'restockable');
}
} }

View File

@ -40,6 +40,11 @@ public function outlets(): BelongsToMany
return $this->belongsToMany(Outlet::class)->withPivot('stock'); return $this->belongsToMany(Outlet::class)->withPivot('stock');
} }
public function warehouses(): BelongsToMany
{
return $this->belongsToMany(Warehouse::class)->withPivot('stock');
}
public function purchaseItems(): MorphMany public function purchaseItems(): MorphMany
{ {
return $this->morphMany(PurchaseItem::class, 'purchasable'); return $this->morphMany(PurchaseItem::class, 'purchasable');
@ -49,4 +54,9 @@ public function items(): MorphMany
{ {
return $this->morphMany(OrderItem::class, 'orderable'); return $this->morphMany(OrderItem::class, 'orderable');
} }
public function restockItems(): MorphMany
{
return $this->morphMany(RestockItem::class, 'restockable');
}
} }

View File

@ -2,14 +2,167 @@
namespace App\Services; namespace App\Services;
use App\Models\Order;
use App\Models\Outlet;
use App\Models\Purchase;
use App\Models\Restock;
use App\Models\StockOpname;
use App\Models\Warehouse;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
class StockActivityLogService class StockActivityLogService
{ {
public const LOG_PURCHASE = 'purchase';
public const LOG_DISTRIBUTION = 'distribution';
public const LOG_SALE = 'sale';
public const LOG_STOCK_OPNAME = 'stock_opname';
/** /**
* Log stock related activity. * Log purchase activity (Incoming to Warehouse).
*/ */
public static function log( public static function purchase(
Purchase $purchase,
Model $product,
int $quantity,
int $previousStock,
int $newStock,
?Model $causedBy = null
): void {
self::logWarehouse(
logName: self::LOG_PURCHASE,
event: 'increase',
description: "Purchase #{$purchase->hash_id}: Added {$quantity} stock to Warehouse.",
performedOn: $product,
warehouse: $purchase->warehouse,
quantityChange: $quantity,
previousStock: $previousStock,
newStock: $newStock,
extraProperties: [
'purchase_id' => $purchase->id,
'purchase_hash' => $purchase->hash_id,
],
causedBy: $causedBy
);
}
/**
* Log distribution activity (Warehouse to Outlet).
* This logs two events: Out from Warehouse, In to Outlet.
*/
public static function distribution(
Restock $restock,
Model $product,
int $quantity,
int $warehousePreviousStock,
int $warehouseNewStock,
int $outletPreviousStock,
int $outletNewStock,
?Model $causedBy = null
): void {
// Log Warehouse Out
self::logWarehouse(
logName: self::LOG_DISTRIBUTION,
event: 'decrease',
description: "Restock #{$restock->hash_id}: Moved {$quantity} stock from Warehouse to Outlet.",
performedOn: $product,
warehouse: $restock->warehouse,
quantityChange: -1 * abs($quantity),
previousStock: $warehousePreviousStock,
newStock: $warehouseNewStock,
extraProperties: [
'restock_id' => $restock->id,
'restock_hash' => $restock->hash_id,
'target_outlet_id' => $restock->outlet_id,
],
causedBy: $causedBy
);
// Log Outlet In
self::logOutlet(
logName: self::LOG_DISTRIBUTION,
event: 'increase',
description: "Restock #{$restock->hash_id}: Received {$quantity} stock from Warehouse.",
performedOn: $product,
outlet: $restock->outlet,
quantityChange: abs($quantity),
previousStock: $outletPreviousStock,
newStock: $outletNewStock,
extraProperties: [
'restock_id' => $restock->id,
'restock_hash' => $restock->hash_id,
'source_warehouse_id' => $restock->warehouse_id,
],
causedBy: $causedBy
);
}
/**
* Log sale activity (Outlet to Customer).
*/
public static function sale(
Order $order,
Model $product,
int $quantity,
int $previousStock,
int $newStock,
?Model $causedBy = null
): void {
self::logOutlet(
logName: self::LOG_SALE,
event: 'decrease',
description: "Order #{$order->hash_id}: Sold {$quantity} items.",
performedOn: $product,
outlet: $order->outlet,
quantityChange: -1 * abs($quantity),
previousStock: $previousStock,
newStock: $newStock,
extraProperties: [
'order_id' => $order->id,
'order_hash' => $order->hash_id,
],
causedBy: $causedBy ?? $order->user
);
}
/**
* Log stock opname activity (Adjustment in Outlet).
*/
public static function stockOpname(
StockOpname $stockOpname,
Model $product,
int $quantityChange,
int $previousStock,
int $newStock,
?string $note = null,
?Model $causedBy = null
): void {
$event = $quantityChange > 0 ? 'increase' : 'decrease';
self::logOutlet(
logName: self::LOG_STOCK_OPNAME,
event: $event,
description: "Stock Opname #{$stockOpname->hash_id}: Adjusted stock by {$quantityChange}.",
performedOn: $product,
outlet: $stockOpname->outlet,
quantityChange: $quantityChange,
previousStock: $previousStock,
newStock: $newStock,
extraProperties: [
'stock_opname_id' => $stockOpname->id,
'stock_opname_hash' => $stockOpname->hash_id,
'note' => $note,
],
causedBy: $causedBy
);
}
/**
* Log stock related activity for Outlet.
*/
public static function logOutlet(
string $logName, string $logName,
string $event, string $event,
string $description, string $description,
@ -28,6 +181,42 @@ public static function log(
'quantity_change' => $quantityChange, 'quantity_change' => $quantityChange,
'previous_stock' => $previousStock, 'previous_stock' => $previousStock,
'new_stock' => $newStock, 'new_stock' => $newStock,
'location_type' => 'outlet',
'location_name' => $outlet->name ?? 'Unknown Outlet',
], $extraProperties);
activity($logName)
->performedOn($performedOn)
->causedBy($causedBy)
->withProperties($properties)
->event($event)
->log($description);
}
/**
* Log stock related activity for Warehouse.
*/
public static function logWarehouse(
string $logName,
string $event,
string $description,
Model $performedOn,
Warehouse $warehouse,
int $quantityChange,
int $previousStock,
int $newStock,
array $extraProperties = [],
?Model $causedBy = null
): void {
$causedBy = $causedBy ?: auth()->user();
$properties = array_merge([
'warehouse_id' => $warehouse->getKey(),
'quantity_change' => $quantityChange,
'previous_stock' => $previousStock,
'new_stock' => $newStock,
'location_type' => 'warehouse',
'location_name' => $warehouse->name ?? 'Unknown Warehouse',
], $extraProperties); ], $extraProperties);
activity($logName) activity($logName)

View File

@ -39,24 +39,26 @@ public function increaseOutletStock($outlet, $item): void
'stock' => $currentStock + $quantity, 'stock' => $currentStock + $quantity,
]); ]);
} else { } else {
$currentStock = 0;
$outlet->{$relation}()->attach($item->orderable_id, [ $outlet->{$relation}()->attach($item->orderable_id, [
'stock' => $quantity, 'stock' => $quantity,
]); ]);
} }
$newStock = $currentStock + $quantity;
StockActivityLogService::log( StockActivityLogService::logOutlet(
logName: 'Order', logName: StockActivityLogService::LOG_SALE,
event: 'Menambah', event: 'increase',
description: 'Stok '.$item->orderable->name.' di '.$outlet->name.' bertambah '.formatCurrencyNumber($quantity), description: 'Stok '.$item->orderable->name.' di '.$outlet->name.' bertambah '.formatCurrencyNumber($quantity).' (Pembatalan/Edit Order)',
performedOn: $item->orderable, performedOn: $item->orderable,
outlet: $outlet, outlet: $outlet,
quantityChange: $quantity, quantityChange: abs($quantity),
previousStock: $currentStock, previousStock: $currentStock,
newStock: $currentStock + $quantity, newStock: $newStock,
extraProperties: [ extraProperties: [
'orderable_id' => $item->orderable_id, 'orderable_id' => $item->orderable_id,
'orderable_type' => $item->orderable_type, 'orderable_type' => $item->orderable_type,
'purchase_id' => $item->purchase_id ?? null, 'order_id' => $item->order_id ?? null,
] ]
); );
} }
@ -101,21 +103,33 @@ public function decreaseOutletStock($outlet, $item)
'stock' => $newStock, 'stock' => $newStock,
]); ]);
StockActivityLogService::log( $order = $item->order ?? \App\Models\Order::find($item->order_id);
logName: 'Order',
event: 'Mengurangi', if ($order) {
StockActivityLogService::sale(
order: $order,
product: $item->orderable,
quantity: $quantity,
previousStock: $currentStock,
newStock: $newStock
);
} else {
StockActivityLogService::logOutlet(
logName: StockActivityLogService::LOG_SALE,
event: 'decrease',
description: 'Stok '.$item->orderable->name.' di '.$outlet->name.' berkurang '.formatCurrencyNumber($quantity), description: 'Stok '.$item->orderable->name.' di '.$outlet->name.' berkurang '.formatCurrencyNumber($quantity),
performedOn: $item->orderable, performedOn: $item->orderable,
outlet: $outlet, outlet: $outlet,
quantityChange: $quantity, quantityChange: -1 * abs($quantity),
previousStock: $currentStock, previousStock: $currentStock,
newStock: $currentStock - $quantity, newStock: $newStock,
extraProperties: [ extraProperties: [
'orderable_id' => $item->orderable_id, 'orderable_id' => $item->orderable_id,
'orderable_type' => $item->orderable_type, 'orderable_type' => $item->orderable_type,
'purchase_id' => $item->purchase_id ?? null, 'order_id' => $item->order_id ?? null,
] ]
); );
}
return ['status' => true]; return ['status' => true];
} }

View File

@ -15,6 +15,11 @@ public function run(): void
OutletSeeder::class, OutletSeeder::class,
UserSeeder::class, UserSeeder::class,
TierSeeder::class, TierSeeder::class,
BrandSeeder::class,
CategorySeeder::class,
PerfumeSeeder::class,
ProductSeeder::class,
BottleSeeder::class,
]); ]);
} else { } else {
$this->call([ $this->call([

View File

@ -226,6 +226,7 @@ public function run(): void
'update outlet', 'update outlet',
'delete outlet', 'delete outlet',
'view user',
'create user', 'create user',
'update user', 'update user',
'delete user', 'delete user',
@ -267,7 +268,6 @@ public function run(): void
'delete bottle', 'delete bottle',
'audit bottle', 'audit bottle',
'view payroll',
'manage adjustment', 'manage adjustment',
'delete order', 'delete order',
@ -379,7 +379,6 @@ public function run(): void
'update bottle', 'update bottle',
'delete bottle', 'delete bottle',
'view payroll',
'manage adjustment', 'manage adjustment',
'delete order', 'delete order',
@ -388,6 +387,11 @@ public function run(): void
'create purchase', 'create purchase',
'delete purchase', 'delete purchase',
'view restock',
'create restock',
'update restock',
'delete restock',
'view stock opname', 'view stock opname',
'manage stock opname', 'manage stock opname',
'show stock opname', 'show stock opname',

View File

@ -0,0 +1,213 @@
<flux:main>
<div class="flex justify-between items-center">
<div>
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
</div>
<div>
<flux:button href="{{ route('studio.manage.restock.index') }}" wire:navigate class="text-sm">
Kembali
</flux:button>
</div>
</div>
<div class="mt-6">
<flux:callout icon="megaphone" color="blue" class="mb-6">
<flux:callout.heading>Informasi Restock</flux:callout.heading>
<flux:callout.text>
Proses restock akan **mengurangi** stok di Gudang dan **menambah** stok di Outlet yang dipilih.
</flux:callout.text>
</flux:callout>
<div class="flex flex-col lg:flex-row gap-4">
<div class="w-full lg:w-3/4 space-y-4">
<flux:card class="space-y-6 p-6">
<flux:input label="Catatan" placeholder="Restock rutin mingguan" wire:model="form.note"
autocomplete="off" />
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<flux:field>
<flux:label>Tanggal Restock <span class="text-red-500 ms-1">*</span></flux:label>
<flux:date-picker with-today wire:model="form.restock_date" placeholder="Pilih Tanggal"
autocomplete="off" locale="id-ID" selectable-header />
<flux:error name="form.restock_date" />
</flux:field>
<flux:field>
<flux:label>Gudang <span class="text-red-500 ms-1">*</span></flux:label>
<flux:select variant="listbox" searchable placeholder="Pilih Gudang"
wire:model.live="form.warehouse_id">
@foreach ($warehouses as $key => $value)
<flux:select.option value="{{ $key }}">{{ $value }}
</flux:select.option>
@endforeach
</flux:select>
<flux:error name="form.warehouse_id" />
</flux:field>
<flux:field>
<flux:label>Outlet <span class="text-red-500 ms-1">*</span></flux:label>
<flux:select variant="listbox" searchable placeholder="Pilih Outlet"
wire:model.live="form.outlet_id">
@foreach ($outlets as $key => $value)
<flux:select.option value="{{ $key }}">{{ $value }}
</flux:select.option>
@endforeach
</flux:select>
<flux:error name="form.outlet_id" />
</flux:field>
</div>
</flux:card>
@if ($form->warehouse_id && $form->outlet_id)
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<flux:card class="space-y-6 p-6">
<flux:select label="Parfum" variant="listbox" searchable placeholder="Cari parfum..."
wire:model="form.perfume_id">
@foreach ($perfumes as $key => $perfume)
<flux:select.option value="{{ $key }}">{{ $perfume }}
</flux:select.option>
@endforeach
</flux:select>
<flux:input label="Kuantitas" placeholder="500" wire:model="form.quantity_perfume"
autocomplete="off" x-mask:dynamic="$money($input, ',')" />
<div class="flex justify-end">
<flux:button variant="primary" color="zinc" wire:click="addItem('parfum')">
Tambah
</flux:button>
</div>
</flux:card>
<flux:card class="space-y-6 p-6">
<flux:select label="Produk" variant="listbox" searchable placeholder="Cari produk..."
wire:model="form.product_id">
@foreach ($products as $key => $product)
<flux:select.option value="{{ $key }}">{{ $product }}
</flux:select.option>
@endforeach
</flux:select>
<flux:input label="Kuantitas" placeholder="10" wire:model="form.quantity_product"
autocomplete="off" x-mask:dynamic="$money($input, ',')" />
<div class="flex justify-end">
<flux:button variant="primary" color="zinc" wire:click="addItem('produk')">
Tambah
</flux:button>
</div>
</flux:card>
<flux:card class="space-y-6 p-6">
<flux:select label="Botol" variant="listbox" searchable placeholder="Cari botol..."
wire:model="form.bottle_id">
@foreach ($bottles as $key => $bottle)
<flux:select.option value="{{ $key }}">{{ $bottle }}
</flux:select.option>
@endforeach
</flux:select>
<flux:input label="Kuantitas" placeholder="50" wire:model="form.quantity_bottle"
autocomplete="off" x-mask:dynamic="$money($input, ',')" />
<div class="flex justify-end">
<flux:button variant="primary" color="zinc" wire:click="addItem('botol')">
Tambah
</flux:button>
</div>
</flux:card>
</div>
@else
<flux:card class="p-6 text-center text-zinc-500 italic">
Silakan pilih Gudang Asal dan Outlet Tujuan terlebih dahulu untuk menampilkan daftar item.
</flux:card>
@endif
</div>
<div class="w-full lg:w-1/3 space-y-4">
<flux:card class="space-y-6 p-6">
<div class="space-y-3">
<h3 class="text-sm font-medium">Gambar</h3>
<div class="dropzone-wrapper">
<livewire:dropzone wire:model="form.image" :rules="['image', 'mimes:png,jpeg', 'max:10420']" :max-files="1"
:key="'image'" :files="$form->image" />
@error('form.image')
<div class="mt-3 text-sm font-medium text-red-500 dark:text-red-400">
{{ $message }}
</div>
@enderror
</div>
</div>
</flux:card>
<flux:card class="space-y-6 p-6">
<h3 class="text-sm font-medium">Keranjang Restock</h3>
<div class="divide-y text-sm">
<flux:table>
<flux:table.columns>
<flux:table.column>Item</flux:table.column>
<flux:table.column>Qty</flux:table.column>
<flux:table.column>Aksi</flux:table.column>
</flux:table.columns>
<flux:table.rows>
@forelse($restockItems as $item)
<flux:table.row>
<flux:table.cell>
<flux:heading>
<div>{{ $item->restockable->name }}</div>
</flux:heading>
</flux:table.cell>
<flux:table.cell>
{{ formatCurrencyNumber($item->quantity, '') }}
</flux:table.cell>
<flux:table.cell class="space-x-2">
<flux:modal.trigger name="form-modal">
<flux:tooltip content="Ubah">
<flux:button variant="primary" color="yellow" icon="pencil-square"
size="sm"
wire:click="$dispatch('modal:open', {'item': '{{ $item->id }}'})">
</flux:button>
</flux:tooltip>
</flux:modal.trigger>
<flux:button variant="danger" icon="trash" size="sm"
wire:click="deleteItem('{{ $item->id }}')">
</flux:button>
</flux:table.cell>
</flux:table.row>
@empty
<flux:table.row>
<flux:table.cell colspan="3" class="text-center">
<span class="text-sm text-zinc-500 italic">Keranjang Kosong...</span>
</flux:table.cell>
</flux:table.row>
@endforelse
</flux:table.rows>
</flux:table>
</div>
</flux:card>
</div>
</div>
<div class="flex justify-start mt-4">
<flux:button variant="primary" class="sm:w-auto cursor-pointer" wire:click="save">
Simpan
</flux:button>
</div>
</div>
<flux:modal name="form-modal" class="w-[95%] max-w-sm md:max-w-xl mx-auto" @close="closeModal('form-modal')">
<div class="p-4 space-y-6">
<flux:heading size="lg">Ubah {{ $modalTitle }}</flux:heading>
<flux:input label="Kuantitas" placeholder="Masukkan kuantitas" wire:model="form.quantity_edit"
autocomplete="off" x-mask:dynamic="$money($input, ',')" />
<div class="flex">
<flux:spacer />
<flux:button variant="primary" color="zinc" class="sm:w-auto cursor-pointer"
wire:click="updateItem">
Perbarui
</flux:button>
</div>
</div>
</flux:modal>
</flux:main>

View File

@ -0,0 +1,35 @@
<flux:main>
<div class="flex justify-between items-center">
<div>
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
</div>
@can('create restock')
<div>
<flux:button href="{{ route('studio.manage.restock.create') }}" variant="primary" wire:navigate
class="text-sm">
Tambah
</flux:button>
</div>
@endcan
</div>
<div class="mt-6">
<flux:callout icon="megaphone" color="blue" class="mb-6">
<flux:callout.heading>Informasi</flux:callout.heading>
<flux:callout.text>
Menghapus data restock otomatis mengembalikan stok gudang.
</flux:callout.text>
</flux:callout>
<livewire:datatable.manage.restocks-table />
</div>
@include('components.modals.confirmation', [
'modalName' => 'delete-confirmation',
'modalTitle' => 'Apakah Anda yakin?',
'modalMessage' => 'Data restock akan dihapus dari riwayat.',
'buttonVariant' => 'primary',
'buttonColor' => 'danger',
'buttonText' => 'Ya, Hapus',
])
</flux:main>

View File

@ -22,7 +22,6 @@
use App\Livewire\Studio\Finance\Payroll as PayrollComponent; use App\Livewire\Studio\Finance\Payroll as PayrollComponent;
use App\Livewire\Studio\Information\Broadcast as BroadcastComponent; use App\Livewire\Studio\Information\Broadcast as BroadcastComponent;
use App\Livewire\Studio\Information\Faq as FaqComponent; use App\Livewire\Studio\Information\Faq as FaqComponent;
use App\Livewire\Studio\Information\PriceRequest as PriceRequestComponent;
use App\Livewire\Studio\Information\Testimonial as TestimonialComponent; use App\Livewire\Studio\Information\Testimonial as TestimonialComponent;
use App\Livewire\Studio\Loyalty\Customer as CustomerComponent; use App\Livewire\Studio\Loyalty\Customer as CustomerComponent;
use App\Livewire\Studio\Loyalty\RedemptionVerification; use App\Livewire\Studio\Loyalty\RedemptionVerification;
@ -39,6 +38,8 @@
use App\Livewire\Studio\Manage\Order\Show as OrderShow; use App\Livewire\Studio\Manage\Order\Show as OrderShow;
use App\Livewire\Studio\Manage\Purchase\Create as PurchaseCreate; use App\Livewire\Studio\Manage\Purchase\Create as PurchaseCreate;
use App\Livewire\Studio\Manage\Purchase\Index as PurchaseIndex; use App\Livewire\Studio\Manage\Purchase\Index as PurchaseIndex;
use App\Livewire\Studio\Manage\Restock\Create as RestockCreate;
use App\Livewire\Studio\Manage\Restock\Index as RestockIndex;
use App\Livewire\Studio\Manage\StockOpname\Index as StockOpnameIndex; use App\Livewire\Studio\Manage\StockOpname\Index as StockOpnameIndex;
use App\Livewire\Studio\Manage\StockOpname\Manage as StockOpnameManage; use App\Livewire\Studio\Manage\StockOpname\Manage as StockOpnameManage;
use App\Livewire\Studio\Manage\StockOpname\Show as StockOpnameShow; use App\Livewire\Studio\Manage\StockOpname\Show as StockOpnameShow;
@ -50,7 +51,8 @@
use App\Livewire\Studio\Master\User\Create as UserCreate; use App\Livewire\Studio\Master\User\Create as UserCreate;
use App\Livewire\Studio\Master\User\Edit as UserEdit; use App\Livewire\Studio\Master\User\Edit as UserEdit;
use App\Livewire\Studio\Master\User\Index as UserIndex; use App\Livewire\Studio\Master\User\Index as UserIndex;
use App\Livewire\Studio\Master\Warehouse as WarehouseComponent; use App\Livewire\Studio\Master\Warehouse\Index as WarehouseIndex;
use App\Livewire\Studio\Master\Warehouse\Show as WarehouseShow;
use App\Livewire\Studio\Setting\Account as AccountComponent; use App\Livewire\Studio\Setting\Account as AccountComponent;
use App\Livewire\Studio\Setting\Application as ApplicationComponent; use App\Livewire\Studio\Setting\Application as ApplicationComponent;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -73,8 +75,9 @@
Route::get('/{outlet}/edit', OutletEdit::class)->name('edit')->middleware('can:update outlet'); Route::get('/{outlet}/edit', OutletEdit::class)->name('edit')->middleware('can:update outlet');
}); });
Route::prefix('warehouse')->name('warehouse.')->group(function () { Route::prefix('warehouses')->name('warehouse.')->group(function () {
Route::get('/', WarehouseComponent::class)->name('index')->middleware('can:view warehouse'); Route::get('/', WarehouseIndex::class)->name('index')->middleware('can:view warehouse');
Route::get('/{warehouse}/show', WarehouseShow::class)->name('show')->middleware('can:view warehouse');
}); });
Route::prefix('users')->name('user.')->group(function () { Route::prefix('users')->name('user.')->group(function () {
@ -165,6 +168,11 @@
Route::get('/create', PurchaseCreate::class)->name('create')->middleware('can:create purchase'); Route::get('/create', PurchaseCreate::class)->name('create')->middleware('can:create purchase');
}); });
Route::prefix('restocks')->name('restock.')->group(function () {
Route::get('/', RestockIndex::class)->name('index')->middleware('can:view restock');
Route::get('/create', RestockCreate::class)->name('create')->middleware('can:create restock');
});
Route::prefix('stock-opname')->name('stock_opname.')->group(function () { Route::prefix('stock-opname')->name('stock_opname.')->group(function () {
Route::get('/', StockOpnameIndex::class)->name('index')->middleware('can:view stock opname'); Route::get('/', StockOpnameIndex::class)->name('index')->middleware('can:view stock opname');
Route::get('/{stockOpname}/show', StockOpnameShow::class)->name('show')->middleware('can:show stock opname'); Route::get('/{stockOpname}/show', StockOpnameShow::class)->name('show')->middleware('can:show stock opname');
@ -181,12 +189,6 @@
Route::prefix('information')->name('studio.information.')->group(function () { Route::prefix('information')->name('studio.information.')->group(function () {
Route::get('/broadcasts', BroadcastComponent::class)->name('broadcast.index')->middleware('can:view broadcast'); Route::get('/broadcasts', BroadcastComponent::class)->name('broadcast.index')->middleware('can:view broadcast');
Route::prefix('price-requests')->name('price_request.')->group(function () {
Route::get('/', PriceRequestComponent::class)->name('index')->middleware('can:view price request');
Route::patch('/{priceRequest}/approve', PriceRequestComponent::class)->name('approve')->middleware('can:approve price request');
Route::patch('/{priceRequest}/reject', PriceRequestComponent::class)->name('reject')->middleware('can:reject price request');
});
Route::prefix('faqs')->name('faq.')->group(function () { Route::prefix('faqs')->name('faq.')->group(function () {
Route::get('/', FaqComponent::class)->name('index')->middleware('can:view faq'); Route::get('/', FaqComponent::class)->name('index')->middleware('can:view faq');
}); });