src/Entity/Chat/ChatPendingMember.php line 32

Open in your IDE?
  1. <?php
  2. namespace App\Entity\Chat;
  3. use App\Repository\Chat\ChatPendingMemberRepository;
  4. use Doctrine\ORM\Mapping as ORM;
  5. /**
  6. * Persistencia local de miembros pendientes de incorporacion a chats Hermes.
  7. *
  8. * Trazabilidad:
  9. * - HU-389v2-01 (data-model chat_pending_members)
  10. * - ADR-002 persistencia local
  11. * - ADR-004 idempotencia via (status, attempts)
  12. * - ADR-006 soft-delete via purged_at
  13. *
  14. * Diagrama de estados:
  15. * PENDING -> PROCESSING -> DONE (terminal)
  16. * PROCESSING -> FAILED -> PROCESSING (retry, attempts++)
  17. * * -> purged (soft-delete TTL)
  18. *
  19. * @ORM\Entity(repositoryClass=ChatPendingMemberRepository::class)
  20. * @ORM\Table(
  21. * name="chat_pending_members",
  22. * indexes={
  23. * @ORM\Index(name="idx_pcid_status", columns={"propietario_contenido_id","status"}),
  24. * @ORM\Index(name="idx_chat_uuid", columns={"chat_uuid"}),
  25. * @ORM\Index(name="idx_purge", columns={"created_at"})
  26. * }
  27. * )
  28. */
  29. class ChatPendingMember
  30. {
  31. public const STATUS_PENDING = 'PENDING';
  32. public const STATUS_PROCESSING = 'PROCESSING';
  33. public const STATUS_DONE = 'DONE';
  34. public const STATUS_FAILED = 'FAILED';
  35. public const STATUS_FAILED_PERMANENT = 'FAILED_PERMANENT';
  36. public const PURGE_TTL = 'ttl';
  37. public const PURGE_CHAT_DELETED = 'chat_deleted';
  38. /**
  39. * @ORM\Id
  40. * @ORM\Column(type="bigint")
  41. * @ORM\GeneratedValue(strategy="AUTO")
  42. */
  43. private $id;
  44. /**
  45. * @ORM\Column(type="string", length=36, name="chat_uuid")
  46. */
  47. private string $chatUuid;
  48. /**
  49. * @ORM\Column(type="integer", name="propietario_contenido_id")
  50. */
  51. private int $propietarioContenidoId;
  52. /**
  53. * @ORM\Column(type="integer", name="grupo_id")
  54. */
  55. private int $grupoId;
  56. /**
  57. * @ORM\Column(type="integer", name="created_by")
  58. */
  59. private int $createdBy;
  60. /**
  61. * @ORM\Column(type="string", length=16, options={"default":"PENDING"})
  62. */
  63. private string $status = self::STATUS_PENDING;
  64. /**
  65. * @ORM\Column(type="integer", options={"default":0})
  66. */
  67. private int $attempts = 0;
  68. /**
  69. * @ORM\Column(type="text", nullable=true, name="last_error")
  70. */
  71. private ?string $lastError = null;
  72. /**
  73. * @ORM\Column(type="datetime_immutable", name="created_at")
  74. */
  75. private \DateTimeImmutable $createdAt;
  76. /**
  77. * @ORM\Column(type="datetime_immutable", nullable=true, name="processed_at")
  78. */
  79. private ?\DateTimeImmutable $processedAt = null;
  80. /**
  81. * @ORM\Column(type="datetime_immutable", nullable=true, name="purged_at")
  82. */
  83. private ?\DateTimeImmutable $purgedAt = null;
  84. /**
  85. * @ORM\Column(type="string", length=32, nullable=true, name="purge_reason")
  86. */
  87. private ?string $purgeReason = null;
  88. public function __construct(
  89. string $chatUuid,
  90. int $propietarioContenidoId,
  91. int $grupoId,
  92. int $createdBy
  93. ) {
  94. $this->chatUuid = $chatUuid;
  95. $this->propietarioContenidoId = $propietarioContenidoId;
  96. $this->grupoId = $grupoId;
  97. $this->createdBy = $createdBy;
  98. $this->status = self::STATUS_PENDING;
  99. $this->attempts = 0;
  100. $this->createdAt = new \DateTimeImmutable();
  101. }
  102. public function getId(): ?int
  103. {
  104. return $this->id !== null ? (int) $this->id : null;
  105. }
  106. public function getChatUuid(): string
  107. {
  108. return $this->chatUuid;
  109. }
  110. public function getPropietarioContenidoId(): int
  111. {
  112. return $this->propietarioContenidoId;
  113. }
  114. public function getGrupoId(): int
  115. {
  116. return $this->grupoId;
  117. }
  118. public function getCreatedBy(): int
  119. {
  120. return $this->createdBy;
  121. }
  122. public function getStatus(): string
  123. {
  124. return $this->status;
  125. }
  126. public function getAttempts(): int
  127. {
  128. return $this->attempts;
  129. }
  130. public function getLastError(): ?string
  131. {
  132. return $this->lastError;
  133. }
  134. public function getCreatedAt(): \DateTimeImmutable
  135. {
  136. return $this->createdAt;
  137. }
  138. public function getProcessedAt(): ?\DateTimeImmutable
  139. {
  140. return $this->processedAt;
  141. }
  142. public function getPurgedAt(): ?\DateTimeImmutable
  143. {
  144. return $this->purgedAt;
  145. }
  146. /**
  147. * Transicion PENDING|FAILED -> PROCESSING. Incrementa attempts.
  148. * No permitida desde DONE / FAILED_PERMANENT (terminales).
  149. */
  150. public function markProcessing(): void
  151. {
  152. if (in_array($this->status, [self::STATUS_DONE, self::STATUS_FAILED_PERMANENT], true)) {
  153. throw new \LogicException(sprintf(
  154. 'No se puede transitar a PROCESSING desde estado terminal "%s"',
  155. $this->status
  156. ));
  157. }
  158. $this->status = self::STATUS_PROCESSING;
  159. $this->attempts++;
  160. }
  161. /**
  162. * Transicion PROCESSING -> DONE (terminal). Asigna processed_at.
  163. */
  164. public function markDone(): void
  165. {
  166. $this->status = self::STATUS_DONE;
  167. $this->processedAt = new \DateTimeImmutable();
  168. }
  169. /**
  170. * Transicion PROCESSING -> FAILED. NO incrementa attempts (se incrementa en re-markProcessing).
  171. */
  172. public function markFailed(string $errorMessage): void
  173. {
  174. $this->status = self::STATUS_FAILED;
  175. $this->lastError = $errorMessage;
  176. }
  177. public function getPurgeReason(): ?string
  178. {
  179. return $this->purgeReason;
  180. }
  181. /**
  182. * Soft-delete (ADR-006). Asigna purged_at + razon (ttl|chat_deleted).
  183. */
  184. public function markPurged(string $reason = self::PURGE_TTL): void
  185. {
  186. $this->purgedAt = new \DateTimeImmutable();
  187. $this->purgeReason = $reason;
  188. }
  189. }