feat: 實作銷售單匯入管理、貨道扣庫優化及 UI 細節調整
This commit is contained in:
152
app/Modules/Sales/Controllers/SalesImportController.php
Normal file
152
app/Modules/Sales/Controllers/SalesImportController.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Modules\Sales\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Modules\Sales\Models\SalesImportBatch;
|
||||
use App\Modules\Sales\Imports\SalesImport;
|
||||
use App\Modules\Inventory\Services\InventoryService; // Assuming this exists or we need to use ProductService
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SalesImportController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$perPage = $request->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', '已刪除匯入批次。');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user