get(); } public function getUnits() { return \App\Modules\Inventory\Models\Unit::all(); } public function getInventoriesByIds(array $ids, array $with = []) { return Inventory::whereIn('id', $ids)->with($with)->get(); } public function getProduct(int $id) { return Product::with(['baseUnit', 'largeUnit'])->find($id); } public function getProductsByIds(array $ids) { return Product::whereIn('id', $ids)->with(['baseUnit', 'largeUnit'])->get(); } public function getProductsByName(string $name) { return Product::where('name', 'like', "%{$name}%")->with(['baseUnit', 'largeUnit'])->get(); } public function getWarehouse(int $id) { return Warehouse::find($id); } public function checkStock(int $productId, int $warehouseId, float $quantity): bool { $stock = Inventory::where('product_id', $productId) ->where('warehouse_id', $warehouseId) ->sum('quantity'); return $stock >= $quantity; } public function decreaseStock(int $productId, int $warehouseId, float $quantity, ?string $reason = null): void { DB::transaction(function () use ($productId, $warehouseId, $quantity, $reason) { $inventories = Inventory::where('product_id', $productId) ->where('warehouse_id', $warehouseId) ->where('quantity', '>', 0) ->orderBy('arrival_date', 'asc') ->get(); $remainingToDecrease = $quantity; foreach ($inventories as $inventory) { if ($remainingToDecrease <= 0) break; $decreaseAmount = min($inventory->quantity, $remainingToDecrease); $this->decreaseInventoryQuantity($inventory->id, $decreaseAmount, $reason); $remainingToDecrease -= $decreaseAmount; } if ($remainingToDecrease > 0) { // 這裡可以選擇報錯或允許負庫存,目前為了嚴謹拋出異常 throw new \Exception("庫存不足,無法扣除所有請求的數量。"); } }); } public function getInventoriesByWarehouse(int $warehouseId) { return Inventory::with(['product.baseUnit', 'product.largeUnit']) ->where('warehouse_id', $warehouseId) ->where('quantity', '>', 0) ->orderBy('arrival_date', 'asc') ->get(); } public function createInventoryRecord(array $data) { return DB::transaction(function () use ($data) { // 嘗試查找是否已有相同批號的庫存 $inventory = Inventory::where('warehouse_id', $data['warehouse_id']) ->where('product_id', $data['product_id']) ->where('batch_number', $data['batch_number'] ?? null) ->first(); $balanceBefore = 0; if ($inventory) { // 若存在,則更新數量與相關資訊 (鎖定行以避免併發問題) $inventory = Inventory::lockForUpdate()->find($inventory->id); $balanceBefore = $inventory->quantity; // 加權平均成本計算 (可選,這裡先採簡單邏輯:若有新成本則更新,否則沿用) // 若本次入庫有指定成本,則更新該批次單價 (假設同批號成本相同) if (isset($data['unit_cost'])) { $inventory->unit_cost = $data['unit_cost']; } $inventory->quantity += $data['quantity']; // 更新總價值 $inventory->total_value = $inventory->quantity * $inventory->unit_cost; // 更新其他可能變更的欄位 (如最後入庫日) $inventory->arrival_date = $data['arrival_date'] ?? $inventory->arrival_date; $inventory->save(); } else { // 若不存在,則建立新紀錄 $unitCost = $data['unit_cost'] ?? 0; $inventory = Inventory::create([ 'warehouse_id' => $data['warehouse_id'], 'product_id' => $data['product_id'], 'quantity' => $data['quantity'], 'unit_cost' => $unitCost, 'total_value' => $data['quantity'] * $unitCost, 'batch_number' => $data['batch_number'] ?? null, 'box_number' => $data['box_number'] ?? null, 'origin_country' => $data['origin_country'] ?? 'TW', 'arrival_date' => $data['arrival_date'] ?? now(), 'expiry_date' => $data['expiry_date'] ?? null, 'quality_status' => $data['quality_status'] ?? 'normal', 'source_purchase_order_id' => $data['source_purchase_order_id'] ?? null, ]); } \App\Modules\Inventory\Models\InventoryTransaction::create([ 'inventory_id' => $inventory->id, 'type' => '入庫', 'quantity' => $data['quantity'], 'unit_cost' => $inventory->unit_cost, // 記錄當下成本 'balance_before' => $balanceBefore, 'balance_after' => $inventory->quantity, 'reason' => $data['reason'] ?? '手動入庫', 'reference_type' => $data['reference_type'] ?? null, 'reference_id' => $data['reference_id'] ?? null, 'user_id' => auth()->id(), 'actual_time' => now(), ]); return $inventory; }); } public function decreaseInventoryQuantity(int $inventoryId, float $quantity, ?string $reason = null, ?string $referenceType = null, $referenceId = null): void { DB::transaction(function () use ($inventoryId, $quantity, $reason, $referenceType, $referenceId) { $inventory = Inventory::lockForUpdate()->findOrFail($inventoryId); $balanceBefore = $inventory->quantity; $inventory->decrement('quantity', $quantity); // decrement 不會自動觸發 total_value 更新 // 需要手動更新總價值 $inventory->refresh(); $inventory->total_value = $inventory->quantity * $inventory->unit_cost; $inventory->save(); \App\Modules\Inventory\Models\InventoryTransaction::create([ 'inventory_id' => $inventory->id, 'type' => '出庫', 'quantity' => -$quantity, 'unit_cost' => $inventory->unit_cost, // 記錄出庫時的成本 'balance_before' => $balanceBefore, 'balance_after' => $inventory->quantity, 'reason' => $reason ?? '庫存扣減', 'reference_type' => $referenceType, 'reference_id' => $referenceId, 'user_id' => auth()->id(), 'actual_time' => now(), ]); }); } public function findInventoryByBatch(int $warehouseId, int $productId, ?string $batchNumber) { return Inventory::where('warehouse_id', $warehouseId) ->where('product_id', $productId) ->where('batch_number', $batchNumber) ->first(); } public function getDashboardStats(): array { // 庫存總表 join 安全庫存表,計算低庫存 $lowStockCount = DB::table('warehouse_product_safety_stocks as ss') ->join(DB::raw('(SELECT warehouse_id, product_id, SUM(quantity) as total_qty FROM inventories WHERE deleted_at IS NULL GROUP BY warehouse_id, product_id) as inv'), function ($join) { $join->on('ss.warehouse_id', '=', 'inv.warehouse_id') ->on('ss.product_id', '=', 'inv.product_id'); }) ->whereRaw('inv.total_qty <= ss.safety_stock') ->count(); return [ 'productsCount' => Product::count(), 'warehousesCount' => Warehouse::count(), 'lowStockCount' => $lowStockCount, 'totalInventoryQuantity' => Inventory::sum('quantity'), ]; } }