Merge branch 'master' into issue394_address_confidential

This commit is contained in:
2022-01-27 15:39:10 +01:00
222 changed files with 9243 additions and 1925 deletions

View File

@@ -64,7 +64,7 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
/**
* @ORM\Column(type="string", length=255, nullable=true)
* @Serializer\Groups({"read", "write"})
* @Serializer\Groups({"read", "write", "docgen:read"})
*/
private ?string $email = null;

View File

@@ -23,6 +23,9 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\Entity
* @ORM\Table(
* name="chill_main_notification",
* indexes={
* @ORM\Index(name="chill_main_notification_related_entity_idx", columns={"relatedentityclass", "relatedentityid"})
* }
* )
* @ORM\HasLifecycleCallbacks
*/

View File

@@ -0,0 +1,439 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
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 DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Iterator;
use RuntimeException;
use Symfony\Component\Serializer\Annotation as Serializer;
use function count;
/**
* @ORM\Entity
* @ORM\Table("chill_main_workflow_entity")
* @EntityWorkflowCreation(groups={"creation"})
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
* "entity_workflow": EntityWorkflow::class
* })
*/
class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
{
use TrackCreationTrait;
use TrackUpdateTrait;
/**
* @ORM\OneToMany(targetEntity=EntityWorkflowComment::class, mappedBy="entityWorkflow", orphanRemoval=true)
*
* @var Collection|EntityWorkflowComment[]
*/
private Collection $comments;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Serializer\Groups({"read"})
*/
private ?int $id = null;
/**
* @ORM\Column(type="string", length=255)
*/
private string $relatedEntityClass = '';
/**
* @ORM\Column(type="integer")
*/
private int $relatedEntityId;
/**
* @ORM\OneToMany(targetEntity=EntityWorkflowStep::class, mappedBy="entityWorkflow", orphanRemoval=true, cascade={"persist"})
* @ORM\OrderBy({"transitionAt": "ASC", "id": "ASC"})
*
* @var Collection|EntityWorkflowStep[]
*/
private Collection $steps;
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_final")
*
* @var Collection|User[]
*/
private Collection $subscriberToFinal;
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_step")
*
* @var Collection|User[]
*/
private Collection $subscriberToStep;
/**
* a step which will store all the transition data.
*/
private ?EntityWorkflowStep $transitionningStep = null;
/**
* @ORM\Column(type="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);
if ($this->isFinalize()) {
$step->setFinalizeAfter(true);
}
}
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 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();
}
/**
* @return ArrayCollection|Collection
*/
public function getSteps()
{
return $this->steps;
}
public function getStepsChained(): array
{
$iterator = $this->steps->getIterator();
$previous = $next = $current = null;
$steps = [];
$iterator->rewind();
while ($iterator->valid()) {
$previous = $current;
$steps[] = $current = $iterator->current();
$current->setPrevious($previous);
$iterator->next();
if ($iterator->valid()) {
$next = $iterator->current();
} else {
$next = null;
}
$current->setNext($next);
}
return $steps;
}
/**
* @return ArrayCollection|Collection
*/
public function getSubscriberToFinal()
{
return $this->subscriberToFinal;
}
/**
* @return ArrayCollection|Collection
*/
public function getSubscriberToStep()
{
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;
}
public function getWorkflowName(): string
{
return $this->workflowName;
}
public function isFinalize(): bool
{
$steps = $this->getStepsChained();
if (1 === count($steps)) {
// the initial step cannot be finalized
return false;
}
/** @var EntityWorkflowStep $last */
$last = end($steps);
return $last->getPrevious()->isFinalizeAfter();
}
public function isFreeze(): bool
{
$steps = $this->getStepsChained();
if (1 === count($steps)) {
// the initial step cannot be finalized
return false;
}
/** @var EntityWorkflowStep $last */
$last = end($steps);
return $last->getPrevious()->isFreezeAfter();
}
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->getCurrentStep()->isFreezeAfter()) {
$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);
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
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 Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table("chill_main_workflow_entity_comment")
*/
class EntityWorkflowComment implements TrackCreationInterface, TrackUpdateInterface
{
use TrackCreationTrait;
use TrackUpdateTrait;
/**
* @ORM\Column(type="text", options={"default": ""})
*/
private string $comment = '';
/**
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="comments")
*/
private ?EntityWorkflow $entityWorkflow = null;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
public function getComment(): string
{
return $this->comment;
}
public function getEntityWorkflow(): ?EntityWorkflow
{
return $this->entityWorkflow;
}
public function getId(): ?int
{
return $this->id;
}
public function setComment(string $comment): self
{
$this->comment = $comment;
return $this;
}
/**
* @internal use @see{EntityWorkflow::addComment}
*/
public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): self
{
$this->entityWorkflow = $entityWorkflow;
return $this;
}
}

