Merge branch 'issue706_cc_in_workflow-2' into 'master'

Feature: [workflow] add users in cc

Closes champs-libres/departement-de-la-vendee/chill#706

See merge request Chill-Projet/chill-bundles!517
This commit is contained in:
Julien Fastré 2023-04-13 14:51:32 +00:00
commit 48f5e7d21f
27 changed files with 438 additions and 100 deletions

View File

@ -93,6 +93,45 @@ class WorkflowApiController
);
}
/**
* Return a list of workflow which are waiting an action for the user.
*
* @Route("/api/1.0/main/workflow/my-cc", methods={"GET"})
*/
public function myWorkflowCc(Request $request): JsonResponse
{
if (!$this->security->isGranted('ROLE_USER') || !$this->security->getUser() instanceof User) {
throw new AccessDeniedException();
}
$total = $this->entityWorkflowRepository->countByCc($this->security->getUser());
if ($request->query->getBoolean('countOnly', false)) {
return new JsonResponse(
$this->serializer->serialize(new Counter($total), 'json'),
JsonResponse::HTTP_OK,
[],
true
);
}
$paginator = $this->paginatorFactory->create($total);
$workflows = $this->entityWorkflowRepository->findByCc(
$this->security->getUser(),
['id' => 'DESC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);
return new JsonResponse(
$this->serializer->serialize(new Collection($workflows, $paginator), 'json', ['groups' => ['read']]),
JsonResponse::HTTP_OK,
[],
true
);
}
/**
* @Route("/api/1.0/main/workflow/{id}/subscribe", methods={"POST"})
*/

View File

@ -228,6 +228,33 @@ class WorkflowController extends AbstractController
);
}
/**
* @Route("/{_locale}/main/workflow/list/cc", name="chill_main_workflow_list_cc")
*/
public function myWorkflowsCc(Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
$total = $this->entityWorkflowRepository->countByDest($this->getUser());
$paginator = $this->paginatorFactory->create($total);
$workflows = $this->entityWorkflowRepository->findByCc(
$this->getUser(),
['createdAt' => 'DESC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);
return $this->render(
'@ChillMain/Workflow/list.html.twig',
[
'workflows' => $this->buildHandler($workflows),
'paginator' => $paginator,
'step' => 'cc',
]
);
}
/**
* @Route("/{_locale}/main/workflow/list/dest", name="chill_main_workflow_list_dest")
*/
@ -306,7 +333,11 @@ class WorkflowController extends AbstractController
$transitionForm = $this->createForm(
WorkflowStepType::class,
$entityWorkflow->getCurrentStep(),
['transition' => true, 'entity_workflow' => $entityWorkflow, 'suggested_users' => $usersInvolved]
[
'transition' => true,
'entity_workflow' => $entityWorkflow,
'suggested_users' => $usersInvolved
]
);
$transitionForm->handleRequest($request);
@ -330,6 +361,7 @@ class WorkflowController extends AbstractController
}
// TODO symfony 5: add those "future" on context ($workflow->apply($entityWorkflow, $transition, $context)
$entityWorkflow->futureCcUsers = $transitionForm['future_cc_users']->getData();
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData();
$entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData();

View File

@ -16,6 +16,7 @@ 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\Validator\Constraints\Entity\WorkflowStepUsersOnTransition;
use Chill\MainBundle\Workflow\Validator\EntityWorkflowCreation;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
@ -24,6 +25,7 @@ use Doctrine\ORM\Mapping as ORM;
use Iterator;
use RuntimeException;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
use function count;
use function is_array;
@ -41,6 +43,13 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
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.
*
@ -90,7 +99,7 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
/**
* @ORM\OneToMany(targetEntity=EntityWorkflowStep::class, mappedBy="entityWorkflow", orphanRemoval=true, cascade={"persist"})
* @ORM\OrderBy({"transitionAt": "ASC", "id": "ASC"})
*
* @Assert\Valid(traverse=true)
* @var Collection|EntityWorkflowStep[]
*/
private Collection $steps;

View File

@ -32,6 +32,12 @@ class EntityWorkflowStep
*/
private string $accessKey;
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_main_workflow_entity_step_cc_user")
*/
private Collection $ccUser;
/**
* @ORM\Column(type="text", options={"default": ""})
*/
@ -114,11 +120,21 @@ class EntityWorkflowStep
public function __construct()
{
$this->ccUser = new ArrayCollection();
$this->destUser = new ArrayCollection();
$this->destUserByAccessKey = new ArrayCollection();
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(32));
}
public function addCcUser(User $user): self
{
if (!$this->ccUser->contains($user)) {
$this->ccUser[] = $user;
}
return $this;
}
public function addDestEmail(string $email): self
{
if (!in_array($email, $this->destEmail, true)) {
@ -167,6 +183,11 @@ class EntityWorkflowStep
);
}
public function getCcUser(): Collection
{
return $this->ccUser;
}
public function getComment(): string
{
return $this->comment;
@ -261,6 +282,13 @@ class EntityWorkflowStep
return true;
}
public function removeCcUser(User $user): self
{
$this->ccUser->removeElement($user);
return $this;
}
public function removeDestEmail(string $email): self
{
$this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) {

View File

@ -156,6 +156,13 @@ class WorkflowStepType extends AbstractType
'mapped' => false,
'suggested' => $options['suggested_users'],
])
->add('future_cc_users', PickUserDynamicType::class, [
'label' => 'workflow.cc for next steps',
'multiple' => true,
'mapped' => false,
'required' => false,
'suggested' => $options['suggested_users'],
])
->add('future_dest_emails', ChillCollectionType::class, [
'label' => 'workflow.dest by email',
'help' => 'workflow.dest by email help',
@ -236,6 +243,20 @@ class WorkflowStepType extends AbstractType
}
}
),
new Callback(
function ($step, ExecutionContextInterface $context, $payload) {
$form = $context->getObject();
foreach($form->get('future_dest_users')->getData() as $u) {
if (in_array($u, $form->get('future_cc_users')->getData(), true)) {
$context
->buildViolation('workflow.The user in cc cannot be a dest user in the same workflow step')
->atPath('ccUsers')
->addViolation();
}
}
}
)
]);
}
}

