user()->can('view_pipelines'), 403); $query = Pipeline::with('stages')->where('is_active', true)->orderBy('sort_order'); if (! $request->user()->hasRole('admin')) { $teamIds = AccessControl::teamIds($request->user()); $query->where(fn ($scope) => $scope->whereNull('team_id')->orWhereIn('team_id', $teamIds)); } return response()->json($query->get()); } public function store(Request $request): JsonResponse { abort_unless($request->user()->can('manage_pipelines'), 403); $data = $request->validate([ 'name' => 'required|string|max:120', 'team_id' => 'nullable|exists:teams,id', 'is_default' => 'boolean', 'stages' => 'required|array|min:2|max:20', 'stages.*.name' => 'required|string|max:80', 'stages.*.color' => ['nullable', 'regex:/^#[0-9A-Fa-f]{6}$/'], 'stages.*.probability' => 'required|integer|min:0|max:100', 'stages.*.is_won' => 'boolean', 'stages.*.is_lost' => 'boolean', ]); if (! $request->user()->hasRole('admin') && ! empty($data['team_id'])) { abort_unless(in_array((int) $data['team_id'], AccessControl::teamIds($request->user()), true), 403); } $pipeline = DB::transaction(function () use ($data): Pipeline { if ($data['is_default'] ?? false) { Pipeline::query()->update(['is_default' => false]); } $pipeline = Pipeline::create([ 'name' => $data['name'], 'slug' => Str::slug($data['name']).'-'.Str::lower(Str::random(5)), 'team_id' => $data['team_id'] ?? null, 'is_default' => $data['is_default'] ?? false, 'is_active' => true, ]); foreach ($data['stages'] as $index => $stage) { $pipeline->stages()->create($stage + ['slug' => Str::slug($stage['name']).'-'.$index, 'sort_order' => $index, 'is_active' => true]); } return $pipeline; }); return response()->json($pipeline->load('stages'), 201); } public function board(Request $request, Pipeline $pipeline): JsonResponse { abort_unless($request->user()->can('view_pipelines'), 403); abort_unless($this->visible($pipeline, $request), 403); return response()->json($this->service->board($pipeline, $request->user(), $request->only(['search', 'owner_id', 'forecast_category', 'status']))); } public function move(Request $request, Deal $deal): JsonResponse { abort_unless($request->user()->can('move_deals'), 403); abort_unless(AccessControl::canAccessDeal($request->user(), $deal), 403); $data = $request->validate(['deal_stage_id' => 'required|exists:deal_stages,id', 'version' => 'required|integer|min:1', 'reason' => 'nullable|string|max:1000', 'final_amount' => 'nullable|numeric|min:0']); $stage = DealStage::findOrFail($data['deal_stage_id']); return response()->json($this->service->move($deal, $stage, $request->user(), $data['version'], $data['reason'] ?? null, isset($data['final_amount']) ? (float) $data['final_amount'] : null)); } private function visible(Pipeline $pipeline, Request $request): bool { return $request->user()->hasRole('admin') || $pipeline->team_id === null || in_array($pipeline->team_id, AccessControl::teamIds($request->user()), true); } }