商品ID => 顶级分类名 */ private array $categoryNames = []; /** @var array 供应商ID => 名称 */ private array $supplierNames = []; /** @var array 门店ID => 名称(导出列) */ private array $storeNames = []; private ?Collection $items = null; /** * @param PurchaseOrderModel $purchase 采购单 * @param string $type all 全品类 / category 仅蔬果分类 */ public function __construct( private readonly PurchaseOrderModel $purchase, private readonly string $type = 'all', ) { } /** * 导出行(商品聚合行,按 分类sort → 商品sort 排序;蔬果分类时按顶级分类名过滤) */ public function collection(): Collection { if ($this->items !== null) { return $this->items; } $orderItems = StoreOrderItemModel::query() ->join('store_order', 'store_order.id', '=', 'store_order_item.order_id') ->where('store_order_item.purchase_id', $this->purchase->id) ->whereNull('store_order.deleted_at') ->select('store_order_item.*') ->get() ->makeVisible('cost_price'); $this->storeNames = StoreModel::withTrashed() ->whereIn('id', $orderItems->pluck('store_id')->unique()) ->pluck('name', 'id') ->toArray(); $products = ProductModel::withTrashed() ->with('category:id,sort') ->whereIn('id', $orderItems->pluck('product_id')->unique()) ->get() ->keyBy('id'); $rows = []; foreach ($orderItems->groupBy('product_id') as $productId => $group) { $first = $group->first(); $product = $products->get((int) $productId); $unitCost = PurchaseEditService::unitCost((string) ($first->cost_price ?? '0'), (string) $first->product_spec); $quantity = '0'; $weight = '0'; $amount = '0'; $storeQuantities = array_fill_keys(array_keys($this->storeNames), '0'); foreach ($group as $item) { $quantity = bcadd($quantity, (string) $item->quantity, 2); $weight = bcadd($weight, (string) $item->weight, 3); $amount = bcadd($amount, PurchaseEditService::costAmount( (string) $item->quantity, (string) $item->weight, $unitCost ), 2); $storeQuantities[(int) $item->store_id] = bcadd( $storeQuantities[(int) $item->store_id] ?? '0', (string) $item->quantity, 2 ); } $rows[] = [ 'product_id' => (int) $productId, 'supplier_id' => (int) $first->supplier_id, 'product_name' => $first->product_name, 'product_spec' => $first->product_spec, 'unit' => $first->unit, 'cost_price' => (float) ($first->cost_price ?? 0), 'unit_cost' => (float) $unitCost, 'quantity' => (float) $quantity, 'weight' => (float) $weight, 'amount' => (float) $amount, 'store_quantities' => array_map('floatval', $storeQuantities), 'category_sort' => (int) ($product->category->sort ?? 9999), 'product_sort' => (int) ($product->sort ?? 9999), ]; } usort($rows, static fn (array $a, array $b): int => [$a['category_sort'], $a['product_sort'], $a['product_id']] <=> [$b['category_sort'], $b['product_sort'], $b['product_id']]); $items = collect($rows); $this->loadLookups($items); if ($this->type === 'category') { $items = $items->filter(function (array $row): bool { $rootName = $this->categoryNames[$row['product_id']] ?? ''; return str_contains($rootName, '蔬菜') || str_contains($rootName, '水果'); })->values(); } $sort = 1; return $this->items = $items->map(static function (array $row) use (&$sort): array { $row['sort'] = $sort++; return $row; })->values(); } public function headings(): array { $this->collection(); return array_merge( ['序号', '分类', '品名', '供应商', '包规', '单位', '成本', '单价', '数量', '实际称重', '金额'], array_map(static fn (string $name): string => $name . '(数量)', array_values($this->storeNames)), ); } public function map($row): array { return array_merge([ $row['sort'], $this->categoryNames[$row['product_id']] ?? '', $row['product_name'], $this->supplierNames[$row['supplier_id']] ?? '', $row['product_spec'], $row['unit'], $row['cost_price'], $row['unit_cost'], $row['quantity'], $row['weight'], $row['amount'], ], array_values($row['store_quantities'])); } /** * 表头加粗 + 冻结首行 */ public function styles(Worksheet $sheet): array { $sheet->freezePane('A2'); return [ 1 => ['font' => ['bold' => true]], ]; } /** * PDF 模板视图数据 * * @return array{purchase: PurchaseOrderModel, items: Collection, storeNames: array, categoryNames: array, supplierNames: array} */ public function viewData(): array { return [ 'purchase' => $this->purchase, 'items' => $this->collection(), 'storeNames' => $this->storeNames, 'categoryNames' => $this->categoryNames, 'supplierNames' => $this->supplierNames, ]; } /** * 预加载供应商名与商品顶级分类名(商品/供应商均含软删除,保证历史单据可导出) */ private function loadLookups(Collection $items): void { $this->supplierNames = SupplierModel::withTrashed() ->whereIn('id', $items->pluck('supplier_id')->unique()) ->pluck('name', 'id') ->toArray(); $productCategoryIds = ProductModel::withTrashed() ->whereIn('id', $items->pluck('product_id')->unique()) ->pluck('category_id', 'id'); $categories = ProductCategoryModel::all()->keyBy('id'); $this->categoryNames = []; foreach ($productCategoryIds as $productId => $categoryId) { $rootName = ''; $cursor = (int) $categoryId; $guard = 0; while ($cursor > 0 && $guard++ < 20) { $category = $categories->get($cursor); if ($category === null) { break; } $rootName = $category->name; $cursor = (int) $category->parent_id; } $this->categoryNames[(int) $productId] = $rootName; } } }