207 lines
6.9 KiB
PHP
207 lines
6.9 KiB
PHP
<?php
|
|
|
|
namespace Modules\AssetExchange\Services;
|
|
|
|
use App\Business;
|
|
use App\User;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\AssetExchange\Models\Listing;
|
|
use Modules\AssetExchange\Models\PipelineEvent;
|
|
use Modules\AssetExchange\Models\PipelineLink;
|
|
|
|
class PipelineService
|
|
{
|
|
public const STAGES = [
|
|
'draft_listing',
|
|
'submitted',
|
|
'under_review',
|
|
'appraisal_in_progress',
|
|
'appraised',
|
|
'deal_negotiation',
|
|
'company_buy',
|
|
'broker_sale',
|
|
'renovation_contract',
|
|
'acquired',
|
|
'in_renovation',
|
|
'ready_for_sale',
|
|
'sold',
|
|
'rejected',
|
|
];
|
|
|
|
public const STAGE_LABELS = [
|
|
'draft_listing' => 'پیشنویس عرضه',
|
|
'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();
|
|
}
|
|
}
|