View File

@ -74,7 +74,8 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
}
return $environment->render('@ChillMain/Notification/extension_list_notifications_for.html.twig', [
'notifications' => $notifications, 'appendCommentForms' => $appendCommentForms,
'notifications' => $notifications,
'appendCommentForms' => $appendCommentForms,
]);
}
}

View File

@ -27,6 +27,13 @@ class EntityWorkflowRepository implements ObjectRepository
$this->repository = $entityManager->getRepository(EntityWorkflow::class);
}
public function countByCc(User $user): int
{
$qb = $this->buildQueryByCc($user)->select('count(ew)');
return (int) $qb->getQuery()->getSingleScalarResult();
}
public function countByDest(User $user): int
{
$qb = $this->buildQueryByDest($user)->select('count(ew)');
@ -103,6 +110,19 @@ class EntityWorkflowRepository implements ObjectRepository
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findByCc(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
{
$qb = $this->buildQueryByCc($user)->select('ew');
foreach ($orderBy as $key => $sort) {
$qb->addOrderBy('ew.' . $key, $sort);
}
$qb->setMaxResults($limit)->setFirstResult($offset);
return $qb->getQuery()->getResult();
}
public function findByDest(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
{
$qb = $this->buildQueryByDest($user)->select('ew');
@ -165,6 +185,25 @@ class EntityWorkflowRepository implements ObjectRepository
return EntityWorkflow::class;
}
private function buildQueryByCc(User $user): QueryBuilder
{
$qb = $this->repository->createQueryBuilder('ew');
$qb->join('ew.steps', 'step');
$qb->where(
$qb->expr()->andX(
$qb->expr()->isMemberOf(':user', 'step.ccUser'),
$qb->expr()->isNull('step.transitionAfter'),
$qb->expr()->eq('step.isFinal', "'FALSE'")
)
);
$qb->setParameter('user', $user);
return $qb;
}
private function buildQueryByDest(User $user): QueryBuilder
{
$qb = $this->repository->createQueryBuilder('ew');

View File

@ -46,8 +46,7 @@
:class="{'active': activeTab === 'MyTasks'}"
@click="selectTab('MyTasks')">
{{ $t('my_tasks.tab') }}
<tab-counter :count="state.tasks.warning.count"></tab-counter>
<tab-counter :count="state.tasks.alert.count"></tab-counter>
<tab-counter :count="state.tasks.warning.count + state.tasks.alert.count"></tab-counter>
</a>
</li>
<li class="nav-item">
@ -55,7 +54,7 @@
:class="{'active': activeTab === 'MyWorkflows'}"
@click="selectTab('MyWorkflows')">
{{ $t('my_workflows.tab') }}
<tab-counter :count="state.workflows.count"></tab-counter>
<tab-counter :count="state.workflows.count + state.workflowsCc.count"></tab-counter>
</a>
</li>
<li class="nav-item loading ms-auto py-2" v-if="loading">

View File

@ -1,88 +1,25 @@
<template>
<div class="alert alert-light">{{ $t('my_workflows.description') }}</div>
<my-workflows-table :workflows="workflows" />
<div class="alert alert-light">{{ $t('my_workflows.description') }}</div>
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
<tab-table v-else>
<template v-slot:thead>
<th scope="col">{{ $t('Object_workflow') }}</th>
<th scope="col">{{ $t('Step') }}</th>
<th scope="col">{{ $t('concerned_users') }}</th>
<th scope="col"></th>
</template>
<template v-slot:tbody>
<tr v-for="(w, i) in workflows.results" :key="`workflow-${i}`">
<td>{{ w.title }}</td>
<td>
<div class="workflow">
<div class="breadcrumb">
<i class="fa fa-circle me-1 text-chill-yellow mx-2"></i>
<span class="mx-2">{{ getStep(w) }}</span>
</div>
</div>
</td>
<td v-if="w.datas.persons !== null">
<span v-for="p in w.datas.persons" class="me-1" :key="p.id">
<on-the-fly
:type="p.type"
:id="p.id"
:buttonText="p.textAge"
:displayBadge="'true' === 'true'"
action="show">
</on-the-fly>
</span>
</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(w)">
{{ $t('show_entity', { entity: $t('the_workflow') }) }}
</a>
</td>
</tr>
</template>
</tab-table>
<div class="alert alert-light">{{ $t('my_workflows.description_cc') }}</div>
<my-workflows-table :workflows="workflowsCc" />
</template>
<script>
import { mapState, mapGetters } from "vuex";
import TabTable from "./TabTable";
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
import { mapState } from "vuex";
import MyWorkflowsTable from './MyWorkflowsTable.vue';
export default {
name: "MyWorkflows",
components: {
TabTable,
OnTheFly
MyWorkflowsTable
},
computed: {
...mapState([
'workflows',
'workflowsCc',
]),
...mapGetters([
'isWorkflowsLoaded',
]),
noResults() {
if (!this.isWorkflowsLoaded) {
return false;
} else {
return this.workflows.count === 0;
}
},
},
methods: {
getUrl(w) {
return `/fr/main/workflow/${w.id}/show`;
},
getStep(w) {
const lastStep = w.steps.length - 1
return w.steps[lastStep].currentStep.text;
}
},
}
</script>
<style scoped>
span.outdated {
font-weight: bold;
color: var(--bs-warning);
}
</style>

View File

@ -0,0 +1,83 @@
<template>
<span v-if="hasNoResults(workflows)" class="chill-no-data-statement">{{ $t('no_data') }}</span>
<tab-table v-else>
<template v-slot:thead>
<th scope="col">{{ $t('Object_workflow') }}</th>
<th scope="col">{{ $t('Step') }}</th>
<th scope="col">{{ $t('concerned_users') }}</th>
<th scope="col"></th>
</template>
<template v-slot:tbody>
<tr v-for="(w, i) in workflows.results" :key="`workflow-${i}`">
<td>{{ w.title }}</td>
<td>
<div class="workflow">
<div class="breadcrumb">
<i class="fa fa-circle me-1 text-chill-yellow mx-2"></i>
<span class="mx-2">{{ getStep(w) }}</span>
</div>
</div>
</td>
<td v-if="w.datas.persons !== null">
<span v-for="p in w.datas.persons" class="me-1" :key="p.id">
<on-the-fly
:type="p.type"
:id="p.id"
:buttonText="p.textAge"
:displayBadge="'true' === 'true'"
action="show">
</on-the-fly>
</span>
</td>
<td>
<a class="btn btn-sm btn-show" :href="getUrl(w)">
{{ $t('show_entity', { entity: $t('the_workflow') }) }}
</a>
</td>
</tr>
</template>
</tab-table>
</template>
<script>
import { mapGetters } from "vuex";
import TabTable from "./TabTable";
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
export default {
name: "MyWorkflows",
components: {
TabTable,
OnTheFly
},
props: ['workflows'],
computed: {
...mapGetters([
'isWorkflowsLoaded',
]),
},
methods: {
hasNoResults(workflows) {
if (!this.isWorkflowsLoaded) {
return false;
} else {
return workflows.count === 0;
}
},
getUrl(w) {
return `/fr/main/workflow/${w.id}/show`;
},
getStep(w) {
const lastStep = w.steps.length - 1
return w.steps[lastStep].currentStep.text;
}
},
}
</script>
<style scoped>
span.outdated {
font-weight: bold;
color: var(--bs-warning);
}
</style>

View File

@ -24,7 +24,8 @@ const appMessages = {
},
my_workflows: {
tab: "Mes workflows",
description: "Liste des workflows en attente d'une action."
description: "Liste des workflows en attente d'une action.",
description_cc: "Liste des workflows dont je suis en copie."
},
opening_date: "Date d'ouverture",
social_issues: "Problématiques sociales",

View File

@ -22,6 +22,7 @@ const store = createStore({
accompanyingCourses: {},
notifications: {},
workflows: {},
workflowsCc: {},
errorMsg: [],
loading: false
},
@ -87,6 +88,9 @@ const store = createStore({
addWorkflows(state, workflows) {
state.workflows = workflows;
},
addWorkflowsCc(state, workflows) {
state.workflowsCc = workflows;
},
setLoading(state, bool) {
state.loading = bool;
},
@ -195,17 +199,23 @@ const store = createStore({
case 'MyWorkflows':
if (!getters.isWorflowsLoaded) {
commit('setLoading', true);
const url = '/api/1.0/main/workflow/my';
makeFetch('GET', url)
.then((response) => {
console.log('workflows', response)
commit('addWorkflows', response);
commit('setLoading', false);
})
.catch((error) => {
commit('catchError', error);
throw error;
});
makeFetch('GET', '/api/1.0/main/workflow/my')
.then((response) => {
commit('addWorkflows', response);
makeFetch('GET', '/api/1.0/main/workflow/my-cc')
.then((response) => {
commit('addWorkflowsCc', response);
commit('setLoading', false);
})
.catch((error) => {
commit('catchError', error);
throw error;
});
})
.catch((error) => {
commit('catchError', error);
throw error;
});
}
break;
default:

View File

@ -30,11 +30,27 @@
{% endif %}
{% if c.notification.addressees|length > 0 %}
<li class="notification-to">
<span class="item-key">
{% if c.notification_cc is defined %}
{% if c.notification_cc %}
<span class="item-key">
<abbr title="{{ 'notification.sent_cc'|trans }}">
{{ 'notification.cc'|trans }} :
</abbr>
</span>
{% else %}
<span class="item-key">
<abbr title="{{ 'notification.sent_to'|trans }}">
{{ 'notification.to'|trans }} :
</abbr>
</span>
{% endif %}
{% else %}
<span class="item-key">
<abbr title="{{ 'notification.sent_to'|trans }}">
{{ 'notification.to'|trans }} :
</abbr>
</span>
{% endif %}
{% for a in c.notification.addressees %}
<span class="badge-user">
{{ a|chill_entity_render_string }}

View File

@ -50,7 +50,8 @@
{% for data in datas %}
{% set notification = data.notification %}
{% include 'ChillMainBundle:Notification:_list_item.html.twig' with {
'fold_item': true
'fold_item': true,
'notification_cc': data.template_data.notificationCc is defined ? data.template_data.notificationCc : false
} %}
{% endfor %}
</div>

View File

@ -27,7 +27,8 @@
},
'action_button': false,
'full_content': true,
'fold_item': false
'fold_item': false,
'notification_cc': handler.getTemplateData(notification).notificationCc is defined ? handler.getTemplateData(notification).notificationCc : false
} %}
</div>

View File

@ -65,6 +65,8 @@
<div id="futureDests">
{{ form_row(transition_form.future_dest_users) }}
{{ form_row(transition_form.future_cc_users) }}
{{ form_row(transition_form.future_dest_emails) }}
{{ form_errors(transition_form.future_dest_users) }}
</div>

View File

@ -81,6 +81,15 @@
</ul>
{% endif %}
{% if step.ccUser|length > 0 %}
<p><b>{{ 'workflow.Users put in Cc'|trans }}&nbsp;: </b></p>
<ul>
{% for u in step.ccUser %}
<li>{{ u|chill_entity_render_box }}</li>
{% endfor %}
</ul>
{% endif %}
{% if entity_workflow.currentStep.destEmail|length > 0 %}
<p><b>{{ 'workflow.An access key was also sent to those addresses'|trans }}&nbsp;:</b></p>
<ul>

View File

@ -7,7 +7,7 @@
{% endblock %}
{% block content %}
<div class="col-10 workflow">
<div class="col-12 workflow">
<h1 class="mb-5">{{ block('title') }}</h1>
@ -25,6 +25,12 @@
{{ 'workflow.dest'|trans }}
</a>
</li>
<li class="nav-item">
<a href="{{ path('chill_main_workflow_list_cc') }}"
class="nav-link {% if step == 'cc' %}active{% endif %}">
{{ 'workflow.cc'|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 %}">

View File

@ -15,6 +15,12 @@
{% for d in step.destUser %}{{ d|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}
</b>
</li>
<li>
<span class="item-key">{{ 'workflow.Cc'|trans ~ ' : ' }}</span>
<b>
{% for u in step.ccUser %}{{ u|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}
</b>
</li>
{% else %}
<li>
<span class="item-key">{{ 'workflow.Created by'|trans ~ ' : ' }}</span>

View File

@ -50,6 +50,10 @@ class EntityWorkflowTransitionEventSubscriber implements EventSubscriberInterfac
/** @var EntityWorkflow $entityWorkflow */
$entityWorkflow = $event->getSubject();
foreach ($entityWorkflow->futureCcUsers as $user) {
$entityWorkflow->getCurrentStep()->addCcUser($user);
}
foreach ($entityWorkflow->futureDestUsers as $user) {
$entityWorkflow->getCurrentStep()->addDestUser($user);
}

View File

@ -85,7 +85,9 @@ class NotificationOnTransition implements EventSubscriberInterface
// the subscriber to final, only if final
$entityWorkflow->isFinal() ? $entityWorkflow->getSubscriberToFinal()->toArray() : [],
// the dests for the current step
$entityWorkflow->getCurrentStep()->getDestUser()->toArray()
$entityWorkflow->getCurrentStep()->getDestUser()->toArray(),
// the cc users for the current step
$entityWorkflow->getCurrentStep()->getCcUser()->toArray()
) as $dest) {
$dests[spl_object_hash($dest)] = $dest;
}

View File

@ -16,6 +16,7 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Symfony\Component\Security\Core\Security;
class WorkflowNotificationHandler implements NotificationHandlerInterface
{
@ -23,10 +24,16 @@ class WorkflowNotificationHandler implements NotificationHandlerInterface
private EntityWorkflowRepository $entityWorkflowRepository;
public function __construct(EntityWorkflowRepository $entityWorkflowRepository, EntityWorkflowManager $entityWorkflowManager)
{
private Security $security;
public function __construct(
EntityWorkflowRepository $entityWorkflowRepository,
EntityWorkflowManager $entityWorkflowManager,
Security $security
) {
$this->entityWorkflowRepository = $entityWorkflowRepository;
$this->entityWorkflowManager = $entityWorkflowManager;
$this->security = $security;
}
public function getTemplate(Notification $notification, array $options = []): string
@ -37,13 +44,28 @@ class WorkflowNotificationHandler implements NotificationHandlerInterface
public function getTemplateData(Notification $notification, array $options = []): array
{
$entityWorkflow = $this->entityWorkflowRepository->find($notification->getRelatedEntityId());
return [
'entity_workflow' => $entityWorkflow,
'handler' => $this->entityWorkflowManager->getHandler($entityWorkflow),
'notificationCc' => $this->isNotificationCc($notification),
];
}
private function isNotificationCc(Notification $notification): bool
{
$notificationCc = false;
if ($notification->getRelatedEntityClass() === EntityWorkflow::class) {
$relatedEntity = $this->entityWorkflowRepository->findOneBy(['id' => $notification->getRelatedEntityId()]);
if ($relatedEntity->getCurrentStepCreatedBy() !== $this->security->getUser()) {
$notificationCc = true;
}
}
return $notificationCc;
}
public function supports(Notification $notification, array $options = []): bool
{
return $notification->getRelatedEntityClass() === EntityWorkflow::class;

View File

@ -826,4 +826,20 @@ paths:
$ref: '#/components/schemas/Workflow'
403:
description: "Unauthorized"
/1.0/main/workflow/my-cc:
get:
tags:
- workflow
summary: Return a list of workflows for which user was notified in Cc
responses:
200:
description: "ok"
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Workflow'
403:
description: "Unauthorized"

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20230321134155 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_cc_user DROP CONSTRAINT FK_9FC79037E6AF9D4');
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_cc_user DROP CONSTRAINT FK_9FC7903A76ED395');
$this->addSql('DROP TABLE chill_main_workflow_entity_step_cc_user');
}
public function getDescription(): string
{
return 'Add cc User to workflow step';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE chill_main_workflow_entity_step_cc_user (entityworkflowstep_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflowstep_id, user_id))');
$this->addSql('CREATE INDEX IDX_9FC79037E6AF9D4 ON chill_main_workflow_entity_step_cc_user (entityworkflowstep_id)');
$this->addSql('CREATE INDEX IDX_9FC7903A76ED395 ON chill_main_workflow_entity_step_cc_user (user_id)');
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_cc_user ADD CONSTRAINT FK_9FC79037E6AF9D4 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_cc_user ADD CONSTRAINT FK_9FC7903A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
}
}

View File

@ -447,6 +447,7 @@ workflow:
Created by: Créé par
My decision: Ma décision
Next step: Prochaine étape
cc for next steps: Utilisateurs en copie
dest for next steps: Utilisateurs qui valideront la prochaine étape
Freeze: Geler
Freezed: Gelé
@ -465,12 +466,14 @@ workflow:
Document (n°%doc%): "Document (n°%doc%)"
Work (n°%w%): "Action d'accompagnement (n°%w%)"
subscribed: Workflows suivis
cc: Workflows dont je suis en copie
dest: Workflows en attente d'action
you subscribed to all steps: Vous recevrez une notification à chaque étape
you subscribed to final step: Vous recevrez une notification à l'étape finale
Current step: Étape actuelle
Comment on last change: Commentaire à la transition précédente
Users allowed to apply transition: Utilisateurs pouvant valider cette étape
Users put in Cc: Utilisateurs mis en copie
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 ?
@ -487,6 +490,7 @@ workflow:
Previous transitionned: Anciens workflows
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
For: Pour
Cc: Cc
You must select a next step, pick another decision if no next steps are available: Il faut une prochaine étape. Choissisez une autre décision si nécessaire.
An access key was also sent to those addresses: Un lien d'accès a été envoyé à ces adresses
Those users are also granted to apply a transition by using an access key: Ces utilisateurs ont obtenu l'accès grâce au lien reçu par email
@ -521,7 +525,9 @@ notification:
list: Notifications
Sent: Envoyé
to: À
cc: Cc
sent_to: Destinataire(s)
sent_cc: En copie
from: De
received_from: Expéditeur
you were notified by %sender%: Vous avez été notifié par %sender%

View File

@ -373,6 +373,7 @@ workflow:
Created by: Créé par
My decision: Ma décision
Next step: Prochaine étape
cc for next steps: Utilisateurs en copie
dest for next steps: Utilisateurs qui valideront la prochaine étape
Freeze: Geler
Freezed: Gelé
@ -392,11 +393,13 @@ workflow:
Work (n°%w%): "Action d'accompagnement (n°%w%)"
subscribed: Workflows suivis
dest: Workflows en attente d'action
cc: Workflows dont je suis en copie
you subscribed to all steps: Vous recevrez une notification à chaque étape
you subscribed to final step: Vous recevrez une notification à l'étape finale
Current step: Étape actuelle
Comment on last change: Commentaire à la transition précédente
Users allowed to apply transition: Utilisateurs pouvant valider cette étape
Users put in Cc: Utilisateurs mis en copie
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 ?
@ -414,6 +417,7 @@ workflow:
Previous transitionned: Anciens workflows
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
For: Pour
Cc: Cc
Subscribe final: Recevoir une notification à l'étape finale
@ -442,7 +446,9 @@ notification:
list: Notifications
Sent: Envoyé
to: À
cc: Cc
sent_to: Destinataire(s)
sent_cc: En copie
from: De
received_from: Expéditeur
you were notified by %sender%: Vous avez été notifié par %sender%

View File

@ -11,6 +11,8 @@ A permission is already present for the same role and scope: Une permission est
#UserCircleConsistency
"{{ username }} is not allowed to see entities published in this circle": "{{ username }} n'est pas autorisé à voir l'élément publié dans ce cercle."
The user in cc cannot be a dest user in the same workflow step: Un utilisateur en Cc ne peut pas être un utilisateur qui valide.
#password request
This username or email does not exists: Cet utilisateur ou email n'est pas présent dans la base de donnée
@ -31,6 +33,7 @@ notification:
workflow:
You must add at least one dest user or email: Indiquez au moins un destinataire ou une adresse email
The user in cc cannot be a dest user in the same workflow step: L'utilisateur en copie ne peut pas être présent dans les utilisateurs qui valideront la prochaine étape
rolling_date:
When fixed date is selected, you must provide a date: Indiquez la date fixe choisie