'پیش‌نویس عرضه', 'submitted' => 'ارسال‌شده', 'under_review' => 'در حال بررسی', 'appraisal_in_progress' => 'کارشناسی در جریان', 'appraised' => 'کارشناسی‌شده', 'deal_negotiation' => 'مذاکره معامله', 'company_buy' => 'خرید شرکت', 'broker_sale' => 'واسطه‌گری', 'renovation_contract' => 'قرارداد نوسازی', 'acquired' => 'تحصیل‌شده', 'in_renovation' => 'در حال نوسازی', 'ready_for_sale' => 'آماده فروش', 'sold' => 'فروخته‌شده', 'rejected' => 'رد‌شده', ]; public function logEvent( Business $business, string $entityType, int $entityId, string $eventType, ?string $description = null, ?User $user = null, array $metadata = [] ): ?PipelineEvent { if (! Schema::hasTable('aex_pipeline_events')) { return null; } return PipelineEvent::create([ 'business_id' => $business->id, 'entity_type' => $entityType, 'entity_id' => $entityId, 'event_type' => $eventType, 'description' => $description, 'user_id' => $user?->id, 'metadata' => $metadata ?: null, ]); } public function getPipelineForListing(Listing $listing): array { $listing->loadMissing(['pipelineLinks', 'deals']); $events = collect(); if (Schema::hasTable('aex_pipeline_events')) { $events = PipelineEvent::where('business_id', $listing->business_id) ->where(function ($q) use ($listing) { $q->where(function ($inner) use ($listing) { $inner->where('entity_type', Listing::class) ->where('entity_id', $listing->id); }); if ($listing->appraisals()->exists()) { $appraisalIds = $listing->appraisals()->pluck('id'); $inner = \Modules\AssetExchange\Models\Appraisal::class; $q->orWhere(function ($sub) use ($inner, $appraisalIds) { $sub->where('entity_type', $inner) ->whereIn('entity_id', $appraisalIds); }); } if ($listing->deals()->exists()) { $dealIds = $listing->deals()->pluck('id'); $inner = \Modules\AssetExchange\Models\Deal::class; $q->orWhere(function ($sub) use ($inner, $dealIds) { $sub->where('entity_type', $inner) ->whereIn('entity_id', $dealIds); }); } }) ->orderBy('created_at') ->get(); } $links = $listing->pipelineLinks->sortBy('created_at'); return [ 'listing_id' => $listing->id, 'current_stage' => $this->resolveCurrentStage($listing), 'stages' => $this->buildStageTimeline($listing, $links, $events), 'events' => $events, 'links' => $links, ]; } public function advanceStage( Business $business, Listing $listing, string $stage, ?int $dealId = null, ?string $linkedType = null, ?int $linkedId = null, ?User $user = null ): PipelineLink { if (! in_array($stage, self::STAGES, true)) { throw new \InvalidArgumentException('مرحله خط لوله نامعتبر است: '.$stage); } return DB::transaction(function () use ($business, $listing, $stage, $dealId, $linkedType, $linkedId, $user) { $previous = PipelineLink::where('business_id', $business->id) ->where('listing_id', $listing->id) ->whereNull('completed_at') ->latest('id') ->first(); if ($previous) { $previous->update(['completed_at' => now()]); } $link = PipelineLink::create([ 'business_id' => $business->id, 'listing_id' => $listing->id, 'deal_id' => $dealId, 'stage' => $stage, 'linked_type' => $linkedType, 'linked_id' => $linkedId, ]); $this->logEvent( $business, Listing::class, $listing->id, 'stage_advanced', 'انتقال به مرحله: '.(self::STAGE_LABELS[$stage] ?? $stage), $user, ['stage' => $stage, 'pipeline_link_id' => $link->id] ); return $link; }); } protected function resolveCurrentStage(Listing $listing): string { $latest = $listing->pipelineLinks() ->orderByDesc('id') ->value('stage'); if ($latest) { return $latest; } return match ($listing->status) { 'draft' => 'draft_listing', 'submitted' => 'submitted', 'under_review' => 'under_review', 'appraised' => 'appraised', 'in_deal' => 'deal_negotiation', 'sold' => 'sold', 'rejected' => 'rejected', default => 'draft_listing', }; } protected function buildStageTimeline(Listing $listing, Collection $links, Collection $events): array { $completedStages = $links->pluck('stage')->unique()->values()->all(); return collect(self::STAGES)->map(function ($stage) use ($completedStages, $links) { $link = $links->firstWhere('stage', $stage); return [ 'stage' => $stage, 'label' => self::STAGE_LABELS[$stage] ?? $stage, 'completed' => in_array($stage, $completedStages, true), 'completed_at' => $link?->completed_at, 'linked_type' => $link?->linked_type, 'linked_id' => $link?->linked_id, ]; })->values()->all(); } }