feat: add step tracking, retry and article lookup to AIPipelineJob

- currentStep column (migration 20260517230000) written per pipeline stage
- recordStep() stores per-step output data and updates currentStep
- resetForRetry() requeues a failed/needs-review job
- getInventoryNumber() / getStatusLabel() helpers for admin display
- markCompleted() now merges rather than replacing outputData
- findByArticleId() added to repository for re-run lookups

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Simon Kuehn 2026-05-18 07:18:29 +00:00
parent 9e59123683
commit 6d8a06f151
4 changed files with 75 additions and 1 deletions

View file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260517230000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add current_step column to ai_pipeline_jobs for step tracking';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE app.ai_pipeline_jobs ADD COLUMN current_step VARCHAR(50) NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE app.ai_pipeline_jobs DROP COLUMN current_step');
}
}

View file

@ -42,6 +42,9 @@ class AIPipelineJob
#[ORM\Column(type: 'text', nullable: true)]
private ?string $errorMessage = null;
#[ORM\Column(type: 'string', length: 50, nullable: true)]
private ?string $currentStep = null;
#[ORM\Column(type: 'datetime_immutable')]
private \DateTimeImmutable $createdAt;
@ -83,6 +86,16 @@ class AIPipelineJob
return $this->status;
}
public function getStatusLabel(): string
{
return $this->status->value;
}
public function getAiResults(): string
{
return '';
}
public function getAttemptCount(): int
{
return $this->attemptCount;
@ -111,6 +124,30 @@ class AIPipelineJob
return $this->errorMessage;
}
public function getCurrentStep(): ?string
{
return $this->currentStep;
}
public function getInventoryNumber(): ?string
{
return $this->inputData['inventoryNumber'] ?? null;
}
/** @param array<string, mixed> $data */
public function recordStep(string $step, array $data): void
{
$this->currentStep = $step;
$this->outputData[$step] = $data;
}
public function resetForRetry(): void
{
$this->status = AIPipelineJobStatus::Queued;
$this->errorMessage = null;
$this->completedAt = null;
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
@ -130,7 +167,7 @@ class AIPipelineJob
public function markCompleted(array $outputData = []): void
{
$this->status = AIPipelineJobStatus::Completed;
$this->outputData = $outputData;
$this->outputData = array_merge($this->outputData, $outputData);
$this->completedAt = new \DateTimeImmutable();
}

View file

@ -15,5 +15,7 @@ interface AIPipelineJobRepositoryInterface
/** @return list<AIPipelineJob> */
public function findByStatus(AIPipelineJobStatus $status): array;
public function findByArticleId(Uuid $articleId): ?AIPipelineJob;
public function save(AIPipelineJob $job): void;
}

View file

@ -28,6 +28,15 @@ final class DoctrineAIPipelineJobRepository implements AIPipelineJobRepositoryIn
return $this->em->getRepository(AIPipelineJob::class)->findBy(['status' => $status]);
}
public function findByArticleId(Uuid $articleId): ?AIPipelineJob
{
/** @var AIPipelineJob|null */
return $this->em->getRepository(AIPipelineJob::class)->findOneBy(
['articleId' => $articleId],
['createdAt' => 'DESC'],
);
}
public function save(AIPipelineJob $job): void
{
$this->em->persist($job);