mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
105 worflow
This commit is contained in:
parent
daff4e4200
commit
c7dbaae8d6
@ -19,6 +19,9 @@ and this project adheres to
|
|||||||
* [notification] add `[Chill]` in the subject of each notification, automatically
|
* [notification] add `[Chill]` in the subject of each notification, automatically
|
||||||
* [notification] add a counter for notification in activity list and accompanying period list, and search results
|
* [notification] add a counter for notification in activity list and accompanying period list, and search results
|
||||||
* [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before.
|
* [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before.
|
||||||
|
* [workflow] add breadcrumb to show steps
|
||||||
|
* [popover] add popover html popup mechanism (used by workflow breadcrumb)
|
||||||
|
* [templates] improve updatedBy macro in item metadatas
|
||||||
|
|
||||||
## Test releases
|
## Test releases
|
||||||
|
|
||||||
@ -28,6 +31,10 @@ and this project adheres to
|
|||||||
* [main] location form type: fix unmapped address field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/246)
|
* [main] location form type: fix unmapped address field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/246)
|
||||||
* [activity] fix wrong import of js assets for adding and viewing documents in activity (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/83 & https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/176)
|
* [activity] fix wrong import of js assets for adding and viewing documents in activity (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/83 & https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/176)
|
||||||
* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380)
|
* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380)
|
||||||
|
* [workflow]
|
||||||
|
* add My workflow section with my opened subscriptions
|
||||||
|
* apply workflow on documents, accompanyingCourseWork and Evaluations
|
||||||
|
* [wopi-link] a new vue component allow to open wopi link in a fullscreen chill-themed modal
|
||||||
|
|
||||||
### test release 2022-01-17
|
### test release 2022-01-17
|
||||||
|
|
||||||
|
@ -143,9 +143,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item-row separator">
|
<div class="item-row separator">
|
||||||
<ul class="record_actions">
|
<div class="item-col item-meta">
|
||||||
{{ recordAction }}
|
{% set notif_counter = chill_count_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) %}
|
||||||
</ul>
|
{% if notif_counter.total > 0 %}
|
||||||
|
{{ chill_counter_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="item-col">
|
||||||
|
<ul class="record_actions">
|
||||||
|
{{ recordAction }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
{% macro recordAction(activity, context = null, person_id = null, accompanying_course_id = null) %}
|
{% macro recordAction(activity, context = null, person_id = null, accompanying_course_id = null) %}
|
||||||
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
{% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
|
||||||
{% if no_action is not defined or no_action == false %}
|
{% if no_action is not defined or no_action == false %}
|
||||||
{% set notif_counter = chill_count_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) %}
|
|
||||||
{% if notif_counter.total > 0 %}
|
|
||||||
<li>{{ chill_counter_notifications('Chill\\ActivityBundle\\Entity\\Activity', activity.id) }}</li>
|
|
||||||
{% endif %}
|
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {
|
<a class="btn btn-notify" href="{{ chill_path_add_return_path('chill_main_notification_create', {
|
||||||
'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity',
|
'entityClass': 'Chill\\ActivityBundle\\Entity\\Activity',
|
||||||
|
@ -86,7 +86,7 @@
|
|||||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
|
||||||
'context': context,
|
'context': context,
|
||||||
'render': 'bloc',
|
'render': 'bloc',
|
||||||
'badge_person': 'true'
|
'badge_person': true
|
||||||
} %}
|
} %}
|
||||||
|
|
||||||
<h2 class="chill-blue">{{ 'Activity data'|trans }}</h2>
|
<h2 class="chill-blue">{{ 'Activity data'|trans }}</h2>
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
|
||||||
|
|
||||||
|
<div class="flex-table accompanying_course_work-list">
|
||||||
|
<div class="item-bloc document-item bg-chill-llight-gray">
|
||||||
|
<div class="row justify-content-center my-4">
|
||||||
|
<div class="col-2">
|
||||||
|
<i class="fa fa-4x fa-file-text-o text-success"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<h3>{{ document.title }}</h3>
|
||||||
|
<small>{{ document.object.type }}</small>
|
||||||
|
|
||||||
|
{% if document.description is not empty %}
|
||||||
|
<blockquote class="chill-user-quote mt-2">
|
||||||
|
{{ document.description }}
|
||||||
|
</blockquote>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if display_action is defined and display_action == true %}
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
{{ m.download_button(document.object, document.title) }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
|
||||||
|
{#
|
||||||
|
data-button is optional !
|
||||||
|
OPTIONS:
|
||||||
|
'changeIcon' string
|
||||||
|
'changeClass' string
|
||||||
|
'noText' boolean
|
||||||
|
|
||||||
|
#}{% set button = {
|
||||||
|
'changeIcon': 'fa-unlock',
|
||||||
|
} %}
|
||||||
|
|
||||||
|
{# vue component #}
|
||||||
|
<span
|
||||||
|
data-module="wopi-link"
|
||||||
|
data-wopi-url="{{ path('chill_wopi_file_edit', {'fileId': document.object.uuid}) }}"
|
||||||
|
data-doc-title="{{ document.title|e('html_attr') }}"
|
||||||
|
data-doc-type="{{ document.object.type|e('html_attr') }}"
|
||||||
|
data-button="{{ button|json_encode }}"
|
||||||
|
></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
@ -0,0 +1,19 @@
|
|||||||
|
{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as m %}
|
||||||
|
|
||||||
|
<div class="flex-grow-1 {% if add_classes is defined %}{{ add_classes }}{% else %}h2{% endif %}">
|
||||||
|
<div>
|
||||||
|
{% if concerne is defined and concerne == true %}
|
||||||
|
<span class="item-key">{{ 'Concerne'|trans }}: </span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ 'workflow.Document (n°%doc%)'|trans({'%doc%': document.id}) }}
|
||||||
|
|
||||||
|
{% if description is defined and description == true %}
|
||||||
|
{{ ' — ' ~ document.title }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if breadcrumb is defined and breadcrumb == true %}
|
||||||
|
{{ m.breadcrumb(_context) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
@ -6,61 +6,71 @@
|
|||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{# {{ 'Detail of document of %name%'|trans({ '%name%': accompanyingCourse|chill_entity_render_string } ) }} #}
|
{# {{ 'Detail of document of %name%'|trans({ '%name%': accompanyingCourse|chill_entity_render_string } ) }} #}
|
||||||
{% endblock %}
|
{{ 'Document %title%' | trans({ '%title%': document.title }) }}
|
||||||
|
|
||||||
|
|
||||||
{% block js %}
|
|
||||||
{{ parent() }}
|
|
||||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||||
|
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="document-show">
|
||||||
|
<h1>{{ block('title') }}</h1>
|
||||||
|
|
||||||
<h1>{{ 'Document %title%' | trans({ '%title%': document.title }) }}</h1>
|
<dl class="chill_view_data">
|
||||||
|
<dt>{{ 'Title'|trans }}</dt>
|
||||||
<dl class="chill_view_data">
|
<dd>{{ document.title }}</dd>
|
||||||
<dt>{{ 'Title'|trans }}</dt>
|
|
||||||
<dd>{{ document.title }}</dd>
|
|
||||||
|
|
||||||
{% if document.category is not null %}
|
{% if document.category is not null %}
|
||||||
<dt>{{ 'Category'|trans }}</dt>
|
<dt>{{ 'Category'|trans }}</dt>
|
||||||
<dd>{{ document.category.name|localize_translatable_string }}</dd>
|
<dd>{{ document.category.name|localize_translatable_string }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<dt>{{ 'Description' | trans }}</dt>
|
<dt>{{ 'Description' | trans }}</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{% if document.description is empty %}
|
{% if document.description is empty %}
|
||||||
<span class="chill-no-data-statement">{{ 'Any description'|trans }}</span>
|
<span class="chill-no-data-statement">{{ 'Any description'|trans }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<blockquote class="chill-user-quote">
|
<blockquote class="chill-user-quote">
|
||||||
{{ document.description|chill_markdown_to_html }}
|
{{ document.description|chill_markdown_to_html }}
|
||||||
</blockquote>
|
</blockquote>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dd>
|
</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
</dl>
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li class="cancel">
|
||||||
|
<a href="{{ path('accompanying_course_document_index', {'course': accompanyingCourse.id}) }}" class="btn btn-cancel">
|
||||||
|
{{ 'Back to the list' | trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{{ m.download_button(document.object, document.title) }}
|
||||||
|
</li>
|
||||||
|
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('accompanying_course_document_edit', {'id': document.id, 'course': accompanyingCourse.id}) }}" class="btn btn-edit">
|
||||||
|
{{ 'Edit' | trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<ul class="record_actions">
|
{% block block_post_menu %}
|
||||||
<li class="cancel">
|
<div class="post-menu pt-4">
|
||||||
<a href="{{ path('accompanying_course_document_index', {'course': accompanyingCourse.id}) }}" class="btn btn-cancel">
|
{% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %}
|
||||||
{{ 'Back to the list' | trans }}
|
{% if workflows_frame is not empty %}
|
||||||
</a>
|
{{ workflows_frame|raw }}
|
||||||
</li>
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
<li>
|
{% block js %}
|
||||||
{{ m.download_button(document.object, document.title) }}
|
{{ parent() }}
|
||||||
</li>
|
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||||
|
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
||||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
|
{% endblock %}
|
||||||
<li>
|
|
||||||
<a href="{{ path('accompanying_course_document_edit', {'id': document.id, 'course': accompanyingCourse.id}) }}" class="btn btn-edit">
|
|
||||||
{{ 'Edit' | trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -0,0 +1,74 @@
|
|||||||
|
<?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\DocStoreBundle\Workflow;
|
||||||
|
|
||||||
|
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
|
||||||
|
class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandlerInterface
|
||||||
|
{
|
||||||
|
private EntityRepository $repository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: injecter le repository directement.
|
||||||
|
*/
|
||||||
|
public function __construct(EntityManagerInterface $em)
|
||||||
|
{
|
||||||
|
$this->repository = $em->getRepository(AccompanyingCourseDocument::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingCourseDocument
|
||||||
|
{
|
||||||
|
return $this->repository->find($entityWorkflow->getRelatedEntityId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string
|
||||||
|
{
|
||||||
|
return '@ChillDocStore/AccompanyingCourseDocument/_workflow.html.twig';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'entity_workflow' => $entityWorkflow,
|
||||||
|
'document' => $this->getRelatedEntity($entityWorkflow),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplateTitle(EntityWorkflow $entityWorkflow, array $options = []): string
|
||||||
|
{
|
||||||
|
return '@ChillDocStore/AccompanyingCourseDocument/_workflow.title.html.twig';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplateTitleData(EntityWorkflow $entityWorkflow, array $options = []): array
|
||||||
|
{
|
||||||
|
return $this->getTemplateData($entityWorkflow, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool
|
||||||
|
{
|
||||||
|
return $entityWorkflow->getRelatedEntityClass() === AccompanyingCourseDocument::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -27,3 +27,8 @@ services:
|
|||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
tags:
|
tags:
|
||||||
- { name: chill.role }
|
- { name: chill.role }
|
||||||
|
|
||||||
|
Chill\DocStoreBundle\Workflow\:
|
||||||
|
resource: './../Workflow/'
|
||||||
|
autoconfigure: true
|
||||||
|
autowire: true
|
||||||
|
@ -9,6 +9,7 @@ Create new document: Créer un nouveau document
|
|||||||
New document for %name%: Nouveau document pour %name%
|
New document for %name%: Nouveau document pour %name%
|
||||||
Editing document for %name%: Modification d'un document pour %name%
|
Editing document for %name%: Modification d'un document pour %name%
|
||||||
Edit Document: Modification d'un document
|
Edit Document: Modification d'un document
|
||||||
|
Update document: Modifier le document
|
||||||
Existing document: Document existant
|
Existing document: Document existant
|
||||||
No document to download: Aucun document à télécharger
|
No document to download: Aucun document à télécharger
|
||||||
'Choose a document category': Choisissez une catégorie de document
|
'Choose a document category': Choisissez une catégorie de document
|
||||||
|
@ -31,6 +31,7 @@ use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
|
|||||||
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
|
use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface;
|
||||||
use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass;
|
use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass;
|
||||||
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
|
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
|
|
||||||
@ -56,6 +57,8 @@ class ChillMainBundle extends Bundle
|
|||||||
->addTag('chill_main.notification_handler');
|
->addTag('chill_main.notification_handler');
|
||||||
$container->registerForAutoconfiguration(NotificationCounterInterface::class)
|
$container->registerForAutoconfiguration(NotificationCounterInterface::class)
|
||||||
->addTag('chill.count_notification.user');
|
->addTag('chill.count_notification.user');
|
||||||
|
$container->registerForAutoconfiguration(EntityWorkflowHandlerInterface::class)
|
||||||
|
->addTag('chill_main.workflow_handler');
|
||||||
|
|
||||||
$container->addCompilerPass(new SearchableServicesCompilerPass());
|
$container->addCompilerPass(new SearchableServicesCompilerPass());
|
||||||
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
|
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
|
||||||
|
118
src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php
Normal file
118
src/Bundle/ChillMainBundle/Controller/WorkflowApiController.php
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?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\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use LogicException;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
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\Security\Core\Security;
|
||||||
|
|
||||||
|
class WorkflowApiController
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
public function __construct(Security $security, EntityManagerInterface $entityManager)
|
||||||
|
{
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->security = $security;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/1.0/main/workflow/{id}/subscribe", methods={"POST"})
|
||||||
|
*/
|
||||||
|
public function subscribe(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||||
|
{
|
||||||
|
return $this->handleSubscription($entityWorkflow, $request, 'subscribe');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/api/1.0/main/workflow/{id}/unsubscribe", methods={"POST"})
|
||||||
|
*/
|
||||||
|
public function unsubscribe(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||||
|
{
|
||||||
|
return $this->handleSubscription($entityWorkflow, $request, 'unsubscribe');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleSubscription(EntityWorkflow $entityWorkflow, Request $request, string $action): JsonResponse
|
||||||
|
{
|
||||||
|
if (!$this->security->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
|
||||||
|
throw new AccessDeniedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$request->query->has('subscribe')) {
|
||||||
|
throw new BadRequestHttpException('missing subscribe parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
|
||||||
|
switch ($request->query->get('subscribe')) {
|
||||||
|
case 'final':
|
||||||
|
switch ($action) {
|
||||||
|
case 'subscribe':
|
||||||
|
$entityWorkflow->addSubscriberToFinal($user);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'unsubscribe':
|
||||||
|
$entityWorkflow->removeSubscriberToFinal($user);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new LogicException();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'step':
|
||||||
|
switch ($action) {
|
||||||
|
case 'subscribe':
|
||||||
|
$entityWorkflow->addSubscriberToStep($user);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'unsubscribe':
|
||||||
|
$entityWorkflow->removeSubscriberToStep($user);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new LogicException();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new BadRequestHttpException('subscribe parameter must be equal to "step" or "final"');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new JsonResponse(
|
||||||
|
[
|
||||||
|
'step' => $entityWorkflow->isUserSubscribedToStep($user),
|
||||||
|
'final' => $entityWorkflow->isUserSubscribedToFinal($user),
|
||||||
|
],
|
||||||
|
JsonResponse::HTTP_OK,
|
||||||
|
[],
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
257
src/Bundle/ChillMainBundle/Controller/WorkflowController.php
Normal file
257
src/Bundle/ChillMainBundle/Controller/WorkflowController.php
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
<?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\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowComment;
|
||||||
|
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 Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
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\Validator\Validator\ValidatorInterface;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
use Symfony\Component\Workflow\TransitionBlocker;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
use function count;
|
||||||
|
|
||||||
|
class WorkflowController extends AbstractController
|
||||||
|
{
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private EntityWorkflowManager $entityWorkflowManager;
|
||||||
|
|
||||||
|
private EntityWorkflowRepository $entityWorkflowRepository;
|
||||||
|
|
||||||
|
private PaginatorFactory $paginatorFactory;
|
||||||
|
|
||||||
|
private Registry $registry;
|
||||||
|
|
||||||
|
private TranslatorInterface $translator;
|
||||||
|
|
||||||
|
private ValidatorInterface $validator;
|
||||||
|
|
||||||
|
public function __construct(EntityWorkflowManager $entityWorkflowManager, EntityWorkflowRepository $entityWorkflowRepository, ValidatorInterface $validator, PaginatorFactory $paginatorFactory, Registry $registry, EntityManagerInterface $entityManager, TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||||
|
$this->entityWorkflowRepository = $entityWorkflowRepository;
|
||||||
|
$this->validator = $validator;
|
||||||
|
$this->paginatorFactory = $paginatorFactory;
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->translator = $translator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{_locale}/main/workflow/create", name="chill_main_workflow_create")
|
||||||
|
*/
|
||||||
|
public function create(Request $request): Response
|
||||||
|
{
|
||||||
|
if (!$request->query->has('entityClass')) {
|
||||||
|
throw new BadRequestHttpException('Missing entityClass parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$request->query->has('entityId')) {
|
||||||
|
throw new BadRequestHttpException('missing entityId parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$request->query->has('workflow')) {
|
||||||
|
throw new BadRequestHttpException('missing workflow parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
$entityWorkflow
|
||||||
|
->setRelatedEntityClass($request->query->get('entityClass'))
|
||||||
|
->setRelatedEntityId($request->query->getInt('entityId'))
|
||||||
|
->setWorkflowName($request->query->get('workflow'));
|
||||||
|
|
||||||
|
$errors = $this->validator->validate($entityWorkflow, null, ['creation']);
|
||||||
|
|
||||||
|
if (count($errors) > 0) {
|
||||||
|
$msg = [];
|
||||||
|
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
/** @var \Symfony\Component\Validator\ConstraintViolationInterface $error */
|
||||||
|
$msg[] = $error->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(implode("\n", $msg), Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->denyAccessUnlessGranted(EntityWorkflowVoter::CREATE, $entityWorkflow);
|
||||||
|
|
||||||
|
$em = $this->getDoctrine()->getManager();
|
||||||
|
$em->persist($entityWorkflow);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{_locale}/main/workflow/list/dest", name="chill_main_workflow_list_dest")
|
||||||
|
*/
|
||||||
|
public function myWorkflowsDest(Request $request): Response
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||||
|
|
||||||
|
$total = $this->entityWorkflowRepository->countByDest($this->getUser());
|
||||||
|
$paginator = $this->paginatorFactory->create($total);
|
||||||
|
|
||||||
|
$workflows = $this->entityWorkflowRepository->findByDest(
|
||||||
|
$this->getUser(),
|
||||||
|
['createdAt' => 'DESC'],
|
||||||
|
$paginator->getItemsPerPage(),
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->render(
|
||||||
|
'@ChillMain/Workflow/list.html.twig',
|
||||||
|
[
|
||||||
|
'workflows' => $this->buildHandler($workflows),
|
||||||
|
'paginator' => $paginator,
|
||||||
|
'step' => 'dest',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{_locale}/main/workflow/list/subscribed", name="chill_main_workflow_list_subscribed")
|
||||||
|
*/
|
||||||
|
public function myWorkflowsSubscribed(Request $request): Response
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||||
|
|
||||||
|
$total = $this->entityWorkflowRepository->countBySubscriber($this->getUser());
|
||||||
|
$paginator = $this->paginatorFactory->create($total);
|
||||||
|
|
||||||
|
$workflows = $this->entityWorkflowRepository->findBySubscriber(
|
||||||
|
$this->getUser(),
|
||||||
|
['createdAt' => 'DESC'],
|
||||||
|
$paginator->getItemsPerPage(),
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->render(
|
||||||
|
'@ChillMain/Workflow/list.html.twig',
|
||||||
|
[
|
||||||
|
'workflows' => $this->buildHandler($workflows),
|
||||||
|
'paginator' => $paginator,
|
||||||
|
'step' => 'subscribed',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{_locale}/main/workflow/{id}/show", name="chill_main_workflow_show")
|
||||||
|
*/
|
||||||
|
public function show(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted(EntityWorkflowVoter::SEE, $entityWorkflow);
|
||||||
|
|
||||||
|
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
|
||||||
|
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
||||||
|
|
||||||
|
if (count($workflow->getEnabledTransitions($entityWorkflow)) > 0) {
|
||||||
|
// possible transition
|
||||||
|
$transitionForm = $this->createForm(
|
||||||
|
WorkflowStepType::class,
|
||||||
|
$entityWorkflow->getCurrentStep(),
|
||||||
|
['transition' => true, 'entity_workflow' => $entityWorkflow]
|
||||||
|
);
|
||||||
|
|
||||||
|
$transitionForm->handleRequest($request);
|
||||||
|
|
||||||
|
if ($transitionForm->isSubmitted() && $transitionForm->isValid()) {
|
||||||
|
if (!$workflow->can($entityWorkflow, $transition = $transitionForm['transition']->getData()->getName())) {
|
||||||
|
$blockers = $workflow->buildTransitionBlockerList($entityWorkflow, $transition);
|
||||||
|
$msgs = array_map(function (TransitionBlocker $tb) {
|
||||||
|
return $this->translator->trans(
|
||||||
|
$tb->getMessage(),
|
||||||
|
$tb->getParameters()
|
||||||
|
);
|
||||||
|
}, iterator_to_array($blockers));
|
||||||
|
|
||||||
|
throw $this->createAccessDeniedException(
|
||||||
|
sprintf(
|
||||||
|
"not allowed to apply transition {$transition}: %s",
|
||||||
|
implode(', ', $msgs)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$workflow->apply($entityWorkflow, $transition);
|
||||||
|
|
||||||
|
foreach ($transitionForm['future_dest_users']->getData() as $user) {
|
||||||
|
$entityWorkflow->getCurrentStep()->addDestUser($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($transitionForm->isSubmitted() && !$transitionForm->isValid()) {
|
||||||
|
$this->addFlash('error', $this->translator->trans('This form contains errors'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
$commentForm = $this->createForm(EntityWorkflowCommentType::class, $newComment = new EntityWorkflowComment());
|
||||||
|
$commentForm->handleRequest($request);
|
||||||
|
|
||||||
|
if ($commentForm->isSubmitted() && $commentForm->isValid()) {
|
||||||
|
$this->entityManager->persist($newComment);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
$this->addFlash('success', $this->translator->trans('workflow.Comment added'));
|
||||||
|
|
||||||
|
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||||
|
} elseif ($commentForm->isSubmitted() && !$commentForm->isValid()) {
|
||||||
|
$this->addFlash('error', $this->translator->trans('This form contains errors'));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return $this->render(
|
||||||
|
'@ChillMain/Workflow/index.html.twig',
|
||||||
|
[
|
||||||
|
'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,
|
||||||
|
//'comment_form' => $commentForm->createView(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildHandler(array $workflows): array
|
||||||
|
{
|
||||||
|
$lines = [];
|
||||||
|
|
||||||
|
foreach ($workflows as $workflow) {
|
||||||
|
$handler = $this->entityWorkflowManager->getHandler($workflow);
|
||||||
|
$lines[] = [
|
||||||
|
'handler' => $handler,
|
||||||
|
'entity_workflow' => $workflow,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lines;
|
||||||
|
}
|
||||||
|
}
|
439
src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php
Normal file
439
src/Bundle/ChillMainBundle/Entity/Workflow/EntityWorkflow.php
Normal file
@ -0,0 +1,439 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Entity\Workflow;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Workflow\Validator\EntityWorkflowCreation;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Iterator;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||||
|
use function count;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table("chill_main_workflow_entity")
|
||||||
|
* @EntityWorkflowCreation(groups={"creation"})
|
||||||
|
* @Serializer\DiscriminatorMap(typeProperty="type", mapping={
|
||||||
|
* "entity_workflow": EntityWorkflow::class
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||||
|
{
|
||||||
|
use TrackCreationTrait;
|
||||||
|
use TrackUpdateTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity=EntityWorkflowComment::class, mappedBy="entityWorkflow", orphanRemoval=true)
|
||||||
|
*
|
||||||
|
* @var Collection|EntityWorkflowComment[]
|
||||||
|
*/
|
||||||
|
private Collection $comments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
* @Serializer\Groups({"read"})
|
||||||
|
*/
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
private string $relatedEntityClass = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private int $relatedEntityId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\OneToMany(targetEntity=EntityWorkflowStep::class, mappedBy="entityWorkflow", orphanRemoval=true, cascade={"persist"})
|
||||||
|
* @ORM\OrderBy({"transitionAt": "ASC", "id": "ASC"})
|
||||||
|
*
|
||||||
|
* @var Collection|EntityWorkflowStep[]
|
||||||
|
*/
|
||||||
|
private Collection $steps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToMany(targetEntity=User::class)
|
||||||
|
* @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_final")
|
||||||
|
*
|
||||||
|
* @var Collection|User[]
|
||||||
|
*/
|
||||||
|
private Collection $subscriberToFinal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToMany(targetEntity=User::class)
|
||||||
|
* @ORM\JoinTable(name="chill_main_workflow_entity_subscriber_to_step")
|
||||||
|
*
|
||||||
|
* @var Collection|User[]
|
||||||
|
*/
|
||||||
|
private Collection $subscriberToStep;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a step which will store all the transition data.
|
||||||
|
*/
|
||||||
|
private ?EntityWorkflowStep $transitionningStep = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text")
|
||||||
|
*/
|
||||||
|
private string $workflowName;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->subscriberToFinal = new ArrayCollection();
|
||||||
|
$this->subscriberToStep = new ArrayCollection();
|
||||||
|
$this->comments = new ArrayCollection();
|
||||||
|
$this->steps = new ArrayCollection();
|
||||||
|
|
||||||
|
$initialStep = new EntityWorkflowStep();
|
||||||
|
$initialStep
|
||||||
|
->setCurrentStep('initial');
|
||||||
|
$this->addStep($initialStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addComment(EntityWorkflowComment $comment): self
|
||||||
|
{
|
||||||
|
if (!$this->comments->contains($comment)) {
|
||||||
|
$this->comments[] = $comment;
|
||||||
|
$comment->setEntityWorkflow($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal You should prepare a step and run a workflow transition instead of manually adding a step
|
||||||
|
*/
|
||||||
|
public function addStep(EntityWorkflowStep $step): self
|
||||||
|
{
|
||||||
|
if (!$this->steps->contains($step)) {
|
||||||
|
$this->steps[] = $step;
|
||||||
|
$step->setEntityWorkflow($this);
|
||||||
|
|
||||||
|
if ($this->isFinalize()) {
|
||||||
|
$step->setFinalizeAfter(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addSubscriberToFinal(User $user): self
|
||||||
|
{
|
||||||
|
if (!$this->subscriberToFinal->contains($user)) {
|
||||||
|
$this->subscriberToFinal[] = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addSubscriberToStep(User $user): self
|
||||||
|
{
|
||||||
|
if (!$this->subscriberToStep->contains($user)) {
|
||||||
|
$this->subscriberToStep[] = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComments(): Collection
|
||||||
|
{
|
||||||
|
return $this->comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentStep(): ?EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$step = $this->steps->last();
|
||||||
|
|
||||||
|
if (false !== $step) {
|
||||||
|
return $step;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentStepCreatedAt(): ?DateTimeInterface
|
||||||
|
{
|
||||||
|
if (null !== $previous = $this->getPreviousStepIfAny()) {
|
||||||
|
return $previous->getTransitionAt();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentStepCreatedBy(): ?User
|
||||||
|
{
|
||||||
|
if (null !== $previous = $this->getPreviousStepIfAny()) {
|
||||||
|
return $previous->getTransitionBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityClass(): string
|
||||||
|
{
|
||||||
|
return $this->relatedEntityClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRelatedEntityId(): int
|
||||||
|
{
|
||||||
|
return $this->relatedEntityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used by MarkingStore.
|
||||||
|
*
|
||||||
|
* get a string representation of the step
|
||||||
|
*/
|
||||||
|
public function getStep(): string
|
||||||
|
{
|
||||||
|
return $this->getCurrentStep()->getCurrentStep();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStepAfter(EntityWorkflowStep $step): ?EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$iterator = $this->steps->getIterator();
|
||||||
|
|
||||||
|
if ($iterator instanceof Iterator) {
|
||||||
|
$iterator->rewind();
|
||||||
|
|
||||||
|
while ($iterator->valid()) {
|
||||||
|
$curStep = $iterator->current();
|
||||||
|
|
||||||
|
if ($curStep === $step) {
|
||||||
|
$iterator->next();
|
||||||
|
|
||||||
|
if ($iterator->valid()) {
|
||||||
|
return $iterator->current();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$iterator->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ArrayCollection|Collection
|
||||||
|
*/
|
||||||
|
public function getSteps()
|
||||||
|
{
|
||||||
|
return $this->steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStepsChained(): array
|
||||||
|
{
|
||||||
|
$iterator = $this->steps->getIterator();
|
||||||
|
$previous = $next = $current = null;
|
||||||
|
$steps = [];
|
||||||
|
|
||||||
|
$iterator->rewind();
|
||||||
|
|
||||||
|
while ($iterator->valid()) {
|
||||||
|
$previous = $current;
|
||||||
|
$steps[] = $current = $iterator->current();
|
||||||
|
$current->setPrevious($previous);
|
||||||
|
|
||||||
|
$iterator->next();
|
||||||
|
|
||||||
|
if ($iterator->valid()) {
|
||||||
|
$next = $iterator->current();
|
||||||
|
} else {
|
||||||
|
$next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current->setNext($next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ArrayCollection|Collection
|
||||||
|
*/
|
||||||
|
public function getSubscriberToFinal()
|
||||||
|
{
|
||||||
|
return $this->subscriberToFinal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ArrayCollection|Collection
|
||||||
|
*/
|
||||||
|
public function getSubscriberToStep()
|
||||||
|
{
|
||||||
|
return $this->subscriberToStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the step which is transitionning. Should be called only by event which will
|
||||||
|
* concern the transition.
|
||||||
|
*/
|
||||||
|
public function getTransitionningStep(): ?EntityWorkflowStep
|
||||||
|
{
|
||||||
|
return $this->transitionningStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWorkflowName(): string
|
||||||
|
{
|
||||||
|
return $this->workflowName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFinalize(): bool
|
||||||
|
{
|
||||||
|
$steps = $this->getStepsChained();
|
||||||
|
|
||||||
|
if (1 === count($steps)) {
|
||||||
|
// the initial step cannot be finalized
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var EntityWorkflowStep $last */
|
||||||
|
$last = end($steps);
|
||||||
|
|
||||||
|
return $last->getPrevious()->isFinalizeAfter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFreeze(): bool
|
||||||
|
{
|
||||||
|
$steps = $this->getStepsChained();
|
||||||
|
|
||||||
|
if (1 === count($steps)) {
|
||||||
|
// the initial step cannot be finalized
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var EntityWorkflowStep $last */
|
||||||
|
$last = end($steps);
|
||||||
|
|
||||||
|
return $last->getPrevious()->isFreezeAfter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isUserSubscribedToFinal(User $user): bool
|
||||||
|
{
|
||||||
|
return $this->subscriberToFinal->contains($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isUserSubscribedToStep(User $user): bool
|
||||||
|
{
|
||||||
|
return $this->subscriberToStep->contains($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function prepareStepBeforeTransition(EntityWorkflowStep $step): self
|
||||||
|
{
|
||||||
|
$this->transitionningStep = $step;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeComment(EntityWorkflowComment $comment): self
|
||||||
|
{
|
||||||
|
if ($this->comments->removeElement($comment)) {
|
||||||
|
$comment->setEntityWorkflow(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeStep(EntityWorkflowStep $step): self
|
||||||
|
{
|
||||||
|
if ($this->steps->removeElement($step)) {
|
||||||
|
$step->setEntityWorkflow(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeSubscriberToFinal(User $user): self
|
||||||
|
{
|
||||||
|
$this->subscriberToFinal->removeElement($user);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeSubscriberToStep(User $user): self
|
||||||
|
{
|
||||||
|
$this->subscriberToStep->removeElement($user);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRelatedEntityClass(string $relatedEntityClass): EntityWorkflow
|
||||||
|
{
|
||||||
|
$this->relatedEntityClass = $relatedEntityClass;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setRelatedEntityId(int $relatedEntityId): EntityWorkflow
|
||||||
|
{
|
||||||
|
$this->relatedEntityId = $relatedEntityId;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method use by marking store.
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setStep(string $step): self
|
||||||
|
{
|
||||||
|
$newStep = new EntityWorkflowStep();
|
||||||
|
$newStep->setCurrentStep($step);
|
||||||
|
|
||||||
|
// copy the freeze
|
||||||
|
if ($this->getCurrentStep()->isFreezeAfter()) {
|
||||||
|
$newStep->setFreezeAfter(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addStep($newStep);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setWorkflowName(string $workflowName): EntityWorkflow
|
||||||
|
{
|
||||||
|
$this->workflowName = $workflowName;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPreviousStepIfAny(): ?EntityWorkflowStep
|
||||||
|
{
|
||||||
|
if (1 === count($this->steps)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->steps->get($this->steps->count() - 2);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Entity\Workflow;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||||
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table("chill_main_workflow_entity_comment")
|
||||||
|
*/
|
||||||
|
class EntityWorkflowComment implements TrackCreationInterface, TrackUpdateInterface
|
||||||
|
{
|
||||||
|
use TrackCreationTrait;
|
||||||
|
use TrackUpdateTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text", options={"default": ""})
|
||||||
|
*/
|
||||||
|
private string $comment = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="comments")
|
||||||
|
*/
|
||||||
|
private ?EntityWorkflow $entityWorkflow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
public function getComment(): string
|
||||||
|
{
|
||||||
|
return $this->comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityWorkflow(): ?EntityWorkflow
|
||||||
|
{
|
||||||
|
return $this->entityWorkflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setComment(string $comment): self
|
||||||
|
{
|
||||||
|
$this->comment = $comment;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal use @see{EntityWorkflow::addComment}
|
||||||
|
*/
|
||||||
|
public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): self
|
||||||
|
{
|
||||||
|
$this->entityWorkflow = $entityWorkflow;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,336 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chill is a software for social workers
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view
|
||||||
|
* the LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Entity\Workflow;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
|
use function count;
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table("chill_main_workflow_entity_step")
|
||||||
|
*/
|
||||||
|
class EntityWorkflowStep
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text", options={"default": ""})
|
||||||
|
*/
|
||||||
|
private string $comment = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text")
|
||||||
|
*/
|
||||||
|
private ?string $currentStep = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="json")
|
||||||
|
*/
|
||||||
|
private array $destEmail = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToMany(targetEntity=User::class)
|
||||||
|
* @ORM\JoinTable(name="chill_main_workflow_entity_step_user")
|
||||||
|
*/
|
||||||
|
private Collection $destUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="steps")
|
||||||
|
*/
|
||||||
|
private ?EntityWorkflow $entityWorkflow = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="boolean", options={"default": false})
|
||||||
|
*/
|
||||||
|
private bool $finalizeAfter = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="boolean", options={"default": false})
|
||||||
|
*/
|
||||||
|
private bool $freezeAfter = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\GeneratedValue
|
||||||
|
* @ORM\Column(type="integer")
|
||||||
|
*/
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* filled by @see{EntityWorkflow::getStepsChained}.
|
||||||
|
*/
|
||||||
|
private ?EntityWorkflowStep $next = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* filled by @see{EntityWorkflow::getStepsChained}.
|
||||||
|
*/
|
||||||
|
private ?EntityWorkflowStep $previous = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text", nullable=true, options={"default": null})
|
||||||
|
*/
|
||||||
|
private ?string $transitionAfter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="datetime_immutable")
|
||||||
|
*/
|
||||||
|
private ?DateTimeImmutable $transitionAt = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity=User::class)
|
||||||
|
* @ORM\JoinColumn(nullable=true)
|
||||||
|
*/
|
||||||
|
private ?User $transitionBy = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="text", nullable=true)
|
||||||
|
*/
|
||||||
|
private ?string $transitionByEmail = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->destUser = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addDestEmail(string $email): self
|
||||||
|
{
|
||||||
|
if (!in_array($email, $this->destEmail, true)) {
|
||||||
|
$this->destEmail[] = $email;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addDestUser(User $user): self
|
||||||
|
{
|
||||||
|
if (!$this->destUser->contains($user)) {
|
||||||
|
$this->destUser[] = $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getComment(): string
|
||||||
|
{
|
||||||
|
return $this->comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentStep(): ?string
|
||||||
|
{
|
||||||
|
return $this->currentStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDestEmail(): array
|
||||||
|
{
|
||||||
|
return $this->destEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ArrayCollection|Collection
|
||||||
|
*/
|
||||||
|
public function getDestUser()
|
||||||
|
{
|
||||||
|
return $this->destUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntityWorkflow(): ?EntityWorkflow
|
||||||
|
{
|
||||||
|
return $this->entityWorkflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNext(): ?EntityWorkflowStep
|
||||||
|
{
|
||||||
|
return $this->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPrevious(): ?EntityWorkflowStep
|
||||||
|
{
|
||||||
|
return $this->previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransitionAfter(): ?string
|
||||||
|
{
|
||||||
|
return $this->transitionAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransitionAt(): ?DateTimeImmutable
|
||||||
|
{
|
||||||
|
return $this->transitionAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransitionBy(): ?User
|
||||||
|
{
|
||||||
|
return $this->transitionBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransitionByEmail(): ?string
|
||||||
|
{
|
||||||
|
return $this->transitionByEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFinalizeAfter(): bool
|
||||||
|
{
|
||||||
|
return $this->finalizeAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFreezeAfter(): bool
|
||||||
|
{
|
||||||
|
return $this->freezeAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeDestEmail(string $email): self
|
||||||
|
{
|
||||||
|
$this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) {
|
||||||
|
return $email !== $existing;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeDestUser(User $user): self
|
||||||
|
{
|
||||||
|
$this->destUser->removeElement($user);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setComment(?string $comment): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->comment = (string) $comment;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCurrentStep(?string $currentStep): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->currentStep = $currentStep;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDestEmail(array $destEmail): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->destEmail = $destEmail;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal use @see(EntityWorkflow::addStep} instead
|
||||||
|
*/
|
||||||
|
public function setEntityWorkflow(?EntityWorkflow $entityWorkflow): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->entityWorkflow = $entityWorkflow;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFinalizeAfter(bool $finalizeAfter): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->finalizeAfter = $finalizeAfter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFreezeAfter(bool $freezeAfter): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->freezeAfter = $freezeAfter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return EntityWorkflowStep
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function setNext(?EntityWorkflowStep $next): self
|
||||||
|
{
|
||||||
|
$this->next = $next;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return EntityWorkflowStep
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function setPrevious(?EntityWorkflowStep $previous): self
|
||||||
|
{
|
||||||
|
$this->previous = $previous;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTransitionAfter(?string $transitionAfter): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->transitionAfter = $transitionAfter;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTransitionAt(?DateTimeImmutable $transitionAt): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->transitionAt = $transitionAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTransitionBy(?User $transitionBy): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->transitionBy = $transitionBy;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTransitionByEmail(?string $transitionByEmail): EntityWorkflowStep
|
||||||
|
{
|
||||||
|
$this->transitionByEmail = $transitionByEmail;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Assert\Callback
|
||||||
|
*
|
||||||
|
* @param mixed $payload
|
||||||
|
*/
|
||||||
|
public function validateOnCreation(ExecutionContextInterface $context, $payload): void
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ($this->isFinalizeAfter()) {
|
||||||
|
if (0 !== count($this->getDestUser())) {
|
||||||
|
$context->buildViolation('workflow.No dest users when the workflow is finalized')
|
||||||
|
->atPath('finalizeAfter')
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (0 === count($this->getDestUser())) {
|
||||||
|
$context->buildViolation('workflow.The next step must count at least one dest')
|
||||||
|
->atPath('finalizeAfter')
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?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\Form;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class EntityWorkflowCommentType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('comment', ChillTextareaType::class, [
|
||||||
|
'required' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -36,14 +36,20 @@ class UserToJsonTransformer implements DataTransformerInterface
|
|||||||
|
|
||||||
public function reverseTransform($value)
|
public function reverseTransform($value)
|
||||||
{
|
{
|
||||||
|
$denormalized = json_decode($value, true);
|
||||||
|
|
||||||
if ($this->multiple) {
|
if ($this->multiple) {
|
||||||
|
if (null === $denormalized) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
return array_map(
|
return array_map(
|
||||||
function ($item) { return $this->denormalizeOne($item); },
|
function ($item) { return $this->denormalizeOne($item); },
|
||||||
json_decode($value, true)
|
$denormalized
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->denormalizeOne(json_decode($value, true));
|
return $this->denormalizeOne($denormalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
111
src/Bundle/ChillMainBundle/Form/WorkflowStepType.php
Normal file
111
src/Bundle/ChillMainBundle/Form/WorkflowStepType.php
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<?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\Form;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||||
|
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||||
|
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
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\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
use Symfony\Component\Workflow\Transition;
|
||||||
|
|
||||||
|
class WorkflowStepType extends AbstractType
|
||||||
|
{
|
||||||
|
private EntityWorkflowManager $entityWorkflowManager;
|
||||||
|
|
||||||
|
private Registry $registry;
|
||||||
|
|
||||||
|
public function __construct(EntityWorkflowManager $entityWorkflowManager, Registry $registry)
|
||||||
|
{
|
||||||
|
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||||
|
$this->registry = $registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
/** @var \Chill\MainBundle\Entity\Workflow\EntityWorkflow $entityWorkflow */
|
||||||
|
$entityWorkflow = $options['entity_workflow'];
|
||||||
|
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
|
||||||
|
|
||||||
|
if (true === $options['transition']) {
|
||||||
|
if (null === $options['entity_workflow']) {
|
||||||
|
throw new LogicException('if transition is true, entity_workflow should be defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
$transitions = $this->registry
|
||||||
|
->get($options['entity_workflow'], $entityWorkflow->getWorkflowName())
|
||||||
|
->getEnabledTransitions($entityWorkflow);
|
||||||
|
|
||||||
|
$choices = array_combine(
|
||||||
|
array_map(static function (Transition $transition) { return $transition->getName(); }, $transitions),
|
||||||
|
$transitions
|
||||||
|
);
|
||||||
|
|
||||||
|
$builder
|
||||||
|
->add('transition', ChoiceType::class, [
|
||||||
|
'label' => 'workflow.Transition',
|
||||||
|
'mapped' => false,
|
||||||
|
'multiple' => false,
|
||||||
|
'expanded' => true,
|
||||||
|
'choices' => $choices,
|
||||||
|
'choice_label' => static function (Transition $transition) {
|
||||||
|
return implode(', ', $transition->getTos());
|
||||||
|
},
|
||||||
|
])
|
||||||
|
->add('future_dest_users', PickUserDynamicType::class, [
|
||||||
|
'label' => 'workflow.dest for next steps',
|
||||||
|
'multiple' => true,
|
||||||
|
'mapped' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
$handler->supportsFreeze($entityWorkflow)
|
||||||
|
&& !$entityWorkflow->isFreeze()
|
||||||
|
) {
|
||||||
|
$builder
|
||||||
|
->add('freezeAfter', CheckboxType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'label' => 'workflow.Freeze',
|
||||||
|
'help' => 'workflow.The associated element will be freezed',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$builder
|
||||||
|
->add('finalizeAfter', CheckboxType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'label' => 'workflow.Finalize',
|
||||||
|
'help' => 'workflow.The workflow will be finalized',
|
||||||
|
])
|
||||||
|
->add('comment', ChillTextareaType::class, [
|
||||||
|
'required' => false,
|
||||||
|
'label' => 'Comment',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver
|
||||||
|
->setDefined('class', EntityWorkflowStep::class)
|
||||||
|
->setRequired('transition')
|
||||||
|
->setAllowedTypes('transition', 'bool')
|
||||||
|
->setRequired('entity_workflow')
|
||||||
|
->setAllowedTypes('entity_workflow', EntityWorkflow::class);
|
||||||
|
}
|
||||||
|
}
|
36
src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php
Normal file
36
src/Bundle/ChillMainBundle/Form/WorkflowTransitionType.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?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\Form;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
|
||||||
|
class WorkflowTransitionType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('current_step', WorkflowStepType::class, [
|
||||||
|
'transition' => true,
|
||||||
|
'entity_workflow' => $options['entity_workflow'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver
|
||||||
|
->setRequired('entity_workflow')
|
||||||
|
->setAllowedTypes('entity_workflow', EntityWorkflow::class);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
<?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\Repository\Workflow;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
class EntityWorkflowRepository implements ObjectRepository
|
||||||
|
{
|
||||||
|
private EntityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
|
{
|
||||||
|
$this->repository = $entityManager->getRepository(EntityWorkflow::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countByDest(User $user): int
|
||||||
|
{
|
||||||
|
$qb = $this->buildQueryByDest($user)->select('count(ew)');
|
||||||
|
|
||||||
|
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function countBySubscriber(User $user): int
|
||||||
|
{
|
||||||
|
$qb = $this->buildQueryBySubscriber($user)->select('count(ew)');
|
||||||
|
|
||||||
|
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id): ?EntityWorkflow
|
||||||
|
{
|
||||||
|
return $this->repository->find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|EntityWorkflow[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null|mixed $limit
|
||||||
|
* @param null|mixed $offset
|
||||||
|
*
|
||||||
|
* @return array|EntityWorkflow[]
|
||||||
|
*/
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||||
|
{
|
||||||
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByDest(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||||
|
{
|
||||||
|
$qb = $this->buildQueryByDest($user)->select('ew');
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
foreach ($orderBy as $key => $sort) {
|
||||||
|
$qb->addOrderBy('ew.' . $key, $sort);
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb->setMaxResults($limit)->setFirstResult($offset);
|
||||||
|
|
||||||
|
return $qb->getQuery()->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?EntityWorkflow
|
||||||
|
{
|
||||||
|
return $this->repository->findOneBy($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName(): string
|
||||||
|
{
|
||||||
|
return EntityWorkflow::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildQueryByDest(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()->isNull('step.transitionAfter')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$qb->setParameter('user', $user);
|
||||||
|
|
||||||
|
return $qb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildQueryBySubscriber(User $user): QueryBuilder
|
||||||
|
{
|
||||||
|
$qb = $this->repository->createQueryBuilder('ew');
|
||||||
|
|
||||||
|
$qb->where(
|
||||||
|
$qb->expr()->orX(
|
||||||
|
$qb->expr()->isMemberOf(':user', 'ew.subscriberToStep'),
|
||||||
|
$qb->expr()->isMemberOf(':user', 'ew.subscriberToFinal'),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$qb->setParameter('user', $user);
|
||||||
|
|
||||||
|
return $qb;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
// Access to Bootstrap variables and mixins
|
// Access to Bootstrap variables and mixins
|
||||||
@import '~ChillMainAssets/module/bootstrap/shared';
|
@import 'ChillMainAssets/module/bootstrap/shared';
|
||||||
|
|
||||||
// Chill variables
|
// Chill variables
|
||||||
@import './scss/chill_variables';
|
@import './scss/chill_variables';
|
||||||
@ -277,11 +277,17 @@ table.table-bordered {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// meta-data
|
||||||
|
div.updatedBy,
|
||||||
|
div.metadata {
|
||||||
|
span.user, span.date {
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
}
|
||||||
div.metadata {
|
div.metadata {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
color: $gray-600;
|
color: $gray-600;
|
||||||
span.user, span.date {
|
span.user, span.date {
|
||||||
text-decoration: underline dotted;
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $gray-700;
|
color: $gray-700;
|
||||||
}
|
}
|
||||||
@ -424,6 +430,62 @@ span.item-key {
|
|||||||
//text-decoration: dotted underline;
|
//text-decoration: dotted underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Workflows
|
||||||
|
div.workflow {
|
||||||
|
section.step {
|
||||||
|
border: 1px solid $chill-l-gray;
|
||||||
|
padding: 1em 2em;
|
||||||
|
div.flex-table {
|
||||||
|
margin: 1.5em -2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div.to-decision,
|
||||||
|
div.decided {
|
||||||
|
font-variant: all-small-caps;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
div.to-decision {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
div.decided {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
div.breadcrumb {
|
||||||
|
display: initial;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
background-color: tint-color($chill-yellow, 90%);
|
||||||
|
border: 1px solid $chill-yellow;
|
||||||
|
color: $primary;
|
||||||
|
border-radius: 1.5em;
|
||||||
|
font-size: 12pt;
|
||||||
|
font-weight: 500;
|
||||||
|
font-variant: small-caps;
|
||||||
|
span, a {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override bootstrap popover styles
|
||||||
|
div.popover {
|
||||||
|
box-shadow: 0 0 10px -5px $dark;
|
||||||
|
.popover-arrow {}
|
||||||
|
.popover-header {}
|
||||||
|
.popover-body {}
|
||||||
|
|
||||||
|
// Specific worflow breadcrumb popover
|
||||||
|
&.workflow-transition {
|
||||||
|
.popover-header {
|
||||||
|
font-variant: small-caps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// increase toast message z-index (above all modals)
|
// increase toast message z-index (above all modals)
|
||||||
div.v-toast {
|
div.v-toast {
|
||||||
z-index: 10000!important;
|
z-index: 10000!important;
|
||||||
|
@ -18,6 +18,7 @@ $chill-theme-buttons: (
|
|||||||
"show": $chill-blue,
|
"show": $chill-blue,
|
||||||
"view": $chill-blue,
|
"view": $chill-blue,
|
||||||
"misc": $gray-300,
|
"misc": $gray-300,
|
||||||
|
"download": $gray-300,
|
||||||
"cancel": $gray-300,
|
"cancel": $gray-300,
|
||||||
"choose": $gray-300,
|
"choose": $gray-300,
|
||||||
"notify": $gray-300,
|
"notify": $gray-300,
|
||||||
@ -78,6 +79,7 @@ $chill-theme-buttons: (
|
|||||||
&.btn-choose::before,
|
&.btn-choose::before,
|
||||||
&.btn-notify::before,
|
&.btn-notify::before,
|
||||||
&.btn-tpchild::before,
|
&.btn-tpchild::before,
|
||||||
|
&.btn-download::before,
|
||||||
&.btn-cancel::before {
|
&.btn-cancel::before {
|
||||||
font: normal normal normal 14px/1 ForkAwesome;
|
font: normal normal normal 14px/1 ForkAwesome;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
@ -105,6 +107,7 @@ $chill-theme-buttons: (
|
|||||||
&.btn-unlink::before { content: "\f127"; } // fa-chain-broken
|
&.btn-unlink::before { content: "\f127"; } // fa-chain-broken
|
||||||
&.btn-notify::before { content: "\f1d8"; } // fa-paper-plane
|
&.btn-notify::before { content: "\f1d8"; } // fa-paper-plane
|
||||||
&.btn-tpchild::before { content: "\f007"; } // fa-user
|
&.btn-tpchild::before { content: "\f007"; } // fa-user
|
||||||
|
&.btn-download::before { content: "\f019"; } // fa-download
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,6 +41,15 @@ div.flex-table {
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.item-meta {
|
||||||
|
flex-grow: 1 !important;
|
||||||
|
flex-shrink: 1 !important;
|
||||||
|
width: unset !important;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -68,6 +68,7 @@ div.notification-show {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Override bootstrap accordion
|
// Override bootstrap accordion
|
||||||
|
div#workflow-fold,
|
||||||
div#notification-fold {
|
div#notification-fold {
|
||||||
.accordion-button {
|
.accordion-button {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@ -78,3 +79,14 @@ div#notification-fold {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Counter
|
||||||
|
div.notification-counter {
|
||||||
|
span {
|
||||||
|
&:not(:first-child) {
|
||||||
|
&::before {
|
||||||
|
content: '/ ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -106,6 +106,8 @@ section.chill-entity {
|
|||||||
// used for comment-embeddable
|
// used for comment-embeddable
|
||||||
&.entity-comment-embeddable {
|
&.entity-comment-embeddable {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
/* already defined !!
|
||||||
div.metadata {
|
div.metadata {
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
color: $gray-600;
|
color: $gray-600;
|
||||||
@ -116,5 +118,6 @@ section.chill-entity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
const buildLinkCreate = function(workflowName, relatedEntityClass, relatedEntityId) {
|
||||||
|
let params = new URLSearchParams();
|
||||||
|
params.set('entityClass', relatedEntityClass);
|
||||||
|
params.set('entityId', relatedEntityId);
|
||||||
|
params.set('workflow', workflowName);
|
||||||
|
|
||||||
|
return `/fr/main/workflow/create?`+params.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
buildLinkCreate,
|
||||||
|
};
|
@ -9,9 +9,10 @@ import Dropdown from 'bootstrap/js/src/dropdown';
|
|||||||
import Modal from 'bootstrap/js/dist/modal';
|
import Modal from 'bootstrap/js/dist/modal';
|
||||||
import Collapse from 'bootstrap/js/src/collapse';
|
import Collapse from 'bootstrap/js/src/collapse';
|
||||||
import Carousel from 'bootstrap/js/src/carousel';
|
import Carousel from 'bootstrap/js/src/carousel';
|
||||||
|
import Popover from 'bootstrap/js/src/popover';
|
||||||
|
|
||||||
//
|
//
|
||||||
// ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section
|
// Carousel: ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section
|
||||||
// Initialize options, and show/hide controls in first/last slides
|
// Initialize options, and show/hide controls in first/last slides
|
||||||
//
|
//
|
||||||
let ACHeaderSlider = document.querySelector('#ACHeaderSlider');
|
let ACHeaderSlider = document.querySelector('#ACHeaderSlider');
|
||||||
@ -48,3 +49,14 @@ if (ACHeaderSlider) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Popover: used in workflow breadcrumb,
|
||||||
|
// (expected in: contextual help, notification-box, workflow-box )
|
||||||
|
//
|
||||||
|
const triggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
||||||
|
const popoverList = triggerList.map(function (el) {
|
||||||
|
return new Popover(el, {
|
||||||
|
html: true,
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,49 @@
|
|||||||
|
import { createApp } from "vue";
|
||||||
|
import PickWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue';
|
||||||
|
import ListWorkflowVue from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflow.vue';
|
||||||
|
|
||||||
|
// pick workflow
|
||||||
|
document.querySelectorAll('[data-pick-workflow]')
|
||||||
|
.forEach(function(el) {
|
||||||
|
const app = {
|
||||||
|
components: {
|
||||||
|
PickWorkflowVue
|
||||||
|
},
|
||||||
|
template:
|
||||||
|
'<pick-workflow-vue ' +
|
||||||
|
':relatedEntityClass="relatedEntityClass" ' +
|
||||||
|
':relatedEntityId="relatedEntityId" ' +
|
||||||
|
':workflowsAvailables="workflowsAvailables" ' +
|
||||||
|
'></pick-workflow-vue>',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
relatedEntityClass: el.dataset.relatedEntityClass,
|
||||||
|
relatedEntityId: Number.parseInt(el.dataset.relatedEntityId),
|
||||||
|
workflowsAvailables: JSON.parse(el.dataset.workflowsAvailables),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
createApp(app).mount(el);
|
||||||
|
})
|
||||||
|
;
|
||||||
|
|
||||||
|
// list workflow
|
||||||
|
document.querySelectorAll('[data-list-workflows]')
|
||||||
|
.forEach(function (el) {
|
||||||
|
const app = {
|
||||||
|
components: {
|
||||||
|
ListWorkflowVue,
|
||||||
|
},
|
||||||
|
template:
|
||||||
|
'<list-workflow-vue ' +
|
||||||
|
':workflows="workflows" ' +
|
||||||
|
'></list-workflow-vue>',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
workflows: JSON.parse(el.dataset.workflows),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
createApp(app).mount(el);
|
||||||
|
})
|
||||||
|
;
|
@ -0,0 +1,32 @@
|
|||||||
|
import {createApp} from "vue";
|
||||||
|
import EntityWorkflowVueSubscriber from 'ChillMainAssets/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue';
|
||||||
|
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
|
||||||
|
import { appMessages } from 'ChillMainAssets/vuejs/PickEntity/i18n';
|
||||||
|
|
||||||
|
const i18n = _createI18n(appMessages);
|
||||||
|
|
||||||
|
let containers = document.querySelectorAll('[data-entity-workflow-subscribe]');
|
||||||
|
|
||||||
|
containers.forEach(container => {
|
||||||
|
let app = {
|
||||||
|
components: {
|
||||||
|
EntityWorkflowVueSubscriber,
|
||||||
|
},
|
||||||
|
template: '<entity-workflow-vue-subscriber :entityWorkflowId="this.entityWorkflowId" :subscriberStep="this.subscriberStep" :subscriberFinal="this.subscriberFinal" @subscriptionUpdated="onUpdate"></entity-workflow-vue-subscriber>',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
entityWorkflowId: Number.parseInt(container.dataset.entityWorkflowId),
|
||||||
|
subscriberStep: container.dataset.subscribeStep === "1",
|
||||||
|
subscriberFinal: container.dataset.subscribeFinal === "1",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onUpdate(status) {
|
||||||
|
this.subscriberStep = status.step;
|
||||||
|
this.subscriberFinal = status.final;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(app).use(i18n).mount(container);
|
||||||
|
})
|
@ -31,7 +31,7 @@ window.addEventListener('DOMContentLoaded', function(e) {
|
|||||||
return {
|
return {
|
||||||
multiple: isMultiple,
|
multiple: isMultiple,
|
||||||
types: JSON.parse(el.dataset.types),
|
types: JSON.parse(el.dataset.types),
|
||||||
picked,
|
picked: picked === null ? [] : picked,
|
||||||
uniqid: el.dataset.uniqid,
|
uniqid: el.dataset.uniqid,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import { createApp } from 'vue';
|
||||||
|
import OpenWopiLink from 'ChillMainAssets/vuejs/_components/OpenWopiLink';
|
||||||
|
import {_createI18n} from "ChillMainAssets/vuejs/_js/i18n";
|
||||||
|
|
||||||
|
const i18n = _createI18n({});
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function (e) {
|
||||||
|
document.querySelectorAll('span[data-module="wopi-link"]')
|
||||||
|
.forEach(function (el) {
|
||||||
|
createApp({
|
||||||
|
template: '<open-wopi-link :wopiUrl="wopiUrl" :title="title" :type="type" :button="button"></open-wopi-link>',
|
||||||
|
components: {
|
||||||
|
OpenWopiLink
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
wopiUrl: el.dataset.wopiUrl,
|
||||||
|
title: el.dataset.docTitle,
|
||||||
|
type: el.dataset.docType,
|
||||||
|
button: el.dataset.button ? JSON.parse(el.dataset.button) : {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.use(i18n)
|
||||||
|
.mount(el)
|
||||||
|
;
|
||||||
|
})
|
||||||
|
;
|
||||||
|
});
|
@ -0,0 +1,30 @@
|
|||||||
|
import {ShowHide} from 'ChillMainAssets/lib/show_hide/show_hide.js';
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', function() {
|
||||||
|
let
|
||||||
|
finalizeAfterContainer = document.querySelector('#finalizeAfter'),
|
||||||
|
futureDestUsersContainer = document.querySelector('#futureDestUsers')
|
||||||
|
;
|
||||||
|
|
||||||
|
if (null === finalizeAfterContainer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new ShowHide({
|
||||||
|
load_event: null,
|
||||||
|
froms: [finalizeAfterContainer],
|
||||||
|
container: [futureDestUsersContainer],
|
||||||
|
test: function(containers, arg2, arg3) {
|
||||||
|
for (let container of containers) {
|
||||||
|
for (let input of container.querySelectorAll('input')) {
|
||||||
|
if (!input.checked) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
});
|
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<div class="d-grid gap-2 my-3">
|
||||||
|
<button class="btn btn-misc" type="button" v-if="!subscriberFinal" @click="subscribeTo('subscribe', 'final')">
|
||||||
|
<i class="fa fa-check fa-fw"></i>
|
||||||
|
{{ $t('subscribe_final') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-misc" type="button" v-if="subscriberFinal" @click="subscribeTo('unsubscribe', 'final')">
|
||||||
|
<i class="fa fa-times fa-fw"></i>
|
||||||
|
{{ $t('unsubscribe_final') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-misc" type="button" v-if="!subscriberStep" @click="subscribeTo('subscribe', 'step')">
|
||||||
|
<i class="fa fa-check fa-fw"></i>
|
||||||
|
{{ $t('subscribe_all_steps') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-misc" type="button" v-if="subscriberStep" @click="subscribeTo('unsubscribe', 'step')">
|
||||||
|
<i class="fa fa-times fa-fw"></i>
|
||||||
|
{{ $t('unsubscribe_all_steps') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "EntityWorkflowVueSubscriber",
|
||||||
|
i18n: {
|
||||||
|
messages: {
|
||||||
|
fr: {
|
||||||
|
subscribe_final: "Recevoir une notification à l'étape finale",
|
||||||
|
unsubscribe_final: "Ne plus recevoir de notification à l'étape finale",
|
||||||
|
subscribe_all_steps: "Recevoir une notification à chaque étape du suivi",
|
||||||
|
unsubscribe_all_steps: "Ne plus recevoir de notification à chaque étape du suivi",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
entityWorkflowId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
subscriberStep: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
subscriberFinal: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['subscriptionUpdated'],
|
||||||
|
methods: {
|
||||||
|
subscribeTo(step, to) {
|
||||||
|
let params = new URLSearchParams();
|
||||||
|
params.set('subscribe', to);
|
||||||
|
|
||||||
|
const url = `/api/1.0/main/workflow/${this.entityWorkflowId}/${step}?` + params.toString();
|
||||||
|
|
||||||
|
makeFetch('POST', url).then(response => {
|
||||||
|
this.$emit('subscriptionUpdated', response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* ALTERNATIVES
|
||||||
|
*
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="laststep">
|
||||||
|
<label class="form-check-label" for="laststep">{{ $t('subscribe_final') }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="allsteps">
|
||||||
|
<label class="form-check-label" for="allsteps">{{ $t('subscribe_all_steps') }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-group my-3">
|
||||||
|
<label class="list-group-item">
|
||||||
|
<input class="form-check-input me-1" type="checkbox" value="">
|
||||||
|
{{ $t('subscribe_final') }}
|
||||||
|
</label>
|
||||||
|
<label class="list-group-item">
|
||||||
|
<input class="form-check-input me-1" type="checkbox" value="">
|
||||||
|
{{ $t('subscribe_all_steps') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group-vertical my-3" role="group">
|
||||||
|
<button type="button" class="btn btn-outline-primary">
|
||||||
|
<i class="fa fa-check fa-fw"></i>
|
||||||
|
{{ $t('subscribe_final') }}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-primary">
|
||||||
|
<i class="fa fa-check fa-fw"></i>
|
||||||
|
{{ $t('subscribe_all_steps') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<div class="list-group my-2 workflow workflow-box">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<h4>Workflow associés</h4>
|
||||||
|
</div>
|
||||||
|
<div class="list-group-item" v-for="w in workflows">
|
||||||
|
{{ w.id }}
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-sm btn-outline-primary"
|
||||||
|
title="voir"
|
||||||
|
:href="goToUrl(w)">
|
||||||
|
<i class="fa fa-eye fa-fw"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ListWorkflow",
|
||||||
|
props: {
|
||||||
|
workflows: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goToUrl(w) {
|
||||||
|
return `/fr/main/workflow/${w.id}/show`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<template v-if="workflowsAvailables.length >= 1">
|
||||||
|
<div class="dropdown d-grid gap-2">
|
||||||
|
<button class="btn btn-primary dropdown-toggle" type="button" id="createWorkflowButton" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
Créer un workflow
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="createWorkflowButton">
|
||||||
|
<li v-for="w in workflowsAvailables" :key="w.name">
|
||||||
|
<a class="dropdown-item" :href="makeLink(w.name)" @click="goToGenerateWorkflow($event, w.name)">{{ w.text }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "PickWorkflow",
|
||||||
|
props: {
|
||||||
|
relatedEntityClass: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
relatedEntityId: {
|
||||||
|
type: Number,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
workflowsAvailables: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['goToGenerateWorkflow'],
|
||||||
|
methods: {
|
||||||
|
makeLink(workflowName) {
|
||||||
|
return buildLinkCreate(workflowName, this.relatedEntityClass, this.relatedEntityId);
|
||||||
|
},
|
||||||
|
goToGenerateWorkflow(event, workflowName) {
|
||||||
|
this.$emit('goToGenerateWorkflow', {event, workflowName, link: this.makeLink(workflowName)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -15,7 +15,7 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<slot name="body"></slot>
|
<slot name="body"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer" v-if="!hideFooter">
|
||||||
<button class="btn btn-cancel" @click="$emit('close')">{{ $t('action.close') }}</button>
|
<button class="btn btn-cancel" @click="$emit('close')">{{ $t('action.close') }}</button>
|
||||||
<slot name="footer"></slot>
|
<slot name="footer"></slot>
|
||||||
</div>
|
</div>
|
||||||
@ -38,7 +38,16 @@
|
|||||||
*/
|
*/
|
||||||
export default {
|
export default {
|
||||||
name: 'Modal',
|
name: 'Modal',
|
||||||
props: ['modalDialogClass'],
|
props: {
|
||||||
|
modalDialogClass: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
hideFooter: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
emits: ['close']
|
emits: ['close']
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -0,0 +1,243 @@
|
|||||||
|
<template>
|
||||||
|
<a v-if="isOpenDocument"
|
||||||
|
class="btn change-icon" :class="[isChangeClass ? button.changeClass : 'btn-edit']"
|
||||||
|
@click="openModal">
|
||||||
|
|
||||||
|
<i class="fa me-2" :class="[isChangeIcon ? button.changeIcon : 'fa-pencil']"></i>
|
||||||
|
|
||||||
|
<span v-if="!noText">
|
||||||
|
{{ $t('Update_document') }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<teleport to="body">
|
||||||
|
<div class="wopi-frame" v-if="isOpenDocument">
|
||||||
|
<modal v-if="modal.showModal"
|
||||||
|
:modalDialogClass="modal.modalDialogClass"
|
||||||
|
:hideFooter=true
|
||||||
|
@close="modal.showModal = false">
|
||||||
|
|
||||||
|
<template v-slot:header>
|
||||||
|
<img class="logo" :src="logo" height="45"/>
|
||||||
|
<span class="ms-auto me-3">
|
||||||
|
{{ this.title }}
|
||||||
|
</span>
|
||||||
|
<a class="btn btn-outline-light">
|
||||||
|
<i class="fa fa-save fa-fw"></i>
|
||||||
|
{{ $t('save_and_quit') }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body>
|
||||||
|
<div v-if="loading" class="loading">
|
||||||
|
<i class="fa fa-circle-o-notch fa-spin fa-3x" :title="$t('loading')"></i>
|
||||||
|
</div>
|
||||||
|
<iframe
|
||||||
|
:src="this.wopiUrl"
|
||||||
|
@load="loaded"
|
||||||
|
></iframe>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</modal>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<modal v-if="modal.showModal"
|
||||||
|
modalDialogClass="modal-sm"
|
||||||
|
@close="modal.showModal = false">
|
||||||
|
|
||||||
|
<template v-slot:header>
|
||||||
|
<h3>{{ $t('invalid_title') }}</h3>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body>
|
||||||
|
<div class="alert alert-warning">{{ $t('invalid_message') }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</modal>
|
||||||
|
</div>
|
||||||
|
</teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||||
|
import logo from 'ChillMainAssets/chill/img/logo-chill-sans-slogan_white.png';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "OpenWopiLink",
|
||||||
|
components: {
|
||||||
|
Modal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
wopiUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
type: Object,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
modal: {
|
||||||
|
showModal: false,
|
||||||
|
modalDialogClass: "modal-fullscreen" //modal-dialog-scrollable
|
||||||
|
},
|
||||||
|
logo: logo,
|
||||||
|
loading: false,
|
||||||
|
mime: [
|
||||||
|
// TODO temporary hardcoded. to be replaced by twig extension or a collabora server query
|
||||||
|
'application/clarisworks',
|
||||||
|
'application/coreldraw',
|
||||||
|
'application/macwriteii',
|
||||||
|
'application/msword',
|
||||||
|
'application/pdf',
|
||||||
|
'application/vnd.lotus-1-2-3',
|
||||||
|
'application/vnd.ms-excel',
|
||||||
|
'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
|
||||||
|
'application/vnd.ms-excel.sheet.macroEnabled.12',
|
||||||
|
'application/vnd.ms-excel.template.macroEnabled.12',
|
||||||
|
'application/vnd.ms-powerpoint',
|
||||||
|
'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
|
||||||
|
'application/vnd.ms-powerpoint.template.macroEnabled.12',
|
||||||
|
'application/vnd.ms-visio.drawing',
|
||||||
|
'application/vnd.ms-word.document.macroEnabled.12',
|
||||||
|
'application/vnd.ms-word.template.macroEnabled.12',
|
||||||
|
'application/vnd.ms-works',
|
||||||
|
'application/vnd.oasis.opendocument.chart',
|
||||||
|
'application/vnd.oasis.opendocument.formula',
|
||||||
|
'application/vnd.oasis.opendocument.graphics',
|
||||||
|
'application/vnd.oasis.opendocument.graphics-flat-xml',
|
||||||
|
'application/vnd.oasis.opendocument.graphics-template',
|
||||||
|
'application/vnd.oasis.opendocument.presentation',
|
||||||
|
'application/vnd.oasis.opendocument.presentation-flat-xml',
|
||||||
|
'application/vnd.oasis.opendocument.presentation-template',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet-flat-xml',
|
||||||
|
'application/vnd.oasis.opendocument.spreadsheet-template',
|
||||||
|
'application/vnd.oasis.opendocument.text',
|
||||||
|
'application/vnd.oasis.opendocument.text-flat-xml',
|
||||||
|
'application/vnd.oasis.opendocument.text-master',
|
||||||
|
'application/vnd.oasis.opendocument.text-master-template',
|
||||||
|
'application/vnd.oasis.opendocument.text-template',
|
||||||
|
'application/vnd.oasis.opendocument.text-web',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.template',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
|
||||||
|
'application/vnd.sun.xml.calc',
|
||||||
|
'application/vnd.sun.xml.calc.template',
|
||||||
|
'application/vnd.sun.xml.chart',
|
||||||
|
'application/vnd.sun.xml.draw',
|
||||||
|
'application/vnd.sun.xml.draw.template',
|
||||||
|
'application/vnd.sun.xml.impress',
|
||||||
|
'application/vnd.sun.xml.impress.template',
|
||||||
|
'application/vnd.sun.xml.math',
|
||||||
|
'application/vnd.sun.xml.writer',
|
||||||
|
'application/vnd.sun.xml.writer.global',
|
||||||
|
'application/vnd.sun.xml.writer.template',
|
||||||
|
'application/vnd.visio',
|
||||||
|
'application/vnd.visio2013',
|
||||||
|
'application/vnd.wordperfect',
|
||||||
|
'application/x-abiword',
|
||||||
|
'application/x-aportisdoc',
|
||||||
|
'application/x-dbase',
|
||||||
|
'application/x-dif-document',
|
||||||
|
'application/x-fictionbook+xml',
|
||||||
|
'application/x-gnumeric',
|
||||||
|
'application/x-hwp',
|
||||||
|
'application/x-iwork-keynote-sffkey',
|
||||||
|
'application/x-iwork-numbers-sffnumbers',
|
||||||
|
'application/x-iwork-pages-sffpages',
|
||||||
|
'application/x-mspublisher',
|
||||||
|
'application/x-mswrite',
|
||||||
|
'application/x-pagemaker',
|
||||||
|
'application/x-sony-bbeb',
|
||||||
|
'application/x-t602',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isOpenDocument() {
|
||||||
|
if (this.mime.indexOf(this.type) !== -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
noText() {
|
||||||
|
if (typeof this.button.noText !== 'undefined') {
|
||||||
|
return this.button.noText === true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isChangeIcon() {
|
||||||
|
if (typeof this.button.changeIcon !== 'undefined') {
|
||||||
|
return (!(this.button.changeIcon === null || this.button.changeIcon === ''))
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isChangeClass() {
|
||||||
|
if (typeof this.button.changeClass !== 'undefined') {
|
||||||
|
return (!(this.button.changeClass === null || this.button.changeClass === ''))
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openModal() {
|
||||||
|
this.loading = true;
|
||||||
|
this.modal.showModal = true;
|
||||||
|
},
|
||||||
|
loaded() {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
i18n: {
|
||||||
|
messages: {
|
||||||
|
fr: {
|
||||||
|
Update_document: "Modifier le document",
|
||||||
|
save_and_quit: "Enregistrer et quitter",
|
||||||
|
loading: "Chargement de l'éditeur en ligne",
|
||||||
|
invalid_title: "Format incompatible",
|
||||||
|
invalid_message: "Désolé, ce format de document n'est pas éditable en ligne.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
div.wopi-frame {
|
||||||
|
div.modal-header {
|
||||||
|
border-bottom: 0;
|
||||||
|
background-color: var(--bs-primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
div.modal-body {
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: unset !important;
|
||||||
|
iframe {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
div.loading {
|
||||||
|
position: absolute;
|
||||||
|
color: var(--bs-chill-gray);
|
||||||
|
top: calc(50% - 30px);
|
||||||
|
left: calc(50% - 30px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
|||||||
{% block navigation_search_bar %}{% endblock %}
|
{% block navigation_search_bar %}{% endblock %}
|
||||||
{% block navigation_section_menu %}
|
{% block navigation_section_menu %}
|
||||||
{{ chill_menu('admin_section', {
|
{{ chill_menu('admin_section', {
|
||||||
'layout': '@ChillMain/Menu/adminSection.html.twig',
|
'layout': '@ChillMain/Menu/admin.html.twig',
|
||||||
}) }}
|
}) }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -23,22 +23,22 @@
|
|||||||
|
|
||||||
{% if options['metadata'] %}
|
{% if options['metadata'] %}
|
||||||
<div class="metadata">
|
<div class="metadata">
|
||||||
{% if user is not empty %}
|
|
||||||
{{ 'Last updated by'| trans }}
|
|
||||||
<span class="user">
|
|
||||||
{{ user|chill_entity_render_box(options['user']) }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if comment.date is not empty %}
|
{% if comment.date is not empty %}
|
||||||
{% if user is empty %}
|
{{ 'Last updated on'|trans ~ ' ' }}
|
||||||
{{ 'Last updated on'|trans ~ ' ' }}
|
|
||||||
{% else %}
|
|
||||||
{{ 'on'|trans ~ ' ' }}
|
|
||||||
{% endif %}
|
|
||||||
<span class="date">
|
<span class="date">
|
||||||
{{ comment.date|format_datetime("medium", "short") }}
|
{{ comment.date|format_datetime("medium", "short") }}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if user is not empty %}
|
||||||
|
{% if comment.date is empty %}
|
||||||
|
{{ 'Last updated by'| trans }}
|
||||||
|
{% else %}
|
||||||
|
{{ 'by_user'|trans ~ ' ' }}
|
||||||
|
{% endif %}
|
||||||
|
<span class="user">
|
||||||
|
{{ user|chill_entity_render_box(options['user']) }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</blockquote>
|
</blockquote>
|
||||||
|
@ -21,13 +21,13 @@
|
|||||||
layout ../layoutWithVerticalMenu.html.twig.
|
layout ../layoutWithVerticalMenu.html.twig.
|
||||||
#}
|
#}
|
||||||
|
|
||||||
<ul class="tab-nav follow-href-path">
|
<div class="list-group vertical-menu {{ 'menu-' ~ menus.name }}">
|
||||||
<li class="title">
|
<a class="list-group-item title">
|
||||||
{% block v_menu_title %}<!-- title of the verticalMenu is empty -->{% endblock %}
|
{% block v_menu_title %}<!-- title of the verticalMenu is empty -->{% endblock %}
|
||||||
</li>
|
</a>
|
||||||
{% for menu in menus %}
|
{% for menu in menus %}
|
||||||
<li class="{% if menu is knp_menu_current %}current {% endif %}">
|
<a class="list-group-item list-group-item-action" href="{{ menu.uri }}">
|
||||||
<a href="{{ menu.uri }}" >{{ menu.label|trans }}</a>
|
{{ menu.label|upper }}
|
||||||
</li>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="item-row title">
|
<div class="item-row title">
|
||||||
<h2 class="notification-title">
|
<h2 class="notification-title">
|
||||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">
|
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">
|
||||||
{{ 'notification.object_prefix'|trans ~ c.notification.title }}
|
{{ c.notification.title }}
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -58,7 +58,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="item-row">
|
<div class="item-row">
|
||||||
<div class="notification-content">
|
<div class="notification-content">
|
||||||
{% if c.full_content is defined and c.full_content == 'true' %}
|
{% if c.full_content is defined and c.full_content == true %}
|
||||||
{{ c.notification.message|chill_markdown_to_html }}
|
{{ c.notification.message|chill_markdown_to_html }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
||||||
@ -66,7 +66,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if c.action_button is not defined or c.action_button != 'false' %}
|
{% if c.action_button is not defined or c.action_button != false %}
|
||||||
<div class="item-row separator">
|
<div class="item-row separator">
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
@ -102,7 +102,7 @@
|
|||||||
|
|
||||||
<div class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}">
|
<div class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}">
|
||||||
|
|
||||||
{% if fold_item is defined and fold_item != 'false' %}
|
{% if fold_item is defined and fold_item != false %}
|
||||||
<div class="accordion-header" id="flush-heading-{{ notification.id }}">
|
<div class="accordion-header" id="flush-heading-{{ notification.id }}">
|
||||||
<button type="button" class="accordion-button collapsed"
|
<button type="button" class="accordion-button collapsed"
|
||||||
data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ notification.id }}"
|
data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ notification.id }}"
|
||||||
|
@ -1,2 +1,12 @@
|
|||||||
{% if counter.total > 0 %}<span class="badge rounded-pill bg-primary">{{ 'notification.counter total notifications'|trans({'total': counter.total }) }}</span>{% endif %}
|
<div class="notification-counter">
|
||||||
{% if counter.unread > 0 %}<span class="badge rounded-pill bg-danger">{{ 'notification.counter unread notifications'|trans({'unread': counter.unread })}}</span>{% endif %}
|
{% if counter.total > 0 %}
|
||||||
|
<span>
|
||||||
|
{{ 'notification.counter total notifications'|trans({'total': counter.total }) }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if counter.unread > 0 %}
|
||||||
|
<span>
|
||||||
|
{{ 'notification.counter unread notifications'|trans({'unread': counter.unread }) }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
@ -50,7 +50,7 @@
|
|||||||
{% for data in datas %}
|
{% for data in datas %}
|
||||||
{% set notification = data.notification %}
|
{% set notification = data.notification %}
|
||||||
{% include 'ChillMainBundle:Notification:_list_item.html.twig' with {
|
{% include 'ChillMainBundle:Notification:_list_item.html.twig' with {
|
||||||
'fold_item': 'true'
|
'fold_item': true
|
||||||
} %}
|
} %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,8 +40,8 @@
|
|||||||
'template': handler.getTemplate(notification),
|
'template': handler.getTemplate(notification),
|
||||||
'template_data': handler.getTemplateData(notification)
|
'template_data': handler.getTemplateData(notification)
|
||||||
},
|
},
|
||||||
'action_button': 'false',
|
'action_button': false,
|
||||||
'full_content': 'true'
|
'full_content': true
|
||||||
} %}
|
} %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
{# TODO Adapt condition #}
|
||||||
|
{% if random(1) == 0 %}
|
||||||
|
|
||||||
|
{# For a document #}
|
||||||
|
<h2>{{ 'Document'|trans ~ 'target'|trans }}</h2>
|
||||||
|
|
||||||
|
<div class="row justify-content-center mt-5">
|
||||||
|
<div class="col-2">
|
||||||
|
<i class="fa fa-4x fa-file-text-o text-success"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<h3>Imprimé unique, parcours n°14635</h3>
|
||||||
|
<small>Document PDF (6.2 Mo)</small>
|
||||||
|
<p class="mt-2">
|
||||||
|
Description du document. Sed euismod nisi porta lorem mollis aliquam. Non curabitur gravida arcu ac tortor.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{# For an action #}
|
||||||
|
<h2>{{ 'Accompanying Course Action'|trans ~ 'target'|trans }}</h2>
|
||||||
|
|
||||||
|
<div class="flex-table accompanying_course_work-list">
|
||||||
|
{# dynamic insertion
|
||||||
|
::: TODO delete all static insertion, remove condition and pass work object in inclusion
|
||||||
|
#}{% if dynamic is defined %}
|
||||||
|
|
||||||
|
{% set work = '<pass work object here>' %}
|
||||||
|
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with { 'w': work } %}
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
{# BEGIN static insertion #}
|
||||||
|
<div class="item-bloc">
|
||||||
|
<div class="item-row">
|
||||||
|
<h2 class="badge-title">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">Exercer un AEB > Conclure l'AEB
|
||||||
|
<ul class="small_in_title columns mt-1">
|
||||||
|
<li><span class="item-key">Date de début : </span><b>25/11/2021</b></li>
|
||||||
|
<li><span class="item-key">Date de fin : </span><b>10/03/2022</b></li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="wrap-list">
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title"><h3>Référent</h3></div>
|
||||||
|
<div class="wl-col list"><p class="wl-item">Fred</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title"><h3>Usagers du parcours</h3></div>
|
||||||
|
<div class="wl-col list"><span class="wl-item">
|
||||||
|
<span class="onthefly-container" data-target-name="person" data-target-id="1937" data-action="show" data-button-text="Vernon SUBUTEX" data-display-badge="true" data-v-app=""><a data-v-0c1a1125=""><span class="chill-entity entity-person badge-person" data-v-0c1a1125="">Vernon SUBUTEX</span></a><!--teleport start--><!--teleport end--></span></span>
|
||||||
|
<span class="wl-item"><span class="onthefly-container" data-target-name="person" data-target-id="1941" data-action="show" data-button-text="Juan RAMON" data-display-badge="true" data-v-app=""><a data-v-0c1a1125=""><span class="chill-entity entity-person badge-person" data-v-0c1a1125="">Juan RAMON</span></a><!--teleport start--><!--teleport end--></span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title"><h3>Problématique sociale</h3></div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<p class="wl-item social-issues">
|
||||||
|
<span class="chill-entity entity-social-issue"><span class="badge bg-chill-l-gray text-dark"><span class="parent-0">AD - PREVENTION, ACCES AUX DROITS, BUDGET ></span><span class="child">SOUTIEN EQUILIBRE BUDGET</span></span></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-row column">
|
||||||
|
<table class="obj-res-eval my-3">
|
||||||
|
<thead>
|
||||||
|
<tr><th class="obj"><h4 class="title_label">Objectif - motif - dispositif</h4></th>
|
||||||
|
<th class="res"><h4 class="title_label">Résultats - orientations</h4></th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="obj">
|
||||||
|
<p class="chill-no-data-statement">Aucun objectif - motif - dispositif</p>
|
||||||
|
</td>
|
||||||
|
<td class="res">
|
||||||
|
<ul class="result_list">
|
||||||
|
<li>Résultat : Arrêt à l'initiative du ménage pour déménagement</li>
|
||||||
|
<li>Orientation vers une MASP</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="updatedBy">
|
||||||
|
Dernière mise à jour par
|
||||||
|
<b><span class="chill-entity entity-user">Fred<span class="user-job">(Responsable tous les territoires)</span><span class="main-scope">(ASE)</span></span></b>,<br>
|
||||||
|
le 3 décembre 2021 à 15:19
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{# END static insertion #}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<button type="button" class="btn btn-misc">
|
||||||
|
<i class="fa fa-download fa-fw"></i>{{ 'Download'|trans }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{% set x = random(1) %}
|
||||||
|
<button class="btn btn-update change-icon {% if x == 1 %}disabled{% endif %}">
|
||||||
|
<i class="fa fa-fw fa-{% if x == 0 %}un{% endif %}lock"></i>
|
||||||
|
{{ 'Edit'|trans }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
@ -0,0 +1,13 @@
|
|||||||
|
<h2>{{ 'Join a comment'|trans }}</h2>
|
||||||
|
|
||||||
|
{{ form_start(comment_form) }}
|
||||||
|
|
||||||
|
{{ form_widget(comment_form.comment) }}
|
||||||
|
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<button type="submit" class="btn btn-save">{{ 'Save'|trans }}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{ form_end(comment_form) }}
|
@ -0,0 +1,48 @@
|
|||||||
|
<h2>{{ 'Decision'|trans }}</h2>
|
||||||
|
|
||||||
|
{% if transition_form is not null %}
|
||||||
|
{{ form_start(transition_form) }}
|
||||||
|
|
||||||
|
{{ form_row(transition_form.transition) }}
|
||||||
|
|
||||||
|
<div id="finalizeAfter">
|
||||||
|
{{ form_row(transition_form.finalizeAfter) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if transition_form.freezeAfter is defined %}
|
||||||
|
{{ form_row(transition_form.freezeAfter) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div id="futureDestUsers">
|
||||||
|
{{ form_row(transition_form.future_dest_users) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>{{ form_label(transition_form.comment) }}</p>
|
||||||
|
|
||||||
|
{{ form_widget(transition_form.comment) }}
|
||||||
|
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<button type="submit" class="btn btn-save">{{ 'Save'|trans }}</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{ form_end(transition_form) }}
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-chill-yellow">
|
||||||
|
|
||||||
|
{% if entity_workflow.currentStep.isFinalizeAfter %}
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for u in entity_workflow.currentStep.destUser -%}
|
||||||
|
<li>{{ u|chill_entity_render_box }}</li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
@ -0,0 +1,13 @@
|
|||||||
|
{% if is_granted('CHILL_MAIN_WORKFLOW_CREATE', blank_workflow) %}
|
||||||
|
{# vue component #}
|
||||||
|
<div data-pick-workflow="1"
|
||||||
|
data-related-entity-class="{{ blank_workflow.relatedEntityClass }}"
|
||||||
|
data-related-entity-id="{{ blank_workflow.relatedEntityId }}"
|
||||||
|
data-workflows-availables="{{ workflows_availables|json_encode()|e('html_attr') }}"
|
||||||
|
></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if entity_workflows|length > 0 %}
|
||||||
|
{# vue component #}
|
||||||
|
<div data-list-workflows="1" data-workflows="{{ entity_workflows_json|json_encode|e('html_attr') }}"></div>
|
||||||
|
{% endif %}
|
@ -0,0 +1,8 @@
|
|||||||
|
<h2>{{ 'Follow workflow'|trans }}</h2>
|
||||||
|
|
||||||
|
{# vue component #}
|
||||||
|
<div data-entity-workflow-subscribe="1"
|
||||||
|
data-entity-workflow-id="{{ entity_workflow.id }}"
|
||||||
|
data-subscribe-step="{{ entity_workflow.isUserSubscribedToStep(app.user)|e('html_attr') }}"
|
||||||
|
data-subscribe-final="{{ entity_workflow.isUserSubscribedToFinal(app.user)|e('html_attr') }}"
|
||||||
|
></div>
|
@ -0,0 +1,59 @@
|
|||||||
|
<h2>{{ 'Workflow history'|trans }}</h2>
|
||||||
|
|
||||||
|
<div class="flex-table">
|
||||||
|
{% for step in entity_workflow.stepsChained %}
|
||||||
|
<div class="item-bloc {{ 'bloc' ~ step.id }} {% if loop.first %}initial{% endif %}">
|
||||||
|
<div class="item-row">
|
||||||
|
{% if loop.first and step.next is null %}
|
||||||
|
<div class="item-col">
|
||||||
|
{{ 'workflow.No transitions'|trans }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="item-col flex-column align-items-end">
|
||||||
|
<div class="decided">
|
||||||
|
{% if not loop.first %}
|
||||||
|
<i class="fa fa-check fa-fw text-success"></i>
|
||||||
|
{% endif %}
|
||||||
|
{{ step.currentStep }}
|
||||||
|
</div>
|
||||||
|
{#
|
||||||
|
<div class="decided">
|
||||||
|
<i class="fa fa-times fa-fw text-danger"></i>
|
||||||
|
Refusé
|
||||||
|
</div>
|
||||||
|
#}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if step.next is not null %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col" style="width: inherit;">
|
||||||
|
{% if step.transitionBy is not null %}
|
||||||
|
<div>
|
||||||
|
{{ step.transitionBy|chill_entity_render_box }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<span>{{ step.transitionAt|format_datetime('long', 'medium') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-col flex-column align-items-end">
|
||||||
|
<div class="to-decision">
|
||||||
|
<i class="fa fa-share fa-fw text-secondary" title="transféré"></i>
|
||||||
|
{{ step.next.currentStep }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if step.comment is not empty %}
|
||||||
|
<div class="item-row separator">
|
||||||
|
<blockquote class="chill-user-quote col">
|
||||||
|
{{ step.comment|chill_markdown_to_html }}
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,12 @@
|
|||||||
|
<div class="item-row col">
|
||||||
|
<h2>
|
||||||
|
{{ 'workflow_'|trans }}
|
||||||
|
</h2>
|
||||||
|
{% include handler.templateTitle(l.entity_workflow) with handler.templateTitleData(entity_workflow)|merge({
|
||||||
|
'description': true,
|
||||||
|
'breadcrumb': true,
|
||||||
|
'add_classes': 'ms-3 h3'
|
||||||
|
}) %}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
@ -0,0 +1,56 @@
|
|||||||
|
{% extends '@ChillMain/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ 'Workflow'|trans }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{{ parent() }}
|
||||||
|
|
||||||
|
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||||
|
{{ encore_entry_script_tags('mod_pickentity_type') }}
|
||||||
|
{{ encore_entry_script_tags('mod_entity_workflow_subscribe') }}
|
||||||
|
{{ encore_entry_script_tags('page_workflow_show') }}
|
||||||
|
{{ encore_entry_script_tags('mod_wopi_link') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_link_tags('mod_pickentity_type') }}
|
||||||
|
{{ encore_entry_link_tags('mod_entity_workflow_subscribe') }}
|
||||||
|
{{ encore_entry_link_tags('page_workflow_show') }}
|
||||||
|
{{ encore_entry_link_tags('mod_wopi_link') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% 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
|
||||||
|
- src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig
|
||||||
|
#}
|
||||||
|
<section class="step my-4">
|
||||||
|
<div class="mb-5">
|
||||||
|
{% include handler_template_title with handler_template_data|merge({'breadcrumb': true }) %}
|
||||||
|
</div>
|
||||||
|
{% include handler_template with handler_template_data|merge({'display_action': true }) %}
|
||||||
|
</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> #}
|
||||||
|
<section class="step my-4">{% include '@ChillMain/Workflow/_history.html.twig' %}</section>
|
||||||
|
|
||||||
|
{# useful ?
|
||||||
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<li class="cancel">
|
||||||
|
<a class="btn btn-cancel" href="{{ path('chill_main_workflow_list_dest') }}">
|
||||||
|
{{ 'Back to the list'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
#}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,97 @@
|
|||||||
|
{% extends 'ChillMainBundle::layout.html.twig' %}
|
||||||
|
|
||||||
|
{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as macro %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ 'workflow.My workflows'|trans }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% 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') }}"
|
||||||
|
class="nav-link {% if step == 'subscribed' %}active{% endif %}">
|
||||||
|
{{ 'workflow.subscribed'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="{{ path('chill_main_workflow_list_dest') }}"
|
||||||
|
class="nav-link {% if step == 'dest' %}active{% endif %}">
|
||||||
|
{{ 'workflow.dest'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% if workflows|length == 0 %}
|
||||||
|
<p class="chill-no-data-statement">{{ 'workflow.No workflow'|trans }}</p>
|
||||||
|
{% else %}
|
||||||
|
<div class="flex-table accordion accordion-flush" id="workflow-fold">
|
||||||
|
{% for l in workflows %}
|
||||||
|
<div class="item-bloc">
|
||||||
|
<div class="accordion-header" id="flush-heading-{{ l.entity_workflow.id }}">
|
||||||
|
<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 }}
|
||||||
|
</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>
|
||||||
|
<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
|
||||||
|
}) %}
|
||||||
|
</div>
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col flex-grow-1">
|
||||||
|
<p>
|
||||||
|
{% if l.entity_workflow.isUserSubscribedToStep(app.user) %}
|
||||||
|
<i class="fa fa-check fa-fw"></i>
|
||||||
|
{{ 'workflow.you subscribed to all steps'|trans }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{% if l.entity_workflow.isUserSubscribedToFinal(app.user) %}
|
||||||
|
<i class="fa fa-check fa-fw"></i>
|
||||||
|
{{ 'workflow.you subscribed to final step'|trans }}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="item-col">
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_main_workflow_show', {'id': l.entity_workflow.id}) }}"
|
||||||
|
class="btn btn-show">
|
||||||
|
{{ 'Show'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,45 @@
|
|||||||
|
{% macro popoverContent(step) %}
|
||||||
|
<ul class="small_in_title">
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'By'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ step.transitionBy|chill_entity_render_box }}</b>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ step.transitionAt|format_datetime('short', 'short') }}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro breadcrumb(_ctx) %}
|
||||||
|
<div class="breadcrumb">
|
||||||
|
{% for step in _ctx.entity_workflow.stepsChained %}
|
||||||
|
{% if step.previous is null %}
|
||||||
|
{#
|
||||||
|
{% set popContent = "Point de départ du workflow" %}
|
||||||
|
{{ dump(step) }}
|
||||||
|
#}
|
||||||
|
{% set popContent = _self.popoverContent(step) %}
|
||||||
|
{% else %}
|
||||||
|
{% set popContent = _self.popoverContent(step.previous) %}
|
||||||
|
{% endif %}
|
||||||
|
<span class="mx-2"
|
||||||
|
tabindex="0"
|
||||||
|
data-bs-trigger="focus hover"
|
||||||
|
data-bs-toggle="popover"
|
||||||
|
data-bs-placement="bottom"
|
||||||
|
data-bs-custom-class="workflow-transition"
|
||||||
|
title="{{ step.currentStep }}"
|
||||||
|
data-bs-content="{{ popContent|e('html_attr') }}"
|
||||||
|
>
|
||||||
|
{% if step.currentStep == 'initial' %}
|
||||||
|
<i class="fa fa-circle me-1 text-chill-yellow"></i>
|
||||||
|
{% endif %}
|
||||||
|
{{ step.currentStep }}
|
||||||
|
</span>
|
||||||
|
{% if not loop.last %}
|
||||||
|
→
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
@ -0,0 +1,13 @@
|
|||||||
|
{{ dest.label }},
|
||||||
|
|
||||||
|
Un suivi "{{ workflow.text }}" a atteint une nouvelle étape: {{ workflow.text }}
|
||||||
|
{%- if is_dest %}
|
||||||
|
|
||||||
|
Vous êtes invités à valider cette étape au plus tôt.
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
Vous pouvez visualiser le workflow sur cette page:
|
||||||
|
|
||||||
|
{{ absolute_url(path('chill_main_workflow_show', {'id': entity_workflow.id})) }}
|
||||||
|
|
||||||
|
Cordialement,
|
@ -0,0 +1,5 @@
|
|||||||
|
{%- if is_dest -%}
|
||||||
|
Un suivi {{ workflow.text }} demande votre attention
|
||||||
|
{%- else -%}
|
||||||
|
Un suivi {{ workflow.text }} a atteint une nouvelle étape: {{ place.text }}
|
||||||
|
{%- endif -%}
|
@ -69,6 +69,15 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
|||||||
'counter' => $nbNotifications,
|
'counter' => $nbNotifications,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$menu
|
||||||
|
->addChild(
|
||||||
|
$this->translator->trans('workflow.My workflows'),
|
||||||
|
['route' => 'chill_main_workflow_list_dest']
|
||||||
|
)
|
||||||
|
->setExtras([
|
||||||
|
'order' => 700,
|
||||||
|
]);
|
||||||
|
|
||||||
$menu
|
$menu
|
||||||
->addChild(
|
->addChild(
|
||||||
'Change password',
|
'Change password',
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
<?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\Security\Authorization;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use UnexpectedValueException;
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
class EntityWorkflowVoter extends Voter
|
||||||
|
{
|
||||||
|
public const CREATE = 'CHILL_MAIN_WORKFLOW_CREATE';
|
||||||
|
|
||||||
|
public const SEE = 'CHILL_MAIN_WORKFLOW_SEE';
|
||||||
|
|
||||||
|
private EntityWorkflowManager $manager;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
public function __construct(EntityWorkflowManager $manager, Security $security)
|
||||||
|
{
|
||||||
|
$this->manager = $manager;
|
||||||
|
$this->security = $security;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function supports($attribute, $subject)
|
||||||
|
{
|
||||||
|
return $subject instanceof EntityWorkflow && in_array($attribute, self::getRoles(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||||
|
{
|
||||||
|
switch ($attribute) {
|
||||||
|
case self::CREATE:
|
||||||
|
case self::SEE:
|
||||||
|
$handler = $this->manager->getHandler($subject);
|
||||||
|
|
||||||
|
$entityAttribute = $handler->getRoleShow($subject);
|
||||||
|
|
||||||
|
if (null === $entityAttribute) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->security->isGranted($entityAttribute, $handler->getRelatedEntity($subject));
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new UnexpectedValueException("attribute {$attribute} not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getRoles(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::SEE,
|
||||||
|
self::CREATE,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
<?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\Tests\Entity\Workflow;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
final class EntityWorkflowTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testIsFinalizeWith1Steps()
|
||||||
|
{
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
|
||||||
|
$entityWorkflow->getCurrentStep()->setFinalizeAfter(true);
|
||||||
|
$entityWorkflow->setStep('final');
|
||||||
|
|
||||||
|
$this->assertTrue($entityWorkflow->isFinalize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsFinalizeWith4Steps()
|
||||||
|
{
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
|
||||||
|
$this->assertFalse($entityWorkflow->isFinalize());
|
||||||
|
|
||||||
|
$entityWorkflow->setStep('two');
|
||||||
|
|
||||||
|
$this->assertFalse($entityWorkflow->isFinalize());
|
||||||
|
|
||||||
|
$entityWorkflow->setStep('previous_final');
|
||||||
|
|
||||||
|
$this->assertFalse($entityWorkflow->isFinalize());
|
||||||
|
|
||||||
|
$entityWorkflow->getCurrentStep()->setFinalizeAfter(true);
|
||||||
|
$entityWorkflow->setStep('final');
|
||||||
|
|
||||||
|
$this->assertTrue($entityWorkflow->isFinalize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIsFreeze()
|
||||||
|
{
|
||||||
|
$entityWorkflow = new EntityWorkflow();
|
||||||
|
|
||||||
|
$this->assertFalse($entityWorkflow->isFreeze());
|
||||||
|
|
||||||
|
$entityWorkflow->setStep('step_one');
|
||||||
|
|
||||||
|
$this->assertFalse($entityWorkflow->isFreeze());
|
||||||
|
|
||||||
|
$entityWorkflow->setStep('step_three');
|
||||||
|
|
||||||
|
$this->assertFalse($entityWorkflow->isFreeze());
|
||||||
|
|
||||||
|
$entityWorkflow->getCurrentStep()->setFreezeAfter(true);
|
||||||
|
|
||||||
|
$this->assertFalse($entityWorkflow->isFreeze());
|
||||||
|
|
||||||
|
$entityWorkflow->setStep('freezed');
|
||||||
|
|
||||||
|
$this->assertTrue($entityWorkflow->isFreeze());
|
||||||
|
|
||||||
|
$entityWorkflow->setStep('after_freeze');
|
||||||
|
|
||||||
|
$this->assertTrue($entityWorkflow->isFreeze());
|
||||||
|
|
||||||
|
$this->assertTrue($entityWorkflow->getCurrentStep()->isFreezeAfter());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
|
||||||
|
interface EntityWorkflowHandlerInterface
|
||||||
|
{
|
||||||
|
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a string representing the role required for seeing the workflow.
|
||||||
|
*
|
||||||
|
* Return Null if any check is required, or a role name. The voter will check for
|
||||||
|
* authorization with the role as attribute, and the
|
||||||
|
*/
|
||||||
|
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string;
|
||||||
|
|
||||||
|
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Workflow\Exception\HandlerNotFoundException;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
|
||||||
|
class EntityWorkflowManager
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var iterable|EntityWorkflowHandlerInterface[]
|
||||||
|
*/
|
||||||
|
private iterable $handlers;
|
||||||
|
|
||||||
|
private Registry $registry;
|
||||||
|
|
||||||
|
public function __construct(iterable $handlers, Registry $registry)
|
||||||
|
{
|
||||||
|
$this->handlers = $handlers;
|
||||||
|
$this->registry = $registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHandler(EntityWorkflow $entityWorkflow, array $options = []): EntityWorkflowHandlerInterface
|
||||||
|
{
|
||||||
|
foreach ($this->handlers as $handler) {
|
||||||
|
if ($handler->supports($entityWorkflow, $options)) {
|
||||||
|
return $handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HandlerNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSupportedWorkflows(EntityWorkflow $entityWorkflow): array
|
||||||
|
{
|
||||||
|
return $this->registry->all($entityWorkflow);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
<?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\User;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Templating\Entity\UserRender;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Workflow\Event\Event;
|
||||||
|
use Symfony\Component\Workflow\Event\GuardEvent;
|
||||||
|
use Symfony\Component\Workflow\TransitionBlocker;
|
||||||
|
|
||||||
|
class EntityWorkflowTransitionEventSubscriber implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
private LoggerInterface $chillLogger;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
private UserRender $userRender;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
LoggerInterface $chillLogger,
|
||||||
|
Security $security,
|
||||||
|
UserRender $userRender
|
||||||
|
) {
|
||||||
|
$this->chillLogger = $chillLogger;
|
||||||
|
$this->security = $security;
|
||||||
|
$this->userRender = $userRender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'workflow.transition' => 'onTransition',
|
||||||
|
'workflow.guard' => [
|
||||||
|
['guardEntityWorkflow', 0],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function guardEntityWorkflow(GuardEvent $event)
|
||||||
|
{
|
||||||
|
if (!$event->getSubject() instanceof EntityWorkflow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var EntityWorkflow $entityWorkflow */
|
||||||
|
$entityWorkflow = $event->getSubject();
|
||||||
|
|
||||||
|
if ($entityWorkflow->isFinalize()) {
|
||||||
|
$event->addTransitionBlocker(
|
||||||
|
new TransitionBlocker(
|
||||||
|
'workflow.The workflow is finalized',
|
||||||
|
'd6306280-7535-11ec-a40d-1f7bee26e2c0'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$entityWorkflow->getCurrentStep()->getDestUser()->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%',
|
||||||
|
'f3eeb57c-7532-11ec-9495-e7942a2ac7bc',
|
||||||
|
[
|
||||||
|
'%users%' => implode(
|
||||||
|
', ',
|
||||||
|
$entityWorkflow->getCurrentStep()->getDestUser()->map(function (User $u) {
|
||||||
|
return $this->userRender->renderString($u, []);
|
||||||
|
})->toArray()
|
||||||
|
),
|
||||||
|
]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onTransition(Event $event)
|
||||||
|
{
|
||||||
|
if (!$event->getSubject() instanceof EntityWorkflow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var EntityWorkflow $entityWorkflow */
|
||||||
|
$entityWorkflow = $event->getSubject();
|
||||||
|
$step = $entityWorkflow->getCurrentStep();
|
||||||
|
|
||||||
|
$step
|
||||||
|
->setTransitionAfter($event->getTransition()->getName())
|
||||||
|
->setTransitionAt(new DateTimeImmutable('now'))
|
||||||
|
->setTransitionBy($this->security->getUser());
|
||||||
|
|
||||||
|
$this->chillLogger->info('[workflow] apply transition on entityWorkflow', [
|
||||||
|
'relatedEntityClass' => $entityWorkflow->getRelatedEntityClass(),
|
||||||
|
'relatedEntityId' => $entityWorkflow->getRelatedEntityId(),
|
||||||
|
'transition' => $event->getTransition()->getName(),
|
||||||
|
'by_user' => $this->security->getUser(),
|
||||||
|
'entityWorkflow' => $entityWorkflow->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
<?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\Notification;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
use Symfony\Component\Templating\EngineInterface;
|
||||||
|
use Symfony\Component\Workflow\Event\Event;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
use function in_array;
|
||||||
|
|
||||||
|
class NotificationOnTransition implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
private EngineInterface $engine;
|
||||||
|
|
||||||
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private MetadataExtractor $metadataExtractor;
|
||||||
|
|
||||||
|
private Registry $registry;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager, EngineInterface $engine, MetadataExtractor $metadataExtractor, Security $security, Registry $registry)
|
||||||
|
{
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->engine = $engine;
|
||||||
|
$this->metadataExtractor = $metadataExtractor;
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->security = $security;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'workflow.completed' => 'onCompleted',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onCompleted(Event $event): void
|
||||||
|
{
|
||||||
|
if (!$event->getSubject() instanceof EntityWorkflow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var EntityWorkflow $entityWorkflow */
|
||||||
|
$entityWorkflow = $event->getSubject();
|
||||||
|
|
||||||
|
$dests = array_merge(
|
||||||
|
$entityWorkflow->getSubscriberToStep()->toArray(),
|
||||||
|
$entityWorkflow->isFinalize() ? $entityWorkflow->getSubscriberToFinal()->toArray() : [],
|
||||||
|
$entityWorkflow->getCurrentStep()->getDestUser()->toArray()
|
||||||
|
);
|
||||||
|
|
||||||
|
$place = $this->metadataExtractor->buildArrayPresentationForPlace($entityWorkflow);
|
||||||
|
$workflow = $this->metadataExtractor->buildArrayPresentationForWorkflow(
|
||||||
|
$this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName())
|
||||||
|
);
|
||||||
|
|
||||||
|
$visited = [];
|
||||||
|
|
||||||
|
foreach ($dests as $subscriber) {
|
||||||
|
if (
|
||||||
|
$this->security->getUser() === $subscriber
|
||||||
|
|| in_array($subscriber->getId(), $visited, true)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = [
|
||||||
|
'entity_workflow' => $entityWorkflow,
|
||||||
|
'dest' => $subscriber,
|
||||||
|
'place' => $place,
|
||||||
|
'workflow' => $workflow,
|
||||||
|
'is_dest' => $entityWorkflow->getCurrentStep()->getDestUser()->contains($subscriber),
|
||||||
|
];
|
||||||
|
|
||||||
|
$notification = new Notification();
|
||||||
|
$notification
|
||||||
|
->setRelatedEntityId($entityWorkflow->getId())
|
||||||
|
->setRelatedEntityClass(EntityWorkflow::class)
|
||||||
|
->setTitle($this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_title.fr.txt.twig', $context))
|
||||||
|
->setMessage($this->engine->render('@ChillMain/Workflow/workflow_notification_on_transition_completed_content.fr.txt.twig', $context))
|
||||||
|
->addAddressee($subscriber);
|
||||||
|
$this->entityManager->persist($notification);
|
||||||
|
|
||||||
|
$visited[] = $subscriber->getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
<?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\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class HandlerNotFoundException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
<?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\Helper;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
use Symfony\Component\Workflow\WorkflowInterface;
|
||||||
|
use function array_key_exists;
|
||||||
|
|
||||||
|
class MetadataExtractor
|
||||||
|
{
|
||||||
|
private Registry $registry;
|
||||||
|
|
||||||
|
private TranslatableStringHelperInterface $translatableStringHelper;
|
||||||
|
|
||||||
|
public function __construct(Registry $registry, TranslatableStringHelperInterface $translatableStringHelper)
|
||||||
|
{
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function availableWorkflowFor(string $relatedEntityClass, ?int $relatedEntityId = 0): array
|
||||||
|
{
|
||||||
|
$blankEntityWorkflow = new EntityWorkflow();
|
||||||
|
$blankEntityWorkflow
|
||||||
|
->setRelatedEntityId($relatedEntityId)
|
||||||
|
->setRelatedEntityClass($relatedEntityClass);
|
||||||
|
|
||||||
|
// build the list of available workflows, and extract their names from metadata
|
||||||
|
$workflows = $this->registry->all($blankEntityWorkflow);
|
||||||
|
$workflowsList = [];
|
||||||
|
|
||||||
|
foreach ($workflows as $workflow) {
|
||||||
|
$metadata = $workflow->getMetadataStore()->getWorkflowMetadata();
|
||||||
|
$text = array_key_exists('label', $metadata) ?
|
||||||
|
$this->translatableStringHelper->localize($metadata['label']) : $workflow->getName();
|
||||||
|
|
||||||
|
$workflowsList[] = ['name' => $workflow->getName(), 'text' => $text];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $workflowsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildArrayPresentationForPlace(EntityWorkflow $entityWorkflow): array
|
||||||
|
{
|
||||||
|
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
|
||||||
|
|
||||||
|
$markingMetadata = $workflow->getMetadataStore()->getPlaceMetadata($entityWorkflow->getCurrentStep()->getCurrentStep());
|
||||||
|
|
||||||
|
$text = array_key_exists('label', $markingMetadata) ?
|
||||||
|
$this->translatableStringHelper->localize($markingMetadata['label']) : $entityWorkflow->getCurrentStep()->getCurrentStep();
|
||||||
|
|
||||||
|
return ['name' => $entityWorkflow->getCurrentStep()->getCurrentStep(), 'text' => $text];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildArrayPresentationForWorkflow(WorkflowInterface $workflow): array
|
||||||
|
{
|
||||||
|
$metadata = $workflow->getMetadataStore()->getWorkflowMetadata();
|
||||||
|
$text = array_key_exists('label', $metadata) ?
|
||||||
|
$this->translatableStringHelper->localize($metadata['label']) : $workflow->getName();
|
||||||
|
|
||||||
|
return ['name' => $workflow->getName(), 'text' => $text];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
<?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\Notification;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Notification;
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Notification\NotificationHandlerInterface;
|
||||||
|
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
|
||||||
|
class WorkflowNotificationHandler implements NotificationHandlerInterface
|
||||||
|
{
|
||||||
|
private EntityWorkflowManager $entityWorkflowManager;
|
||||||
|
|
||||||
|
private EntityWorkflowRepository $entityWorkflowRepository;
|
||||||
|
|
||||||
|
public function getTemplate(Notification $notification, array $options = []): string
|
||||||
|
{
|
||||||
|
return '@ChillMain/Workflow/_notification_include.html.twig';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplateData(Notification $notification, array $options = []): array
|
||||||
|
{
|
||||||
|
$entityWorkflow = $this->entityWorkflowRepository->find($notification->getRelatedEntityId());
|
||||||
|
|
||||||
|
return [
|
||||||
|
'entity_workflow' => $entityWorkflow,
|
||||||
|
'handler' => $this->entityWorkflowManager->getHandler($entityWorkflow),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports(Notification $notification, array $options = []): bool
|
||||||
|
{
|
||||||
|
return $notification->getRelatedEntityClass() === EntityWorkflow::class;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface;
|
||||||
|
use Symfony\Component\Workflow\WorkflowInterface;
|
||||||
|
|
||||||
|
class RelatedEntityWorkflowSupportsStrategy implements WorkflowSupportStrategyInterface
|
||||||
|
{
|
||||||
|
public function supports(WorkflowInterface $workflow, $subject): bool
|
||||||
|
{
|
||||||
|
if (!$subject instanceof EntityWorkflow) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($workflow->getMetadataStore()->getWorkflowMetadata()['related_entity']
|
||||||
|
as $relatedEntityClass) {
|
||||||
|
if ($subject->getRelatedEntityClass() === $relatedEntityClass) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
<?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\Templating;
|
||||||
|
|
||||||
|
use Twig\Extension\AbstractExtension;
|
||||||
|
use Twig\TwigFunction;
|
||||||
|
|
||||||
|
class WorkflowTwigExtension extends AbstractExtension
|
||||||
|
{
|
||||||
|
public function getFunctions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new TwigFunction(
|
||||||
|
'chill_entity_workflow_list',
|
||||||
|
[WorkflowTwigExtensionRuntime::class, 'listWorkflows'],
|
||||||
|
['needs_environment' => true, 'is_safe' => ['html']]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
<?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\Templating;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
|
||||||
|
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||||
|
use Symfony\Component\Workflow\Registry;
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Extension\RuntimeExtensionInterface;
|
||||||
|
|
||||||
|
class WorkflowTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||||
|
{
|
||||||
|
private EntityWorkflowManager $entityWorkflowManager;
|
||||||
|
|
||||||
|
private MetadataExtractor $metadataExtractor;
|
||||||
|
|
||||||
|
private NormalizerInterface $normalizer;
|
||||||
|
|
||||||
|
private Registry $registry;
|
||||||
|
|
||||||
|
private EntityWorkflowRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
EntityWorkflowManager $entityWorkflowManager,
|
||||||
|
Registry $registry,
|
||||||
|
EntityWorkflowRepository $repository,
|
||||||
|
MetadataExtractor $metadataExtractor,
|
||||||
|
NormalizerInterface $normalizer
|
||||||
|
) {
|
||||||
|
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||||
|
$this->registry = $registry;
|
||||||
|
$this->repository = $repository;
|
||||||
|
$this->metadataExtractor = $metadataExtractor;
|
||||||
|
$this->normalizer = $normalizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listWorkflows(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
|
||||||
|
{
|
||||||
|
$blankEntityWorkflow = new EntityWorkflow();
|
||||||
|
$blankEntityWorkflow
|
||||||
|
->setRelatedEntityId($relatedEntityId)
|
||||||
|
->setRelatedEntityClass($relatedEntityClass);
|
||||||
|
|
||||||
|
$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 $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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<?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\Validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validator which will test that:.
|
||||||
|
*
|
||||||
|
* * a handler exists;
|
||||||
|
* * a related entity does exists;
|
||||||
|
* * a workflow can be associated with this entity.
|
||||||
|
*
|
||||||
|
* @Annotation
|
||||||
|
*/
|
||||||
|
class EntityWorkflowCreation extends \Symfony\Component\Validator\Constraint
|
||||||
|
{
|
||||||
|
public string $messageEntityNotFound = 'Related entity is not found';
|
||||||
|
|
||||||
|
public string $messageHandlerNotFound = 'Handler not found for this entity';
|
||||||
|
|
||||||
|
public string $messageWorkflowNotAvailable = 'Workflow is not valid';
|
||||||
|
|
||||||
|
public function getTargets()
|
||||||
|
{
|
||||||
|
return [self::CLASS_CONSTRAINT];
|
||||||
|
}
|
||||||
|
}
|
@ -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\Validator;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||||
|
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||||
|
use Chill\MainBundle\Workflow\Exception\HandlerNotFoundException;
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||||
|
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||||
|
use Symfony\Component\Workflow\WorkflowInterface;
|
||||||
|
use function count;
|
||||||
|
|
||||||
|
class EntityWorkflowCreationValidator extends \Symfony\Component\Validator\ConstraintValidator
|
||||||
|
{
|
||||||
|
private EntityWorkflowManager $entityWorkflowManager;
|
||||||
|
|
||||||
|
public function __construct(EntityWorkflowManager $entityWorkflowManager)
|
||||||
|
{
|
||||||
|
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param EntityWorkflow $value
|
||||||
|
* @param Constraint|EntityWorkflowCreation $constraint
|
||||||
|
*/
|
||||||
|
public function validate($value, Constraint $constraint)
|
||||||
|
{
|
||||||
|
if (!$value instanceof EntityWorkflow) {
|
||||||
|
throw new UnexpectedValueException($value, EntityWorkflow::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$constraint instanceof EntityWorkflowCreation) {
|
||||||
|
throw new UnexpectedTypeException($constraint, EntityWorkflowCreation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$handler = $this->entityWorkflowManager->getHandler($value);
|
||||||
|
} catch (HandlerNotFoundException $e) {
|
||||||
|
$this->context->buildViolation($constraint->messageHandlerNotFound)
|
||||||
|
->addViolation();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $handler->getRelatedEntity($value)) {
|
||||||
|
$this->context->buildViolation($constraint->messageEntityNotFound)
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
|
||||||
|
$workflows = $this->entityWorkflowManager->getSupportedWorkflows($value);
|
||||||
|
|
||||||
|
$matched = array_filter($workflows, static function (WorkflowInterface $workflow) use ($value) {
|
||||||
|
return $workflow->getName() === $value->getWorkflowName();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (0 === count($matched)) {
|
||||||
|
$this->context->buildViolation($constraint->messageWorkflowNotAvailable)
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -52,6 +52,7 @@ module.exports = function(encore, entries)
|
|||||||
// Page entrypoints
|
// Page entrypoints
|
||||||
encore.addEntry('page_login', __dirname + '/Resources/public/page/login/index.js');
|
encore.addEntry('page_login', __dirname + '/Resources/public/page/login/index.js');
|
||||||
encore.addEntry('page_location', __dirname + '/Resources/public/page/location/index.js');
|
encore.addEntry('page_location', __dirname + '/Resources/public/page/location/index.js');
|
||||||
|
encore.addEntry('page_workflow_show', __dirname + '/Resources/public/page/workflow-show/index.js');
|
||||||
|
|
||||||
buildCKEditor(encore);
|
buildCKEditor(encore);
|
||||||
|
|
||||||
@ -64,6 +65,9 @@ module.exports = function(encore, entries)
|
|||||||
encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js');
|
encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js');
|
||||||
encore.addEntry('mod_notification_toggle_read_status', __dirname + '/Resources/public/module/notification/toggle_read.js');
|
encore.addEntry('mod_notification_toggle_read_status', __dirname + '/Resources/public/module/notification/toggle_read.js');
|
||||||
encore.addEntry('mod_pickentity_type', __dirname + '/Resources/public/module/pick-entity/index.js');
|
encore.addEntry('mod_pickentity_type', __dirname + '/Resources/public/module/pick-entity/index.js');
|
||||||
|
encore.addEntry('mod_entity_workflow_subscribe', __dirname + '/Resources/public/module/entity-workflow-subscribe/index.js');
|
||||||
|
encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js');
|
||||||
|
encore.addEntry('mod_wopi_link', __dirname + '/Resources/public/module/wopi-link/index.js');
|
||||||
|
|
||||||
// Vue entrypoints
|
// Vue entrypoints
|
||||||
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');
|
encore.addEntry('vue_address', __dirname + '/Resources/public/vuejs/Address/index.js');
|
||||||
|
@ -26,6 +26,20 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: 'doctrine.event_subscriber' }
|
- { name: 'doctrine.event_subscriber' }
|
||||||
|
|
||||||
|
# workflow related
|
||||||
|
Chill\MainBundle\Workflow\:
|
||||||
|
resource: '../Workflow/'
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
|
||||||
|
Chill\MainBundle\Workflow\EntityWorkflowManager:
|
||||||
|
autoconfigure: true
|
||||||
|
autowire: true
|
||||||
|
arguments:
|
||||||
|
$handlers: !tagged_iterator chill_main.workflow_handler
|
||||||
|
|
||||||
|
# other stuffes
|
||||||
|
|
||||||
chill.main.helper.translatable_string:
|
chill.main.helper.translatable_string:
|
||||||
class: Chill\MainBundle\Templating\TranslatableStringHelper
|
class: Chill\MainBundle\Templating\TranslatableStringHelper
|
||||||
|
|
||||||
|
@ -141,3 +141,5 @@ services:
|
|||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
Chill\MainBundle\Form\Type\LocationFormType: ~
|
Chill\MainBundle\Form\Type\LocationFormType: ~
|
||||||
|
|
||||||
|
Chill\MainBundle\Form\WorkflowStepType: ~
|
||||||
|
@ -26,6 +26,8 @@ services:
|
|||||||
|
|
||||||
Chill\MainBundle\Security\Authorization\NotificationVoter: ~
|
Chill\MainBundle\Security\Authorization\NotificationVoter: ~
|
||||||
|
|
||||||
|
Chill\MainBundle\Security\Authorization\EntityWorkflowVoter: ~
|
||||||
|
|
||||||
Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface: '@Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory'
|
Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface: '@Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory'
|
||||||
|
|
||||||
chill.main.security.authorization.helper:
|
chill.main.security.authorization.helper:
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
<?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 Version20220112123436 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_final DROP CONSTRAINT FK_C2CE504C7D99CE94');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_step DROP CONSTRAINT FK_ECB8F5417D99CE94');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_step DROP CONSTRAINT FK_440AA6FEFB054143');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_entity_workflow_step_user DROP CONSTRAINT FK_A9F001FA7E6AF9D4');
|
||||||
|
$this->addSql('DROP SEQUENCE chill_main_workflow_entity_id_seq CASCADE');
|
||||||
|
$this->addSql('DROP SEQUENCE chill_main_workflow_entity_step_id_seq CASCADE');
|
||||||
|
$this->addSql('DROP TABLE chill_main_workflow_entity');
|
||||||
|
$this->addSql('DROP TABLE chill_main_workflow_entity_subscriber_to_final');
|
||||||
|
$this->addSql('DROP TABLE chill_main_workflow_entity_subscriber_to_step');
|
||||||
|
$this->addSql('DROP TABLE chill_main_workflow_entity_step');
|
||||||
|
$this->addSql('DROP TABLE chill_main_entity_workflow_step_user');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Create tables for workflow';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE SEQUENCE chill_main_workflow_entity_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||||
|
$this->addSql('CREATE SEQUENCE chill_main_workflow_entity_step_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||||
|
$this->addSql('CREATE TABLE chill_main_workflow_entity (id INT NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, relatedEntityClass VARCHAR(255) NOT NULL, relatedEntityId INT NOT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, workflowName TEXT NOT NULL, createdBy_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_5F087D553174800F ON chill_main_workflow_entity (createdBy_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_5F087D5565FF1AEC ON chill_main_workflow_entity (updatedBy_id)');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity.updatedAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('CREATE TABLE chill_main_workflow_entity_subscriber_to_final (entityworkflow_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflow_id, user_id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_C2CE504C7D99CE94 ON chill_main_workflow_entity_subscriber_to_final (entityworkflow_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_C2CE504CA76ED395 ON chill_main_workflow_entity_subscriber_to_final (user_id)');
|
||||||
|
$this->addSql('CREATE TABLE chill_main_workflow_entity_subscriber_to_step (entityworkflow_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflow_id, user_id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_ECB8F5417D99CE94 ON chill_main_workflow_entity_subscriber_to_step (entityworkflow_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_ECB8F541A76ED395 ON chill_main_workflow_entity_subscriber_to_step (user_id)');
|
||||||
|
$this->addSql('CREATE TABLE chill_main_workflow_entity_step (id INT NOT NULL, currentStep TEXT NOT NULL, destEmail JSON NOT NULL, finalizeAfter BOOLEAN DEFAULT \'false\' NOT NULL, freezeAfter BOOLEAN DEFAULT \'false\' NOT NULL, transitionAfter TEXT DEFAULT NULL, transitionByEmail TEXT DEFAULT NULL, transitionAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, entityWorkflow_id INT DEFAULT NULL, transitionBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_440AA6FEFB054143 ON chill_main_workflow_entity_step (entityWorkflow_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_440AA6FE8829EF37 ON chill_main_workflow_entity_step (transitionBy_id)');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_step.transitionAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('CREATE TABLE chill_main_entity_workflow_step_user (entityworkflowstep_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflowstep_id, user_id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_A9F001FA7E6AF9D4 ON chill_main_entity_workflow_step_user (entityworkflowstep_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_A9F001FAA76ED395 ON chill_main_entity_workflow_step_user (user_id)');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity ADD CONSTRAINT FK_5F087D553174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity ADD CONSTRAINT FK_5F087D5565FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_final ADD CONSTRAINT FK_C2CE504C7D99CE94 FOREIGN KEY (entityworkflow_id) REFERENCES chill_main_workflow_entity (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_final ADD CONSTRAINT FK_C2CE504CA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_step ADD CONSTRAINT FK_ECB8F5417D99CE94 FOREIGN KEY (entityworkflow_id) REFERENCES chill_main_workflow_entity (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_subscriber_to_step ADD CONSTRAINT FK_ECB8F541A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ADD CONSTRAINT FK_440AA6FEFB054143 FOREIGN KEY (entityWorkflow_id) REFERENCES chill_main_workflow_entity (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ADD CONSTRAINT FK_440AA6FE8829EF37 FOREIGN KEY (transitionBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_entity_workflow_step_user ADD CONSTRAINT FK_A9F001FA7E6AF9D4 FOREIGN KEY (entityworkflowstep_id) REFERENCES chill_main_workflow_entity_step (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_entity_workflow_step_user ADD CONSTRAINT FK_A9F001FAA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
<?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 Version20220114132105 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('DROP SEQUENCE chill_main_workflow_entity_comment_id_seq CASCADE');
|
||||||
|
$this->addSql('DROP TABLE chill_main_workflow_entity_comment');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_step DROP comment');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_user RENAME TO chill_main_entity_workflow_step_user');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add comment to entity workflow';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE SEQUENCE chill_main_workflow_entity_comment_id_seq INCREMENT BY 1 MINVALUE 1 START 1;');
|
||||||
|
$this->addSql('CREATE TABLE chill_main_workflow_entity_comment (id INT NOT NULL, comment TEXT NOT NULL DEFAULT \'\', createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, createdBy_id INT DEFAULT NULL, entityWorkflow_id INT DEFAULT NULL, updatedBy_id INT DEFAULT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_2655252F3174800F ON chill_main_workflow_entity_comment (createdBy_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_2655252FFB054143 ON chill_main_workflow_entity_comment (entityWorkflow_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_2655252F65FF1AEC ON chill_main_workflow_entity_comment (updatedBy_id)');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_comment.createdAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('COMMENT ON COLUMN chill_main_workflow_entity_comment.updatedAt IS \'(DC2Type:datetime_immutable)\'');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_comment ADD CONSTRAINT FK_2655252F3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_comment ADD CONSTRAINT FK_2655252FFB054143 FOREIGN KEY (entityWorkflow_id) REFERENCES chill_main_workflow_entity (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_comment ADD CONSTRAINT FK_2655252F65FF1AEC FOREIGN KEY (updatedBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ADD comment TEXT NOT NULL DEFAULT \'\'');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_entity_workflow_step_user RENAME TO chill_main_workflow_entity_step_user');
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20220114165950 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER transitionat SET NOT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'remove not null on transition at';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER transitionat DROP NOT NULL');
|
||||||
|
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER transitionat SET DEFAULT NULL');
|
||||||
|
}
|
||||||
|
}
|
@ -36,8 +36,9 @@ Choose an user: Choisir un utilisateur
|
|||||||
"You are going to leave a page with unsubmitted data. Are you sure you want to leave ?": "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?"
|
"You are going to leave a page with unsubmitted data. Are you sure you want to leave ?": "Vous allez quitter la page alors que des données n'ont pas été enregistrées. Êtes vous sûr de vouloir partir ?"
|
||||||
No value: Aucune information
|
No value: Aucune information
|
||||||
Last updated by: Dernière mise à jour par
|
Last updated by: Dernière mise à jour par
|
||||||
Last updated on: Dernière mise à jour le
|
|
||||||
on: "le "
|
on: "le "
|
||||||
|
Last updated on: Dernière mise à jour le
|
||||||
|
by_user: "par "
|
||||||
|
|
||||||
Edit: Modifier
|
Edit: Modifier
|
||||||
Update: Mettre à jour
|
Update: Mettre à jour
|
||||||
@ -354,6 +355,43 @@ For: Pour
|
|||||||
Created for: Créé pour
|
Created for: Créé pour
|
||||||
Created by: Créé par
|
Created by: Créé par
|
||||||
|
|
||||||
|
|
||||||
|
# Workflows 💊
|
||||||
|
Workflow: Workflow — chemin de décision
|
||||||
|
Workflow n°%id%: 'Workflow (n°%id%)'
|
||||||
|
workflow_: Workflow
|
||||||
|
target: ' (cible)'
|
||||||
|
Decision: Décision
|
||||||
|
Join a comment: Laisser un commentaire
|
||||||
|
Follow workflow: Suivre la décision
|
||||||
|
Workflow history: Historique de la décision
|
||||||
|
|
||||||
|
workflow:
|
||||||
|
Created by: Créé par
|
||||||
|
Transition: Prochaine étape
|
||||||
|
dest for next steps: Utilisateurs qui valideront la prochaine étape
|
||||||
|
Freeze: Geler
|
||||||
|
The associated element will be freezed: L'élément associé sera gelé et ne pourra plus être modifié après cette décision.
|
||||||
|
Finalize: Étape finale
|
||||||
|
The workflow will be finalized: Le suivi est clôturé lors de cette décision.
|
||||||
|
No transitions: Aucune transition
|
||||||
|
Comment added: Commentaire ajouté
|
||||||
|
This workflow is finalized: Ce suivi est finalisé.
|
||||||
|
You are not allowed to apply a transition on this workflow: Vous n'êtes pas autorisé à appliquer une décision pour ce suivi
|
||||||
|
Only those users are allowed: Seuls ces utilisateurs sont autorisés
|
||||||
|
My workflows: Mes workflows
|
||||||
|
No workflow: Aucun workflow
|
||||||
|
Evaluation (n°%eval%): "Évaluation (n°%eval%)"
|
||||||
|
Document (n°%doc%): "Document (n°%doc%)"
|
||||||
|
Work (n°%w%): "Action d'accompagnement (n°%w%)"
|
||||||
|
subscribed: Souscrit
|
||||||
|
dest: Destinataire de l'étape finale
|
||||||
|
you subscribed to all steps: Vous recevrez une notification à chaque étape
|
||||||
|
you subscribed to final step: Vous recevrez une notification à l'étape finale
|
||||||
|
|
||||||
|
Subscribe final: Recevoir une notification à l'étape finale
|
||||||
|
Subscribe all steps: Recevoir une notification à chaque étape
|
||||||
|
|
||||||
notification:
|
notification:
|
||||||
Notification: Notification
|
Notification: Notification
|
||||||
My own notifications: Mes notifications
|
My own notifications: Mes notifications
|
||||||
|
@ -0,0 +1,61 @@
|
|||||||
|
<?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\PersonBundle\Repository\AccompanyingPeriod;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
|
||||||
|
class AccompanyingPeriodWorkEvaluationRepository implements ObjectRepository
|
||||||
|
{
|
||||||
|
private EntityRepository $repository;
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager)
|
||||||
|
{
|
||||||
|
$this->repository = $entityManager->getRepository(AccompanyingPeriodWorkEvaluation::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find($id): ?AccompanyingPeriodWorkEvaluation
|
||||||
|
{
|
||||||
|
return $this->repository->find($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|AccompanyingPeriodWorkEvaluation[]
|
||||||
|
*/
|
||||||
|
public function findAll(): array
|
||||||
|
{
|
||||||
|
return $this->repository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null|mixed $limit
|
||||||
|
* @param null|mixed $offset
|
||||||
|
*
|
||||||
|
* @return array|AccompanyingPeriodWorkEvaluation[]
|
||||||
|
*/
|
||||||
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||||
|
{
|
||||||
|
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findOneBy(array $criteria): ?AccompanyingPeriodWorkEvaluation
|
||||||
|
{
|
||||||
|
return $this->findOneBy($criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName()
|
||||||
|
{
|
||||||
|
return AccompanyingPeriodWorkEvaluation::class;
|
||||||
|
}
|
||||||
|
}
|
@ -241,7 +241,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
|
<!--
|
||||||
|
FAIT REPETER tout le template de App.vue plusieurs fois
|
||||||
|
<li>
|
||||||
|
<pick-workflow
|
||||||
|
relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork"
|
||||||
|
:relatedEntityId="this.work.id"
|
||||||
|
:workflows="this.workflows"
|
||||||
|
></pick-workflow>
|
||||||
|
</li>
|
||||||
|
-->
|
||||||
|
|
||||||
<li v-if="!isPosting">
|
<li v-if="!isPosting">
|
||||||
<button class="btn btn-save" @click="submit">
|
<button class="btn btn-save" @click="submit">
|
||||||
@ -270,6 +280,7 @@ import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRe
|
|||||||
import ThirdPartyRenderBox from 'ChillThirdPartyAssets/vuejs/_components/Entity/ThirdPartyRenderBox.vue';
|
import ThirdPartyRenderBox from 'ChillThirdPartyAssets/vuejs/_components/Entity/ThirdPartyRenderBox.vue';
|
||||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
||||||
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
|
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
|
||||||
|
import PickWorkflow from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue';
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
messages: {
|
messages: {
|
||||||
@ -317,6 +328,7 @@ export default {
|
|||||||
AddressRenderBox,
|
AddressRenderBox,
|
||||||
ThirdPartyRenderBox,
|
ThirdPartyRenderBox,
|
||||||
PickTemplate,
|
PickTemplate,
|
||||||
|
PickWorkflow,
|
||||||
OnTheFly
|
OnTheFly
|
||||||
},
|
},
|
||||||
i18n,
|
i18n,
|
||||||
@ -347,11 +359,11 @@ export default {
|
|||||||
display: false
|
display: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
'work',
|
'work',
|
||||||
'resultsForAction',
|
'resultsForAction',
|
||||||
'evaluationsForAction',
|
'evaluationsForAction',
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="item-title">
|
<div class="item-title" :title="evaluation.id || 'no id yet'">
|
||||||
<span>{{ evaluation.evaluation.title.fr }}</span>
|
<span>{{ evaluation.evaluation.title.fr }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<form-evaluation ref="FormEvaluation" :key="evaluation.key" :evaluation="evaluation"></form-evaluation>
|
<form-evaluation ref="FormEvaluation" :key="evaluation.key" :evaluation="evaluation"></form-evaluation>
|
||||||
<ul class="record_actions">
|
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li v-if="evaluation.workflows_availables.length > 0">
|
||||||
|
<pick-workflow
|
||||||
|
relatedEntityClass="faked"
|
||||||
|
:relatedEntityId="evaluation.id"
|
||||||
|
:workflowsAvailables="evaluation.workflows_availables"
|
||||||
|
@goToGenerateWorkflow="goToGenerateWorkflow"
|
||||||
|
></pick-workflow>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-delete" @click="modal.showModal = true" :title="$t('action.delete')"></a>
|
<a class="btn btn-delete" @click="modal.showModal = true" :title="$t('action.delete')"></a>
|
||||||
</li>
|
</li>
|
||||||
@ -34,6 +43,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import FormEvaluation from './FormEvaluation.vue';
|
import FormEvaluation from './FormEvaluation.vue';
|
||||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||||
|
import PickWorkflow from 'ChillMainAssets/vuejs/_components/EntityWorkflow/PickWorkflow.vue';
|
||||||
|
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
|
||||||
|
|
||||||
const i18n = {
|
const i18n = {
|
||||||
messages: {
|
messages: {
|
||||||
@ -60,7 +71,8 @@ export default {
|
|||||||
name: "AddEvaluation",
|
name: "AddEvaluation",
|
||||||
components: {
|
components: {
|
||||||
FormEvaluation,
|
FormEvaluation,
|
||||||
Modal
|
Modal,
|
||||||
|
PickWorkflow,
|
||||||
},
|
},
|
||||||
props: ['evaluation'],
|
props: ['evaluation'],
|
||||||
i18n,
|
i18n,
|
||||||
@ -88,10 +100,19 @@ export default {
|
|||||||
submitForm() {
|
submitForm() {
|
||||||
this.toggleEditEvaluation();
|
this.toggleEditEvaluation();
|
||||||
},
|
},
|
||||||
buildEditLink(storedObject) {
|
goToGenerateWorkflow({event, link, workflowName}) {
|
||||||
return `/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent(
|
event.preventDefault();
|
||||||
window.location.pathname + window.location.search + window.location.hash);
|
console.log(event, link, workflowName);
|
||||||
},
|
|
||||||
|
const callback = (data) => {
|
||||||
|
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
|
||||||
|
window.location.assign(buildLinkCreate(workflowName,
|
||||||
|
'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation', evaluationId));
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.$store.dispatch('submit', callback)
|
||||||
|
.catch(e => { console.log(e); throw e; });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -61,6 +61,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="evaluation.documents.length > 0" class="row mb-3">
|
||||||
|
<h5>{{ $t('Documents') }} :</h5>
|
||||||
|
|
||||||
|
<div class="flex-table">
|
||||||
|
<div class="item-bloc" v-for="d in evaluation.documents">
|
||||||
|
<div class="item-row">
|
||||||
|
<div class="item-col"><h6>{{ d.template.name.fr }}</h6></div>
|
||||||
|
<div class="item-col">
|
||||||
|
<p>Créé par {{ d.createdBy.text }}<br/>
|
||||||
|
Le {{ $d(ISOToDatetime(d.createdAt.datetime), 'long') }}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item-row">
|
||||||
|
<ul class="record_actions" >
|
||||||
|
<li>
|
||||||
|
<a :href="buildEditLink(d.storedObject)" class="btn btn-action btn-sm">
|
||||||
|
<i class="fa fa-edit"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<pick-template
|
<pick-template
|
||||||
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
|
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
|
||||||
@ -99,7 +125,8 @@ const i18n = {
|
|||||||
evaluation_generate_a_document: "Générer un document",
|
evaluation_generate_a_document: "Générer un document",
|
||||||
evaluation_choose_a_template: "Choisir un gabarit",
|
evaluation_choose_a_template: "Choisir un gabarit",
|
||||||
evaluation_add_a_document: "Ajouter un document",
|
evaluation_add_a_document: "Ajouter un document",
|
||||||
evaluation_add: "Ajouter une évaluation"
|
evaluation_add: "Ajouter une évaluation",
|
||||||
|
Documents: "Documents",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -163,6 +190,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
ISOToDatetime,
|
||||||
listAllStatus() {
|
listAllStatus() {
|
||||||
console.log('load all status');
|
console.log('load all status');
|
||||||
let url = `/api/`;
|
let url = `/api/`;
|
||||||
@ -175,7 +203,11 @@ export default {
|
|||||||
})
|
})
|
||||||
;
|
;
|
||||||
},
|
},
|
||||||
submitBeforeGenerate() {
|
buildEditLink(storedObject) {
|
||||||
|
return `/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent(
|
||||||
|
window.location.pathname + window.location.search + window.location.hash);
|
||||||
|
},
|
||||||
|
submitBeforeGenerate() {
|
||||||
const callback = (data) => {
|
const callback = (data) => {
|
||||||
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
|
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
|
||||||
return Promise.resolve({entityId: evaluationId});
|
return Promise.resolve({entityId: evaluationId});
|
||||||
|
@ -205,6 +205,7 @@ const store = createStore({
|
|||||||
warningInterval: null,
|
warningInterval: null,
|
||||||
comment: "",
|
comment: "",
|
||||||
editEvaluation: true,
|
editEvaluation: true,
|
||||||
|
workflows_availables: state.work.workflows_availables_evaluation,
|
||||||
};
|
};
|
||||||
state.evaluationsPicked.push(e);
|
state.evaluationsPicked.push(e);
|
||||||
},
|
},
|
||||||
@ -371,7 +372,8 @@ const store = createStore({
|
|||||||
if (typeof(callback) !== 'undefined') {
|
if (typeof(callback) !== 'undefined') {
|
||||||
return callback(data);
|
return callback(data);
|
||||||
} else {
|
} else {
|
||||||
console.info('nothing to do here, bye bye');window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`);
|
console.info('nothing to do here, bye bye');
|
||||||
|
window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`);
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.log('error on submit', error);
|
console.log('error on submit', error);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
{% import '@ChillPerson/AccompanyingCourse/Comment/macro_showItem.html.twig' as m %}
|
{% import '@ChillPerson/AccompanyingCourse/Comment/macro_showItem.html.twig' as m %}
|
||||||
|
|
||||||
{% macro recordAction(comment, isPinned) %}
|
{% macro recordAction(comment, isPinned) %}
|
||||||
{% if isPinned is defined and isPinned == 'true' %}
|
{% if isPinned is defined and isPinned == true %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<li>
|
<li>
|
||||||
<form method="post" action="{{ chill_path_forward_return_path('chill_person_accompanying_period_comment_pin', {'id': comment.id}) }}">
|
<form method="post" action="{{ chill_path_forward_return_path('chill_person_accompanying_period_comment_pin', {'id': comment.id}) }}">
|
||||||
@ -66,8 +66,8 @@
|
|||||||
{{ _self.form_comment('edit', edit_form) }}
|
{{ _self.form_comment('edit', edit_form) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ m.show_comment(accompanyingCourse.pinnedComment, {
|
{{ m.show_comment(accompanyingCourse.pinnedComment, {
|
||||||
'pinned': 'true',
|
'pinned': true,
|
||||||
'recordAction': _self.recordAction(accompanyingCourse.pinnedComment, 'true')
|
'recordAction': _self.recordAction(accompanyingCourse.pinnedComment, true)
|
||||||
}) }}
|
}) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -212,16 +212,13 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block block_post_menu %}
|
{% block block_post_menu %}
|
||||||
<div class="post-menu pt-4">
|
<div class="post-menu pt-4">
|
||||||
|
<div class="d-grid gap-2">
|
||||||
<div class="d-grid gap-2">
|
<a class="btn btn-primary" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod', 'entityId': accompanyingCourse.id}) }}">
|
||||||
<a class="btn btn-primary" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod', 'entityId': accompanyingCourse.id}) }}">
|
<i class="fa fa-paper-plane fa-fw"></i>
|
||||||
<i class="fa fa-paper-plane fa-fw"></i>
|
{{ 'notification.Notify'|trans }}
|
||||||
{{ 'notification.Notify'|trans }}
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ chill_list_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) }}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{{ chill_list_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) }}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -0,0 +1,127 @@
|
|||||||
|
<div class="item-bloc accompanying_course_work-item{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
|
||||||
|
|
||||||
|
<div class="item-row">
|
||||||
|
<h2 class="badge-title">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">{{ w.socialAction|chill_entity_render_string }}
|
||||||
|
|
||||||
|
<ul class="small_in_title columns mt-1">
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ w.startDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% if w.endDate %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ w.endDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="wrap-list">
|
||||||
|
|
||||||
|
{% if w.createdBy %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Referrer'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<p class="wl-item">
|
||||||
|
{{ w.createdBy|chill_entity_render_box }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if w.persons -%}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Persons in accompanying course'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{% for p in w.persons %}
|
||||||
|
<span class="wl-item">
|
||||||
|
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||||
|
action: 'show', displayBadge: true,
|
||||||
|
targetEntity: { name: 'person', id: p.id },
|
||||||
|
buttonText: p|chill_entity_render_string,
|
||||||
|
isDead: p.deathdate is not null
|
||||||
|
} %}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if w.handlingThierParty -%}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Thirdparty handling'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<span class="wl-item">
|
||||||
|
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||||
|
action: 'show', displayBadge: true,
|
||||||
|
targetEntity: { name: 'thirdparty', id: w.handlingThierParty.id },
|
||||||
|
buttonText: w.handlingThierParty|chill_entity_render_string
|
||||||
|
} %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{%- if w.socialAction.issue -%}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>{{ 'Social issue'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<p class="wl-item social-issues">
|
||||||
|
{{ w.socialAction.issue|chill_entity_render_box }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-row column">
|
||||||
|
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-row separator">
|
||||||
|
<div class="item-col item-meta">
|
||||||
|
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) %}
|
||||||
|
{% if notif_counter.total > 0 %}
|
||||||
|
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
|
||||||
|
{{ macro.updatedBy(w) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if displayAction is defined and displayAction == true %}
|
||||||
|
<div class="item-col">
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
|
||||||
|
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
|
||||||
|
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
|
||||||
|
></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@ -2,27 +2,34 @@
|
|||||||
|
|
||||||
{% block title 'accompanying_course_work.Edit accompanying course work'|trans %}
|
{% block title 'accompanying_course_work.Edit accompanying course work'|trans %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{{ parent() }}
|
||||||
|
{{ encore_entry_link_tags('vue_accourse_work_edit') }}
|
||||||
|
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="accompanying_course_work-edit">
|
<div class="accompanying_course_work-edit">
|
||||||
|
|
||||||
<h1>{{ block('title') }}</h1>
|
<h1>{{ block('title') }}</h1>
|
||||||
|
|
||||||
<div id="accompanying_course_work_edit"></div>
|
<div id="accompanying_course_work_edit"></div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block block_post_menu %}
|
||||||
|
<div class="post-menu pt-4">
|
||||||
|
{% set workflows_frame = chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', work.id) %}
|
||||||
|
{% if workflows_frame is not empty %}
|
||||||
|
{{ workflows_frame|raw }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
{{ parent() }}
|
{{ parent() }}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.accompanyingCourseWork = {{ json|json_encode|raw }};
|
window.accompanyingCourseWork = {{ json|json_encode|raw }};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{{ encore_entry_script_tags('vue_accourse_work_edit') }}
|
{{ encore_entry_script_tags('vue_accourse_work_edit') }}
|
||||||
|
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block css %}
|
|
||||||
{{ parent() }}
|
|
||||||
{{ encore_entry_link_tags('vue_accourse_work_edit') }}
|
|
||||||
{% endblock %}
|
|
||||||
|
@ -18,137 +18,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="flex-table accompanying_course_work-list">
|
<div class="flex-table accompanying_course_work-list">
|
||||||
{% for w in works %}
|
{% for w in works %}
|
||||||
<div class="item-bloc">
|
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with { 'displayAction': true } %}
|
||||||
|
|
||||||
<div class="item-row">
|
|
||||||
<h2 class="badge-title">
|
|
||||||
<span class="title_label"></span>
|
|
||||||
<span class="title_action">{{ w.socialAction|chill_entity_render_string }}
|
|
||||||
|
|
||||||
<ul class="small_in_title columns mt-1">
|
|
||||||
<li>
|
|
||||||
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
|
|
||||||
<b>{{ w.startDate|format_date('short') }}</b>
|
|
||||||
</li>
|
|
||||||
{% if w.endDate %}
|
|
||||||
<li>
|
|
||||||
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
|
|
||||||
<b>{{ w.endDate|format_date('short') }}</b>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="wrap-list">
|
|
||||||
|
|
||||||
{% if w.createdBy %}
|
|
||||||
<div class="wl-row">
|
|
||||||
<div class="wl-col title">
|
|
||||||
<h3>{{ 'Referrer'|trans }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="wl-col list">
|
|
||||||
<p class="wl-item">
|
|
||||||
{{ w.createdBy|chill_entity_render_box }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{%- if w.persons -%}
|
|
||||||
<div class="wl-row">
|
|
||||||
<div class="wl-col title">
|
|
||||||
<h3>{{ 'Persons in accompanying course'|trans }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="wl-col list">
|
|
||||||
{% for p in w.persons %}
|
|
||||||
<span class="wl-item">
|
|
||||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
|
||||||
action: 'show', displayBadge: true,
|
|
||||||
targetEntity: { name: 'person', id: p.id },
|
|
||||||
buttonText: p|chill_entity_render_string,
|
|
||||||
isDead: p.deathdate is not null
|
|
||||||
} %}
|
|
||||||
</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{%- if w.handlingThierParty -%}
|
|
||||||
<div class="wl-row">
|
|
||||||
<div class="wl-col title">
|
|
||||||
<h3>{{ 'Thirdparty handling'|trans }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="wl-col list">
|
|
||||||
<span class="wl-item">
|
|
||||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
|
||||||
action: 'show', displayBadge: true,
|
|
||||||
targetEntity: { name: 'thirdparty', id: w.handlingThierParty.id },
|
|
||||||
buttonText: w.handlingThierParty|chill_entity_render_string,
|
|
||||||
parent: {
|
|
||||||
'type': 'accompanying_period_resource',
|
|
||||||
'id': r.id,
|
|
||||||
'comment': r.comment,
|
|
||||||
'parent': {
|
|
||||||
'type': 'accompanying_period',
|
|
||||||
'id': accompanyingCourse.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
%}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{%- if w.socialAction.issue -%}
|
|
||||||
<div class="wl-row">
|
|
||||||
<div class="wl-col title">
|
|
||||||
<h3>{{ 'Social issue'|trans }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="wl-col list">
|
|
||||||
<p class="wl-item social-issues">
|
|
||||||
{{ w.socialAction.issue|chill_entity_render_box }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-row column">
|
|
||||||
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {} %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="item-row separator">
|
|
||||||
<div class="updatedBy">
|
|
||||||
{{ 'Last updated by'|trans}} <b>{{ w.updatedBy|chill_entity_render_box }}</b>,<br>
|
|
||||||
{{ 'le ' ~ w.updatedAt|format_datetime('long', 'short') }}
|
|
||||||
</div>
|
|
||||||
<ul class="record_actions">
|
|
||||||
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) %}
|
|
||||||
{% if notif_counter.total > 0 %}
|
|
||||||
<li>{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) }}</li>
|
|
||||||
{% endif %}
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
|
|
||||||
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
|
|
||||||
></a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
|
|
||||||
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
|
|
||||||
></a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -46,16 +46,15 @@
|
|||||||
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {} %}
|
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {} %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="metadata text-end" style="font-size: 60%">
|
<div class="metadata text-end">
|
||||||
{{ 'Last updated by'|trans }}
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
|
||||||
<span class="user">{{ w.updatedBy|chill_entity_render_box }}</span>:
|
{{ macro.updatedBy(w) }}
|
||||||
<span class="date">{{ w.updatedAt|format_datetime('short', 'short') }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</a>{# {{ dump(w) }} #}
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -5,10 +5,6 @@
|
|||||||
|
|
||||||
{% macro recordAction(period, contextEntity) %}
|
{% macro recordAction(period, contextEntity) %}
|
||||||
{# TODO if enable_accompanying_course_with_multiple_persons is true ... #}
|
{# TODO if enable_accompanying_course_with_multiple_persons is true ... #}
|
||||||
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %}
|
|
||||||
{% if notif_counter.total > 0 %}
|
|
||||||
<li>{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }}</li>
|
|
||||||
{% endif %}
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': period.id }) }}"
|
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': period.id }) }}"
|
||||||
class="btn btn-show" title="{{ 'See accompanying period'|trans }}">{# {{ 'See this period'|trans }} #}</a>
|
class="btn btn-show" title="{{ 'See accompanying period'|trans }}">{# {{ 'See this period'|trans }} #}</a>
|
||||||
|
@ -113,11 +113,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if recordAction is defined %}
|
<div class="item-row separator">
|
||||||
<div class="item-row separator">
|
<div class="item-col item-meta">
|
||||||
<ul class="record_actions">
|
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %}
|
||||||
{{ recordAction }}
|
{% if notif_counter.total > 0 %}
|
||||||
</ul>
|
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<div class="item-col">
|
||||||
|
{% if recordAction is defined %}
|
||||||
|
<ul class="record_actions">
|
||||||
|
{{ recordAction }}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
{% macro updatedBy(entity) %}
|
||||||
|
<div class="updatedBy">
|
||||||
|
{{ 'Last updated on'|trans }}
|
||||||
|
<span class="date">
|
||||||
|
{{ entity.updatedAt|format_datetime('medium', 'short') }}
|
||||||
|
</span>,
|
||||||
|
{{ 'by_user'|trans }}
|
||||||
|
<span class="user">
|
||||||
|
{{ entity.updatedBy|chill_entity_render_box }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
@ -125,7 +125,7 @@
|
|||||||
{% if not person.isSharingHousehold() %}
|
{% if not person.isSharingHousehold() %}
|
||||||
<ul class="record_actions">
|
<ul class="record_actions">
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-misc" href="{{chill_path_add_return_path('chill_person_household_members_editor', { 'persons': [ person.id ], 'followAfter': 'true'}) }}">
|
<a class="btn btn-misc" href="{{chill_path_add_return_path('chill_person_household_members_editor', { 'persons': [ person.id ], 'followAfter': true}) }}">
|
||||||
<i class="fa fa-sign-in fa-fw"></i>
|
<i class="fa fa-sign-in fa-fw"></i>
|
||||||
{{ 'household.Join'|trans }}
|
{{ 'household.Join'|trans }}
|
||||||
</a>
|
</a>
|
||||||
|
@ -74,137 +74,158 @@
|
|||||||
{# add as requestor #}
|
{# add as requestor #}
|
||||||
|
|
||||||
{% if acps|length > 0 %}
|
{% if acps|length > 0 %}
|
||||||
<div class="item-row">
|
{% for acp in acps %}
|
||||||
<div class="wrap-list periods-list">
|
{% set app = person.findParticipationForPeriod(acp) %}
|
||||||
{% for acp in acps %}
|
<div class="item-row separator">
|
||||||
{% set app = person.findParticipationForPeriod(acp) %}
|
<div class="wrap-list periods-list">
|
||||||
<div class="wl-row separator">
|
|
||||||
|
|
||||||
|
<div class="wl-row">
|
||||||
<div class="wl-col title">
|
<div class="wl-col title">
|
||||||
<div>
|
<h3 class="courseid mb-2">
|
||||||
{% if acp.emergency %}
|
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': acp.id }) }}"
|
||||||
<span class="badge rounded-pill bg-danger">{{- 'Emergency'|trans|upper -}}</span>
|
title="{{ 'See accompanying period'|trans }}" class="btn btn-outline-primary">
|
||||||
{% endif %}
|
<i class="fa fa-random fa-fw"></i>
|
||||||
{% if acp.confidential %}
|
{{ 'File number'|trans }} {{ acp.id }}
|
||||||
<span class="badge rounded-pill bg-confidential">{{- 'Confidential'|trans|upper -}}</span>
|
</a>
|
||||||
{% endif %}
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{% if acp.step == 'DRAFT' %}
|
<div class="wl-col list">
|
||||||
<div class="is-draft">
|
<div class="d-flex flex-column justify-content-center">
|
||||||
<span class="course-draft badge bg-secondary" title="{{ 'course.draft'|trans }}">{{ 'course.draft'|trans }}</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if acp.requestorPerson == person %}
|
|
||||||
<div>
|
|
||||||
<span class="as-requestor badge bg-info" title="{{ 'Requestor'|trans|e('html_attr') }}">
|
|
||||||
{{ 'Requestor'|trans({'gender': person.gender}) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="date">
|
|
||||||
{% if app != null %}
|
{% if app != null %}
|
||||||
{{ 'Since %date%'|trans({'%date%': app.startDate|format_date('medium') }) }}
|
<div class="date">
|
||||||
|
{{ 'Since %date%'|trans({'%date%': app.startDate|format_date('medium') }) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) %}
|
||||||
|
{% if notif_counter.total > 0 %}
|
||||||
|
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="ms-auto">
|
||||||
|
{% if acp.emergency %}
|
||||||
|
<span class="badge rounded-pill bg-danger">{{- 'Emergency'|trans|upper -}}</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if acp.user is not null %}
|
{% if acp.confidential %}
|
||||||
|
<span class="badge rounded-pill bg-confidential">{{- 'Confidential'|trans|upper -}}</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if acp.step == 'DRAFT' %}
|
||||||
|
<span class="badge bg-secondary" style="font-size: 85%;" title="{{ 'course.draft'|trans }}">{{ 'course.draft'|trans }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if acp.user is not null %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3 class="referrer">{{ 'Referrer'|trans }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
<div class="user">
|
<div class="user">
|
||||||
<abbr class="referrer" title="{{ 'Referrer'|trans }}">ref:</abbr>
|
|
||||||
{{ acp.user|chill_entity_render_box }}
|
{{ acp.user|chill_entity_render_box }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="courseid">
|
|
||||||
{{ 'File number'|trans }} {{ acp.id }}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) %}
|
|
||||||
{% if notif_counter.total > 0 %}
|
|
||||||
<div class="counter">{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', acp.id) }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="wl-col list">
|
|
||||||
|
|
||||||
{% for issue in acp.socialIssues %}
|
|
||||||
{{ issue|chill_entity_render_box }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<ul class="record_actions record_actions_column">
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': acp.id }) }}"
|
|
||||||
class="btn btn-sm btn-outline-primary" title="{{ 'See accompanying period'|trans }}">
|
|
||||||
<i class="fa fa-random fa-fw"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if acp.currentParticipations|length > 1 %}
|
|
||||||
<div class="wl-row">
|
|
||||||
<div class="wl-col title">
|
|
||||||
<div class="participants">
|
|
||||||
{{ 'Participants'|trans }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="wl-col list">
|
|
||||||
{% set participating = false %}
|
|
||||||
{% for part in acp.currentParticipations %}
|
|
||||||
{% if part.person.id != person.id %}
|
|
||||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
|
||||||
targetEntity: { name: 'person', id: part.person.id },
|
|
||||||
action: 'show',
|
|
||||||
displayBadge: true,
|
|
||||||
buttonText: part.person|chill_entity_render_string,
|
|
||||||
isDead: part.person.deathdate is not null
|
|
||||||
} %}
|
|
||||||
{% else %}
|
|
||||||
{% set participating = true %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% if participating %}
|
|
||||||
{{ 'person.and_himself'|trans({'gender': person.gender}) }}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if (acp.requestorPerson is not null and acp.requestorPerson.id != person.id) or acp.requestorThirdParty is not null %}
|
|
||||||
<div class="wl-row">
|
{% if acp.socialIssues|length > 0 %}
|
||||||
<div class="wl-col title">
|
<div class="wl-row">
|
||||||
<div>
|
<div class="wl-col title">
|
||||||
{% if acp.requestorPerson is not null %}
|
<h3>{{ 'Social issues'|trans }}</h3>
|
||||||
{{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }}
|
</div>
|
||||||
{% else %}
|
<div class="wl-col list">
|
||||||
{{ 'Requestor'|trans({'gender': 'other'})}}
|
{% for issue in acp.socialIssues %}
|
||||||
{% endif %}
|
{{ issue|chill_entity_render_box }}
|
||||||
</div>
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="wl-col list">
|
</div>
|
||||||
{% if acp.requestorThirdParty is not null %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# ????
|
||||||
|
{% if acp.requestorPerson == person %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>
|
||||||
|
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
<span class="as-requestor badge bg-info" title="{{ 'Requestor'|trans|e('html_attr') }}">
|
||||||
|
{{ 'Requestor'|trans({'gender': person.gender}) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
#}
|
||||||
|
|
||||||
|
{% if acp.currentParticipations|length > 1 %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3 class="participants">
|
||||||
|
{{ 'Participants'|trans }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{% set participating = false %}
|
||||||
|
{% for part in acp.currentParticipations %}
|
||||||
|
{% if part.person.id != person.id %}
|
||||||
|
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||||
|
targetEntity: { name: 'person', id: part.person.id },
|
||||||
|
action: 'show',
|
||||||
|
displayBadge: true,
|
||||||
|
buttonText: part.person|chill_entity_render_string,
|
||||||
|
isDead: part.person.deathdate is not null
|
||||||
|
} %}
|
||||||
|
{% else %}
|
||||||
|
{% set participating = true %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if participating %}
|
||||||
|
{{ 'person.and_himself'|trans({'gender': person.gender}) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if (acp.requestorPerson is not null and acp.requestorPerson.id != person.id) or acp.requestorThirdParty is not null %}
|
||||||
|
<div class="wl-row">
|
||||||
|
<div class="wl-col title">
|
||||||
|
<h3>
|
||||||
|
{% if acp.requestorPerson is not null %}
|
||||||
|
{{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }}
|
||||||
|
{% else %}
|
||||||
|
{{ 'Requestor'|trans({'gender': 'other'})}}
|
||||||
|
{% endif %}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="wl-col list">
|
||||||
|
{% if acp.requestorThirdParty is not null %}
|
||||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||||
targetEntity: { name: 'thirdparty', id: acp.requestorThirdParty.id },
|
targetEntity: { name: 'thirdparty', id: acp.requestorThirdParty.id },
|
||||||
action: 'show',
|
action: 'show',
|
||||||
displayBadge: true,
|
displayBadge: true,
|
||||||
buttonText: acp.requestorThirdParty|chill_entity_render_string
|
buttonText: acp.requestorThirdParty|chill_entity_render_string
|
||||||
} %}
|
} %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||||
targetEntity: { name: 'person', id: acp.requestorPerson.id },
|
targetEntity: { name: 'person', id: acp.requestorPerson.id },
|
||||||
action: 'show',
|
action: 'show',
|
||||||
displayBadge: true,
|
displayBadge: true,
|
||||||
buttonText: acp.requestorPerson|chill_entity_render_string,
|
buttonText: acp.requestorPerson|chill_entity_render_string,
|
||||||
isDead: acp.requestorPerson.deathdate is not null
|
isDead: acp.requestorPerson.deathdate is not null
|
||||||
} %}
|
} %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -301,10 +301,8 @@ This view should receive those arguments:
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if person.updatedBy %}
|
{% if person.updatedBy %}
|
||||||
<div class="updatedBy">
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
|
||||||
{{ 'Last updated by'|trans}}: <b>{{ person.updatedBy|chill_entity_render_box }}</b>,<br>
|
{{ macro.updatedBy(person) }}
|
||||||
{{ 'on'|trans ~ person.updatedAt|format_datetime('long', 'short') }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
<div class="flex-table accompanying_course_work-list">
|
||||||
|
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with {
|
||||||
|
'w': work,
|
||||||
|
'itemBlocClass': 'bg-chill-llight-gray'
|
||||||
|
} %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if display_action is defined and display_action == true %}
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-update"
|
||||||
|
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': work.id }) }}">
|
||||||
|
{{ 'Edit'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
@ -0,0 +1,19 @@
|
|||||||
|
{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as m %}
|
||||||
|
|
||||||
|
<div class="flex-grow-1 {% if add_classes is defined %}{{ add_classes }}{% else %}h2{% endif %}">
|
||||||
|
<div>
|
||||||
|
{% if concerne is defined and concerne == true %}
|
||||||
|
<span class="item-key">{{ 'Concerne'|trans }}: </span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ 'workflow.Work (n°%w%)'|trans({'%w%': work.id }) }}
|
||||||
|
|
||||||
|
{% if description is defined and description == true %}
|
||||||
|
{{ ' — ' ~ work.socialAction|chill_entity_render_string }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if breadcrumb is defined and breadcrumb == true %}
|
||||||
|
{{ m.breadcrumb(_context) }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
@ -0,0 +1,99 @@
|
|||||||
|
<div class="flex-table accompanying_course_work-list">
|
||||||
|
<div class="item-bloc evaluation-item bg-chill-llight-gray">
|
||||||
|
<div class="item-row mb-2">
|
||||||
|
<h2 class="badge-title">
|
||||||
|
<span class="title_label"></span>
|
||||||
|
<span class="title_action">
|
||||||
|
{{ evaluation.accompanyingPeriodWork.socialAction|chill_entity_render_string }}
|
||||||
|
<ul class="small_in_title columns mt-1">
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ evaluation.accompanyingPeriodWork.startDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% if evaluation.accompanyingPeriodWork.endDate %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ evaluation.accompanyingPeriodWork.endDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div class="item-row column">
|
||||||
|
<table class="obj-res-eval my-3" style="font-size: 110% !important;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="eval">
|
||||||
|
<h4 class="title_label">
|
||||||
|
{{ 'Évaluation'|trans }}
|
||||||
|
</h4>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="eval">
|
||||||
|
<ul class="eval_title">
|
||||||
|
<li class="my-2">
|
||||||
|
{{ evaluation.evaluation.title|localize_translatable_string }}
|
||||||
|
<ul class="columns pt-2">
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ evaluation.startDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% if evaluation.endDate %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ evaluation.endDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if evaluation.maxDate %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
|
||||||
|
<b>{{ evaluation.maxDate|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if evaluation.warningInterval and evaluation.warningInterval.d > 0 %}
|
||||||
|
<li>
|
||||||
|
{% set days = (evaluation.warningInterval.d + evaluation.warningInterval.m * 30) %}
|
||||||
|
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
|
||||||
|
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li>
|
||||||
|
<span class="item-key">créé par</span>
|
||||||
|
<b>{{ evaluation.createdBy.username }}</b>
|
||||||
|
<span class="item-key">{{ 'le'|trans }}</span>
|
||||||
|
<b>{{ evaluation.createdAt|format_date('short') }}</b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% if evaluation.comment %}
|
||||||
|
<blockquote class="chill-user-quote" style="margin-left: 0;">
|
||||||
|
{{ evaluation.comment }}
|
||||||
|
</blockquote>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="item-row">
|
||||||
|
{% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
|
||||||
|
{{ macro.updatedBy(evaluation) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if display_action is defined and display_action == true %}
|
||||||
|
{# TODO add acl #}
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-show" href="{{ path('chill_person_accompanying_period_work_edit', {'id': evaluation.accompanyingPeriodWork.id}) }}">
|
||||||
|
{{ 'Show'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user