View File

@@ -0,0 +1,336 @@
<?php
/**
* 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.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Entity\Workflow;
use Chill\MainBundle\Entity\User;
use DateTimeImmutable;
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;
use function count;
use function in_array;
/**
* @ORM\Entity
* @ORM\Table("chill_main_workflow_entity_step")
*/
class EntityWorkflowStep
{
/**
* @ORM\Column(type="text", options={"default": ""})
*/
private string $comment = '';
/**
* @ORM\Column(type="text")
*/
private ?string $currentStep = '';
/**
* @ORM\Column(type="json")
*/
private array $destEmail = [];
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_workflow_entity_step_user")
*/
private Collection $destUser;
/**
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="steps")
*/
private ?EntityWorkflow $entityWorkflow = null;
/**
* @ORM\Column(type="boolean", options={"default": false})
*/
private bool $finalizeAfter = false;
/**
* @ORM\Column(type="boolean", options={"default": false})
*/
private bool $freezeAfter = false;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* filled by @see{EntityWorkflow::getStepsChained}.
*/
private ?EntityWorkflowStep $next = null;
/**
* filled by @see{EntityWorkflow::getStepsChained}.
*/
private ?EntityWorkflowStep $previous = null;
/**
* @ORM\Column(type="text", nullable=true, options={"default": null})
*/
private ?string $transitionAfter = null;
/**
* @ORM\Column(type="datetime_immutable")
*/
private ?DateTimeImmutable $transitionAt = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=true)
*/
private ?User $transitionBy = null;
/**
* @ORM\Column(type="text", nullable=true)
*/
private ?string $transitionByEmail = null;
public function __construct()
{
$this->destUser = new ArrayCollection();
}
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 getComment(): string
{
return $this->comment;
}
public function getCurrentStep(): ?string
{
return $this->currentStep;
}
public function getDestEmail(): array
{
return $this->destEmail;
}
/**
* @return ArrayCollection|Collection
*/
public function getDestUser()
{
return $this->destUser;
}
public function getEntityWorkflow(): ?EntityWorkflow
{
return $this->entityWorkflow;
}
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 isFinalizeAfter(): bool
{
return $this->finalizeAfter;
}
public function isFreezeAfter(): bool
{
return $this->freezeAfter;
}
public function removeDestEmail(string $email): self
{
$this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) {
return $email !== $existing;
});
return $this;
}
public function removeDestUser(User $user): self
{
$this->destUser->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 setFinalizeAfter(bool $finalizeAfter): EntityWorkflowStep
{
$this->finalizeAfter = $finalizeAfter;
return $this;
}
public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep
{
$this->freezeAfter = $freezeAfter;
return $this;
}
/**
* @return EntityWorkflowStep
*
* @internal
*/
public function setNext(?EntityWorkflowStep $next): self
{
$this->next = $next;
return $this;
}
/**
* @return EntityWorkflowStep
*
* @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
*
* @param mixed $payload
*/
public function validateOnCreation(ExecutionContextInterface $context, $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();
}
}
}
}