mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-20 14:43:49 +00:00
merge with master
This commit is contained in:
@@ -29,7 +29,6 @@ use Chill\MainBundle\Security\ProvideRoleInterface;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
|
||||
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
|
||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
|
||||
use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass;
|
||||
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
@@ -70,7 +69,6 @@ class ChillMainBundle extends Bundle
|
||||
$container->addCompilerPass(new MenuCompilerPass());
|
||||
$container->addCompilerPass(new ACLFlagsCompilerPass());
|
||||
$container->addCompilerPass(new GroupingCenterCompilerPass());
|
||||
$container->addCompilerPass(new RenderEntityCompilerPass());
|
||||
$container->addCompilerPass(new CRUDControllerCompilerPass());
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,24 @@
|
||||
<?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\Controller;
|
||||
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class CivilityApiController extends ApiController
|
||||
{
|
||||
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
|
||||
{
|
||||
return $query->addOrderBy('e.order', 'ASC');
|
||||
}
|
||||
}
|
@@ -11,21 +11,26 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowComment;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Form\EntityWorkflowCommentType;
|
||||
use Chill\MainBundle\Form\WorkflowStepType;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
|
||||
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Chill\MainBundle\Workflow\Validator\StepDestValid;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
use Symfony\Component\Workflow\TransitionBlocker;
|
||||
@@ -80,7 +85,9 @@ class WorkflowController extends AbstractController
|
||||
$entityWorkflow
|
||||
->setRelatedEntityClass($request->query->get('entityClass'))
|
||||
->setRelatedEntityId($request->query->getInt('entityId'))
|
||||
->setWorkflowName($request->query->get('workflow'));
|
||||
->setWorkflowName($request->query->get('workflow'))
|
||||
->addSubscriberToStep($this->getUser())
|
||||
->addSubscriberToFinal($this->getUser());
|
||||
|
||||
$errors = $this->validator->validate($entityWorkflow, null, ['creation']);
|
||||
|
||||
@@ -104,6 +111,121 @@ class WorkflowController extends AbstractController
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow/{id}/delete", name="chill_main_workflow_delete")
|
||||
*/
|
||||
public function delete(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(EntityWorkflowVoter::DELETE, $entityWorkflow);
|
||||
|
||||
$form = $this->createForm(FormType::class);
|
||||
$form->add('submit', SubmitType::class, ['label' => 'workflow.Delete workflow']);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->remove($entityWorkflow);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', $this->translator->trans('workflow.Workflow deleted with success'));
|
||||
|
||||
return $this->redirectToRoute('chill_main_homepage');
|
||||
}
|
||||
|
||||
return $this->render('@ChillMain/Workflow/delete.html.twig', [
|
||||
'entityWorkflow' => $entityWorkflow,
|
||||
'delete_form' => $form->createView(),
|
||||
'handler' => $this->entityWorkflowManager->getHandler($entityWorkflow),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow-step/{id}/access_key", name="chill_main_workflow_grant_access_by_key")
|
||||
*/
|
||||
public function getAccessByAccessKey(EntityWorkflowStep $entityWorkflowStep, Request $request): Response
|
||||
{
|
||||
if (null === $accessKey = $request->query->get('accessKey', null)) {
|
||||
throw new BadRequestException('accessKey is missing');
|
||||
}
|
||||
|
||||
if (!$this->getUser() instanceof User) {
|
||||
throw new AccessDeniedException('Not a valid user');
|
||||
}
|
||||
|
||||
if ($entityWorkflowStep->getAccessKey() !== $accessKey) {
|
||||
throw new AccessDeniedException('Access key is invalid');
|
||||
}
|
||||
|
||||
if (!$entityWorkflowStep->isWaitingForTransition()) {
|
||||
$this->addFlash('error', $this->translator->trans('workflow.Steps is not waiting for transition. Maybe someone apply the transition before you ?'));
|
||||
} else {
|
||||
$entityWorkflowStep->addDestUserByAccessKey($this->getUser());
|
||||
$this->entityManager->flush();
|
||||
$this->addFlash('success', $this->translator->trans('workflow.You get access to this step'));
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflowStep->getEntityWorkflow()->getId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Previous workflows where the user has applyed a transition.
|
||||
*
|
||||
* @Route("/{_locale}/main/workflow/list/previous_transitionned", name="chill_main_workflow_list_previous_transitionned")
|
||||
*/
|
||||
public function myPreviousWorkflowsTransitionned(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
|
||||
$total = $this->entityWorkflowRepository->countByPreviousTransitionned($this->getUser());
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
|
||||
$workflows = $this->entityWorkflowRepository->findByPreviousTransitionned(
|
||||
$this->getUser(),
|
||||
['createdAt' => 'DESC'],
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/list.html.twig',
|
||||
[
|
||||
'help' => 'workflow.Previous workflow transitionned help',
|
||||
'workflows' => $this->buildHandler($workflows),
|
||||
'paginator' => $paginator,
|
||||
'step' => 'previous_transitionned',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Previous workflows where the user was mentioned, but did not give any reaction.
|
||||
*
|
||||
* @Route("/{_locale}/main/workflow/list/previous_without_reaction", name="chill_main_workflow_list_previous_without_reaction")
|
||||
*/
|
||||
public function myPreviousWorkflowsWithoutReaction(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
|
||||
$total = $this->entityWorkflowRepository->countByPreviousDestWithoutReaction($this->getUser());
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
|
||||
$workflows = $this->entityWorkflowRepository->findByPreviousDestWithoutReaction(
|
||||
$this->getUser(),
|
||||
['createdAt' => 'DESC'],
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/list.html.twig',
|
||||
[
|
||||
'help' => 'workflow.Previous workflow without reaction help',
|
||||
'workflows' => $this->buildHandler($workflows),
|
||||
'paginator' => $paginator,
|
||||
'step' => 'previous_without_reaction',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow/list/dest", name="chill_main_workflow_list_dest")
|
||||
*/
|
||||
@@ -198,6 +320,7 @@ class WorkflowController extends AbstractController
|
||||
}
|
||||
|
||||
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData();
|
||||
$entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData();
|
||||
|
||||
$workflow->apply($entityWorkflow, $transition);
|
||||
|
||||
@@ -205,18 +328,13 @@ class WorkflowController extends AbstractController
|
||||
$entityWorkflow->getCurrentStep()->addDestUser($user);
|
||||
}
|
||||
|
||||
$errors = $this->validator->validate(
|
||||
$entityWorkflow->getCurrentStep(),
|
||||
new StepDestValid()
|
||||
);
|
||||
|
||||
if (count($errors) === 0) {
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||
foreach ($transitionForm['future_dest_emails']->getData() as $email) {
|
||||
$entityWorkflow->getCurrentStep()->addDestEmail($email);
|
||||
}
|
||||
|
||||
return new Response((string) $errors, Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||
}
|
||||
|
||||
if ($transitionForm->isSubmitted() && !$transitionForm->isValid()) {
|
||||
@@ -243,8 +361,8 @@ class WorkflowController extends AbstractController
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/index.html.twig',
|
||||
[
|
||||
'handler' => $handler,
|
||||
'handler_template' => $handler->getTemplate($entityWorkflow),
|
||||
'handler_template_title' => $handler->getTemplateTitle($entityWorkflow),
|
||||
'handler_template_data' => $handler->getTemplateData($entityWorkflow),
|
||||
'transition_form' => isset($transitionForm) ? $transitionForm->createView() : null,
|
||||
'entity_workflow' => $entityWorkflow,
|
||||
|
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\DependencyInjection;
|
||||
|
||||
use Chill\MainBundle\Controller\AddressApiController;
|
||||
use Chill\MainBundle\Controller\CivilityApiController;
|
||||
use Chill\MainBundle\Controller\LocationController;
|
||||
use Chill\MainBundle\Controller\LocationTypeController;
|
||||
use Chill\MainBundle\Controller\UserController;
|
||||
@@ -559,6 +560,21 @@ class ChillMainExtension extends Extension implements
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'class' => \Chill\MainBundle\Entity\Civility::class,
|
||||
'name' => 'civility',
|
||||
'base_path' => '/api/1.0/main/civility',
|
||||
'base_role' => 'ROLE_USER',
|
||||
'controller' => CivilityApiController::class,
|
||||
'actions' => [
|
||||
'_index' => [
|
||||
'methods' => [
|
||||
Request::METHOD_GET => true,
|
||||
Request::METHOD_HEAD => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
@@ -17,6 +17,7 @@ use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
/**
|
||||
* @ORM\Table(name="chill_main_civility")
|
||||
* @ORM\Entity
|
||||
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={"chill_main_civility": Civility::class})
|
||||
*/
|
||||
class Civility
|
||||
{
|
||||
@@ -29,6 +30,7 @@ class Civility
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private bool $active = true;
|
||||
|
||||
|
@@ -41,6 +41,17 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
|
||||
use TrackUpdateTrait;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@@ -27,6 +27,11 @@ use function in_array;
|
||||
*/
|
||||
class EntityWorkflowStep
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false)
|
||||
*/
|
||||
private string $accessKey;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", options={"default": ""})
|
||||
*/
|
||||
@@ -48,6 +53,12 @@ class EntityWorkflowStep
|
||||
*/
|
||||
private Collection $destUser;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=User::class)
|
||||
* @ORM\JoinTable(name="chill_main_workflow_entity_step_user_by_accesskey")
|
||||
*/
|
||||
private Collection $destUserByAccessKey;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="steps")
|
||||
*/
|
||||
@@ -86,7 +97,7 @@ class EntityWorkflowStep
|
||||
private ?string $transitionAfter = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable")
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": null})
|
||||
*/
|
||||
private ?DateTimeImmutable $transitionAt = null;
|
||||
|
||||
@@ -104,6 +115,8 @@ class EntityWorkflowStep
|
||||
public function __construct()
|
||||
{
|
||||
$this->destUser = new ArrayCollection();
|
||||
$this->destUserByAccessKey = new ArrayCollection();
|
||||
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(32));
|
||||
}
|
||||
|
||||
public function addDestEmail(string $email): self
|
||||
@@ -119,11 +132,45 @@ class EntityWorkflowStep
|
||||
{
|
||||
if (!$this->destUser->contains($user)) {
|
||||
$this->destUser[] = $user;
|
||||
$this->getEntityWorkflow()
|
||||
->addSubscriberToFinal($user)
|
||||
->addSubscriberToStep($user);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addDestUserByAccessKey(User $user): self
|
||||
{
|
||||
if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) {
|
||||
$this->destUserByAccessKey[] = $user;
|
||||
$this->getEntityWorkflow()
|
||||
->addSubscriberToFinal($user)
|
||||
->addSubscriberToStep($user);
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
public function getAllDestUser(): Collection
|
||||
{
|
||||
return new ArrayCollection(
|
||||
[
|
||||
...$this->getDestUser()->toArray(),
|
||||
...$this->getDestUserByAccessKey()->toArray(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getComment(): string
|
||||
{
|
||||
return $this->comment;
|
||||
@@ -140,13 +187,21 @@ class EntityWorkflowStep
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection|Collection
|
||||
* 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()
|
||||
public function getDestUser(): collection
|
||||
{
|
||||
return $this->destUser;
|
||||
}
|
||||
|
||||
public function getDestUserByAccessKey(): Collection
|
||||
{
|
||||
return $this->destUserByAccessKey;
|
||||
}
|
||||
|
||||
public function getEntityWorkflow(): ?EntityWorkflow
|
||||
{
|
||||
return $this->entityWorkflow;
|
||||
@@ -197,6 +252,19 @@ class EntityWorkflowStep
|
||||
return $this->freezeAfter;
|
||||
}
|
||||
|
||||
public function isWaitingForTransition(): bool
|
||||
{
|
||||
if (null !== $this->transitionAfter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isFinal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeDestEmail(string $email): self
|
||||
{
|
||||
$this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) {
|
||||
@@ -213,6 +281,13 @@ class EntityWorkflowStep
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeDestUserByAccessKey(User $user): self
|
||||
{
|
||||
$this->destUserByAccessKey->removeElement($user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setComment(?string $comment): EntityWorkflowStep
|
||||
{
|
||||
$this->comment = (string) $comment;
|
||||
|
@@ -13,6 +13,7 @@ namespace Chill\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
@@ -21,8 +22,14 @@ use LogicException;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Callback;
|
||||
use Symfony\Component\Validator\Constraints\Email;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\NotNull;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
use Symfony\Component\Workflow\Transition;
|
||||
use function array_key_exists;
|
||||
@@ -146,6 +153,22 @@ class WorkflowStepType extends AbstractType
|
||||
'label' => 'workflow.dest for next steps',
|
||||
'multiple' => true,
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('future_dest_emails', ChillCollectionType::class, [
|
||||
'label' => 'workflow.dest by email',
|
||||
'help' => 'workflow.dest by email help',
|
||||
'mapped' => false,
|
||||
'allow_add' => true,
|
||||
'entry_type' => EmailType::class,
|
||||
'button_add_label' => 'workflow.Add an email',
|
||||
'button_remove_label' => 'workflow.Remove an email',
|
||||
'empty_collection_explain' => 'workflow.Any email',
|
||||
'entry_options' => [
|
||||
'constraints' => [
|
||||
new NotNull(), new NotBlank(), new Email(['checkMX' => true]),
|
||||
],
|
||||
'label' => 'Email',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -175,6 +198,37 @@ class WorkflowStepType extends AbstractType
|
||||
->setRequired('transition')
|
||||
->setAllowedTypes('transition', 'bool')
|
||||
->setRequired('entity_workflow')
|
||||
->setAllowedTypes('entity_workflow', EntityWorkflow::class);
|
||||
->setAllowedTypes('entity_workflow', EntityWorkflow::class)
|
||||
->setDefault('constraints', [
|
||||
new Callback(
|
||||
function ($step, ExecutionContextInterface $context, $payload) {
|
||||
/** @var EntityWorkflowStep $step */
|
||||
$form = $context->getObject();
|
||||
$workflow = $this->registry->get($step->getEntityWorkflow(), $step->getEntityWorkflow()->getWorkflowName());
|
||||
$transition = $form['transition']->getData();
|
||||
$toFinal = true;
|
||||
|
||||
foreach ($transition->getTos() as $to) {
|
||||
$meta = $workflow->getMetadataStore()->getPlaceMetadata($to);
|
||||
|
||||
if (
|
||||
!array_key_exists('isFinal', $meta) || false === $meta['isFinal']
|
||||
) {
|
||||
$toFinal = false;
|
||||
}
|
||||
}
|
||||
|
||||
$destUsers = $form['future_dest_users']->getData();
|
||||
$destEmails = $form['future_dest_emails']->getData();
|
||||
|
||||
if (!$toFinal && [] === $destUsers && [] === $destEmails) {
|
||||
$context
|
||||
->buildViolation('workflow.You must add at least one dest user or email')
|
||||
->atPath('future_dest_users')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -34,6 +34,20 @@ class EntityWorkflowRepository implements ObjectRepository
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countByPreviousDestWithoutReaction(User $user): int
|
||||
{
|
||||
$qb = $this->buildQueryByPreviousDestWithoutReaction($user)->select('count(ew)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countByPreviousTransitionned(User $user): int
|
||||
{
|
||||
$qb = $this->buildQueryByPreviousTransitionned($user)->select('count(ew)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countBySubscriber(User $user): int
|
||||
{
|
||||
$qb = $this->buildQueryBySubscriber($user)->select('count(ew)');
|
||||
@@ -78,6 +92,32 @@ class EntityWorkflowRepository implements ObjectRepository
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findByPreviousDestWithoutReaction(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
$qb = $this->buildQueryByPreviousDestWithoutReaction($user)->select('ew');
|
||||
|
||||
foreach ($orderBy as $key => $sort) {
|
||||
$qb->addOrderBy('ew.' . $key, $sort);
|
||||
}
|
||||
|
||||
$qb->setMaxResults($limit)->setFirstResult($offset);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findByPreviousTransitionned(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
$qb = $this->buildQueryByPreviousTransitionned($user)->select('ew')->distinct(true);
|
||||
|
||||
foreach ($orderBy as $key => $sort) {
|
||||
$qb->addOrderBy('ew.' . $key, $sort);
|
||||
}
|
||||
|
||||
$qb->setMaxResults($limit)->setFirstResult($offset);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findBySubscriber(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
$qb = $this->buildQueryBySubscriber($user)->select('ew');
|
||||
@@ -120,6 +160,41 @@ class EntityWorkflowRepository implements ObjectRepository
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function buildQueryByPreviousDestWithoutReaction(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('ew');
|
||||
|
||||
$qb->join('ew.steps', 'step');
|
||||
|
||||
$qb->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->isMemberOf(':user', 'step.destUser'),
|
||||
$qb->expr()->neq('step.transitionBy', ':user'),
|
||||
)
|
||||
);
|
||||
|
||||
$qb->setParameter('user', $user);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function buildQueryByPreviousTransitionned(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('ew');
|
||||
|
||||
$qb->join('ew.steps', 'step');
|
||||
|
||||
$qb->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('step.transitionBy', ':user'),
|
||||
)
|
||||
);
|
||||
|
||||
$qb->setParameter('user', $user);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function buildQueryBySubscriber(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('ew');
|
||||
|
@@ -72,7 +72,7 @@ section.chill-entity {
|
||||
}
|
||||
}
|
||||
p {
|
||||
display: inline-block;
|
||||
// display: inline-block;
|
||||
margin: 0 0 0 1.5em;
|
||||
text-indent: -1.5em;
|
||||
|
||||
|
@@ -50,7 +50,7 @@ var handleAdd = function(button) {
|
||||
empty_explain = collection.querySelector('li[data-collection-empty-explain]'),
|
||||
entry = document.createElement('li'),
|
||||
event = new CustomEvent('collection-add-entry', { detail: { collection: collection, entry: entry } }),
|
||||
counter = collection.childNodes.length,
|
||||
counter = collection.childNodes.length + parseInt(Math.random() * 1000000)
|
||||
content
|
||||
;
|
||||
content = prototype.replace(new RegExp('__name__', 'g'), counter);
|
||||
|
@@ -1,10 +1,15 @@
|
||||
.confidential{
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
.toggle{
|
||||
margin-left: 30px;
|
||||
margin-top: 5px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 10px;
|
||||
z-index: 5;
|
||||
}
|
||||
.blur {
|
||||
-webkit-filter: blur(5px);
|
||||
|
@@ -3,7 +3,7 @@ import {ShowHide} from 'ChillMainAssets/lib/show_hide/show_hide.js';
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
let
|
||||
divTransitions = document.querySelector('#transitions'),
|
||||
futureDestUsersContainer = document.querySelector('#futureDestUsers')
|
||||
futureDestUsersContainer = document.querySelector('#futureDests')
|
||||
;
|
||||
|
||||
if (null !== divTransitions) {
|
||||
@@ -67,24 +67,4 @@ window.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// validate form
|
||||
let form = document.querySelector('form[name="workflow_step"]');
|
||||
|
||||
if (form === null) {
|
||||
console.error('form to validate not found');
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
const datas = new FormData(event.target);
|
||||
|
||||
if (datas.has('workflow_step[future_dest_users]')) {
|
||||
const dests = JSON.parse(datas.get('workflow_step[future_dest_users]'));
|
||||
if (dests === null || (dests instanceof Array && dests.length === 0)) {
|
||||
event.preventDefault();
|
||||
console.log('no users!');
|
||||
window.alert('Indiquez un utilisateur pour traiter la prochaine étape.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -77,8 +77,7 @@ export default {
|
||||
return this.$refs.castPerson.$data.person;
|
||||
case 'thirdparty':
|
||||
let data = this.$refs.castThirdparty.$data.thirdparty;
|
||||
data.name = data.text;
|
||||
if (data.address !== null) {
|
||||
if (data.address !== undefined && data.address !== null) {
|
||||
data.address = { id: data.address.address_id }
|
||||
} else {
|
||||
data.address = null;
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
|
||||
<a v-if="isDisplayBadge" @click="openModal">
|
||||
<span class="chill-entity" :class="badgeType">
|
||||
{{ buttonText }}<span v-if="isDead"> (‡)</span>
|
||||
</span>
|
||||
</a>
|
||||
<a v-else class="btn btn-sm" target="_blank"
|
||||
<a v-if="isDisplayBadge" @click="openModal">
|
||||
<span class="chill-entity" :class="badgeType">
|
||||
{{ buttonText }}<span v-if="isDead"> (‡)</span>
|
||||
</span>
|
||||
</a>
|
||||
<a v-else class="btn btn-sm" target="_blank"
|
||||
:class="classAction"
|
||||
:title="$t(titleAction)"
|
||||
@click="openModal">
|
||||
@@ -18,7 +18,8 @@
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<template v-slot:header>
|
||||
<h3 class="modal-title">{{ $t(titleModal) }}</h3>
|
||||
<h3 v-if="parent" class="modal-title">{{ $t(titleModal, {q: parent.text}) }}</h3>
|
||||
<h3 v-else class="modal-title">{{ $t(titleModal) }}</h3>
|
||||
</template>
|
||||
|
||||
<template v-slot:body v-if="type === 'person'">
|
||||
@@ -29,10 +30,10 @@
|
||||
ref="castPerson">
|
||||
</on-the-fly-person>
|
||||
<div v-if="hasResourceComment">
|
||||
<h3>{{ $t('onthefly.resource_comment_title') }}</h3>
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ parent.comment }}
|
||||
</blockquote>
|
||||
<h3>{{ $t('onthefly.resource_comment_title') }}</h3>
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ parent.comment }}
|
||||
</blockquote>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -44,13 +45,22 @@
|
||||
ref="castThirdparty">
|
||||
</on-the-fly-thirdparty>
|
||||
<div v-if="hasResourceComment">
|
||||
<h3>{{ $t('onthefly.resource_comment_title') }}</h3>
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ parent.comment }}
|
||||
</blockquote>
|
||||
<h3>{{ $t('onthefly.resource_comment_title') }}</h3>
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ parent.comment }}
|
||||
</blockquote>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:body v-else-if="parent">
|
||||
<on-the-fly-thirdparty
|
||||
:parent="parent"
|
||||
:action="action"
|
||||
type="thirdparty"
|
||||
ref="castThirdparty">
|
||||
</on-the-fly-thirdparty>
|
||||
</template>
|
||||
|
||||
<template v-slot:body v-else>
|
||||
<on-the-fly-create
|
||||
:action="action"
|
||||
@@ -83,6 +93,7 @@ import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
|
||||
import OnTheFlyCreate from './Create.vue';
|
||||
import OnTheFlyPerson from 'ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue';
|
||||
import OnTheFlyThirdparty from 'ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue';
|
||||
import { postThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly";
|
||||
|
||||
export default {
|
||||
name: 'OnTheFly',
|
||||
@@ -104,12 +115,11 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
hasResourceComment() {
|
||||
//console.log('hasResourceComment', this.parent);
|
||||
return (typeof this.parent !== 'undefined' && this.parent !== null)
|
||||
&& this.action === 'show'
|
||||
&& this.parent.type === 'accompanying_period_resource'
|
||||
&& (this.parent.comment !== null && this.parent.comment !== '')
|
||||
;
|
||||
return (typeof this.parent !== 'undefined' && this.parent !== null)
|
||||
&& this.action === 'show'
|
||||
&& this.parent.type === 'accompanying_period_resource'
|
||||
&& (this.parent.comment !== null && this.parent.comment !== '')
|
||||
;
|
||||
},
|
||||
classAction() {
|
||||
switch (this.action) {
|
||||
@@ -119,6 +129,8 @@ export default {
|
||||
return 'btn-update';
|
||||
case 'create':
|
||||
return 'btn-create';
|
||||
case 'addContact':
|
||||
return 'btn-tpchild';
|
||||
}
|
||||
},
|
||||
titleAction() {
|
||||
@@ -129,6 +141,8 @@ export default {
|
||||
return 'action.edit';
|
||||
case 'create':
|
||||
return 'action.create';
|
||||
case 'addContact':
|
||||
return 'action.addContact';
|
||||
}
|
||||
},
|
||||
titleCreate() {
|
||||
@@ -146,6 +160,8 @@ export default {
|
||||
return 'onthefly.edit.' + this.type;
|
||||
case 'create':
|
||||
return this.titleCreate;
|
||||
case 'addContact':
|
||||
return 'onthefly.addContact.title';
|
||||
}
|
||||
},
|
||||
titleMessage() {
|
||||
@@ -179,8 +195,8 @@ export default {
|
||||
this.modal.showModal = false;
|
||||
},
|
||||
openModal() {
|
||||
//console.log('## OPEN ON THE FLY MODAL');
|
||||
//console.log('## type:', this.type, ', action:', this.action);
|
||||
// console.log('## OPEN ON THE FLY MODAL');
|
||||
// console.log('## type:', this.type, ', action:', this.action);
|
||||
this.modal.showModal = true;
|
||||
this.$nextTick(function() {
|
||||
//this.$refs.search.focus();
|
||||
@@ -190,7 +206,7 @@ export default {
|
||||
this.$data.action = action;
|
||||
},
|
||||
saveAction() {
|
||||
console.log('saveAction button: create/edit action with', this.type);
|
||||
// console.log('saveAction button: create/edit action with', this.type);
|
||||
let
|
||||
type = this.type,
|
||||
data = {} ;
|
||||
@@ -202,17 +218,28 @@ export default {
|
||||
|
||||
case 'thirdparty':
|
||||
data = this.$refs.castThirdparty.$data.thirdparty;
|
||||
/* never executed ? */
|
||||
break;
|
||||
|
||||
default:
|
||||
if (typeof this.type === 'undefined') { // action=create
|
||||
type = this.$refs.castNew.radioType;
|
||||
data = this.$refs.castNew.castDataByType();
|
||||
if (typeof this.type === 'undefined') { // action=create or addContact
|
||||
console.log('will rewrite data');
|
||||
if (this.action === 'addContact') {
|
||||
type = 'thirdparty'
|
||||
data = this.$refs.castThirdparty.$data.thirdparty;
|
||||
console.log('data original', data);
|
||||
data.parent = {type: "thirdparty", id: this.parent.id};
|
||||
} else {
|
||||
type = this.$refs.castNew.radioType;
|
||||
data = this.$refs.castNew.castDataByType();
|
||||
console.log(data)
|
||||
}
|
||||
} else {
|
||||
throw 'error with object type';
|
||||
}
|
||||
}
|
||||
|
||||
console.log('type', type);
|
||||
console.log('data', data);
|
||||
// pass datas to parent
|
||||
this.$emit('saveFormOnTheFly', { type: type, data: data });
|
||||
},
|
||||
@@ -232,4 +259,8 @@ export default {
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* .btn-add-contact {
|
||||
background-color: pink;
|
||||
} */
|
||||
</style>
|
||||
|
@@ -21,7 +21,13 @@ const ontheflyMessages = {
|
||||
person: "un nouvel usager",
|
||||
thirdparty: "un nouveau tiers professionnel"
|
||||
},
|
||||
resource_comment_title: "Un commentaire est associé à cet interlocuteur"
|
||||
addContact: {
|
||||
title: "Créer un contact pour {q}"
|
||||
},
|
||||
resource_comment_title: "Un commentaire est associé à cet interlocuteur",
|
||||
addContact: {
|
||||
title: "Créer un contact pour {q}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<div class="confidential-content" :class="{ 'blur': isBlurred }">
|
||||
<div class="confidential">
|
||||
<div :class="{ 'blur': isBlurred }">
|
||||
<slot name="confidential-content"></slot>
|
||||
</div>
|
||||
<div>
|
||||
@@ -29,18 +29,5 @@ export default {
|
||||
<style scoped lang='scss'>
|
||||
.confidential{
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.toggle{
|
||||
margin-top: 28px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.blur {
|
||||
-webkit-filter: blur(5px);
|
||||
-moz-filter: blur(5px);
|
||||
filter: blur(5px);
|
||||
}
|
||||
</style>
|
||||
|
@@ -27,7 +27,6 @@
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</confidential>
|
||||
</div>
|
||||
|
||||
@@ -94,6 +93,7 @@ export default {
|
||||
return this.isMultiline === true ? "multiline" : "";
|
||||
},
|
||||
isConfidential() {
|
||||
console.log(this.address.confidential)
|
||||
return this.address.confidential;
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
<div>
|
||||
<div class="item-row col">
|
||||
<h2>Workflow</h2>
|
||||
<h2>{{ w.title }}</h2>
|
||||
<div class="flex-grow-1 ms-3 h3">
|
||||
<div class="visually-hidden">
|
||||
{{ w.relatedEntityClass }}
|
||||
|
@@ -12,6 +12,8 @@
|
||||
:relatedEntityClass="this.relatedEntityClass"
|
||||
:relatedEntityId="this.relatedEntityId"
|
||||
:workflowsAvailables="workflowsAvailables"
|
||||
:preventDefaultMoveToGenerate="this.$props.preventDefaultMoveToGenerate"
|
||||
:goToGenerateWorkflowPayload="this.goToGenerateWorkflowPayload"
|
||||
@go-to-generate-workflow="goToGenerateWorkflow"
|
||||
></pick-workflow>
|
||||
|
||||
@@ -36,6 +38,7 @@
|
||||
:relatedEntityId="this.relatedEntityId"
|
||||
:workflowsAvailables="workflowsAvailables"
|
||||
:preventDefaultMoveToGenerate="this.$props.preventDefaultMoveToGenerate"
|
||||
:goToGenerateWorkflowPayload="this.goToGenerateWorkflowPayload"
|
||||
@go-to-generate-workflow="this.goToGenerateWorkflow"
|
||||
></pick-workflow>
|
||||
</template>
|
||||
@@ -83,6 +86,10 @@ export default {
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
goToGenerateWorkflowPayload: {
|
||||
required: false,
|
||||
default: {}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -105,7 +112,7 @@ export default {
|
||||
this.modal.showModal = true;
|
||||
},
|
||||
goToGenerateWorkflow(data) {
|
||||
console.log('go to generate workflow intercepted');
|
||||
console.log('go to generate workflow intercepted', data);
|
||||
this.$emit('goToGenerateWorkflow', data);
|
||||
}
|
||||
},
|
||||
|
@@ -37,6 +37,10 @@ export default {
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
goToGenerateWorkflowPayload: {
|
||||
required: false,
|
||||
default: {}
|
||||
},
|
||||
},
|
||||
emits: ['goToGenerateWorkflow'],
|
||||
methods: {
|
||||
@@ -51,7 +55,7 @@ export default {
|
||||
window.location.assign(this.makeLink(workflowName));
|
||||
}
|
||||
|
||||
this.$emit('goToGenerateWorkflow', {event, workflowName, link: this.makeLink(workflowName)});
|
||||
this.$emit('goToGenerateWorkflow', {event, workflowName, link: this.makeLink(workflowName), payload: this.goToGenerateWorkflowPayload});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -46,7 +46,8 @@ const messages = {
|
||||
person: "Quitter la page et ouvrir la fiche de l'usager",
|
||||
thirdparty: "Quitter la page et voir le tiers",
|
||||
},
|
||||
refresh: 'Rafraîchir'
|
||||
refresh: 'Rafraîchir',
|
||||
addContact: 'Ajouter un contact'
|
||||
},
|
||||
nav: {
|
||||
next: "Suivant",
|
||||
|
@@ -3,6 +3,8 @@
|
||||
{% if transition_form is not null %}
|
||||
{{ form_start(transition_form) }}
|
||||
|
||||
{{ form_errors(transition_form) }}
|
||||
|
||||
{% set step = entity_workflow.currentStepChained %}
|
||||
{% set labels = workflow_metadata(entity_workflow, 'label', step.currentStep) %}
|
||||
{% set label = labels is null ? step.currentStep : labels|localize_translatable_string %}
|
||||
@@ -60,8 +62,10 @@
|
||||
{{ form_row(transition_form.freezeAfter) }}
|
||||
{% endif %}
|
||||
|
||||
<div id="futureDestUsers">
|
||||
<div id="futureDests">
|
||||
{{ form_row(transition_form.future_dest_users) }}
|
||||
|
||||
{{ form_row(transition_form.future_dest_emails) }}
|
||||
</div>
|
||||
|
||||
<p>{{ form_label(transition_form.comment) }}</p>
|
||||
@@ -82,13 +86,23 @@
|
||||
<p>{{ 'workflow.This workflow is finalized'|trans }}</p>
|
||||
{% else %}
|
||||
<p>{{ 'workflow.You are not allowed to apply a transition on this workflow'|trans }}</p>
|
||||
<p>{{ 'workflow.Only those users are allowed'|trans }}:</p>
|
||||
{% if entity_workflow.currentStep.destUser|length > 0 %}
|
||||
<p><b>{{ 'workflow.Only those users are allowed'|trans }} :</b></p>
|
||||
<ul>
|
||||
{% for u in entity_workflow.currentStep.destUser -%}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
<ul>
|
||||
{% for u in entity_workflow.currentStep.destUser -%}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}
|
||||
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
||||
<ul>
|
||||
{% for u in entity_workflow.currentStep.destUserByAccessKey %}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<div data-list-workflows="1"
|
||||
data-workflows="{{ entity_workflows_json|json_encode|e('html_attr') }}"
|
||||
data-allow-create="{{ acl }}"
|
||||
data-related-entity-class="{{ blank_workflow.relatedEntityClass }}"
|
||||
data-related-entity-id="{{ blank_workflow.relatedEntityId }}"
|
||||
data-workflows-availables="{{ workflows_availables|json_encode()|e('html_attr') }}"
|
||||
data-related-entity-class="{{ relatedEntityClass }}"
|
||||
data-related-entity-id="{{ relatedEntityId }}"
|
||||
data-workflows-availables="{{ workflows_available|json_encode()|e('html_attr') }}"
|
||||
></div>
|
||||
|
@@ -69,15 +69,26 @@
|
||||
</blockquote>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if loop.last and step.destUser|length > 0 %}
|
||||
{% if loop.last and step.allDestUser|length > 0 %}
|
||||
<div class="item-row separator">
|
||||
<div>
|
||||
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }} : </b></p>
|
||||
<ul>
|
||||
{% for u in step.destUser %}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if step.destUser|length > 0 %}
|
||||
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }} : </b></p>
|
||||
<ul>
|
||||
{% for u in step.destUser %}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if step.destUserByAccessKey|length > 0 %}
|
||||
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
||||
<ul>
|
||||
{% for u in step.destUserByAccessKey %}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
{% extends '@ChillMain/layout.html.twig' %}
|
||||
|
||||
{% block title 'workflow.Delete workflow ?'|trans %}
|
||||
|
||||
{% block display_content %}
|
||||
<h2>
|
||||
{{ handler.entityTitle(entityWorkflow) }}
|
||||
</h2>
|
||||
|
||||
{% include handler.template(entityWorkflow) with handler.templateData(entityWorkflow)|merge({
|
||||
'display_action': false
|
||||
}) %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container chill-md-10">
|
||||
{{ include('@ChillMain/Util/confirmation_template.html.twig',
|
||||
{
|
||||
'title' : 'workflow.Delete workflow ?'|trans,
|
||||
'confirm_question' : 'workflow.Are you sure you want to delete this workflow ?'|trans,
|
||||
'display_content' : block('display_content'),
|
||||
'cancel_route' : 'chill_main_workflow_show',
|
||||
'cancel_parameters' : {'id' : entityWorkflow.id},
|
||||
'form' : delete_form
|
||||
} ) }}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@@ -21,10 +21,12 @@
|
||||
{{ encore_entry_link_tags('mod_wopi_link') }}
|
||||
{% endblock %}
|
||||
|
||||
{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as macro %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-10 workflow">
|
||||
<h1 class="mb-5">{{ block('title') }}</h1>
|
||||
|
||||
|
||||
{# handler_template:
|
||||
- src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation.html.twig
|
||||
- src/Bundle/ChillPersonBundle/Resources/views/Workflow/_accompanying_period_work.html.twig
|
||||
@@ -32,11 +34,26 @@
|
||||
#}
|
||||
<section class="step my-4">
|
||||
<div class="mb-5">
|
||||
{% include handler_template_title with handler_template_data|merge({'breadcrumb': true }) %}
|
||||
<h2>{{ handler.entityTitle(entity_workflow) }}</h2>
|
||||
|
||||
{{ macro.breadcrumb({'entity_workflow': entity_workflow}) }}
|
||||
</div>
|
||||
|
||||
{% include handler_template with handler_template_data|merge({'display_action': true }) %}
|
||||
|
||||
{% if is_granted('CHILL_MAIN_WORKFLOW_DELETE', entity_workflow) %}
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-delete"
|
||||
href="{{ chill_path_add_return_path('chill_main_workflow_delete', {'id': entity_workflow.id}) }}"
|
||||
>
|
||||
{{ 'workflow.Delete workflow'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
|
||||
<section class="step my-4">{% include '@ChillMain/Workflow/_follow.html.twig' %}</section>
|
||||
<section class="step my-4">{% include '@ChillMain/Workflow/_decision.html.twig' %}</section>{#
|
||||
<section class="step my-4">{% include '@ChillMain/Workflow/_comment.html.twig' %}</section> #}
|
||||
|
@@ -8,9 +8,10 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-10 workflow">
|
||||
|
||||
|
||||
<h1 class="mb-5">{{ block('title') }}</h1>
|
||||
|
||||
|
||||
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li class="nav-item">
|
||||
<a href="{{ path('chill_main_workflow_list_subscribed') }}"
|
||||
@@ -24,8 +25,24 @@
|
||||
{{ 'workflow.dest'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ path('chill_main_workflow_list_previous_without_reaction') }}"
|
||||
class="nav-link {% if step == 'previous_without_reaction' %}active{% endif %}">
|
||||
{{ 'workflow.Previous dest without reaction'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ path('chill_main_workflow_list_previous_transitionned') }}"
|
||||
class="nav-link {% if step == 'previous_transitionned' %}active{% endif %}">
|
||||
{{ 'workflow.Previous transitionned'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% if help is defined %}
|
||||
<p style="margin-top: 2rem;">{{ help|trans }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if workflows|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'workflow.No workflow'|trans }}</p>
|
||||
{% else %}
|
||||
@@ -36,26 +53,24 @@
|
||||
<button type="button" class="accordion-button collapsed"
|
||||
data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ l.entity_workflow.id }}"
|
||||
aria-expanded="false" aria-controls="flush-collapse-{{ l.entity_workflow.id }}">
|
||||
|
||||
|
||||
<div class="item-row col">
|
||||
<h2>
|
||||
{{ 'workflow_'|trans }}
|
||||
{{ l.handler.entityTitle(l.entity_workflow) }}
|
||||
</h2>
|
||||
{% include l.handler.templateTitle(l.entity_workflow) with l.handler.templateTitleData(l.entity_workflow)|merge({
|
||||
'description': true,
|
||||
'add_classes': 'ms-3 h3'
|
||||
}) %}
|
||||
</div>
|
||||
|
||||
|
||||
</button>
|
||||
{{ macro.breadcrumb(l) }}
|
||||
|
||||
<div>
|
||||
{{ macro.breadcrumb(l) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="flush-collapse-{{ l.entity_workflow.id }}"
|
||||
class="accordion-collapse collapse"
|
||||
aria-labelledby="flush-heading-{{ l.entity_workflow.id }}"
|
||||
data-bs-parent="#workflow-fold">
|
||||
|
||||
|
||||
<div class="item-row flex-column">
|
||||
{% include l.handler.template(l.entity_workflow) with l.handler.templateData(l.entity_workflow)|merge({
|
||||
'display_action': false
|
||||
@@ -78,6 +93,13 @@
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_MAIN_WORKFLOW_DELETE', l.entity_workflow) %}
|
||||
<li>
|
||||
<a class="btn btn-delete"
|
||||
href="{{ chill_path_add_return_path('chill_main_workflow_delete', {'id': l.entity_workflow.id}) }}"
|
||||
></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{{ path('chill_main_workflow_show', {'id': l.entity_workflow.id}) }}"
|
||||
class="btn btn-show">
|
||||
@@ -87,7 +109,7 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@@ -0,0 +1,15 @@
|
||||
Madame, Monsieur,
|
||||
|
||||
Un suivi "{{ workflow.text }}" a atteint une nouvelle étape: {{ workflow.text }}.
|
||||
|
||||
Titre du workflow: "{{ entityTitle }}".
|
||||
|
||||
Vous êtes invité·e à valider cette étape. Pour obtenir un accès, vous pouvez cliquer sur le lien suivant:
|
||||
|
||||
{{ absolute_url(path('chill_main_workflow_grant_access_by_key', {'id': entity_workflow.currentStep.id, 'accessKey': entity_workflow.currentStep.accessKey})) }}
|
||||
|
||||
Dès que vous aurez cliqué une fois sur le lien, vous serez autorisé à valider cette étape.
|
||||
|
||||
Notez que vous devez disposer d'un compte utilisateur valide dans Chill.
|
||||
|
||||
Cordialement,
|
@@ -0,0 +1 @@
|
||||
Un suivi {{ workflow.text }} demande votre attention: {{ entityTitle }}
|
@@ -23,6 +23,8 @@ class EntityWorkflowVoter extends Voter
|
||||
{
|
||||
public const CREATE = 'CHILL_MAIN_WORKFLOW_CREATE';
|
||||
|
||||
public const DELETE = 'CHILL_MAIN_WORKFLOW_DELETE';
|
||||
|
||||
public const SEE = 'CHILL_MAIN_WORKFLOW_SEE';
|
||||
|
||||
private EntityWorkflowManager $manager;
|
||||
@@ -40,7 +42,11 @@ class EntityWorkflowVoter extends Voter
|
||||
return $subject instanceof EntityWorkflow && in_array($attribute, self::getRoles(), true);
|
||||
}
|
||||
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
/**
|
||||
* @param EntityWorkflow $subject
|
||||
* @param mixed $attribute
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
|
||||
{
|
||||
switch ($attribute) {
|
||||
case self::CREATE:
|
||||
@@ -55,6 +61,9 @@ class EntityWorkflowVoter extends Voter
|
||||
|
||||
return $this->security->isGranted($entityAttribute, $handler->getRelatedEntity($subject));
|
||||
|
||||
case self::DELETE:
|
||||
return $subject->getStep() === 'initial';
|
||||
|
||||
default:
|
||||
throw new UnexpectedValueException("attribute {$attribute} not supported");
|
||||
}
|
||||
@@ -65,6 +74,7 @@ class EntityWorkflowVoter extends Voter
|
||||
return [
|
||||
self::SEE,
|
||||
self::CREATE,
|
||||
self::DELETE,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -25,21 +25,17 @@ class ChillEntityRenderExtension extends AbstractExtension
|
||||
protected $defaultRender;
|
||||
|
||||
/**
|
||||
* @var ChillEntityRenderInterface
|
||||
* @var iterable|ChillEntityRenderInterface[]
|
||||
*/
|
||||
protected $renders = [];
|
||||
|
||||
/**
|
||||
* ChillEntityRenderExtension constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct(iterable $renders)
|
||||
{
|
||||
$this->defaultRender = new ChillEntityRender();
|
||||
}
|
||||
|
||||
public function addRender(ChillEntityRenderInterface $render)
|
||||
{
|
||||
$this->renders[] = $render;
|
||||
$this->renders = $renders;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,31 +0,0 @@
|
||||
<?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\Templating\Entity;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Add service tagged with `chill.render_entity` to appropriate service.
|
||||
*/
|
||||
class CompilerPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
$extension = $container->getDefinition(ChillEntityRenderExtension::class);
|
||||
|
||||
foreach ($container->findTaggedServiceIds('chill.render_entity') as $id => $tags) {
|
||||
$extension->addMethodCall('addRender', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
@@ -33,10 +33,6 @@ interface EntityWorkflowHandlerInterface
|
||||
|
||||
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array;
|
||||
|
||||
public function getTemplateTitle(EntityWorkflow $entityWorkflow, array $options = []): string;
|
||||
|
||||
public function getTemplateTitleData(EntityWorkflow $entityWorkflow, array $options = []): array;
|
||||
|
||||
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool;
|
||||
|
||||
public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool;
|
||||
|
@@ -72,7 +72,7 @@ class EntityWorkflowTransitionEventSubscriber implements EventSubscriberInterfac
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entityWorkflow->getCurrentStep()->getDestUser()->contains($this->security->getUser())) {
|
||||
if (!$entityWorkflow->getCurrentStep()->getAllDestUser()->contains($this->security->getUser())) {
|
||||
if (!$event->getMarking()->has('initial')) {
|
||||
$event->addTransitionBlocker(new TransitionBlocker(
|
||||
'workflow.You are not allowed to apply a transition on this workflow. Only those users are allowed: %users%',
|
||||
@@ -80,7 +80,7 @@ class EntityWorkflowTransitionEventSubscriber implements EventSubscriberInterfac
|
||||
[
|
||||
'%users%' => implode(
|
||||
', ',
|
||||
$entityWorkflow->getCurrentStep()->getDestUser()->map(function (User $u) {
|
||||
$entityWorkflow->getCurrentStep()->getAllDestUser()->map(function (User $u) {
|
||||
return $this->userRender->renderString($u, []);
|
||||
})->toArray()
|
||||
),
|
||||
|
@@ -52,11 +52,11 @@ class NotificationOnTransition implements EventSubscriberInterface
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'workflow.completed' => 'onCompleted',
|
||||
'workflow.completed' => 'onCompletedSendNotification',
|
||||
];
|
||||
}
|
||||
|
||||
public function onCompleted(Event $event): void
|
||||
public function onCompletedSendNotification(Event $event): void
|
||||
{
|
||||
if (!$event->getSubject() instanceof EntityWorkflow) {
|
||||
return;
|
||||
|
@@ -0,0 +1,71 @@
|
||||
<?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\Workflow\EventSubscriber;
|
||||
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
|
||||
class SendAccessKeyEventSubscriber
|
||||
{
|
||||
private EngineInterface $engine;
|
||||
|
||||
private EntityWorkflowManager $entityWorkflowManager;
|
||||
|
||||
private MailerInterface $mailer;
|
||||
|
||||
private MetadataExtractor $metadataExtractor;
|
||||
|
||||
private Registry $registry;
|
||||
|
||||
public function __construct(EngineInterface $engine, MetadataExtractor $metadataExtractor, Registry $registry, EntityWorkflowManager $entityWorkflowManager, MailerInterface $mailer)
|
||||
{
|
||||
$this->engine = $engine;
|
||||
$this->metadataExtractor = $metadataExtractor;
|
||||
$this->registry = $registry;
|
||||
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
public function postPersist(EntityWorkflowStep $step): void
|
||||
{
|
||||
$entityWorkflow = $step->getEntityWorkflow();
|
||||
|
||||
$place = $this->metadataExtractor->buildArrayPresentationForPlace($entityWorkflow);
|
||||
$workflow = $this->metadataExtractor->buildArrayPresentationForWorkflow(
|
||||
$this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName())
|
||||
);
|
||||
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
|
||||
|
||||
foreach ($entityWorkflow->futureDestEmails as $emailAddress) {
|
||||
$context = [
|
||||
'entity_workflow' => $entityWorkflow,
|
||||
'dest' => $emailAddress,
|
||||
'place' => $place,
|
||||
'workflow' => $workflow,
|
||||
'entityTitle' => $handler->getEntityTitle($entityWorkflow),
|
||||
];
|
||||
|
||||
$email = new Email();
|
||||
$email
|
||||
->addTo($emailAddress)
|
||||
->subject($this->engine->render('@ChillMain/Workflow/workflow_send_access_key_title.fr.txt.twig', $context))
|
||||
->text($this->engine->render('@ChillMain/Workflow/workflow_send_access_key.fr.txt.twig', $context));
|
||||
|
||||
$this->mailer->send($email);
|
||||
}
|
||||
}
|
||||
}
|
@@ -61,7 +61,38 @@ class WorkflowTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
public function listWorkflows(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
|
||||
/**
|
||||
* @param array{relatedEntityClass: string, relatedEntityId: int} $supplementaryRelated
|
||||
*
|
||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
*/
|
||||
public function listWorkflows(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = [], array $supplementaryRelated = []): string
|
||||
{
|
||||
[$blankEntityWorkflow, $workflowsAvailable, $entityWorkflows] = $this->getWorkflowsForRelated($relatedEntityClass, $relatedEntityId);
|
||||
|
||||
foreach ($supplementaryRelated as $supplementary) {
|
||||
[$supplementaryBlankEntityWorkflow, $supplementaryWorkflowsAvailable, $supplementaryEntityWorkflows]
|
||||
= $this->getWorkflowsForRelated($supplementary['relatedEntityClass'], $supplementary['relatedEntityId']);
|
||||
|
||||
$entityWorkflows = array_merge($entityWorkflows, $supplementaryEntityWorkflows);
|
||||
}
|
||||
|
||||
return $environment->render('@ChillMain/Workflow/_extension_list_workflow_for.html.twig', [
|
||||
'entity_workflows_json' => $this->normalizer->normalize($entityWorkflows, 'json', ['groups' => 'read']),
|
||||
'blank_workflow' => $blankEntityWorkflow,
|
||||
'workflows_available' => $workflowsAvailable,
|
||||
'relatedEntityClass' => $relatedEntityClass,
|
||||
'relatedEntityId' => $relatedEntityId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array where keys are: {0: blankWorkflow, 1: entityWorkflows, 2: workflowList}
|
||||
*/
|
||||
private function getWorkflowsForRelated(string $relatedEntityClass, int $relatedEntityId): array
|
||||
{
|
||||
$blankEntityWorkflow = new EntityWorkflow();
|
||||
$blankEntityWorkflow
|
||||
@@ -70,25 +101,13 @@ class WorkflowTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||
|
||||
$workflowsList = $this->metadataExtractor->availableWorkflowFor($relatedEntityClass, $relatedEntityId);
|
||||
|
||||
// get the related entity already created
|
||||
$entityWorkflows = [];
|
||||
|
||||
foreach ($entityWorkflowsNaked = $this->repository->findBy(
|
||||
['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId]
|
||||
) as $entityWorkflow) {
|
||||
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
||||
$entityWorkflows[] = [
|
||||
'entity_workflow' => $entityWorkflow,
|
||||
'workflow' => $this->metadataExtractor->buildArrayPresentationForWorkflow($workflow),
|
||||
'handler' => $this->entityWorkflowManager->getHandler($entityWorkflow),
|
||||
return
|
||||
[
|
||||
$blankEntityWorkflow,
|
||||
$workflowsList,
|
||||
$this->repository->findBy(
|
||||
['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId]
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return $environment->render('@ChillMain/Workflow/_extension_list_workflow_for.html.twig', [
|
||||
'entity_workflows_json' => $this->normalizer->normalize($entityWorkflowsNaked, 'json', ['groups' => 'read']),
|
||||
'entity_workflows' => $entityWorkflows,
|
||||
'blank_workflow' => $blankEntityWorkflow,
|
||||
'workflows_availables' => $workflowsList,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -788,6 +788,16 @@ paths:
|
||||
description: "accepted"
|
||||
403:
|
||||
description: "unauthorized"
|
||||
/1.0/main/civility.json:
|
||||
get:
|
||||
tags:
|
||||
- civility
|
||||
summary: Return all civility types
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
/1.0/main/user-job.json:
|
||||
get:
|
||||
tags:
|
||||
|
@@ -38,6 +38,17 @@ services:
|
||||
arguments:
|
||||
$handlers: !tagged_iterator chill_main.workflow_handler
|
||||
|
||||
Chill\MainBundle\Workflow\EventSubscriber\SendAccessKeyEventSubscriber:
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
tags:
|
||||
-
|
||||
name: 'doctrine.orm.entity_listener'
|
||||
event: 'postPersist'
|
||||
entity: 'Chill\MainBundle\Entity\Workflow\EntityWorkflowStep'
|
||||
# set the 'lazy' option to TRUE to only instantiate listeners when they are used
|
||||
lazy: true
|
||||
|
||||
# other stuffes
|
||||
|
||||
chill.main.helper.translatable_string:
|
||||
|
@@ -32,6 +32,8 @@ services:
|
||||
- { name: twig.extension }
|
||||
|
||||
Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension:
|
||||
arguments:
|
||||
$renders: !tagged_iterator chill.render_entity
|
||||
tags:
|
||||
- { name: twig.extension }
|
||||
|
||||
|
@@ -0,0 +1,51 @@
|
||||
<?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\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20220223171457 extends AbstractMigration
|
||||
{
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step DROP accessKey');
|
||||
$this->addSql('DROP TABLE chill_main_workflow_entity_step_user_by_accesskey');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add access key to EntityWorkflowStep';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ADD accessKey TEXT DEFAULT NULL');
|
||||
$this->addSql('WITH randoms AS (select
|
||||
cmwes.id,
|
||||
string_agg(substr(characters, (random() * length(characters) + 0.5)::integer, 1), \'\') as random_word
|
||||
from (values(\'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\')) as symbols(characters)
|
||||
-- length of word
|
||||
join generate_series(1, 32) on 1 = 1
|
||||
JOIN chill_main_workflow_entity_step cmwes ON true
|
||||
GROUP BY cmwes.id)
|
||||
UPDATE chill_main_workflow_entity_step SET accessKey = randoms.random_word FROM randoms WHERE chill_main_workflow_entity_step.id = randoms.id');
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER accessKey DROP DEFAULT ');
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER accessKey SET NOT NULL');
|
||||
|
||||
$this->addSql('CREATE TABLE chill_main_workflow_entity_step_user_by_accesskey (entityworkflowstep_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflowstep_id, user_id))');
|
||||
$this->addSql('CREATE INDEX IDX_8296D8397E6AF9D4 ON chill_main_workflow_entity_step_user_by_accesskey (entityworkflowstep_id)');
|
||||
$this->addSql('CREATE INDEX IDX_8296D839A76ED395 ON chill_main_workflow_entity_step_user_by_accesskey (user_id)');
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_user_by_accesskey ADD CONSTRAINT FK_8296D8397E6AF9D4 FOREIGN KEY (entityworkflowstep_id) REFERENCES chill_main_workflow_entity_step (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_user_by_accesskey ADD CONSTRAINT FK_8296D839A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
}
|
@@ -356,6 +356,7 @@ By: Par
|
||||
For: Pour
|
||||
Created for: Créé pour
|
||||
Created by: Créé par
|
||||
Created on: Créé le
|
||||
|
||||
|
||||
# Workflows 💊
|
||||
@@ -396,6 +397,23 @@ workflow:
|
||||
Current step: Étape actuelle
|
||||
Comment on last change: Commentaire à la transition précédente
|
||||
Users allowed to apply transition: Utilisateurs pouvant valider cette étape
|
||||
Workflow deleted with success: Le workflow a été supprimé
|
||||
Delete workflow ?: Supprimer le workflow ?
|
||||
Are you sure you want to delete this workflow ?: Êtes-vous sûr·e de vouloir supprimer ce workflow ?
|
||||
Delete workflow: Supprimer le workflow
|
||||
Steps is not waiting for transition. Maybe someone apply the transition before you ?: L'étape que vous cherchez a déjà été modifiée par un autre utilisateur. Peut-être quelqu'un a-t-il modifié cette étape avant vous ?
|
||||
You get access to this step: Vous avez acquis les droits pour appliquer une transition sur ce workflow.
|
||||
Those users are also granted to apply a transition by using an access key: Ces utilisateurs peuvent également valider cette étape, grâce à un lien d'accès
|
||||
dest by email: Liens d'autorisation par email
|
||||
dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Ce lien d'accès permettra à l'utilisateur de valider cette étape.
|
||||
Add an email: Ajouter une adresse email
|
||||
Remove an email: Enlever cette adresse email
|
||||
Any email: Aucune adresse email
|
||||
Previous dest without reaction: Workflows clotûrés après action d'un autre utilisateur
|
||||
Previous workflow without reaction help: Liste des workflows où vous avez été cité comme pouvant réagir à une étape, mais où un autre utilisateur a exécuté une action avant vous.
|
||||
Previous transitionned: Anciens workflows
|
||||
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
|
||||
|
||||
|
||||
Subscribe final: Recevoir une notification à l'étape finale
|
||||
Subscribe all steps: Recevoir une notification à chaque étape
|
||||
|
@@ -28,3 +28,6 @@ notification:
|
||||
At least one addressee: Indiquez au moins un destinataire
|
||||
Title must be defined: Un titre doit être indiqué
|
||||
Comment content might not be blank: Le commentaire ne peut pas être vide
|
||||
|
||||
workflow:
|
||||
You must add at least one dest user or email: Indiquez au moins un destinataire ou une adresse email
|
||||
|
Reference in New Issue
Block a user