Hi,
First of all, thank you so much for your response!
This is actually my first post here — and also my first time working on a project of this size and complexity.
To be honest, I’m not quite sure where to start in terms of sharing code, since there are a lot of classes involved in this process.
Could you help me understand which parts would be the most relevant?
Would it be best to start with the Job class itself, or the place where the job gets dispatched?
I’m happy to share whatever information you need.
I’ve already added some logging inside the Job class, and I can confirm that the job is being dispatched — however, the handle() method is never executed.
To get things started, I’ll post the Job class and its parent classes below.
Actual Job Class
<?php
namespace App\Domain\ERP\Xentral\Jobs;
use App\Common\FileStorage\FileStorageItem;
use App\Common\Jobs\ImportJob;
use App\Common\Models\DeliveryNote;
use App\Common\Models\ERP;
use App\Domain\ERP\Xentral\Import\DeliveryNoteFileProcessor;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProcessDeliveryNoteFile extends ImportJob
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public int $uniqueFor = 3600;
public function __construct(
private ERP $erp,
private DeliveryNoteFileProcessor $fileProcessor,
private array $file,
) {
}
/**
* @throws Exception
*/
public function handle(): void
{
$this->fileProcessor->process($this->file);
}
protected function getSourceType(): string
{
return ERP::class;
}
protected function getSourceId(): string
{
return $this->erp->id;
}
protected function getReferenceType(): ?string
{
return DeliveryNote::class;
}
protected function getReferenceId(): ?string
{
return null;
}
protected function getMessage(): ?string
{
return 'DeliveryNote File could not be processed.';
}
protected function setUniqueId(): string
{
/** @var FileStorageItem $file */
$file = $this->file['file'];
return $file->path;
}
}
First Layer Parent Class
<?php
namespace App\Common\Jobs;
use App\Common\Events\ImportFailed;
use App\Common\Support\Types\TeamSlug;
use App\Exceptions\UnrecoverableErrorException;
use Throwable;
abstract class ImportJob extends OperationJob
{
public function failed(UnrecoverableErrorException | Throwable $exception): void
{
$teamSlug = TeamSlug::General;
if ($exception instanceof UnrecoverableErrorException) {
$teamSlug = $exception->getTeamSlug();
}
ImportFailed::dispatch(
$this->getSourceType(),
$this->getSourceId(),
$this->getReferenceType(),
$this->getReferenceId(),
$this->getFullClientMessage($this->getMessage(), $exception),
$this->getInternalMessage($exception),
$this->job ?
$this->job->uuid() :
'',
$teamSlug,
);
}
}
Second Layer Parent Class
<?php
namespace App\Common\Jobs;
use App\Common\Jobs\Middleware\PreventRetryOnUnrecoverableError;
use App\Common\Jobs\Middleware\RetryOnRecoverableError;
use App\Common\Support\Traits\ConvertsErrorMessages;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
abstract class OperationJob implements ShouldQueue, ShouldBeUnique
{
use ConvertsErrorMessages;
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
public int $maxExceptions = 3;
abstract protected function getSourceType(): string;
abstract protected function getSourceId(): string;
abstract protected function getReferenceType(): ?string;
abstract protected function getReferenceId(): ?string;
abstract protected function getMessage(): ?string;
abstract protected function setUniqueId(): string;
public function getIntegrationType(): string
{
return $this->getSourceType();
}
public function getIntegrationId(): string
{
return $this->getSourceId();
}
protected function shouldNotOverlap(): bool
{
return false;
}
protected function getUniqueKey(): string
{
return $this->getSourceId() . '-' . ($this->getReferenceId() ?? '');
}
public function middleware(): array
{
$middleware = [
PreventRetryOnUnrecoverableError::class,
RetryOnRecoverableError::class,
];
if (!App::environment(['local']) && $this->shouldNotOverlap()) {
array_unshift(
$middleware,
(new WithoutOverlapping($this->getUniqueKey()))
->dontRelease()
->expireAfter(1800)
);
}
return $middleware;
}
public function uniqueId(): string
{
if (config('queue.default') === 'sync') {
return Str::uuid()->toString();
}
return $this->setUniqueId();
}
}
Job Dispatching (Runs inside another Job)
public function run(): void
{
$deliveryNoteFiles = $this->importClient->getDeliveryNoteFiles();
foreach ($deliveryNoteFiles as $file) {
dispatch(
new ProcessDeliveryNoteFile(
$this->erp,
$this->fileProcessor,
$file,
)
);
}
}
Thanks for your support!