format('Y-m'); $this->toast('Stock opname untuk bulan '.formatDateLocalized($periodMonth, 'F Y').' berhasil digenerate.', 'Berhasil'); } public function requestApproval(StockOpname $stockOpname): void { $stockOpname->update(['status' => StockOpnameStatus::PENDING_APPROVAL]); $this->toast('Permohonan persetujuan berhasil diajukan.', 'Berhasil'); $this->redirectRoute('studio.stock.stock_opname.index', navigate: true); } public function approveApproval(StockOpname $stockOpname): void { DB::transaction(function () use ($stockOpname) { $stockOpname->update(['status' => StockOpnameStatus::COMPLETED]); $outlet = $stockOpname->outlet; foreach ($stockOpname->items as $item) { if (is_null($item->qty_physical)) { continue; } $itemable = $item->itemable; if (! $itemable) { continue; } $morphType = class_basename($itemable); $relationMap = [ 'Perfume' => 'perfumes', 'Product' => 'products', 'Bottle' => 'bottles', ]; if (! isset($relationMap[$morphType])) { continue; } $relationName = $relationMap[$morphType]; $currentPivot = $outlet->$relationName() ->where($itemable->getTable().'.id', $itemable->id) ->first(); // Current stock is the stock of the item in the outlet at the time the stock opname was created $currentStock = $currentPivot?->pivot?->stock ?? 0; // Calculate the difference between the physical stock and the system stock AT THE TIME the stock opname was created // then apply the difference to the current stock // Example: qty_system=100, qty_physical=90, transaction 20 occurred // currentStock=80, difference=-10, newStock=80+(-10)=70 ✓ $difference = $item->qty_physical - $item->qty_system; $newStock = max(0, $currentStock + $difference); $outlet->$relationName()->syncWithoutDetaching([ $itemable->id => ['stock' => $newStock], ]); StockActivityLogService::stockOpname( stockOpname: $stockOpname, product: $itemable, quantityChange: $newStock - $currentStock, previousStock: $currentStock, newStock: $newStock, note: $stockOpname->note ); } }); $this->toast('Stock opname berhasil disetujui dan stok outlet disesuaikan.', 'Berhasil'); $this->redirectRoute('studio.stock.stock_opname.index', navigate: true); } 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 ? floor(($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, ]); } }