Add EntityWorkflowSend and EntityWorkflowSendView entities

Introduced EntityWorkflowSend and EntityWorkflowSendView entities to enable tracking of workflow content sent to external parties. Updated EntityWorkflowStep to associate with these entities and added a corresponding database migration script.
This commit is contained in:
Julien Fastré 2024-10-03 11:55:58 +02:00
parent 9a9d14eb5a
commit 2213f6f429
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
4 changed files with 303 additions and 0 deletions

View File

@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Entity\Workflow;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Random\Randomizer;
/**
* An entity which stores then sending of a workflow's content to
* some external entity.
*/
#[ORM\Entity]
#[ORM\Table(name: 'chill_main_workflow_entity_send')]
class EntityWorkflowSend implements TrackCreationInterface
{
use TrackCreationTrait;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: Types::INTEGER)]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: ThirdParty::class)]
#[ORM\JoinColumn(nullable: true)]
private ?ThirdParty $destineeThirdParty = null;
#[ORM\Column(type: Types::TEXT, nullable: false, options: ['default' => ''])]
private string $destineeEmail = '';
#[ORM\Column(type: 'uuid', unique: true, nullable: false)]
private UuidInterface $uuid;
#[ORM\Column(type: Types::STRING, length: 255, nullable: false)]
private string $privateToken;
#[ORM\Column(type: Types::INTEGER, nullable: false, options: ['default' => 0])]
private int $numberOfErrorTrials = 0;
/**
* @var Collection<int, EntityWorkflowSendView>
*/
#[ORM\OneToMany(targetEntity: EntityWorkflowSendView::class, mappedBy: 'send')]
private Collection $views;
public function __construct(
#[ORM\ManyToOne(targetEntity: EntityWorkflowStep::class, inversedBy: 'sends')]
#[ORM\JoinColumn(nullable: false)]
private EntityWorkflowStep $entityWorkflowStep,
string|ThirdParty $destinee,
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: false)]
private \DateTimeImmutable $expireAt,
) {
$this->uuid = Uuid::uuid4();
$random = new Randomizer();
$this->privateToken = bin2hex($random->getBytes(48));
$this->entityWorkflowStep->addSend($this);
if ($destinee instanceof ThirdParty) {
$this->destineeThirdParty = $destinee;
} else {
$this->destineeEmail = $destinee;
}
$this->views = new ArrayCollection();
}
/**
* @internal use the @see{EntityWorkflowSendView}'s constructor instead
*/
public function addView(EntityWorkflowSendView $view): self
{
if (!$this->views->contains($view)) {
$this->views->add($view);
}
return $this;
}
public function getDestineeEmail(): string
{
return $this->destineeEmail;
}
public function getDestineeThirdParty(): ?ThirdParty
{
return $this->destineeThirdParty;
}
public function getId(): ?int
{
return $this->id;
}
public function getNumberOfErrorTrials(): int
{
return $this->numberOfErrorTrials;
}
public function getPrivateToken(): string
{
return $this->privateToken;
}
public function getUuid(): UuidInterface
{
return $this->uuid;
}
public function increaseErrorTrials(): void
{
$this->numberOfErrorTrials = $this->numberOfErrorTrials + 1;
}
public function getDestinee(): string|ThirdParty
{
if (null !== $this->getDestineeThirdParty()) {
return $this->getDestineeThirdParty();
}
return $this->getDestineeEmail();
}
/**
* Determines the kind of destinee based on whether the destinee is a thirdParty or an emailAddress.
*
* @return 'thirdParty'|'email' 'thirdParty' if the destinee is a third party, 'email' otherwise
*/
public function getDestineeKind(): string
{
if (null !== $this->getDestineeThirdParty()) {
return 'thirdParty';
}
return 'email';
}
public function isViewed(): bool
{
return $this->views->count() > 0;
}
public function isExpired(\DateTimeImmutable $now): bool
{
return $now >= $this->expireAt;
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Entity\Workflow;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
/**
* Register the viewing action from an external destinee.
*/
#[ORM\Entity(readOnly: true)]
#[ORM\Table(name: 'chill_main_workflow_entity_send_views')]
class EntityWorkflowSendView
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: Types::INTEGER)]
private ?int $id = null;
public function __construct(
#[ORM\ManyToOne(targetEntity: EntityWorkflowSend::class, inversedBy: 'views')]
#[ORM\JoinColumn(nullable: false)]
private EntityWorkflowSend $send,
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
private \DateTimeInterface $viewAt,
#[ORM\Column(type: Types::TEXT)]
private string $remoteIp = '',
) {
$this->send->addView($this);
}
public function getId(): ?int
{
return $this->id;
}
public function getRemoteIp(): string
{
return $this->remoteIp;
}
public function getSend(): EntityWorkflowSend
{
return $this->send;
}
public function getViewAt(): \DateTimeInterface
{
return $this->viewAt;
}
}

