| Server IP : 35.80.110.71 / Your IP : 216.73.216.221 Web Server : Apache/2.4.58 (Ubuntu) System : Linux ip-172-31-21-44 6.17.0-1019-aws #19~24.04.1-Ubuntu SMP Tue Jun 23 18:53:06 UTC 2026 x86_64 User : ubuntu ( 1000) PHP Version : 8.3.31 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/switchyard/current/app/Jobs/ |
Upload File : |
<?php
namespace App\Jobs;
use App\Enums\LeadEventKind;
use App\Enums\LeadStatus;
use App\Models\Destination;
use App\Models\Lead;
use App\Models\LeadEvent;
use App\Models\LeadSource;
use App\Models\Scopes\WorkspaceScope;
use App\Models\Workspace;
use App\Services\Enrichment\ExampleSuggester;
use App\Services\Enrichment\LeadScorer;
use App\Services\Enrichment\ServiceTagger;
use App\Services\Parsers\LeadParserResolver;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
/**
* Phase-1 lead pipeline orchestrator. One job per inbound payload.
*
* parse → tag → suggest → score → persist Lead + lead_events
*
* If the workspace has an active ClickUpConnection + ClickUpConfig with
* `auto_route_enabled = true`, dispatches RouteLeadToClickUpJob at the
* end. Otherwise the lead sits in the inbox awaiting a manual route.
*/
class IngestLeadJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public function __construct(
public readonly string $workspaceId,
public readonly string $sourceId,
public readonly array $rawPayload,
public readonly ?string $externalRef = null,
) {}
public function handle(
LeadParserResolver $resolver,
ServiceTagger $tagger,
ExampleSuggester $suggester,
LeadScorer $scorer,
): void {
$workspace = Workspace::find($this->workspaceId);
if (! $workspace) {
Log::warning('IngestLeadJob: workspace not found', ['workspace_id' => $this->workspaceId]);
return;
}
$source = LeadSource::withoutGlobalScope(WorkspaceScope::class)
->where('id', $this->sourceId)
->first();
if (! $source) {
Log::warning('IngestLeadJob: source not found', ['source_id' => $this->sourceId]);
return;
}
$parser = $resolver->for($source);
$parsed = $parser->parse($this->rawPayload, $source->field_mapping ?? []);
$config = $workspace->clickupConfig;
$serviceTag = $config ? $tagger->tag($parsed['job'], $config) : null;
$examples = $config ? $suggester->suggest($serviceTag, $config) : [];
$score = $scorer->score($parsed['contact'], $parsed['job'], $serviceTag);
$lead = Lead::create([
'workspace_id' => $workspace->id,
'source_id' => $source->id,
'external_ref' => $this->externalRef,
'raw_payload' => $this->rawPayload,
'parsed_contact' => $parsed['contact'],
'parsed_job' => $parsed['job'],
'service_tag' => $serviceTag,
'suggested_examples' => $examples,
'lead_score' => $score,
'status' => LeadStatus::Enriched,
]);
$this->emit($lead->id, $workspace->id, LeadEventKind::Received, [
'source_slug' => $source->slug,
'external_ref' => $this->externalRef,
]);
$this->emit($lead->id, $workspace->id, LeadEventKind::Enriched, [
'service_tag' => $serviceTag,
'lead_score' => $score,
'suggested_examples' => $examples,
]);
$shouldRoute = $config?->auto_route_enabled
&& $config?->inbound_list_id
&& $workspace->clickupConnection?->isActive();
if ($shouldRoute) {
RouteLeadToClickUpJob::dispatch($lead->id);
}
}
private function emit(string $leadId, string $workspaceId, LeadEventKind $kind, array $payload): void
{
LeadEvent::create([
'workspace_id' => $workspaceId,
'lead_id' => $leadId,
'kind' => $kind,
'payload' => $payload,
'created_at' => now(),
]);
$this->fanOutToDestinations($leadId, $workspaceId, $kind);
}
/**
* Match enabled destinations in the workspace whose trigger_event matches,
* apply min_score + service_tags_include filters, and dispatch one job per
* match. Each dispatch is independent — a failing Slack post never touches
* ClickUp routing or any other destination.
*/
private function fanOutToDestinations(string $leadId, string $workspaceId, LeadEventKind $kind): void
{
$destinations = Destination::withoutGlobalScope(WorkspaceScope::class)
->where('workspace_id', $workspaceId)
->where('trigger_event', $kind->value)
->where('is_enabled', true)
->get();
if ($destinations->isEmpty()) {
return;
}
$lead = Lead::withoutGlobalScope(WorkspaceScope::class)->find($leadId);
if (! $lead) {
return;
}
foreach ($destinations as $destination) {
if (! $destination->matchesLead($lead)) {
continue;
}
DispatchDestinationJob::dispatch($destination->id, $lead->id);
}
}
}