mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
476 lines
12 KiB
PHP
476 lines
12 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\Doctrine\Model\TrackCreationInterface;
|
|
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
|
use Chill\MainBundle\Entity\User;
|
|
use Chill\MainBundle\Workflow\Validator\EntityWorkflowCreation;
|
|
use Doctrine\Common\Collections\ArrayCollection;
|
|
use Doctrine\Common\Collections\Collection;
|
|
use Doctrine\Common\Collections\Order;
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
|
use Symfony\Component\Validator\Constraints as Assert;
|
|
|
|
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['entity_workflow' => EntityWorkflow::class])]
|
|
#[ORM\Entity]
|
|
#[ORM\Table('chill_main_workflow_entity')]
|
|
#[EntityWorkflowCreation(groups: ['creation'])]
|
|
class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
|
{
|
|
use TrackCreationTrait;
|
|
|
|
use TrackUpdateTrait;
|
|
|
|
/**
|
|
* a list of future cc users for the next steps.
|
|
*
|
|
* @var array|User[]
|
|
*/
|
|
public array $futureCcUsers = [];
|
|
|
|
/**
|
|
* a list of future dest emails for the next steps.
|
|
*
|
|
* This is in used in order to let controller inform who will be the future emails which will validate
|
|
* the next step. This is necessary to perform some computation about the next emails, before they are
|
|
* associated to the entity EntityWorkflowStep.
|
|
*
|
|
* @var array|string[]
|
|
*/
|
|
public array $futureDestEmails = [];
|
|
|
|
/**
|
|
* a list of future dest users for the next steps.
|
|
*
|
|
* This is in used in order to let controller inform who will be the future users which will validate
|
|
* the next step. This is necessary to perform some computation about the next users, before they are
|
|
* associated to the entity EntityWorkflowStep.
|
|
*
|
|
* @var array|User[]
|
|
*/
|
|
public array $futureDestUsers = [];
|
|
|
|
/**
|
|
* @var Collection<EntityWorkflowComment>
|
|
*/
|
|
#[ORM\OneToMany(targetEntity: EntityWorkflowComment::class, mappedBy: 'entityWorkflow', orphanRemoval: true)]
|
|
private Collection $comments;
|
|
|
|
#[ORM\Id]
|
|
#[ORM\GeneratedValue]
|
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
|
private ?int $id = null;
|
|
|
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
|
|
private string $relatedEntityClass = '';
|
|
|
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
|
|
private int $relatedEntityId;
|
|
|
|
/**
|
|
* @var Collection<EntityWorkflowStep>
|
|
*/
|
|
#[Assert\Valid(traverse: true)]
|
|
#[ORM\OneToMany(targetEntity: EntityWorkflowStep::class, mappedBy: 'entityWorkflow', orphanRemoval: true, cascade: ['persist'])]
|
|
#[ORM\OrderBy(['transitionAt' => 'ASC', 'id' => 'ASC'])]
|
|
private Collection $steps;
|
|
|
|
/**
|
|
* @var array|EntityWorkflowStep[]|null
|
|
*/
|
|
private ?array $stepsChainedCache = null;
|
|
|
|
/**
|
|
* @var Collection<User>
|
|
*/
|
|
#[ORM\ManyToMany(targetEntity: User::class)]
|
|
#[ORM\JoinTable(name: 'chill_main_workflow_entity_subscriber_to_final')]
|
|
private Collection $subscriberToFinal;
|
|
|
|
/**
|
|
* @var Collection<User>
|
|
*/
|
|
#[ORM\ManyToMany(targetEntity: User::class)]
|
|
#[ORM\JoinTable(name: 'chill_main_workflow_entity_subscriber_to_step')]
|
|
private Collection $subscriberToStep;
|
|
|
|
/**
|
|
* a step which will store all the transition data.
|
|
*/
|
|
private ?EntityWorkflowStep $transitionningStep = null;
|
|
|
|
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT)]
|
|
private string $workflowName;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->subscriberToFinal = new ArrayCollection();
|
|
$this->subscriberToStep = new ArrayCollection();
|
|
$this->comments = new ArrayCollection();
|
|
$this->steps = new ArrayCollection();
|
|
|
|
$initialStep = new EntityWorkflowStep();
|
|
$initialStep
|
|
->setCurrentStep('initial');
|
|
$this->addStep($initialStep);
|
|
}
|
|
|
|
public function addComment(EntityWorkflowComment $comment): self
|
|
{
|
|
if (!$this->comments->contains($comment)) {
|
|
$this->comments[] = $comment;
|
|
$comment->setEntityWorkflow($this);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* @internal You should prepare a step and run a workflow transition instead of manually adding a step
|
|
*/
|
|
public function addStep(EntityWorkflowStep $step): self
|
|
{
|
|
if (!$this->steps->contains($step)) {
|
|
$this->steps[] = $step;
|
|
$step->setEntityWorkflow($this);
|
|
$this->stepsChainedCache = null;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function addSubscriberToFinal(User $user): self
|
|
{
|
|
if (!$this->subscriberToFinal->contains($user)) {
|
|
$this->subscriberToFinal[] = $user;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function addSubscriberToStep(User $user): self
|
|
{
|
|
if (!$this->subscriberToStep->contains($user)) {
|
|
$this->subscriberToStep[] = $user;
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function getComments(): Collection
|
|
{
|
|
return $this->comments;
|
|
}
|
|
|
|
public function getCurrentStep(): ?EntityWorkflowStep
|
|
{
|
|
$step = $this->steps->last();
|
|
|
|
if (false !== $step) {
|
|
return $step;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getCurrentStepChained(): ?EntityWorkflowStep
|
|
{
|
|
$steps = $this->getStepsChained();
|
|
$currentStep = $this->getCurrentStep();
|
|
|
|
foreach ($steps as $step) {
|
|
if ($step === $currentStep) {
|
|
return $step;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getCurrentStepCreatedAt(): ?\DateTimeInterface
|
|
{
|
|
if (null !== $previous = $this->getPreviousStepIfAny()) {
|
|
return $previous->getTransitionAt();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getCurrentStepCreatedBy(): ?User
|
|
{
|
|
if (null !== $previous = $this->getPreviousStepIfAny()) {
|
|
return $previous->getTransitionBy();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function getId(): ?int
|
|
{
|
|
return $this->id;
|
|
}
|
|
|
|
public function getRelatedEntityClass(): string
|
|
{
|
|
return $this->relatedEntityClass;
|
|
}
|
|
|
|
public function getRelatedEntityId(): int
|
|
{
|
|
return $this->relatedEntityId;
|
|
}
|
|
|
|
/**
|
|
* Method used by MarkingStore.
|
|
*
|
|
* get a string representation of the step
|
|
*/
|
|
public function getStep(): string
|
|
{
|
|
return $this->getCurrentStep()->getCurrentStep();
|
|
}
|
|
|
|
public function getStepAfter(EntityWorkflowStep $step): ?EntityWorkflowStep
|
|
{
|
|
$iterator = $this->steps->getIterator();
|
|
|
|
if ($iterator instanceof \Iterator) {
|
|
$iterator->rewind();
|
|
|
|
while ($iterator->valid()) {
|
|
$curStep = $iterator->current();
|
|
|
|
if ($curStep === $step) {
|
|
$iterator->next();
|
|
|
|
if ($iterator->valid()) {
|
|
return $iterator->current();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
$iterator->next();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
throw new \RuntimeException();
|
|
}
|
|
|
|
public function getSteps(): ArrayCollection|Collection
|
|
{
|
|
return $this->steps;
|
|
}
|
|
|
|
public function getStepsChained(): array
|
|
{
|
|
if (\is_array($this->stepsChainedCache)) {
|
|
return $this->stepsChainedCache;
|
|
}
|
|
|
|
$iterator = $this->steps->getIterator();
|
|
$current = null;
|
|
$steps = [];
|
|
|
|
$iterator->rewind();
|
|
|
|
do {
|
|
$previous = $current;
|
|
$current = $iterator->current();
|
|
$steps[] = $current;
|
|
|
|
$current->setPrevious($previous);
|
|
|
|
$iterator->next();
|
|
|
|
if ($iterator->valid()) {
|
|
$current->setNext($iterator->current());
|
|
} else {
|
|
$current->setNext(null);
|
|
}
|
|
} while ($iterator->valid());
|
|
|
|
$this->stepsChainedCache = $steps;
|
|
|
|
return $steps;
|
|
}
|
|
|
|
public function getSubscriberToFinal(): ArrayCollection|Collection
|
|
{
|
|
return $this->subscriberToFinal;
|
|
}
|
|
|
|
public function getSubscriberToStep(): ArrayCollection|Collection
|
|
{
|
|
return $this->subscriberToStep;
|
|
}
|
|
|
|
/**
|
|
* get the step which is transitionning. Should be called only by event which will
|
|
* concern the transition.
|
|
*/
|
|
public function getTransitionningStep(): ?EntityWorkflowStep
|
|
{
|
|
return $this->transitionningStep;
|
|
}
|
|
|
|
/**
|
|
* @return User[]
|
|
*/
|
|
public function getUsersInvolved(): array
|
|
{
|
|
$usersInvolved = [];
|
|
$usersInvolved[spl_object_hash($this->getCreatedBy())] = $this->getCreatedBy();
|
|
|
|
foreach ($this->steps as $step) {
|
|
foreach ($step->getDestUser() as $u) {
|
|
$usersInvolved[spl_object_hash($u)] = $u;
|
|
}
|
|
}
|
|
|
|
return $usersInvolved;
|
|
}
|
|
|
|
public function getWorkflowName(): string
|
|
{
|
|
return $this->workflowName;
|
|
}
|
|
|
|
public function isFinal(): bool
|
|
{
|
|
foreach ($this->getStepsChained() as $step) {
|
|
if ($step->isFinal()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function isFreeze(): bool
|
|
{
|
|
$steps = $this->getStepsChained();
|
|
|
|
foreach ($this->getStepsChained() as $step) {
|
|
if ($step->isFreezeAfter()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public function isUserSubscribedToFinal(User $user): bool
|
|
{
|
|
return $this->subscriberToFinal->contains($user);
|
|
}
|
|
|
|
public function isUserSubscribedToStep(User $user): bool
|
|
{
|
|
return $this->subscriberToStep->contains($user);
|
|
}
|
|
|
|
public function prepareStepBeforeTransition(EntityWorkflowStep $step): self
|
|
{
|
|
$this->transitionningStep = $step;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeComment(EntityWorkflowComment $comment): self
|
|
{
|
|
if ($this->comments->removeElement($comment)) {
|
|
$comment->setEntityWorkflow(null);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeStep(EntityWorkflowStep $step): self
|
|
{
|
|
if ($this->steps->removeElement($step)) {
|
|
$step->setEntityWorkflow(null);
|
|
}
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeSubscriberToFinal(User $user): self
|
|
{
|
|
$this->subscriberToFinal->removeElement($user);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function removeSubscriberToStep(User $user): self
|
|
{
|
|
$this->subscriberToStep->removeElement($user);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setRelatedEntityClass(string $relatedEntityClass): EntityWorkflow
|
|
{
|
|
$this->relatedEntityClass = $relatedEntityClass;
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setRelatedEntityId(int $relatedEntityId): EntityWorkflow
|
|
{
|
|
$this->relatedEntityId = $relatedEntityId;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Method use by marking store.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setStep(string $step): self
|
|
{
|
|
$newStep = new EntityWorkflowStep();
|
|
$newStep->setCurrentStep($step);
|
|
|
|
// copy the freeze
|
|
if ($this->isFreeze()) {
|
|
$newStep->setFreezeAfter(true);
|
|
}
|
|
|
|
$this->addStep($newStep);
|
|
|
|
return $this;
|
|
}
|
|
|
|
public function setWorkflowName(string $workflowName): EntityWorkflow
|
|
{
|
|
$this->workflowName = $workflowName;
|
|
|
|
return $this;
|
|
}
|
|
|
|
private function getPreviousStepIfAny(): ?EntityWorkflowStep
|
|
{
|
|
if (1 === \count($this->steps)) {
|
|
return null;
|
|
}
|
|
|
|
return $this->steps->get($this->steps->count() - 2);
|
|
}
|
|
}
|