function ($q) { $q->withCount(['notes' => function ($query) { $query->where('notable_type', 'App\Models\PipelineItem'); }])->orderBy('position'); }])->orderBy('position')->get(); return $lanes->map(function ($lane) { return ApiDataTransformer::snakeToCamel($lane->toArray()); }); } public function show() { return Inertia::render('Pipeline', [ 'pipeline' => $this->index(), ]); } /** * Update positions in bulk. Expect array of objects with camelCase keys: id, stage, position */ public function updatePositions(Request $request) { $payload = $request->all(); if (!is_array($payload)) { return response()->json(['message' => 'Invalid payload'], 422); } $items = array_map(function ($item) { return ApiDataTransformer::camelToSnake((array)$item); }, $payload); $validator = Validator::make(['items' => $items], [ 'items.*.id' => 'required|integer|exists:pipeline_items,id', 'items.*.pipeline_lane_id' => 'required|integer|exists:pipeline_lanes,id', 'items.*.title' => 'string', 'items.*.position' => 'required|integer|min:0', 'items.*.expected_revenue' => 'decimal', 'items.*.description' => 'string', ]); if ($validator->fails()) { return response()->json(['errors' => $validator->errors()], 422); } DB::beginTransaction(); try { foreach ($items as $it) { PipelineItem::where('id', $it['id'])->update([ 'pipeline_lane_id' => $it['pipeline_lane_id'], 'position' => $it['position'], ]); } DB::commit(); } catch (\Exception $e) { DB::rollBack(); Log::error('Failed to update pipeline positions: ' . $e->getMessage()); return response()->json(['message' => 'Failed to update positions'], 500); } return response()->json(['status' => 'ok']); } }