input('per_page', 10); $batches = SalesImportBatch::with('importer') ->orderByDesc('created_at') ->paginate($perPage) ->withQueryString(); return Inertia::render('Sales/Import/Index', [ 'batches' => $batches, 'filters' => [ 'per_page' => $perPage, ], ]); } public function create() { return Inertia::render('Sales/Import/Create'); } public function store(Request $request) { $request->validate([ 'file' => 'required|file|mimes:xlsx,xls,csv,zip', ]); DB::transaction(function () use ($request) { $batch = SalesImportBatch::create([ 'import_date' => now(), 'imported_by' => auth()->id(), 'status' => 'pending', 'tenant_id' => tenant('id'), // If tenant context requires it, but usually automatic ]); Excel::import(new SalesImport($batch), $request->file('file')); }); return redirect()->route('sales-imports.index')->with('success', '匯入成功,請確認內容。'); } public function show(Request $request, SalesImportBatch $import) { $import->load(['items', 'importer']); $perPage = $request->input('per_page', 10); return Inertia::render('Sales/Import/Show', [ 'import' => $import, 'items' => $import->items()->with(['product', 'warehouse'])->paginate($perPage)->withQueryString(), 'filters' => [ 'per_page' => $perPage, ], ]); } public function confirm(SalesImportBatch $import, InventoryService $inventoryService) { if ($import->status !== 'pending') { return back()->with('error', '此批次無法確認。'); } DB::transaction(function () use ($import, $inventoryService) { // 1. Prepare Aggregation $aggregatedDeductions = []; // Key: "warehouse_id:product_id:slot" // Pre-load necessary warehouses for matching $machineIds = $import->items->pluck('machine_id')->filter()->unique(); $warehouses = \App\Modules\Inventory\Models\Warehouse::whereIn('code', $machineIds)->get()->keyBy('code'); foreach ($import->items as $item) { // Only process shipped items with a valid product if ($item->product_id && $item->original_status === '已出貨') { // Resolve Warehouse from Machine ID $warehouse = $warehouses->get($item->machine_id); // Skip if machine_id is empty or warehouse not found if (!$warehouse) { continue; } // Aggregation Key includes Slot (貨道) $slot = $item->slot ?: ''; $key = "{$warehouse->id}:{$item->product_id}:{$slot}"; if (!isset($aggregatedDeductions[$key])) { $aggregatedDeductions[$key] = [ 'warehouse_id' => $warehouse->id, 'product_id' => $item->product_id, 'slot' => $slot, 'quantity' => 0, 'details' => [] ]; } $aggregatedDeductions[$key]['quantity'] += $item->quantity; $aggregatedDeductions[$key]['details'][] = $item->transaction_serial; } } // 2. Execute Aggregated Deductions foreach ($aggregatedDeductions as $deduction) { // Construct a descriptive reason $serialCount = count($deduction['details']); $reason = "銷售出貨彙總 (批號: {$import->id}, 貨道: {$deduction['slot']}, 共 {$serialCount} 筆交易)"; $inventoryService->decreaseStock( $deduction['product_id'], $deduction['warehouse_id'], $deduction['quantity'], $reason, true, // Force deduction $deduction['slot'] // Location/Slot ); } // 3. Update Batch Status $import->update([ 'status' => 'confirmed', 'confirmed_at' => now(), ]); }); return redirect()->route('sales-imports.index')->with('success', '已彙總(含貨道)並扣除庫存。'); } public function destroy(SalesImportBatch $import) { if ($import->status !== 'pending') { return back()->with('error', '只能刪除待確認的批次。'); } $import->delete(); return redirect()->route('sales-imports.index')->with('success', '已刪除匯入批次。'); } }