$warehouseId, 'status' => 'counting', 'snapshot_date' => now(), 'remarks' => $remarks, 'created_by' => $userId, ]); return $doc; }); } /** * 執行快照:鎖定當前庫存量 */ public function snapshot(InventoryCountDoc $doc, bool $updateDoc = true): void { DB::transaction(function () use ($doc, $updateDoc) { // 清除舊的 items (如果有) $doc->items()->delete(); // 取得該倉庫所有庫存 (包含 quantity = 0 但未軟刪除的) // 這裡可以根據需求決定是否要過濾掉 0 庫存,通常盤點單會希望能看到所有 "帳上有紀錄" 的東西 $inventories = Inventory::where('warehouse_id', $doc->warehouse_id) ->whereNull('deleted_at') ->get(); $items = []; foreach ($inventories as $inv) { $items[] = [ 'count_doc_id' => $doc->id, 'product_id' => $inv->product_id, 'batch_number' => $inv->batch_number, 'system_qty' => $inv->quantity, 'counted_qty' => null, // 預設未盤點 'diff_qty' => 0, 'created_at' => now(), 'updated_at' => now(), ]; } if (!empty($items)) { InventoryCountItem::insert($items); } if ($updateDoc) { $doc->update([ 'status' => 'counting', 'snapshot_date' => now(), ]); } }); } /** * 完成盤點:過帳差異 */ public function complete(InventoryCountDoc $doc, int $userId): void { DB::transaction(function () use ($doc, $userId) { // 僅更新單據狀態為「已完成」,不執行庫存入庫/調整 // 盤點單僅作為記錄,後續調整由盤調單 (AdjustDoc) 執行 $doc->update([ 'status' => 'completed', 'completed_at' => now(), 'completed_by' => $userId, ]); }); } /** * 更新盤點數量 */ public function updateCount(InventoryCountDoc $doc, array $itemsData): void { DB::transaction(function () use ($doc, $itemsData) { $updatedItems = []; $hasChanges = false; $oldDocAttributes = [ 'status' => $doc->status, 'completed_at' => $doc->completed_at ? $doc->completed_at->format('Y-m-d H:i:s') : null, 'completed_by' => $doc->completed_by, ]; foreach ($itemsData as $data) { $item = $doc->items()->with('product')->find($data['id']); if ($item) { $oldQty = $item->counted_qty; $newQty = $data['counted_qty']; $oldNotes = $item->notes; $newNotes = $data['notes'] ?? $item->notes; $isQtyChanged = $oldQty != $newQty; $isNotesChanged = $oldNotes !== $newNotes; if ($isQtyChanged || $isNotesChanged) { $updatedItems[] = [ 'product_name' => $item->product->name, 'old' => [ 'counted_qty' => $oldQty, 'notes' => $oldNotes, ], 'new' => [ 'counted_qty' => $newQty, 'notes' => $newNotes, ] ]; $countedQty = $data['counted_qty']; $diff = is_numeric($countedQty) ? ($countedQty - $item->system_qty) : 0; $item->update([ 'counted_qty' => $countedQty, 'diff_qty' => $diff, 'notes' => $newNotes, ]); $hasChanges = true; } } } // 檢查是否完成 $doc->refresh(); $isAllCounted = $doc->items()->whereNull('counted_qty')->count() === 0; $newDocAttributesLog = []; if ($isAllCounted) { // 檢查是否有任何差異 $hasDiff = $doc->items()->where('diff_qty', '!=', 0)->exists(); $targetStatus = $hasDiff ? 'completed' : 'no_adjust'; if ($doc->status !== $targetStatus) { $doc->status = $targetStatus; $doc->completed_at = now(); $doc->completed_by = auth()->id(); $doc->saveQuietly(); $doc->refresh(); $newDocAttributesLog = [ 'status' => $targetStatus, 'completed_at' => $doc->completed_at->format('Y-m-d H:i:s'), 'completed_by' => $doc->completed_by, ]; $hasChanges = true; } } else { if ($doc->status === 'completed') { $doc->status = 'counting'; $doc->completed_at = null; $doc->completed_by = null; $doc->saveQuietly(); $newDocAttributesLog = [ 'status' => 'counting', 'completed_at' => null, 'completed_by' => null, ]; $hasChanges = true; } } // 記錄操作日誌 if ($hasChanges) { $properties = [ 'items_diff' => [ 'added' => [], 'removed' => [], 'updated' => $updatedItems, ], ]; // 如果有文件層級的屬性變更 (狀態),併入 log if (!empty($newDocAttributesLog)) { $properties['attributes'] = $newDocAttributesLog; $properties['old'] = array_intersect_key($oldDocAttributes, $newDocAttributesLog); } activity() ->performedOn($doc) ->causedBy(auth()->user()) ->event('updated') ->withProperties($properties) ->log('updated'); } }); } }