View File

@ -112,6 +112,11 @@ class EntityWorkflowStep
#[ORM\OneToMany(mappedBy: 'step', targetEntity: EntityWorkflowStepHold::class)] #[ORM\OneToMany(mappedBy: 'step', targetEntity: EntityWorkflowStepHold::class)]
private Collection $holdsOnStep; private Collection $holdsOnStep;
/**
* @var Collection<int, EntityWorkflowSend>
*/
private Collection $sends;
public function __construct() public function __construct()
{ {
$this->ccUser = new ArrayCollection(); $this->ccUser = new ArrayCollection();
@ -120,6 +125,7 @@ class EntityWorkflowStep
$this->destUserByAccessKey = new ArrayCollection(); $this->destUserByAccessKey = new ArrayCollection();
$this->signatures = new ArrayCollection(); $this->signatures = new ArrayCollection();
$this->holdsOnStep = new ArrayCollection(); $this->holdsOnStep = new ArrayCollection();
$this->sends = new ArrayCollection();
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(32)); $this->accessKey = bin2hex(openssl_random_pseudo_bytes(32));
} }
@ -190,6 +196,18 @@ class EntityWorkflowStep
return $this; return $this;
} }
/**
* @internal use @see{EntityWorkflowSend}'s constructor instead
*/
public function addSend(EntityWorkflowSend $send): self
{
if (!$this->sends->contains($send)) {
$this->sends[] = $send;
}
return $this;
}
public function removeSignature(EntityWorkflowStepSignature $signature): self public function removeSignature(EntityWorkflowStepSignature $signature): self
{ {
if ($this->signatures->contains($signature)) { if ($this->signatures->contains($signature)) {

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20241003094904 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create tables for EntityWorkflowSend and EntityWorkflowSendView';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE SEQUENCE chill_main_workflow_entity_send_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE SEQUENCE chill_main_workflow_entity_send_views_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_main_workflow_entity_send (id INT NOT NULL,'
.' destineeEmail TEXT DEFAULT \'\' NOT NULL, uuid UUID NOT NULL, privateToken VARCHAR(255) NOT NULL,'
.' numberOfErrorTrials INT DEFAULT 0 NOT NULL, expireAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, '
.'createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, destineeThirdParty_id INT DEFAULT NULL, '
.'entityWorkflowStep_id INT NOT NULL, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE UNIQUE INDEX UNIQ_A0C0620FD17F50A6 ON chill_main_workflow_entity_send (uuid)');
$this->addSql('CREATE INDEX IDX_A0C0620FDDFA98DE ON chill_main_workflow_entity_send (destineeThirdParty_id)');
$this->addSql('CREATE INDEX IDX_A0C0620F3912FED6 ON chill_main_workflow_entity_send (entityWorkflowStep_id)');
$this->addSql('CREATE INDEX IDX_A0C0620F3174800F ON chill_main_workflow_entity_send (createdBy_id)');
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_send.uuid IS \'(DC2Type:uuid)\'');
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_send.expireAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_send.createdAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('CREATE TABLE chill_main_workflow_entity_send_views (id INT NOT NULL, send_id INT NOT NULL, '
.'viewAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, remoteIp TEXT NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_2659558513933E7B ON chill_main_workflow_entity_send_views (send_id)');
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_send_views.viewAt IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE chill_main_workflow_entity_send ADD CONSTRAINT FK_A0C0620FDDFA98DE FOREIGN KEY (destineeThirdParty_id) REFERENCES chill_3party.third_party (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_main_workflow_entity_send ADD CONSTRAINT FK_A0C0620F3912FED6 FOREIGN KEY (entityWorkflowStep_id) REFERENCES chill_main_workflow_entity_step (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_main_workflow_entity_send ADD CONSTRAINT FK_A0C0620F3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_main_workflow_entity_send_views ADD CONSTRAINT FK_2659558513933E7B FOREIGN KEY (send_id) REFERENCES chill_main_workflow_entity_send (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
$this->addSql('DROP SEQUENCE chill_main_workflow_entity_send_id_seq CASCADE');
$this->addSql('DROP SEQUENCE chill_main_workflow_entity_send_views_id_seq CASCADE');
$this->addSql('ALTER TABLE chill_main_workflow_entity_send DROP CONSTRAINT FK_A0C0620FDDFA98DE');
$this->addSql('ALTER TABLE chill_main_workflow_entity_send DROP CONSTRAINT FK_A0C0620F3912FED6');
$this->addSql('ALTER TABLE chill_main_workflow_entity_send DROP CONSTRAINT FK_A0C0620F3174800F');
$this->addSql('ALTER TABLE chill_main_workflow_entity_send_views DROP CONSTRAINT FK_2659558513933E7B');
$this->addSql('DROP TABLE chill_main_workflow_entity_send');
$this->addSql('DROP TABLE chill_main_workflow_entity_send_views');
}
}