user()); foreach (['status', 'sales_stage', 'company_id', 'lead_id', 'owner_id', 'product_id', 'pipeline_id', 'deal_stage_id', 'forecast_category'] as $filter) { if ($request->filled($filter)) { $query->where($filter, $request->get($filter)); } } if ($request->search) { $query->where('title', 'like', "%{$request->search}%"); } return response()->json($query->latest()->paginate(min((int) $request->get('per_page', 15), 100))); } public function store(Request $request): JsonResponse { Gate::authorize('create', Deal::class); $validated = $this->validated($request); $validated['owner_id'] ??= auth()->id(); $this->authorizeRelations($validated); $deal = Deal::create($validated + ['created_by' => auth()->id()]); ActivityLogger::log('deal_created', "Deal {$deal->title} created", $deal); return response()->json($deal->load('company', 'contact', 'product', 'owner'), 201); } public function show(Deal $deal): JsonResponse { Gate::authorize('view', $deal); return response()->json($deal->load('company', 'lead', 'contact.phones', 'product.salesScript.sections', 'owner:id,name', 'pipeline', 'stage', 'stageHistory.fromStage', 'stageHistory.toStage', 'stageHistory.actor:id,name', 'customFieldValues.definition', 'tasks.assignee:id,name', 'notes.user:id,name', 'attachments.uploader:id,name')); } public function update(Request $request, Deal $deal): JsonResponse { Gate::authorize('update', $deal); $validated = $this->validated($request, true); $this->authorizeRelations($validated); $deal->update($validated); ActivityLogger::log('deal_updated', "Deal {$deal->title} updated", $deal); return response()->json($deal->fresh('company', 'contact', 'product', 'owner')); } public function destroy(Deal $deal): JsonResponse { Gate::authorize('delete', $deal); $deal->delete(); ActivityLogger::log('deal_deleted', "Deal {$deal->id} deleted"); return response()->json(['message' => 'فرصت فروش حذف شد']); } private function validated(Request $request, bool $partial = false): array { $sometimes = $partial ? 'sometimes|' : ''; return $request->validate([ 'title' => $sometimes.'required|string|max:255', 'company_id' => 'nullable|exists:companies,id', 'lead_id' => 'nullable|exists:leads,id', 'contact_id' => 'nullable|exists:contacts,id', 'product_id' => 'nullable|exists:products,id', 'pipeline_id' => 'nullable|exists:pipelines,id', 'deal_stage_id' => 'nullable|exists:deal_stages,id', 'estimated_value' => 'nullable|numeric|min:0', 'final_amount' => 'nullable|numeric|min:0', 'win_probability' => 'nullable|integer|min:0|max:100', 'sales_stage' => 'nullable|string|max:80', 'expected_close_date' => 'nullable|date', 'owner_id' => 'nullable|exists:users,id', 'status' => 'nullable|string|max:40', 'won_lost_reason' => 'nullable|string|max:255', 'competitor' => 'nullable|string|max:255', 'forecast_category' => 'nullable|in:pipeline,best_case,commit,closed', 'notes' => 'nullable|string', ]); } private function authorizeRelations(array $validated): void { $user = auth()->user(); if (array_key_exists('owner_id', $validated)) { abort_unless(AccessControl::canAssignUser($user, $validated['owner_id']), 403, 'مالک انتخاب‌شده خارج از محدوده مجاز است.'); } foreach (['company_id' => Company::class, 'lead_id' => Lead::class, 'contact_id' => Contact::class] as $key => $model) { if (! empty($validated[$key])) { Gate::authorize('view', $model::findOrFail($validated[$key])); } } if (! empty($validated['product_id'])) { Gate::authorize('view', Product::findOrFail($validated['product_id'])); } } }