filters = $filters; } public function collection() { $today = now()->toDateString(); $expiryThreshold = now()->addDays(30)->toDateString(); $query = Inventory::query() ->join('products', 'inventories.product_id', '=', 'products.id') ->join('warehouses', 'inventories.warehouse_id', '=', 'warehouses.id') ->leftJoin('categories', 'products.category_id', '=', 'categories.id') ->leftJoin('warehouse_product_safety_stocks as ss', function ($join) { $join->on('inventories.warehouse_id', '=', 'ss.warehouse_id') ->on('inventories.product_id', '=', 'ss.product_id'); }) ->whereNull('inventories.deleted_at') ->select([ 'inventories.id', 'inventories.quantity', 'inventories.batch_number', 'inventories.expiry_date', 'inventories.quality_status', 'products.code as product_code', 'products.name as product_name', 'categories.name as category_name', 'warehouses.name as warehouse_name', 'ss.safety_stock', ]); // 篩選 if (!empty($this->filters['warehouse_id'])) { $query->where('inventories.warehouse_id', $this->filters['warehouse_id']); } if (!empty($this->filters['category_id'])) { $query->where('products.category_id', $this->filters['category_id']); } if (!empty($this->filters['search'])) { $search = $this->filters['search']; $query->where(function ($q) use ($search) { $q->where('products.code', 'like', "%{$search}%") ->orWhere('products.name', 'like', "%{$search}%"); }); } if (!empty($this->filters['status'])) { switch ($this->filters['status']) { case 'low_stock': $query->whereNotNull('ss.safety_stock') ->whereRaw('inventories.quantity <= ss.safety_stock') ->where('inventories.quantity', '>=', 0); break; case 'negative': $query->where('inventories.quantity', '<', 0); break; case 'expiring': $query->whereNotNull('inventories.expiry_date') ->where('inventories.expiry_date', '>', $today) ->where('inventories.expiry_date', '<=', $expiryThreshold); break; case 'expired': $query->whereNotNull('inventories.expiry_date') ->where('inventories.expiry_date', '<=', $today); break; case 'abnormal': $query->where(function ($q) use ($today, $expiryThreshold) { $q->where('inventories.quantity', '<', 0) ->orWhere(function ($q2) { $q2->whereNotNull('ss.safety_stock') ->whereRaw('inventories.quantity <= ss.safety_stock'); }) ->orWhere(function ($q2) use ($expiryThreshold) { $q2->whereNotNull('inventories.expiry_date') ->where('inventories.expiry_date', '<=', $expiryThreshold); }); }); break; } } return $query->orderBy('products.code', 'asc')->get(); } public function headings(): array { return [ '商品代碼', '商品名稱', '分類', '倉庫', '批號', '數量', '安全庫存', '到期日', '品質狀態', '狀態', ]; } public function map($row): array { $today = now()->toDateString(); $expiryThreshold = now()->addDays(30)->toDateString(); $statuses = []; if ($row->quantity < 0) { $statuses[] = '負庫存'; } if ($row->safety_stock !== null && $row->quantity <= $row->safety_stock && $row->quantity >= 0) { $statuses[] = '低庫存'; } if ($row->expiry_date) { if ($row->expiry_date <= $today) { $statuses[] = '已過期'; } elseif ($row->expiry_date <= $expiryThreshold) { $statuses[] = '即將過期'; } } if (empty($statuses)) { $statuses[] = '正常'; } $qualityLabels = [ 'normal' => '正常', 'inspecting' => '檢驗中', 'rejected' => '不合格', ]; return [ $row->product_code, $row->product_name, $row->category_name ?? '-', $row->warehouse_name, $row->batch_number ?? '-', $row->quantity, $row->safety_stock ?? '-', $row->expiry_date ?? '-', $qualityLabels[$row->quality_status] ?? $row->quality_status ?? '-', implode('、', $statuses), ]; } public function styles(Worksheet $sheet): array { return [ 1 => ['font' => ['bold' => true]], ]; } }