first(); if ($request->ajax()) { $data = Mutation::with(['fromDealer', 'toDealer', 'requestedBy', 'approvedBy', 'receivedBy']) ->select('mutations.*'); // Filter berdasarkan dealer jika user bukan admin if (auth()->user()->dealer_id) { $data->where(function($query) { $query->where('from_dealer_id', auth()->user()->dealer_id) ->orWhere('to_dealer_id', auth()->user()->dealer_id); }); } return DataTables::of($data) ->addIndexColumn() ->addColumn('mutation_number', function($row) { return $row->mutation_number; }) ->addColumn('from_dealer', function($row) { return $row->fromDealer->name ?? '-'; }) ->addColumn('to_dealer', function($row) { return $row->toDealer->name ?? '-'; }) ->addColumn('requested_by', function($row) { return $row->requestedBy->name ?? '-'; }) ->addColumn('status', function($row) { $statusColor = $row->status_color; $statusLabel = $row->status_label; return " {$statusLabel}"; }) ->addColumn('total_items', function($row) { return number_format($row->total_items, 0); }) ->addColumn('created_at', function($row) { return $row->created_at->format('d/m/Y H:i'); }) ->addColumn('action', function($row) { return view('warehouse_management.mutations._action', compact('row'))->render(); }) ->rawColumns(['status', 'action']) ->make(true); } return view('warehouse_management.mutations.index', compact('menu')); } public function create() { $menu = Menu::where('link','mutations.create')->first(); $dealers = Dealer::all(); $products = Product::with('stocks')->get(); return view('warehouse_management.mutations.create', compact('menu', 'dealers', 'products')); } public function store(Request $request) { $request->validate([ 'from_dealer_id' => 'required|exists:dealers,id', 'to_dealer_id' => 'required|exists:dealers,id|different:from_dealer_id', 'notes' => 'nullable|string', 'products' => 'required|array|min:1', 'products.*.product_id' => 'required|exists:products,id', 'products.*.quantity_requested' => 'required|numeric|min:0.01' ]); DB::beginTransaction(); try { // Buat mutation record dengan status SENT (langsung terkirim ke dealer tujuan) $mutation = Mutation::create([ 'from_dealer_id' => $request->from_dealer_id, 'to_dealer_id' => $request->to_dealer_id, 'status' => 'sent', 'requested_by' => auth()->id(), 'notes' => $request->notes ]); // Buat mutation details foreach ($request->products as $productData) { MutationDetail::create([ 'mutation_id' => $mutation->id, 'product_id' => $productData['product_id'], 'quantity_requested' => $productData['quantity_requested'], 'notes' => $productData['notes'] ?? null ]); } DB::commit(); return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil dibuat dan terkirim ke dealer tujuan'); } catch (\Exception $e) { DB::rollback(); return back()->withErrors(['error' => 'Gagal membuat mutasi: ' . $e->getMessage()]); } } public function show(Mutation $mutation) { $mutation->load(['fromDealer', 'toDealer', 'requestedBy', 'approvedBy', 'receivedBy', 'mutationDetails.product']); return view('warehouse_management.mutations.show', compact('mutation')); } public function receive(Mutation $mutation) { if (!$mutation->canBeReceived()) { return back()->withErrors(['error' => 'Mutasi tidak dapat diterima dalam status saat ini']); } try { $mutation->receive(auth()->id()); return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil diterima dan menunggu persetujuan pengirim'); } catch (\Exception $e) { return back()->withErrors(['error' => 'Gagal menerima mutasi: ' . $e->getMessage()]); } } public function approve(Request $request, Mutation $mutation) { $request->validate([ 'notes' => 'nullable|string', 'details' => 'required|array', 'details.*.quantity_approved' => 'required|numeric|min:0' ]); if (!$mutation->canBeApproved()) { return back()->withErrors(['error' => 'Mutasi tidak dapat disetujui dalam status saat ini']); } DB::beginTransaction(); try { // Update mutation details dengan quantity approved foreach ($request->details as $detailId => $detailData) { $mutationDetail = MutationDetail::findOrFail($detailId); $mutationDetail->update([ 'quantity_approved' => $detailData['quantity_approved'] ]); } // Approve mutation $mutation->approve(auth()->id(), $request->notes); DB::commit(); return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil disetujui'); } catch (\Exception $e) { DB::rollback(); return back()->withErrors(['error' => 'Gagal menyetujui mutasi: ' . $e->getMessage()]); } } public function reject(Request $request, Mutation $mutation) { $request->validate([ 'rejection_reason' => 'required|string' ]); if (!$mutation->canBeApproved()) { return back()->withErrors(['error' => 'Mutasi tidak dapat ditolak dalam status saat ini']); } try { $mutation->reject(auth()->id(), $request->rejection_reason); return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil ditolak'); } catch (\Exception $e) { return back()->withErrors(['error' => 'Gagal menolak mutasi: ' . $e->getMessage()]); } } public function complete(Mutation $mutation) { if (!$mutation->canBeCompleted()) { return back()->withErrors(['error' => 'Mutasi tidak dapat diselesaikan dalam status saat ini']); } try { $mutation->complete(); return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil diselesaikan dan stock telah dipindahkan'); } catch (\Exception $e) { return back()->withErrors(['error' => 'Gagal menyelesaikan mutasi: ' . $e->getMessage()]); } } public function cancel(Mutation $mutation) { if (!$mutation->canBeCancelled()) { return back()->withErrors(['error' => 'Mutasi tidak dapat dibatalkan dalam status saat ini']); } try { $mutation->update(['status' => MutationStatus::CANCELLED]); return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil dibatalkan'); } catch (\Exception $e) { return back()->withErrors(['error' => 'Gagal membatalkan mutasi: ' . $e->getMessage()]); } } // API untuk mendapatkan detail mutasi untuk approval public function getDetails(Mutation $mutation) { $mutation->load(['mutationDetails.product', 'fromDealer']); $details = $mutation->mutationDetails->map(function($detail) use ($mutation) { $availableStock = $detail->product->getStockByDealer($mutation->from_dealer_id); return [ 'id' => $detail->id, 'product' => [ 'id' => $detail->product->id, 'name' => $detail->product->name ], 'quantity_requested' => $detail->quantity_requested, 'quantity_approved' => $detail->quantity_approved, 'available_stock' => $availableStock ]; }); return response()->json([ 'mutation' => [ 'id' => $mutation->id, 'mutation_number' => $mutation->mutation_number, 'from_dealer' => $mutation->fromDealer->name, 'to_dealer' => $mutation->toDealer->name ], 'details' => $details ]); } // API untuk mendapatkan stock produk di dealer tertentu public function getProductStock(Request $request) { $dealerId = $request->dealer_id; $productId = $request->product_id; $product = Product::findOrFail($productId); $stock = $product->getStockByDealer($dealerId); return response()->json([ 'product_name' => $product->name, 'current_stock' => $stock ]); } }