Julien Fastré 3db4fff80d
Add signature functionality to workflow entities
Created new files to add signature functionality to the workflow entities, including signature state enums and signature metadata. Added these changes to the migration script as well. Updated EntityWorkflowStep to include a collection for signatures.
2024-07-01 14:50:03 +02:00

438 lines
11 KiB
PHP

<?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\Entity\User;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[ORM\Entity]
#[ORM\Table('chill_main_workflow_entity_step')]
class EntityWorkflowStep
{
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false)]
private string $accessKey;
/**
* @var Collection<User>
*/
#[ORM\ManyToMany(targetEntity: User::class)]
#[ORM\JoinTable(name: 'chill_main_workflow_entity_step_cc_user')]
private Collection $ccUser;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, options: ['default' => ''])]
private string $comment = '';
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
private ?string $currentStep = '';
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
private array $destEmail = [];
/**
* @var Collection<int, User>
*/
#[ORM\ManyToMany(targetEntity: User::class)]
#[ORM\JoinTable(name: 'chill_main_workflow_entity_step_user')]
private Collection $destUser;
/**
* @var Collection<int, User>
*/
#[ORM\ManyToMany(targetEntity: User::class)]
#[ORM\JoinTable(name: 'chill_main_workflow_entity_step_user_by_accesskey')]
private Collection $destUserByAccessKey;
/**
* @var Collection <int, EntityWorkflowStepSignature>
*/
#[ORM\OneToMany(mappedBy: 'step', targetEntity: EntityWorkflowStepSignature::class, cascade: ['persist'], orphanRemoval: true)]
private Collection $signatures;
#[ORM\ManyToOne(targetEntity: EntityWorkflow::class, inversedBy: 'steps')]
private ?EntityWorkflow $entityWorkflow = null;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
private bool $freezeAfter = false;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
private ?int $id = null;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => false])]
private bool $isFinal = false;
/**
* filled by @see{EntityWorkflow::getStepsChained}.
*/
private ?EntityWorkflowStep $next = null;
/**
* filled by @see{EntityWorkflow::getStepsChained}.
*/
private ?EntityWorkflowStep $previous = null;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true, options: ['default' => null])]
private ?string $transitionAfter = null;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true, options: ['default' => null])]
private ?\DateTimeImmutable $transitionAt = null;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(nullable: true)]
private ?User $transitionBy = null;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: true)]
private ?string $transitionByEmail = null;
public function __construct()
{
$this->ccUser = new ArrayCollection();
$this->destUser = new ArrayCollection();
$this->destUserByAccessKey = new ArrayCollection();
$this->signatures = new ArrayCollection();
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(32));
}
public function addCcUser(User $user): self
{
if (!$this->ccUser->contains($user)) {
$this->ccUser[] = $user;
}
return $this;
}
public function addDestEmail(string $email): self
{
if (!\in_array($email, $this->destEmail, true)) {
$this->destEmail[] = $email;
}
return $this;
}
public function addDestUser(User $user): self
{
if (!$this->destUser->contains($user)) {
$this->destUser[] = $user;
}
return $this;
}
public function addDestUserByAccessKey(User $user): self
{
if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) {
$this->destUserByAccessKey[] = $user;
}
return $this;
}
/**
* @internal use @see{EntityWorkflowStepSignature}'s constructor instead
*/
public function addSignature(EntityWorkflowStepSignature $signature): self
{
if (!$this->signatures->contains($signature)) {
$this->signatures[] = $signature;
}
return $this;
}
public function removeSignature(EntityWorkflowStepSignature $signature): self
{
if ($this->signatures->contains($signature)) {
$this->signatures->removeElement($signature);
}
$signature->detachEntityWorkflowStep();
return $this;
}
public function getAccessKey(): string
{
return $this->accessKey;
}
/**
* get all the users which are allowed to apply a transition: those added manually, and
* those added automatically bu using an access key.
*
* @psalm-suppress DuplicateArrayKey
*/
public function getAllDestUser(): Collection
{
return new ArrayCollection(
[
...$this->getDestUser()->toArray(),
...$this->getDestUserByAccessKey()->toArray(),
]
);
}
public function getCcUser(): Collection
{
return $this->ccUser;
}
public function getComment(): string
{
return $this->comment;
}
public function getCurrentStep(): ?string
{
return $this->currentStep;
}
public function getDestEmail(): array
{
return $this->destEmail;
}
/**
* get dest users added by the creator.
*
* You should **not** rely on this method to get all users which are able to
* apply a transition on this step. Use @see{EntityWorkflowStep::getAllDestUser} instead.
*/
public function getDestUser(): Collection
{
return $this->destUser;
}
public function getDestUserByAccessKey(): Collection
{
return $this->destUserByAccessKey;
}
public function getEntityWorkflow(): ?EntityWorkflow
{
return $this->entityWorkflow;
}
/**
* @return Collection<int, EntityWorkflowStepSignature>
*/
public function getSignatures(): Collection
{
return $this->signatures;
}
public function getId(): ?int
{
return $this->id;
}
public function getNext(): ?EntityWorkflowStep
{
return $this->next;
}
public function getPrevious(): ?EntityWorkflowStep
{
return $this->previous;
}
public function getTransitionAfter(): ?string
{
return $this->transitionAfter;
}
public function getTransitionAt(): ?\DateTimeImmutable
{
return $this->transitionAt;
}
public function getTransitionBy(): ?User
{
return $this->transitionBy;
}
public function getTransitionByEmail(): ?string
{
return $this->transitionByEmail;
}
public function isFinal(): bool
{
return $this->isFinal;
}
public function isFreezeAfter(): bool
{
return $this->freezeAfter;
}
public function isWaitingForTransition(): bool
{
if (null !== $this->transitionAfter) {
return false;
}
if ($this->isFinal()) {
return false;
}
return true;
}
public function removeCcUser(User $user): self
{
$this->ccUser->removeElement($user);
return $this;
}
public function removeDestEmail(string $email): self
{
$this->destEmail = array_filter($this->destEmail, static fn (string $existing) => $email !== $existing);
return $this;
}
public function removeDestUser(User $user): self
{
$this->destUser->removeElement($user);
return $this;
}
public function removeDestUserByAccessKey(User $user): self
{
$this->destUserByAccessKey->removeElement($user);
return $this;
}
public function setComment(?string $comment): EntityWorkflowStep
{
$this->comment = (string) $comment;
return $this;
}
public function setCurrentStep(?string $currentStep): EntityWorkflowStep
{
$this->currentStep = $currentStep;
return $this;
}
public function setDestEmail(array $destEmail): EntityWorkflowStep
{
$this->destEmail = $destEmail;
return $this;
}
/**
* @internal use @see(EntityWorkflow::addStep} instead
*/
public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): EntityWorkflowStep
{
$this->entityWorkflow = $entityWorkflow;
return $this;
}
public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep
{
$this->freezeAfter = $freezeAfter;
return $this;
}
public function setIsFinal(bool $isFinal): EntityWorkflowStep
{
$this->isFinal = $isFinal;
return $this;
}
/**
* @internal
*/
public function setNext(?EntityWorkflowStep $next): self
{
$this->next = $next;
return $this;
}
/**
* @internal
*/
public function setPrevious(?EntityWorkflowStep $previous): self
{
$this->previous = $previous;
return $this;
}
public function setTransitionAfter(?string $transitionAfter): EntityWorkflowStep
{
$this->transitionAfter = $transitionAfter;
return $this;
}
public function setTransitionAt(?\DateTimeImmutable $transitionAt): EntityWorkflowStep
{
$this->transitionAt = $transitionAt;
return $this;
}
public function setTransitionBy(?User $transitionBy): EntityWorkflowStep
{
$this->transitionBy = $transitionBy;
return $this;
}
public function setTransitionByEmail(?string $transitionByEmail): EntityWorkflowStep
{
$this->transitionByEmail = $transitionByEmail;
return $this;
}
#[Assert\Callback]
public function validateOnCreation(ExecutionContextInterface $context, mixed $payload): void
{
return;
if ($this->isFinalizeAfter()) {
if (0 !== \count($this->getDestUser())) {
$context->buildViolation('workflow.No dest users when the workflow is finalized')
->atPath('finalizeAfter')
->addViolation();
}
} else {
if (0 === \count($this->getDestUser())) {
$context->buildViolation('workflow.The next step must count at least one dest')
->atPath('finalizeAfter')
->addViolation();
}
}
}
}