feat: Implement stock opname generation, add search functionality to stock opname items, and refine quantity input handling.
This commit is contained in:
parent
d4b7c9d1db
commit
d2b764d3f4
@ -54,7 +54,8 @@ public function update(): void
|
||||
continue;
|
||||
}
|
||||
|
||||
$item->qty_physical = str_replace('.', '', (string) $qtyPhysical);
|
||||
$item->qty_physical = (int) str_replace('.', '', (string) ($qtyPhysical ?? 0));
|
||||
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,9 @@
|
||||
use App\Services\StockActivityLogService;
|
||||
use App\Traits\Components\WithConfirmation;
|
||||
use App\Traits\Components\WithToast;
|
||||
use App\Traits\Utilities\WithRequestNotification;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Component;
|
||||
@ -15,36 +17,15 @@
|
||||
#[Title('Stock Opname')]
|
||||
class Index extends Component
|
||||
{
|
||||
use WithConfirmation, WithToast;
|
||||
use WithConfirmation, WithRequestNotification, WithToast;
|
||||
|
||||
public $stockOpname;
|
||||
|
||||
public function mount(): void
|
||||
public function generate(): void
|
||||
{
|
||||
$this->loadItems();
|
||||
}
|
||||
Artisan::call('stock-opname:generate');
|
||||
|
||||
protected function loadItems(): void
|
||||
{
|
||||
$this->stockOpname = StockOpname::with(['outlet', 'items'])
|
||||
->where('period_month', now()->format('Y-m'))
|
||||
->get()
|
||||
->map(function ($stockOpname) {
|
||||
$total = $stockOpname->items()->count();
|
||||
$periodMonth = now()->format('Y-m');
|
||||
|
||||
$filled = $stockOpname->items()
|
||||
->whereNotNull('qty_physical')
|
||||
->count();
|
||||
|
||||
$percent = $total > 0 ? round(($filled / $total) * 100) : 0;
|
||||
|
||||
$stockOpname->total_item = $total;
|
||||
$stockOpname->progress_filled = $filled;
|
||||
$stockOpname->progress_percent = $percent;
|
||||
$stockOpname->period_month = formatDateLocalized($stockOpname->period_month, 'F Y');
|
||||
|
||||
return $stockOpname;
|
||||
});
|
||||
$this->toast('Stock opname untuk bulan '.formatDateLocalized($periodMonth, 'F Y').' berhasil digenerate.', 'Berhasil');
|
||||
}
|
||||
|
||||
public function requestApproval(StockOpname $stockOpname): void
|
||||
@ -119,8 +100,29 @@ public function approveApproval(StockOpname $stockOpname): void
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$stockOpname = StockOpname::with(['outlet', 'items'])
|
||||
->where('period_month', now()->format('Y-m'))
|
||||
->get()
|
||||
->map(function ($stockOpname) {
|
||||
$total = $stockOpname->items()->count();
|
||||
|
||||
$filled = $stockOpname->items()
|
||||
->whereNotNull('qty_physical')
|
||||
->count();
|
||||
|
||||
$percent = $total > 0 ? round(($filled / $total) * 100) : 0;
|
||||
|
||||
$stockOpname->total_item = $total;
|
||||
$stockOpname->progress_filled = $filled;
|
||||
$stockOpname->progress_percent = $percent;
|
||||
$stockOpname->period_month = formatDateLocalized($stockOpname->period_month, 'F Y');
|
||||
|
||||
return $stockOpname;
|
||||
});
|
||||
|
||||
return view('livewire.studio.stock.stock-opname.index', [
|
||||
'pageTitle' => 'Stock Opname',
|
||||
'stockOpname' => $stockOpname,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
use App\Traits\Components\WithConfirmation;
|
||||
use App\Traits\Components\WithToast;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -21,7 +20,7 @@ class Manage extends Component
|
||||
|
||||
public StockOpnameForm $form;
|
||||
|
||||
public Collection $items;
|
||||
public string $search = '';
|
||||
|
||||
public function mount(StockOpname $stockOpname): void
|
||||
{
|
||||
@ -29,12 +28,12 @@ public function mount(StockOpname $stockOpname): void
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$this->items = StockOpnameItem::with('itemable')
|
||||
$items = StockOpnameItem::with('itemable')
|
||||
->where('stock_opname_id', $stockOpname->id)
|
||||
->get();
|
||||
|
||||
$outletStock = [];
|
||||
foreach ($this->items as $item) {
|
||||
foreach ($items as $item) {
|
||||
$outletStock[$item->id] = $item->qty_physical;
|
||||
}
|
||||
|
||||
@ -43,35 +42,32 @@ public function mount(StockOpname $stockOpname): void
|
||||
$this->form->setStockOpname($stockOpname);
|
||||
}
|
||||
|
||||
public function updated(string $propertyName, $value): void
|
||||
{
|
||||
$this->canOrAbort('manage stock opname');
|
||||
|
||||
if (str_starts_with($propertyName, 'form.outlet_stock.')) {
|
||||
$itemId = explode('.', $propertyName)[2];
|
||||
|
||||
$item = $this->items->firstWhere('id', $itemId);
|
||||
if ($item) {
|
||||
$normalizedValue = str_replace('.', '', (string) $value);
|
||||
$item->qty_physical = (int) $normalizedValue;
|
||||
$item->difference = (int) $normalizedValue - $item->qty_system;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
$this->canOrAbort('manage stock opname');
|
||||
|
||||
$this->form->update();
|
||||
|
||||
$this->toast('Data stock opname berhasil disimpan.', 'Berhasil');
|
||||
$this->redirectRoute('studio.stock.stock_opname.index', [
|
||||
'notification' => 'Stock opname berhasil disimpan.',
|
||||
], navigate: true);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
$items = StockOpnameItem::with('itemable')
|
||||
->where('stock_opname_id', $this->form->stockOpname->id)
|
||||
->when($this->search, function ($query) {
|
||||
$query->whereHasMorph('itemable', '*', function ($query) {
|
||||
$query->where('name', 'like', '%'.$this->search.'%')
|
||||
->orWhere('display_name', 'like', '%'.$this->search.'%');
|
||||
});
|
||||
})
|
||||
->get();
|
||||
|
||||
return view('livewire.studio.stock.stock-opname.manage', [
|
||||
'pageTitle' => 'Kelola Stock Opname',
|
||||
'items' => $items,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ protected function casts(): array
|
||||
return [
|
||||
'qty_system' => 'int',
|
||||
'qty_physical' => 'int',
|
||||
'differences' => 'int',
|
||||
'difference' => 'int',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -159,6 +159,7 @@ public function run(): void
|
||||
'show stock opname',
|
||||
'approve stock opname',
|
||||
'manage stock opname',
|
||||
'generate stock opname',
|
||||
|
||||
'view stock history',
|
||||
'show stock history',
|
||||
@ -291,6 +292,7 @@ public function run(): void
|
||||
'update social settings',
|
||||
|
||||
'approve stock opname',
|
||||
'generate stock opname',
|
||||
|
||||
'create tier',
|
||||
'update tier',
|
||||
@ -434,6 +436,7 @@ public function run(): void
|
||||
'view stock opname',
|
||||
'show stock opname',
|
||||
'manage stock opname',
|
||||
'generate stock opname',
|
||||
|
||||
'view testimonial',
|
||||
'delete testimonial',
|
||||
|
||||
@ -3,10 +3,17 @@
|
||||
<div>
|
||||
<flux:heading size="xl">{{ $pageTitle }}</flux:heading>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@can('generate stock opname')
|
||||
<flux:button variant="primary" class="text-sm" wire:click="generate">
|
||||
Generate
|
||||
</flux:button>
|
||||
@endcan
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 mb-4 text-sm items-start" wire:ignore>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4 mb-4 text-sm items-start">
|
||||
@foreach ($stockOpname as $item)
|
||||
<flux:card class="h-auto" wire:key="stock-opname-{{ $item->id }}">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
|
||||
@ -29,10 +29,11 @@
|
||||
<flux:table.rows>
|
||||
@foreach ($items as $item)
|
||||
<flux:table.row wire:key="{{ $item->id }}">
|
||||
<flux:table.cell>{{ $item->itemable?->display_name ?? $item->itemable?->name }}</flux:table.cell>
|
||||
<flux:table.cell>{{ $item->itemable?->display_name ?? $item->itemable?->name }}
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>{{ $item->qty_system }}</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:input wire:model.live.debounce.500ms="form.outlet_stock.{{ $item->id }}"
|
||||
<flux:input wire:model="form.outlet_stock.{{ $item->id }}"
|
||||
x-mask:dynamic="$money($input, ',')" autocomplete="off" />
|
||||
</flux:table.cell>
|
||||
<flux:table.cell variant="strong">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user