first(); if ($request->ajax()) { // Use a more specific query to avoid join conflicts $data = Mutation::query() ->with(['fromDealer', 'toDealer', 'requestedBy.role', 'approvedBy.role', 'receivedBy.role']) ->select([ 'mutations.*' ]) ->orderBy('mutations.id', 'desc'); // Default order by ID desc // 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) { $status = $row->status instanceof MutationStatus ? $row->status : MutationStatus::from($row->status); $textColorClass = $status->textColorClass(); $label = $status->label(); return "{$label}"; }) ->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(); }) // Enhanced filtering ->filterColumn('mutation_number', function($query, $keyword) { $query->where('mutations.mutation_number', 'like', "%{$keyword}%"); }) ->filterColumn('from_dealer', function($query, $keyword) { $query->whereHas('fromDealer', function($q) use ($keyword) { $q->where('name', 'like', "%{$keyword}%"); }); }) ->filterColumn('to_dealer', function($query, $keyword) { $query->whereHas('toDealer', function($q) use ($keyword) { $q->where('name', 'like', "%{$keyword}%"); }); }) ->filterColumn('requested_by', function($query, $keyword) { $query->whereHas('requestedBy', function($q) use ($keyword) { $q->where('name', 'like', "%{$keyword}%"); }); }) ->filterColumn('status', function($query, $keyword) { $query->where('mutations.status', 'like', "%{$keyword}%"); }) ->filterColumn('created_at', function($query, $keyword) { $query->whereDate('mutations.created_at', 'like', "%{$keyword}%"); }) // Enhanced ordering - avoid join conflicts by using subqueries ->orderColumn('mutation_number', function($query, $order) { $query->orderBy('mutations.mutation_number', $order); }) ->orderColumn('from_dealer', function($query, $order) { $query->orderBy( DB::raw('(SELECT name FROM dealers WHERE dealers.id = mutations.from_dealer_id)'), $order ); }) ->orderColumn('to_dealer', function($query, $order) { $query->orderBy( DB::raw('(SELECT name FROM dealers WHERE dealers.id = mutations.to_dealer_id)'), $order ); }) ->orderColumn('requested_by', function($query, $order) { $query->orderBy( DB::raw('(SELECT name FROM users WHERE users.id = mutations.requested_by)'), $order ); }) ->orderColumn('total_items', function($query, $order) { $query->orderBy( DB::raw('(SELECT SUM(quantity_requested) FROM mutation_details WHERE mutation_details.mutation_id = mutations.id)'), $order ); }) ->orderColumn('status', function($query, $order) { $query->orderBy('mutations.status', $order); }) ->orderColumn('created_at', function($query, $order) { $query->orderBy('mutations.created_at', $order); }) ->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', 'shipping_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' => MutationStatus::SENT, 'requested_by' => auth()->id(), 'shipping_notes' => $request->shipping_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'] ]); } DB::commit(); // Check if request came from transaction page if ($request->has('from_transaction_page') || str_contains($request->header('referer', ''), '/transaction')) { return redirect()->back() ->with('success', 'Mutasi berhasil dibuat dan terkirim ke dealer tujuan'); } 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.role', 'approvedBy.role', 'receivedBy.role', 'rejectedBy.role', 'cancelledBy.role', 'mutationDetails.product' ]); return view('warehouse_management.mutations.show', compact('mutation')); } public function receive(Request $request, Mutation $mutation) { $request->validate([ 'reception_notes' => 'nullable|string', 'products' => 'required|array', 'products.*.quantity_approved' => 'required|numeric|min:0', 'products.*.notes' => 'nullable|string' ]); if (!$mutation->canBeReceived()) { return back()->withErrors(['error' => 'Mutasi tidak dapat diterima dalam status saat ini']); } DB::beginTransaction(); try { // Update product details dengan quantity_approved dan notes if ($request->products) { foreach ($request->products as $detailId => $productData) { $updateData = []; // Set quantity_approved if (isset($productData['quantity_approved'])) { $updateData['quantity_approved'] = $productData['quantity_approved']; } // Set notes jika ada if (isset($productData['notes']) && !empty($productData['notes'])) { $updateData['notes'] = $productData['notes']; } if (!empty($updateData)) { MutationDetail::where('id', $detailId) ->where('mutation_id', $mutation->id) ->update($updateData); } } } // Receive mutation with reception notes $mutation->receive(auth()->id(), $request->reception_notes); DB::commit(); // Check user role and redirect accordingly if (!auth()->user()->dealer_id) { // Users without dealer_id are likely admin, redirect to mutations index return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil diterima dan siap untuk disetujui. Stock akan dipindahkan setelah disetujui.'); } else { // Dealer users redirect back to transaction page return redirect()->route('transaction') ->with('success', 'Mutasi berhasil diterima. Silakan setujui mutasi ini untuk memindahkan stock.') ->with('active_tab', 'penerimaan'); } } catch (\Exception $e) { DB::rollback(); return back()->withErrors(['error' => 'Gagal menerima mutasi: ' . $e->getMessage()]); } } public function approve(Request $request, Mutation $mutation) { $request->validate([ 'approval_notes' => 'nullable|string' ]); if (!$mutation->canBeApproved()) { return back()->withErrors(['error' => 'Mutasi tidak dapat disetujui dalam status saat ini']); } try { // Approve mutation (stock will move automatically) $mutation->approve(auth()->id(), $request->approval_notes); // Check user role and redirect accordingly if (!auth()->user()->dealer_id) { // Admin users redirect to mutations index return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil disetujui dan stock telah dipindahkan'); } else { // Dealer users if ($request->has('from_transaction_page') || str_contains($request->header('referer', ''), '/transaction')) { return redirect()->route('transaction') ->with('success', 'Mutasi berhasil disetujui dan stock telah dipindahkan') ->with('active_tab', 'penerimaan'); } else { return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil disetujui dan stock telah dipindahkan'); } } } catch (\Exception $e) { 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); // Check user role and redirect accordingly if (!auth()->user()->dealer_id) { // Admin users redirect to mutations index return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil ditolak'); } else { // Dealer users if ($request->has('from_transaction_page') || str_contains($request->header('referer', ''), '/transaction')) { return redirect()->route('transaction') ->with('success', 'Mutasi berhasil ditolak') ->with('active_tab', 'penerimaan'); } else { return redirect()->route('mutations.index') ->with('success', 'Mutasi berhasil ditolak'); } } } catch (\Exception $e) { return back()->withErrors(['error' => 'Gagal menolak mutasi: ' . $e->getMessage()]); } } // Complete method removed - Stock moves automatically after approval public function cancel(Request $request, Mutation $mutation) { $request->validate([ 'cancellation_reason' => 'nullable|string' ]); if (!$mutation->canBeCancelled()) { return back()->withErrors(['error' => 'Mutasi tidak dapat dibatalkan dalam status saat ini']); } try { $mutation->cancel(auth()->id(), $request->cancellation_reason); 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 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 ]); } // API untuk mendapatkan mutasi yang perlu diterima oleh dealer public function getPendingMutations(Request $request) { $dealerId = $request->dealer_id; // Get mutations that need action from this dealer: // 1. 'sent' status where this dealer is the recipient (need to receive) // 2. 'received' status where this dealer is the recipient (show as waiting for admin approval) // 3. 'approved' status where this dealer is the recipient (show as completed) // 4. 'rejected' status where this dealer is the recipient (show as rejected) $data = Mutation::with(['fromDealer', 'toDealer', 'requestedBy.role']) ->where(function($query) use ($dealerId) { // Mutations sent to this dealer that need to be received $query->where('to_dealer_id', $dealerId) ->where('status', 'sent'); // OR mutations received by this dealer (waiting for admin approval) $query->orWhere(function($subQuery) use ($dealerId) { $subQuery->where('to_dealer_id', $dealerId) ->where('status', 'received'); }); // OR mutations approved/rejected for this dealer (historical data) $query->orWhere(function($subQuery) use ($dealerId) { $subQuery->where('to_dealer_id', $dealerId) ->whereIn('status', ['approved', 'rejected']); }); }) ->orderBy('mutations.id', 'desc'); // Default order by ID desc 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('status', function($row) { $status = $row->status instanceof MutationStatus ? $row->status : MutationStatus::from($row->status); $textColorClass = $status->textColorClass(); $label = $status->label(); return "{$label}"; }) ->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) use ($dealerId) { $buttons = ''; if ($row->status->value === 'sent' && $row->to_dealer_id == $dealerId) { // For sent mutations where current dealer is recipient - show detail button for receiving $buttons .= ''; } elseif ($row->status->value === 'received' && $row->to_dealer_id == $dealerId) { // For received mutations where current dealer is recipient - only show detail (approval is admin only) $buttons .= ''; $buttons .= '