withCount(['contacts', 'leads', 'deals']); AccessControl::scopeCompanies($query, auth()->user()); if ($request->search) { $search = $request->search; $query->where(fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('website', 'like', "%{$search}%")->orWhere('city', 'like', "%{$search}%")); } if ($request->status) { $query->where('status', $request->status); } if ($request->owner_id) { $query->where('owner_id', $request->owner_id); } return response()->json($query->latest()->paginate(min((int) $request->get('per_page', 15), 100))); } public function store(Request $request): JsonResponse { Gate::authorize('create', Company::class); $validated = $this->validated($request); $validated['owner_id'] ??= auth()->id(); abort_unless(AccessControl::canAssignUser(auth()->user(), $validated['owner_id']), 403, 'مالک انتخاب‌شده خارج از محدوده مجاز است.'); $suggestions = $this->duplicates->companySuggestions($validated, null, auth()->user()); if ($request->boolean('block_duplicates') && $suggestions) { return response()->json(['message' => 'شرکت مشابهی در سیستم وجود دارد.', 'duplicates' => $suggestions], 422); } $company = Company::create($this->prepare($validated) + ['created_by' => auth()->id()]); ActivityLogger::log('company_created', "Company {$company->name} created", $company); $payload = $company->load('owner:id,name')->toArray(); if ($suggestions) { $payload['duplicate_suggestions'] = $suggestions; } return response()->json($payload, 201); } public function show(Company $company): JsonResponse { Gate::authorize('view', $company); return response()->json($company->load([ 'owner:id,name', 'contacts.phones', 'leads.pipelineStage', 'deals.product', 'notes.user:id,name', 'attachments.uploader:id,name', ])); } public function update(Request $request, Company $company): JsonResponse { Gate::authorize('update', $company); $validated = $this->validated($request, true); if (array_key_exists('owner_id', $validated)) { abort_unless(AccessControl::canAssignUser(auth()->user(), $validated['owner_id']), 403, 'مالک انتخاب‌شده خارج از محدوده مجاز است.'); } $company->update($this->prepare($validated)); ActivityLogger::log('company_updated', "Company {$company->name} updated", $company); return response()->json($company->fresh('owner:id,name')); } public function destroy(Company $company): JsonResponse { Gate::authorize('delete', $company); $company->delete(); ActivityLogger::log('company_deleted', "Company {$company->id} deleted"); return response()->json(['message' => 'شرکت حذف شد']); } public function merge(Request $request, Company $company): JsonResponse { Gate::authorize('update', $company); abort_unless(auth()->user()?->can('merge_duplicates'), 403, 'شما مجوز ادغام رکوردهای تکراری را ندارید.'); $validated = $request->validate(['target_id' => 'required|exists:companies,id|different:'.$company->id]); $target = Company::findOrFail($validated['target_id']); Gate::authorize('update', $target); return response()->json($this->duplicates->mergeCompanies($company, $target, auth()->id())); } private function validated(Request $request, bool $partial = false): array { $sometimes = $partial ? 'sometimes|' : ''; return $request->validate([ 'name' => $sometimes.'required|string|max:255', 'company_type' => 'nullable|string|max:80', 'industry' => 'nullable|string|max:120', 'city' => 'nullable|string|max:120', 'address' => 'nullable|string', 'website' => 'nullable|string|max:255', 'description' => 'nullable|string', 'status' => 'nullable|string|max:40', 'owner_id' => 'nullable|exists:users,id', ]); } private function prepare(array $data): array { if (array_key_exists('name', $data)) { $data['normalized_name'] = DuplicateService::normalizeName($data['name']); } if (array_key_exists('website', $data)) { $data['normalized_website'] = DuplicateService::normalizeWebsite($data['website']); } return $data; } }