feat: Implement restock management, refactor warehouse views, and enhance stock activity logging for sales.
This commit is contained in:
parent
77d3d25cd9
commit
cc917ac946
@ -76,6 +76,11 @@ public function approveApproval(StockOpname $stockOpname): void
|
||||
}
|
||||
|
||||
$itemable = $item->itemable;
|
||||
|
||||
if (! $itemable) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$morphType = class_basename($itemable);
|
||||
|
||||
$relationMap = [
|
||||
@ -90,9 +95,11 @@ public function approveApproval(StockOpname $stockOpname): void
|
||||
|
||||
$relationName = $relationMap[$morphType];
|
||||
|
||||
$currentStock = $outlet->$relationName()
|
||||
$currentPivot = $outlet->$relationName()
|
||||
->where($itemable->getTable().'.id', $itemable->id)
|
||||
->first()?->pivot?->stock ?? 0;
|
||||
->first();
|
||||
|
||||
$currentStock = $currentPivot?->pivot?->stock ?? 0;
|
||||
|
||||
$quantity = $item->qty_physical;
|
||||
|
||||
@ -100,26 +107,13 @@ public function approveApproval(StockOpname $stockOpname): void
|
||||
$itemable->id => ['stock' => $quantity],
|
||||
]);
|
||||
|
||||
StockActivityLogService::log(
|
||||
logName: 'Stock Opname',
|
||||
event: 'Mengubah',
|
||||
description: sprintf(
|
||||
'Stok %s di %s diperbarui dari %s menjadi %s',
|
||||
$itemable->name,
|
||||
$outlet->name,
|
||||
$currentStock,
|
||||
$quantity
|
||||
),
|
||||
performedOn: $itemable,
|
||||
outlet: $outlet,
|
||||
StockActivityLogService::stockOpname(
|
||||
stockOpname: $stockOpname,
|
||||
product: $itemable,
|
||||
quantityChange: $quantity - $currentStock,
|
||||
previousStock: $currentStock,
|
||||
newStock: $quantity,
|
||||
extraProperties: [
|
||||
// Preserving 'itemable_id' pointing to StockOpnameItem id as per original code overwriting
|
||||
'itemable_id' => $item->id ?? null,
|
||||
'itemable_type' => $item->itemable_type,
|
||||
]
|
||||
note: $stockOpname->note
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -40,6 +40,11 @@ public function outlets(): BelongsToMany
|
||||
return $this->belongsToMany(Outlet::class)->withPivot('stock');
|
||||
}
|
||||
|
||||
public function warehouses(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Warehouse::class)->withPivot('stock');
|
||||
}
|
||||
|
||||
public function purchaseItems(): MorphMany
|
||||
{
|
||||
return $this->morphMany(PurchaseItem::class, 'purchasable');
|
||||
@ -49,4 +54,9 @@ public function items(): MorphMany
|
||||
{
|
||||
return $this->morphMany(OrderItem::class, 'orderable');
|
||||
}
|
||||
|
||||
public function restockItems(): MorphMany
|
||||
{
|
||||
return $this->morphMany(RestockItem::class, 'restockable');
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,4 +99,9 @@ public function stockOpname(): HasMany
|
||||
{
|
||||
return $this->hasMany(StockOpname::class);
|
||||
}
|
||||
|
||||
public function restocks(): HasMany
|
||||
{
|
||||
return $this->hasMany(Restock::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,6 +53,11 @@ public function outlets(): BelongsToMany
|
||||
return $this->belongsToMany(Outlet::class)->withPivot('stock');
|
||||
}
|
||||
|
||||
public function warehouses(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Warehouse::class)->withPivot('stock');
|
||||
}
|
||||
|
||||
public function purchaseItems(): MorphMany
|
||||
{
|
||||
return $this->morphMany(PurchaseItem::class, 'purchasable');
|
||||
@ -62,4 +67,9 @@ public function items(): MorphMany
|
||||
{
|
||||
return $this->morphMany(OrderItem::class, 'orderable');
|
||||
}
|
||||
|
||||
public function restockItems(): MorphMany
|
||||
{
|
||||
return $this->morphMany(RestockItem::class, 'restockable');
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,6 +40,11 @@ public function outlets(): BelongsToMany
|
||||
return $this->belongsToMany(Outlet::class)->withPivot('stock');
|
||||
}
|
||||
|
||||
public function warehouses(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Warehouse::class)->withPivot('stock');
|
||||
}
|
||||
|
||||
public function purchaseItems(): MorphMany
|
||||
{
|
||||
return $this->morphMany(PurchaseItem::class, 'purchasable');
|
||||
@ -49,4 +54,9 @@ public function items(): MorphMany
|
||||
{
|
||||
return $this->morphMany(OrderItem::class, 'orderable');
|
||||
}
|
||||
|
||||
public function restockItems(): MorphMany
|
||||
{
|
||||
return $this->morphMany(RestockItem::class, 'restockable');
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,14 +2,167 @@
|
||||
|
||||
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;
|
||||
|
||||
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 $event,
|
||||
string $description,
|
||||
@ -28,6 +181,42 @@ public static function log(
|
||||
'quantity_change' => $quantityChange,
|
||||
'previous_stock' => $previousStock,
|
||||
'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);
|
||||
|
||||
activity($logName)
|
||||
|
||||
@ -39,24 +39,26 @@ public function increaseOutletStock($outlet, $item): void
|
||||
'stock' => $currentStock + $quantity,
|
||||
]);
|
||||
} else {
|
||||
$currentStock = 0;
|
||||
$outlet->{$relation}()->attach($item->orderable_id, [
|
||||
'stock' => $quantity,
|
||||
]);
|
||||
}
|
||||
$newStock = $currentStock + $quantity;
|
||||
|
||||
StockActivityLogService::log(
|
||||
logName: 'Order',
|
||||
event: 'Menambah',
|
||||
description: 'Stok '.$item->orderable->name.' di '.$outlet->name.' bertambah '.formatCurrencyNumber($quantity),
|
||||
StockActivityLogService::logOutlet(
|
||||
logName: StockActivityLogService::LOG_SALE,
|
||||
event: 'increase',
|
||||
description: 'Stok '.$item->orderable->name.' di '.$outlet->name.' bertambah '.formatCurrencyNumber($quantity).' (Pembatalan/Edit Order)',
|
||||
performedOn: $item->orderable,
|
||||
outlet: $outlet,
|
||||
quantityChange: $quantity,
|
||||
quantityChange: abs($quantity),
|
||||
previousStock: $currentStock,
|
||||
newStock: $currentStock + $quantity,
|
||||
newStock: $newStock,
|
||||
extraProperties: [
|
||||
'orderable_id' => $item->orderable_id,
|
||||
'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,
|
||||
]);
|
||||
|
||||
StockActivityLogService::log(
|
||||
logName: 'Order',
|
||||
event: 'Mengurangi',
|
||||
description: 'Stok '.$item->orderable->name.' di '.$outlet->name.' berkurang '.formatCurrencyNumber($quantity),
|
||||
performedOn: $item->orderable,
|
||||
outlet: $outlet,
|
||||
quantityChange: $quantity,
|
||||
previousStock: $currentStock,
|
||||
newStock: $currentStock - $quantity,
|
||||
extraProperties: [
|
||||
'orderable_id' => $item->orderable_id,
|
||||
'orderable_type' => $item->orderable_type,
|
||||
'purchase_id' => $item->purchase_id ?? null,
|
||||
]
|
||||
);
|
||||
$order = $item->order ?? \App\Models\Order::find($item->order_id);
|
||||
|
||||
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),
|
||||
performedOn: $item->orderable,
|
||||
outlet: $outlet,
|
||||
quantityChange: -1 * abs($quantity),
|
||||
previousStock: $currentStock,
|
||||
newStock: $newStock,
|
||||
extraProperties: [
|
||||
'orderable_id' => $item->orderable_id,
|
||||
'orderable_type' => $item->orderable_type,
|
||||
'order_id' => $item->order_id ?? null,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
return ['status' => true];
|
||||
}
|
||||
|
||||
@ -15,6 +15,11 @@ public function run(): void
|
||||
OutletSeeder::class,
|
||||
UserSeeder::class,
|
||||
TierSeeder::class,
|
||||
BrandSeeder::class,
|
||||
CategorySeeder::class,
|
||||
PerfumeSeeder::class,
|
||||
ProductSeeder::class,
|
||||
BottleSeeder::class,
|
||||
]);
|
||||
} else {
|
||||
$this->call([
|
||||
|
||||
@ -226,6 +226,7 @@ public function run(): void
|
||||
'update outlet',
|
||||
'delete outlet',
|
||||
|
||||
'view user',
|
||||
'create user',
|
||||
'update user',
|
||||
'delete user',
|
||||
@ -267,7 +268,6 @@ public function run(): void
|
||||
'delete bottle',
|
||||
'audit bottle',
|
||||
|
||||
'view payroll',
|
||||
'manage adjustment',
|
||||
|
||||
'delete order',
|
||||
@ -379,7 +379,6 @@ public function run(): void
|
||||
'update bottle',
|
||||
'delete bottle',
|
||||
|
||||
'view payroll',
|
||||
'manage adjustment',
|
||||
|
||||
'delete order',
|
||||
@ -388,6 +387,11 @@ public function run(): void
|
||||
'create purchase',
|
||||
'delete purchase',
|
||||
|
||||
'view restock',
|
||||
'create restock',
|
||||
'update restock',
|
||||
'delete restock',
|
||||
|
||||
'view stock opname',
|
||||
'manage stock opname',
|
||||
'show stock opname',
|
||||
|
||||
213
resources/views/livewire/studio/manage/restock/form.blade.php
Normal file
213
resources/views/livewire/studio/manage/restock/form.blade.php
Normal 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>
|
||||
@ -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>
|
||||
@ -22,7 +22,6 @@
|
||||
use App\Livewire\Studio\Finance\Payroll as PayrollComponent;
|
||||
use App\Livewire\Studio\Information\Broadcast as BroadcastComponent;
|
||||
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\Loyalty\Customer as CustomerComponent;
|
||||
use App\Livewire\Studio\Loyalty\RedemptionVerification;
|
||||
@ -39,6 +38,8 @@
|
||||
use App\Livewire\Studio\Manage\Order\Show as OrderShow;
|
||||
use App\Livewire\Studio\Manage\Purchase\Create as PurchaseCreate;
|
||||
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\Manage as StockOpnameManage;
|
||||
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\Edit as UserEdit;
|
||||
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\Application as ApplicationComponent;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@ -73,8 +75,9 @@
|
||||
Route::get('/{outlet}/edit', OutletEdit::class)->name('edit')->middleware('can:update outlet');
|
||||
});
|
||||
|
||||
Route::prefix('warehouse')->name('warehouse.')->group(function () {
|
||||
Route::get('/', WarehouseComponent::class)->name('index')->middleware('can:view warehouse');
|
||||
Route::prefix('warehouses')->name('warehouse.')->group(function () {
|
||||
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 () {
|
||||
@ -165,6 +168,11 @@
|
||||
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::get('/', StockOpnameIndex::class)->name('index')->middleware('can:view 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::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::get('/', FaqComponent::class)->name('index')->middleware('can:view faq');
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user