<?php
namespace App\Entity\Chat;
use App\Repository\Chat\ChatPendingMemberRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* Persistencia local de miembros pendientes de incorporacion a chats Hermes.
*
* Trazabilidad:
* - HU-389v2-01 (data-model chat_pending_members)
* - ADR-002 persistencia local
* - ADR-004 idempotencia via (status, attempts)
* - ADR-006 soft-delete via purged_at
*
* Diagrama de estados:
* PENDING -> PROCESSING -> DONE (terminal)
* PROCESSING -> FAILED -> PROCESSING (retry, attempts++)
* * -> purged (soft-delete TTL)
*
* @ORM\Entity(repositoryClass=ChatPendingMemberRepository::class)
* @ORM\Table(
* name="chat_pending_members",
* indexes={
* @ORM\Index(name="idx_pcid_status", columns={"propietario_contenido_id","status"}),
* @ORM\Index(name="idx_chat_uuid", columns={"chat_uuid"}),
* @ORM\Index(name="idx_purge", columns={"created_at"})
* }
* )
*/
class ChatPendingMember
{
public const STATUS_PENDING = 'PENDING';
public const STATUS_PROCESSING = 'PROCESSING';
public const STATUS_DONE = 'DONE';
public const STATUS_FAILED = 'FAILED';
public const STATUS_FAILED_PERMANENT = 'FAILED_PERMANENT';
public const PURGE_TTL = 'ttl';
public const PURGE_CHAT_DELETED = 'chat_deleted';
/**
* @ORM\Id
* @ORM\Column(type="bigint")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=36, name="chat_uuid")
*/
private string $chatUuid;
/**
* @ORM\Column(type="integer", name="propietario_contenido_id")
*/
private int $propietarioContenidoId;
/**
* @ORM\Column(type="integer", name="grupo_id")
*/
private int $grupoId;
/**
* @ORM\Column(type="integer", name="created_by")
*/
private int $createdBy;
/**
* @ORM\Column(type="string", length=16, options={"default":"PENDING"})
*/
private string $status = self::STATUS_PENDING;
/**
* @ORM\Column(type="integer", options={"default":0})
*/
private int $attempts = 0;
/**
* @ORM\Column(type="text", nullable=true, name="last_error")
*/
private ?string $lastError = null;
/**
* @ORM\Column(type="datetime_immutable", name="created_at")
*/
private \DateTimeImmutable $createdAt;
/**
* @ORM\Column(type="datetime_immutable", nullable=true, name="processed_at")
*/
private ?\DateTimeImmutable $processedAt = null;
/**
* @ORM\Column(type="datetime_immutable", nullable=true, name="purged_at")
*/
private ?\DateTimeImmutable $purgedAt = null;
/**
* @ORM\Column(type="string", length=32, nullable=true, name="purge_reason")
*/
private ?string $purgeReason = null;
public function __construct(
string $chatUuid,
int $propietarioContenidoId,
int $grupoId,
int $createdBy
) {
$this->chatUuid = $chatUuid;
$this->propietarioContenidoId = $propietarioContenidoId;
$this->grupoId = $grupoId;
$this->createdBy = $createdBy;
$this->status = self::STATUS_PENDING;
$this->attempts = 0;
$this->createdAt = new \DateTimeImmutable();
}
public function getId(): ?int
{
return $this->id !== null ? (int) $this->id : null;
}
public function getChatUuid(): string
{
return $this->chatUuid;
}
public function getPropietarioContenidoId(): int
{
return $this->propietarioContenidoId;
}
public function getGrupoId(): int
{
return $this->grupoId;
}
public function getCreatedBy(): int
{
return $this->createdBy;
}
public function getStatus(): string
{
return $this->status;
}
public function getAttempts(): int
{
return $this->attempts;
}
public function getLastError(): ?string
{
return $this->lastError;
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
public function getProcessedAt(): ?\DateTimeImmutable
{
return $this->processedAt;
}
public function getPurgedAt(): ?\DateTimeImmutable
{
return $this->purgedAt;
}
/**
* Transicion PENDING|FAILED -> PROCESSING. Incrementa attempts.
* No permitida desde DONE / FAILED_PERMANENT (terminales).
*/
public function markProcessing(): void
{
if (in_array($this->status, [self::STATUS_DONE, self::STATUS_FAILED_PERMANENT], true)) {
throw new \LogicException(sprintf(
'No se puede transitar a PROCESSING desde estado terminal "%s"',
$this->status
));
}
$this->status = self::STATUS_PROCESSING;
$this->attempts++;
}
/**
* Transicion PROCESSING -> DONE (terminal). Asigna processed_at.
*/
public function markDone(): void
{
$this->status = self::STATUS_DONE;
$this->processedAt = new \DateTimeImmutable();
}
/**
* Transicion PROCESSING -> FAILED. NO incrementa attempts (se incrementa en re-markProcessing).
*/
public function markFailed(string $errorMessage): void
{
$this->status = self::STATUS_FAILED;
$this->lastError = $errorMessage;
}
public function getPurgeReason(): ?string
{
return $this->purgeReason;
}
/**
* Soft-delete (ADR-006). Asigna purged_at + razon (ttl|chat_deleted).
*/
public function markPurged(string $reason = self::PURGE_TTL): void
{
$this->purgedAt = new \DateTimeImmutable();
$this->purgeReason = $reason;
}
}