ultimatepos/Modules/AssetExchange/Services/PipelineService.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();
}
}