mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
Merge remote-tracking branch 'origin/master' into issue439_residential_address_otf
This commit is contained in:
commit
fccac80222
@ -29,6 +29,7 @@ variables:
|
||||
REDIS_URL: redis://redis:6379
|
||||
# change vendor dir to make the app install into tests/apps
|
||||
COMPOSER_VENDOR_DIR: tests/app/vendor
|
||||
DEFAULT_CARRIER_CODE: BE
|
||||
|
||||
stages:
|
||||
- Composer install
|
||||
@ -78,6 +79,7 @@ psalm_tests:
|
||||
image: registry.gitlab.com/chill-projet/chill-app/php-base-image:7.4
|
||||
script:
|
||||
- bin/grumphp run --tasks=psalm
|
||||
allow_failure: true
|
||||
artifacts:
|
||||
expire_in: 30 min
|
||||
paths:
|
||||
|
20
CHANGELOG.md
20
CHANGELOG.md
@ -11,11 +11,21 @@ and this project adheres to
|
||||
## Unreleased
|
||||
|
||||
<!-- write down unreleased development here -->
|
||||
* [parcours] Toggle emergency/intensity only by referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/442)
|
||||
* [docstore] Add an API entrypoint for StoredObject (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
|
||||
* [person] Add the possibility of uploading existing documents to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
|
||||
* [person] Add title to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
|
||||
* [person] Order social issues by the field "ordering" (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/388)
|
||||
* [Person/Household list] when listing other simultaneous members of an household, exclude the members on person, not on members (avoid to show two membersship with the same person)
|
||||
* [draft periods] add a delete button (if acl granted) on each draft period listed on draft period page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/463)
|
||||
* [Person] Display suffixText in RenderPerson, PersonText.vue, RenderPersonBox.vue (was made for displaying "enfant confie") (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/441)
|
||||
* [person] residential address: show residential address or info in PersonRenderBox, refactor Residential Address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/439)
|
||||
* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345)
|
||||
* [documents] Improve flex-table item-col placement when long buttons and long metadata
|
||||
* [thirdparty] Fix display of multiple contact badges so they wrap onto next line (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/482)
|
||||
* [confidential] Fix position of toggle button so it does not cover text nor fall outside of box (no issue)
|
||||
* [parcours] Fix edit of both thirdparty and contact name (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/474)
|
||||
* [template] do not list inactive templates (for doc generator)
|
||||
|
||||
## Test releases
|
||||
|
||||
@ -40,9 +50,13 @@ and this project adheres to
|
||||
* [parcours]: Order social activities and only display most recent three in parcours resumé (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/481)
|
||||
* [3party]: 3party: redirect to parent when contact (child) is opened in view page
|
||||
* [parcours / addresses]: launch an event when a person change address (either through changing household or because the household is associated to a new address). If the person is localising a course, the course location go back to a temporarily address.
|
||||
* [thirdparty]: address/phonenumber/email/fonction displayed in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/401)
|
||||
* [thirdparty_contact]: in search results the 'qualité' is displayed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/465)
|
||||
* [bug]: fix confidential toggle of address in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/460)
|
||||
|
||||
|
||||
## Test releases
|
||||
* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType
|
||||
* [renderbox]: Fix display of address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/462)
|
||||
* [renderbox]: Add email in personRenderBox, this was not yet displayed.
|
||||
|
||||
### test release 2022-02-14
|
||||
|
||||
@ -59,6 +73,7 @@ and this project adheres to
|
||||
* [parcours]: Mes parcours brouillon added to user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/440)
|
||||
* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414)
|
||||
* [person]: style fix in parcours listing per person. (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/432)
|
||||
* [parcours]: Only the referrer can toggle the intensity of the parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/442)
|
||||
* [household]: display address of current household (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/415)
|
||||
* ajoute un ordre dans les localisation (api)
|
||||
* [pick entity]: fix translations in modal (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/419)
|
||||
@ -307,6 +322,7 @@ and this project adheres to
|
||||
* add an endpoint for checking permissions. See https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/232
|
||||
* [activity] for a new activity: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
|
||||
* [calendar] for a new rdv: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
|
||||
* [period] Validation added when period is confidential and confirmed -> user cannot be null.
|
||||
|
||||
|
||||
## Test releases
|
||||
|
@ -22,6 +22,7 @@
|
||||
"league/csv": "^9.7.1",
|
||||
"nyholm/psr7": "^1.4",
|
||||
"ocramius/package-versions": "^1.10",
|
||||
"odolbeau/phone-number-bundle": "^3.6",
|
||||
"phpoffice/phpspreadsheet": "^1.16",
|
||||
"ramsey/uuid-doctrine": "^1.7",
|
||||
"sensio/framework-extra-bundle": "^5.5",
|
||||
|
@ -295,11 +295,6 @@ parameters:
|
||||
count: 3
|
||||
path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/DateIntervalTransformer.php
|
||||
|
||||
-
|
||||
message: "#^Only booleans are allowed in a negated boolean, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Bundle/ChillMainBundle/Form/Type/DataTransformer/ObjectToIdTransformer.php
|
||||
|
||||
-
|
||||
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
|
||||
count: 2
|
||||
@ -325,11 +320,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Bundle/ChillMainBundle/Timeline/TimelineBuilder.php
|
||||
|
||||
-
|
||||
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
|
||||
count: 1
|
||||
path: src/Bundle/ChillMainBundle/Validation/Validator/ValidPhonenumber.php
|
||||
|
||||
-
|
||||
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
|
||||
count: 1
|
||||
|
@ -70,6 +70,7 @@ final class DocGeneratorTemplateRepository implements ObjectRepository
|
||||
|
||||
$builder
|
||||
->where('t.entity LIKE :entity')
|
||||
->andWhere($builder->expr()->eq('t.active', "'TRUE'"))
|
||||
->setParameter('entity', addslashes($entity));
|
||||
|
||||
return $builder
|
||||
|
@ -16,8 +16,8 @@
|
||||
<div class="input-group mb-3">
|
||||
<select class="form-select" v-model="template">
|
||||
<option disabled selected value="">{{ $t('choose_a_template') }}</option>
|
||||
<template v-for="t in templates">
|
||||
<option v-bind:value="t.id">{{ t.name.fr || 'Aucun nom défini' }}</option>
|
||||
<template v-for="t in templates" :key="t.id">
|
||||
<option :value="t.id" >{{ t.name.fr || 'Aucun nom défini' }}</option>
|
||||
</template>
|
||||
</select>
|
||||
<a v-if="canGenerate" class="btn btn-update btn-sm change-icon" :href="buildUrlGenerate" @click.prevent="clickGenerate($event, buildUrlGenerate)"><i class="fa fa-fw fa-cog"></i></a>
|
||||
|
@ -17,6 +17,7 @@ use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
|
||||
use Symfony\Component\DependencyInjection\Loader;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||
|
||||
/**
|
||||
@ -46,6 +47,28 @@ class ChillDocStoreExtension extends Extension implements PrependExtensionInterf
|
||||
$this->prependRoute($container);
|
||||
$this->prependAuthorization($container);
|
||||
$this->prependTwig($container);
|
||||
$this->prependApis($container);
|
||||
}
|
||||
|
||||
protected function prependApis(ContainerBuilder $container)
|
||||
{
|
||||
$container->prependExtensionConfig('chill_main', [
|
||||
'apis' => [
|
||||
[
|
||||
'class' => \Chill\DocStoreBundle\Entity\StoredObject::class,
|
||||
'name' => 'stored_object',
|
||||
'base_path' => '/api/1.0/docstore/stored-object',
|
||||
'base_role' => 'ROLE_USER',
|
||||
'actions' => [
|
||||
'_entity' => [
|
||||
'methods' => [
|
||||
Request::METHOD_POST => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function prependAuthorization(ContainerBuilder $container)
|
||||
|
@ -34,19 +34,19 @@ class StoredObject implements AsyncFileInterface, Document
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="datetime", name="creation_date")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private DateTimeInterface $creationDate;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json", name="datas")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private array $datas = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private $filename;
|
||||
|
||||
@ -54,30 +54,32 @@ class StoredObject implements AsyncFileInterface, Document
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
* @ORM\Column(type="json", name="iv")
|
||||
* @Serializer\Groups({"write"})
|
||||
*/
|
||||
private array $iv = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json", name="key")
|
||||
* @Serializer\Groups({"write"})
|
||||
*/
|
||||
private array $keyInfos = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", name="type")
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private string $type = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="uuid", unique=true)
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "write"})
|
||||
*/
|
||||
private UuidInterface $uuid;
|
||||
|
||||
|
@ -37,7 +37,6 @@ var keyDefinition = {
|
||||
|
||||
var searchForZones = function(root) {
|
||||
var zones = root.querySelectorAll('div[data-stored-object]');
|
||||
|
||||
for(let i=0; i < zones.length; i++) {
|
||||
initialize(zones[i]);
|
||||
}
|
||||
@ -370,3 +369,5 @@ window.addEventListener('load', function(e) {
|
||||
window.addEventListener('collection-add-entry', function(e) {
|
||||
searchForZones(e.detail.entry);
|
||||
});
|
||||
|
||||
export { searchForZones };
|
||||
|
@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<a class="btn btn-create" :title="$t(buttonTitle)" @click="openModal">
|
||||
<span>{{ $t(buttonTitle) }}</span>
|
||||
</a>
|
||||
<teleport to="body">
|
||||
<div>
|
||||
<modal v-if="modal.showModal"
|
||||
:modalDialogClass="modal.modalDialogClass"
|
||||
@close="modal.showModal = false">
|
||||
|
||||
<template v-slot:header>
|
||||
{{ $t('upload_a_document') }}
|
||||
</template>
|
||||
|
||||
<template v-slot:body>
|
||||
<div id="dropZoneWrapper" ref="dropZoneWrapper">
|
||||
<div
|
||||
data-stored-object="data-stored-object"
|
||||
:data-label-preparing="$t('data_label_preparing')"
|
||||
:data-label-quiet-button="$t('data_label_quiet_button')"
|
||||
:data-label-ready="$t('data_label_ready')"
|
||||
:data-dict-file-too-big="$t('data_dict_file_too_big')"
|
||||
:data-dict-default-message="$t('data_dict_default_message')"
|
||||
:data-dict-remove-file="$t('data_dict_remove_file')"
|
||||
:data-dict-max-files-exceeded="$t('data_dict_max_files_exceeded')"
|
||||
:data-dict-cancel-upload="$t('data_dict_cancel_upload')"
|
||||
:data-dict-cancel-upload-confirm="$t('data_dict_cancel_upload_confirm')"
|
||||
:data-dict-upload-canceled="$t('data_dict_upload_canceled')"
|
||||
:data-dict-remove="$t('data_dict_remove')"
|
||||
:data-allow-remove="!options.required"
|
||||
data-temp-url-generator="/asyncupload/temp_url/generate/GET">
|
||||
<input
|
||||
type="hidden"
|
||||
data-async-file-upload="data-async-file-upload"
|
||||
data-generate-temp-url-post="/asyncupload/temp_url/generate/post?expires_delay=180&submit_delay=3600"
|
||||
data-temp-url-get="/asyncupload/temp_url/generate/GET"
|
||||
:data-max-files="options.maxFiles"
|
||||
:data-max-post-size="options.maxPostSize"
|
||||
:v-model="dataAsyncFileUpload"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
data-stored-object-key="1"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
data-stored-object-iv="1"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
data-async-file-type="1"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:footer>
|
||||
<button class="btn btn-create"
|
||||
@click.prevent="saveDocument">
|
||||
{{ $t('action.add')}}
|
||||
</button>
|
||||
</template>
|
||||
|
||||
</modal>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||
import { searchForZones } from '../../module/async_upload/uploader';
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
fr: {
|
||||
upload_a_document: "Téléversez un document",
|
||||
data_label_preparing: "Chargement...",
|
||||
data_label_quiet_button: "Téléchargez le fichier existant",
|
||||
data_label_ready: "Prêt à montrer",
|
||||
data_dict_file_too_big: "Fichier trop volumineux",
|
||||
data_dict_default_message: "Glissez votre fichier ou cliquez ici",
|
||||
data_dict_remove_file: "Enlevez votre fichier pour en téléversez un autre",
|
||||
data_dict_max_files_exceeded: "Nombre maximum de fichiers atteint. Enlevez les fichiers précédents",
|
||||
data_dict_cancel_upload: "Annulez le téléversement",
|
||||
data_dict_cancel_upload_confirm: "Êtes-vous sûr·e de vouloir annuler ce téléversement?",
|
||||
data_dict_upload_canceled: "Téléversement annulé",
|
||||
data_dict_remove: "Enlevez le fichier existant",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
name: "AddAsyncUpload",
|
||||
components: {
|
||||
Modal
|
||||
},
|
||||
i18n,
|
||||
props: [
|
||||
'buttonTitle',
|
||||
'options'
|
||||
],
|
||||
emits: ['addDocument'],
|
||||
data() {
|
||||
return {
|
||||
modal: {
|
||||
showModal: false,
|
||||
modalDialogClass: "modal-dialog-centered modal-md"
|
||||
},
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
if (this.modal.showModal){
|
||||
searchForZones(this.$refs.dropZoneWrapper);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.modal.showModal = true;
|
||||
},
|
||||
saveDocument() {
|
||||
const dropzone = this.$refs.dropZoneWrapper;
|
||||
if (dropzone) {
|
||||
const inputKey = dropzone.querySelector('input[data-stored-object-key]');
|
||||
const inputIv = dropzone.querySelector('input[data-stored-object-iv]');
|
||||
const inputObject = dropzone.querySelector('input[data-async-file-upload]');
|
||||
const inputType = dropzone.querySelector('input[data-async-file-type]');
|
||||
|
||||
const url = '/api/1.0/docstore/stored-object.json';
|
||||
const body = {
|
||||
filename: inputObject.value,
|
||||
keyInfos: JSON.parse(inputKey.value),
|
||||
iv: JSON.parse(inputIv.value),
|
||||
type: inputType.value,
|
||||
};
|
||||
makeFetch('POST', url, body)
|
||||
.then(r => {
|
||||
this.$emit("addDocument", r);
|
||||
this.modal.showModal = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.name === 'ValidationException') {
|
||||
for (let v of error.violations) {
|
||||
this.$toast.open({message: v });
|
||||
}
|
||||
} else {
|
||||
console.error(error);
|
||||
this.$toast.open({message: 'An error occurred'});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$toast.open({message: 'An error occurred - drop zone not found'});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,19 +0,0 @@
|
||||
{% 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>
|
@ -10,12 +10,14 @@
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
||||
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -24,8 +24,8 @@
|
||||
<div class="item-col">
|
||||
<div class="container">
|
||||
{% if document.date is not null %}
|
||||
<div class="dates row" style="float: right;">
|
||||
<span>{{ document.createdAt|format_date('short') }}</span>
|
||||
<div class="dates row text-end">
|
||||
<span>{{ document.date|format_date('short') }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -42,9 +42,8 @@
|
||||
<div class="item-col item-meta">
|
||||
{{ mmm.createdBy(document) }}
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="item-col record_actions flex-shrink-1">
|
||||
{% if document.course is defined %}
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
|
||||
<li>
|
||||
{{ m.download_button(document.object, document.title) }}
|
||||
@ -58,9 +57,10 @@
|
||||
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<li>
|
||||
{{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }}
|
||||
</li>
|
||||
{% else %}
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
|
||||
<li>
|
||||
{{ m.download_button(document.object, document.title) }}
|
||||
@ -74,8 +74,8 @@
|
||||
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,55 @@
|
||||
<?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\Serializer\Normalizer;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
|
||||
use function array_key_exists;
|
||||
use function is_array;
|
||||
|
||||
class StoredObjectDenormalizer implements DenormalizerInterface
|
||||
{
|
||||
use ObjectToPopulateTrait;
|
||||
|
||||
private StoredObjectRepository $storedObjectRepository;
|
||||
|
||||
public function __construct(StoredObjectRepository $storedObjectRepository)
|
||||
{
|
||||
$this->storedObjectRepository = $storedObjectRepository;
|
||||
}
|
||||
|
||||
public function denormalize($data, $type, $format = null, array $context = [])
|
||||
{
|
||||
$object = $this->extractObjectToPopulate(StoredObject::class, $context);
|
||||
|
||||
if (null !== $object) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
return $this->storedObjectRepository->find($data['id']);
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, $type, $format = null)
|
||||
{
|
||||
if (false === is_array($data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false === array_key_exists('id', $data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return StoredObject::class === $type;
|
||||
}
|
||||
}
|
@ -15,11 +15,12 @@ use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Twig\Environment;
|
||||
use Twig\Extension\RuntimeExtensionInterface;
|
||||
|
||||
use function array_key_exists;
|
||||
|
||||
class WopiEditTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||
final class WopiEditTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||
{
|
||||
public const TEMPLATE = '@ChillDocStore/Button/wopi_edit_document.html.twig';
|
||||
private const TEMPLATE = '@ChillDocStore/Button/wopi_edit_document.html.twig';
|
||||
|
||||
private DiscoveryInterface $discovery;
|
||||
|
||||
@ -32,10 +33,6 @@ class WopiEditTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||
{
|
||||
$mime_type = $this->discovery->discoverMimeType($document->getType());
|
||||
|
||||
if ([] === $mime_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($mime_type as $item) {
|
||||
if (array_key_exists('default', $item) && 'true' === $item['default']) {
|
||||
return true;
|
||||
|
@ -28,8 +28,10 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
|
||||
/**
|
||||
* TODO: injecter le repository directement.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, TranslatorInterface $translator)
|
||||
{
|
||||
public function __construct(
|
||||
EntityManagerInterface $em,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->repository = $em->getRepository(AccompanyingCourseDocument::class);
|
||||
$this->translator = $translator;
|
||||
}
|
||||
@ -53,7 +55,10 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
|
||||
|
||||
public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string
|
||||
{
|
||||
return $this->translator->trans('workflow.Document (n°%doc%)', ['%doc%' => $entityWorkflow->getRelatedEntityId()]);
|
||||
$doc = $this->getRelatedEntity($entityWorkflow);
|
||||
|
||||
return $this->translator->trans('workflow.Document (n°%doc%)', ['%doc%' => $entityWorkflow->getRelatedEntityId()])
|
||||
. ' - ' . $doc->getTitle();
|
||||
}
|
||||
|
||||
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingCourseDocument
|
||||
@ -79,16 +84,6 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
|
||||
];
|
||||
}
|
||||
|
||||
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;
|
||||
|
46
src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml
Normal file
46
src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml
Normal file
@ -0,0 +1,46 @@
|
||||
---
|
||||
openapi: "3.0.0"
|
||||
info:
|
||||
version: "1.0.0"
|
||||
title: "Chill api"
|
||||
description: "Api documentation for chill. Currently, work in progress"
|
||||
servers:
|
||||
- url: "/api"
|
||||
description: "Your current dev server"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
StoredObject:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
filename:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
|
||||
paths:
|
||||
/1.0/docstore/stored-object.json:
|
||||
post:
|
||||
tags:
|
||||
- storedobject
|
||||
summary: Create a stored object
|
||||
requestBody:
|
||||
description: "A stored object"
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StoredObject"
|
||||
responses:
|
||||
200:
|
||||
description: "OK"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/StoredObject"
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
422:
|
||||
description: "Invalid data"
|
@ -1,4 +1,7 @@
|
||||
module.exports = function(encore)
|
||||
{
|
||||
encore.addAliases({
|
||||
ChillDocStoreAssets: __dirname + '/Resources/public'
|
||||
});
|
||||
encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js');
|
||||
};
|
||||
|
@ -33,3 +33,10 @@ services:
|
||||
resource: './../Workflow/'
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
|
||||
Chill\DocStoreBundle\Serializer\Normalizer\:
|
||||
autowire: true
|
||||
resource: '../Serializer/Normalizer/'
|
||||
tags:
|
||||
- { name: 'serializer.normalizer', priority: 16 }
|
||||
|
||||
|
@ -11,21 +11,26 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowComment;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Form\EntityWorkflowCommentType;
|
||||
use Chill\MainBundle\Form\WorkflowStepType;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
|
||||
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Chill\MainBundle\Workflow\Validator\StepDestValid;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
use Symfony\Component\Workflow\TransitionBlocker;
|
||||
@ -80,7 +85,9 @@ class WorkflowController extends AbstractController
|
||||
$entityWorkflow
|
||||
->setRelatedEntityClass($request->query->get('entityClass'))
|
||||
->setRelatedEntityId($request->query->getInt('entityId'))
|
||||
->setWorkflowName($request->query->get('workflow'));
|
||||
->setWorkflowName($request->query->get('workflow'))
|
||||
->addSubscriberToStep($this->getUser())
|
||||
->addSubscriberToFinal($this->getUser());
|
||||
|
||||
$errors = $this->validator->validate($entityWorkflow, null, ['creation']);
|
||||
|
||||
@ -104,6 +111,121 @@ class WorkflowController extends AbstractController
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow/{id}/delete", name="chill_main_workflow_delete")
|
||||
*/
|
||||
public function delete(EntityWorkflow $entityWorkflow, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(EntityWorkflowVoter::DELETE, $entityWorkflow);
|
||||
|
||||
$form = $this->createForm(FormType::class);
|
||||
$form->add('submit', SubmitType::class, ['label' => 'workflow.Delete workflow']);
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->remove($entityWorkflow);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', $this->translator->trans('workflow.Workflow deleted with success'));
|
||||
|
||||
return $this->redirectToRoute('chill_main_homepage');
|
||||
}
|
||||
|
||||
return $this->render('@ChillMain/Workflow/delete.html.twig', [
|
||||
'entityWorkflow' => $entityWorkflow,
|
||||
'delete_form' => $form->createView(),
|
||||
'handler' => $this->entityWorkflowManager->getHandler($entityWorkflow),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow-step/{id}/access_key", name="chill_main_workflow_grant_access_by_key")
|
||||
*/
|
||||
public function getAccessByAccessKey(EntityWorkflowStep $entityWorkflowStep, Request $request): Response
|
||||
{
|
||||
if (null === $accessKey = $request->query->get('accessKey', null)) {
|
||||
throw new BadRequestException('accessKey is missing');
|
||||
}
|
||||
|
||||
if (!$this->getUser() instanceof User) {
|
||||
throw new AccessDeniedException('Not a valid user');
|
||||
}
|
||||
|
||||
if ($entityWorkflowStep->getAccessKey() !== $accessKey) {
|
||||
throw new AccessDeniedException('Access key is invalid');
|
||||
}
|
||||
|
||||
if (!$entityWorkflowStep->isWaitingForTransition()) {
|
||||
$this->addFlash('error', $this->translator->trans('workflow.Steps is not waiting for transition. Maybe someone apply the transition before you ?'));
|
||||
} else {
|
||||
$entityWorkflowStep->addDestUserByAccessKey($this->getUser());
|
||||
$this->entityManager->flush();
|
||||
$this->addFlash('success', $this->translator->trans('workflow.You get access to this step'));
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflowStep->getEntityWorkflow()->getId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Previous workflows where the user has applyed a transition.
|
||||
*
|
||||
* @Route("/{_locale}/main/workflow/list/previous_transitionned", name="chill_main_workflow_list_previous_transitionned")
|
||||
*/
|
||||
public function myPreviousWorkflowsTransitionned(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
|
||||
$total = $this->entityWorkflowRepository->countByPreviousTransitionned($this->getUser());
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
|
||||
$workflows = $this->entityWorkflowRepository->findByPreviousTransitionned(
|
||||
$this->getUser(),
|
||||
['createdAt' => 'DESC'],
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/list.html.twig',
|
||||
[
|
||||
'help' => 'workflow.Previous workflow transitionned help',
|
||||
'workflows' => $this->buildHandler($workflows),
|
||||
'paginator' => $paginator,
|
||||
'step' => 'previous_transitionned',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Previous workflows where the user was mentioned, but did not give any reaction.
|
||||
*
|
||||
* @Route("/{_locale}/main/workflow/list/previous_without_reaction", name="chill_main_workflow_list_previous_without_reaction")
|
||||
*/
|
||||
public function myPreviousWorkflowsWithoutReaction(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
|
||||
$total = $this->entityWorkflowRepository->countByPreviousDestWithoutReaction($this->getUser());
|
||||
$paginator = $this->paginatorFactory->create($total);
|
||||
|
||||
$workflows = $this->entityWorkflowRepository->findByPreviousDestWithoutReaction(
|
||||
$this->getUser(),
|
||||
['createdAt' => 'DESC'],
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/list.html.twig',
|
||||
[
|
||||
'help' => 'workflow.Previous workflow without reaction help',
|
||||
'workflows' => $this->buildHandler($workflows),
|
||||
'paginator' => $paginator,
|
||||
'step' => 'previous_without_reaction',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/main/workflow/list/dest", name="chill_main_workflow_list_dest")
|
||||
*/
|
||||
@ -198,6 +320,7 @@ class WorkflowController extends AbstractController
|
||||
}
|
||||
|
||||
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData();
|
||||
$entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData();
|
||||
|
||||
$workflow->apply($entityWorkflow, $transition);
|
||||
|
||||
@ -205,20 +328,15 @@ class WorkflowController extends AbstractController
|
||||
$entityWorkflow->getCurrentStep()->addDestUser($user);
|
||||
}
|
||||
|
||||
$errors = $this->validator->validate(
|
||||
$entityWorkflow->getCurrentStep(),
|
||||
new StepDestValid()
|
||||
);
|
||||
foreach ($transitionForm['future_dest_emails']->getData() as $email) {
|
||||
$entityWorkflow->getCurrentStep()->addDestEmail($email);
|
||||
}
|
||||
|
||||
if (count($errors) === 0) {
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('chill_main_workflow_show', ['id' => $entityWorkflow->getId()]);
|
||||
}
|
||||
|
||||
return new Response((string) $errors, Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
if ($transitionForm->isSubmitted() && !$transitionForm->isValid()) {
|
||||
$this->addFlash('error', $this->translator->trans('This form contains errors'));
|
||||
}
|
||||
@ -243,8 +361,8 @@ class WorkflowController extends AbstractController
|
||||
return $this->render(
|
||||
'@ChillMain/Workflow/index.html.twig',
|
||||
[
|
||||
'handler' => $handler,
|
||||
'handler_template' => $handler->getTemplate($entityWorkflow),
|
||||
'handler_template_title' => $handler->getTemplateTitle($entityWorkflow),
|
||||
'handler_template_data' => $handler->getTemplateData($entityWorkflow),
|
||||
'transition_form' => isset($transitionForm) ? $transitionForm->createView() : null,
|
||||
'entity_workflow' => $entityWorkflow,
|
||||
|
@ -39,6 +39,7 @@ use Chill\MainBundle\Form\LocationTypeType;
|
||||
use Chill\MainBundle\Form\UserJobType;
|
||||
use Chill\MainBundle\Form\UserType;
|
||||
use Exception;
|
||||
use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType;
|
||||
use Ramsey\Uuid\Doctrine\UuidType;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
@ -235,6 +236,7 @@ class ChillMainExtension extends Extension implements
|
||||
'dateinterval' => NativeDateIntervalType::class,
|
||||
'point' => PointType::class,
|
||||
'uuid' => UuidType::class,
|
||||
'phone_number' => PhoneNumberType::class,
|
||||
],
|
||||
],
|
||||
]
|
||||
|
@ -97,6 +97,9 @@ class Configuration implements ConfigurationInterface
|
||||
->scalarNode('twilio_secret')
|
||||
->defaultNull()
|
||||
->end()
|
||||
->scalarNode('default_carrier_code')
|
||||
->defaultNull()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->arrayNode('acl')
|
||||
|
@ -18,9 +18,9 @@ use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* @ORM\Table(name="chill_main_location")
|
||||
@ -90,20 +90,18 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
|
||||
private ?string $name = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=64, nullable=true)
|
||||
* @ORM\Column(type="phone_number", nullable=true)
|
||||
* @Serializer\Groups({"read", "write", "docgen:read"})
|
||||
* @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/")
|
||||
* @PhonenumberConstraint(type="any")
|
||||
*/
|
||||
private ?string $phonenumber1 = null;
|
||||
private ?PhoneNumber $phonenumber1 = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=64, nullable=true)
|
||||
* @ORM\Column(type="phone_number", nullable=true)
|
||||
* @Serializer\Groups({"read", "write", "docgen:read"})
|
||||
* @Assert\Regex(pattern="/^([\+{1}])([0-9\s*]{4,20})$/")
|
||||
* @PhonenumberConstraint(type="any")
|
||||
*/
|
||||
private ?string $phonenumber2 = null;
|
||||
private ?PhoneNumber $phonenumber2 = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true)
|
||||
@ -162,12 +160,12 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getPhonenumber1(): ?string
|
||||
public function getPhonenumber1(): ?PhoneNumber
|
||||
{
|
||||
return $this->phonenumber1;
|
||||
}
|
||||
|
||||
public function getPhonenumber2(): ?string
|
||||
public function getPhonenumber2(): ?PhoneNumber
|
||||
{
|
||||
return $this->phonenumber2;
|
||||
}
|
||||
@ -238,14 +236,14 @@ class Location implements TrackCreationInterface, TrackUpdateInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPhonenumber1(?string $phonenumber1): self
|
||||
public function setPhonenumber1(?PhoneNumber $phonenumber1): self
|
||||
{
|
||||
$this->phonenumber1 = $phonenumber1;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPhonenumber2(?string $phonenumber2): self
|
||||
public function setPhonenumber2(?PhoneNumber $phonenumber2): self
|
||||
{
|
||||
$this->phonenumber2 = $phonenumber2;
|
||||
|
||||
|
@ -41,6 +41,17 @@ class EntityWorkflow implements TrackCreationInterface, TrackUpdateInterface
|
||||
|
||||
use TrackUpdateTrait;
|
||||
|
||||
/**
|
||||
* a list of future dest emails for the next steps.
|
||||
*
|
||||
* This is in used in order to let controller inform who will be the future emails which will validate
|
||||
* the next step. This is necessary to perform some computation about the next emails, before they are
|
||||
* associated to the entity EntityWorkflowStep.
|
||||
*
|
||||
* @var array|string[]
|
||||
*/
|
||||
public array $futureDestEmails = [];
|
||||
|
||||
/**
|
||||
* a list of future dest users for the next steps.
|
||||
*
|
||||
|
@ -27,6 +27,11 @@ use function in_array;
|
||||
*/
|
||||
class EntityWorkflowStep
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false)
|
||||
*/
|
||||
private string $accessKey;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", options={"default": ""})
|
||||
*/
|
||||
@ -48,6 +53,12 @@ class EntityWorkflowStep
|
||||
*/
|
||||
private Collection $destUser;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=User::class)
|
||||
* @ORM\JoinTable(name="chill_main_workflow_entity_step_user_by_accesskey")
|
||||
*/
|
||||
private Collection $destUserByAccessKey;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=EntityWorkflow::class, inversedBy="steps")
|
||||
*/
|
||||
@ -86,7 +97,7 @@ class EntityWorkflowStep
|
||||
private ?string $transitionAfter = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable")
|
||||
* @ORM\Column(type="datetime_immutable", nullable=true, options={"default": null})
|
||||
*/
|
||||
private ?DateTimeImmutable $transitionAt = null;
|
||||
|
||||
@ -104,6 +115,8 @@ class EntityWorkflowStep
|
||||
public function __construct()
|
||||
{
|
||||
$this->destUser = new ArrayCollection();
|
||||
$this->destUserByAccessKey = new ArrayCollection();
|
||||
$this->accessKey = bin2hex(openssl_random_pseudo_bytes(32));
|
||||
}
|
||||
|
||||
public function addDestEmail(string $email): self
|
||||
@ -119,11 +132,45 @@ class EntityWorkflowStep
|
||||
{
|
||||
if (!$this->destUser->contains($user)) {
|
||||
$this->destUser[] = $user;
|
||||
$this->getEntityWorkflow()
|
||||
->addSubscriberToFinal($user)
|
||||
->addSubscriberToStep($user);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addDestUserByAccessKey(User $user): self
|
||||
{
|
||||
if (!$this->destUserByAccessKey->contains($user) && !$this->destUser->contains($user)) {
|
||||
$this->destUserByAccessKey[] = $user;
|
||||
$this->getEntityWorkflow()
|
||||
->addSubscriberToFinal($user)
|
||||
->addSubscriberToStep($user);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAccessKey(): string
|
||||
{
|
||||
return $this->accessKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* get all the users which are allowed to apply a transition: those added manually, and
|
||||
* those added automatically bu using an access key.
|
||||
*/
|
||||
public function getAllDestUser(): Collection
|
||||
{
|
||||
return new ArrayCollection(
|
||||
[
|
||||
...$this->getDestUser()->toArray(),
|
||||
...$this->getDestUserByAccessKey()->toArray(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function getComment(): string
|
||||
{
|
||||
return $this->comment;
|
||||
@ -140,13 +187,21 @@ class EntityWorkflowStep
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection|Collection
|
||||
* get dest users added by the creator.
|
||||
*
|
||||
* You should **not** rely on this method to get all users which are able to
|
||||
* apply a transition on this step. Use @see{EntityWorkflowStep::getAllDestUser} instead.
|
||||
*/
|
||||
public function getDestUser()
|
||||
public function getDestUser(): collection
|
||||
{
|
||||
return $this->destUser;
|
||||
}
|
||||
|
||||
public function getDestUserByAccessKey(): Collection
|
||||
{
|
||||
return $this->destUserByAccessKey;
|
||||
}
|
||||
|
||||
public function getEntityWorkflow(): ?EntityWorkflow
|
||||
{
|
||||
return $this->entityWorkflow;
|
||||
@ -197,6 +252,19 @@ class EntityWorkflowStep
|
||||
return $this->freezeAfter;
|
||||
}
|
||||
|
||||
public function isWaitingForTransition(): bool
|
||||
{
|
||||
if (null !== $this->transitionAfter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isFinal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function removeDestEmail(string $email): self
|
||||
{
|
||||
$this->destEmail = array_filter($this->destEmail, static function (string $existing) use ($email) {
|
||||
@ -213,6 +281,13 @@ class EntityWorkflowStep
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeDestUserByAccessKey(User $user): self
|
||||
{
|
||||
$this->destUserByAccessKey->removeElement($user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setComment(?string $comment): EntityWorkflowStep
|
||||
{
|
||||
$this->comment = (string) $comment;
|
||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\LocationType as EntityLocationType;
|
||||
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
|
||||
use Chill\MainBundle\Form\Type\PickAddressType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||
@ -46,8 +47,8 @@ final class LocationFormType extends AbstractType
|
||||
},
|
||||
])
|
||||
->add('name', TextType::class)
|
||||
->add('phonenumber1', TextType::class, ['required' => false])
|
||||
->add('phonenumber2', TextType::class, ['required' => false])
|
||||
->add('phonenumber1', ChillPhoneNumberType::class, ['required' => false])
|
||||
->add('phonenumber2', ChillPhoneNumberType::class, ['required' => false])
|
||||
->add('email', TextType::class, ['required' => false])
|
||||
->add('address', PickAddressType::class, [
|
||||
'required' => false,
|
||||
|
@ -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\MainBundle\Form\Type;
|
||||
|
||||
use libphonenumber\PhoneNumberFormat;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use function array_key_exists;
|
||||
|
||||
class ChillPhoneNumberType extends AbstractType
|
||||
{
|
||||
private string $defaultCarrierCode;
|
||||
|
||||
private PhoneNumberUtil $phoneNumberUtil;
|
||||
|
||||
public function __construct(ParameterBagInterface $parameterBag)
|
||||
{
|
||||
$this->defaultCarrierCode = $parameterBag->get('chill_main')['phone_helper']['default_carrier_code'];
|
||||
$this->phoneNumberUtil = PhoneNumberUtil::getInstance();
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver
|
||||
->setDefault('default_region', $this->defaultCarrierCode)
|
||||
->setDefault('format', PhoneNumberFormat::NATIONAL)
|
||||
->setDefault('type', \libphonenumber\PhoneNumberType::FIXED_LINE_OR_MOBILE)
|
||||
->setNormalizer('attr', function (Options $options, $value) {
|
||||
if (array_key_exists('placeholder', $value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$examplePhoneNumber = $this->phoneNumberUtil->getExampleNumberForType($this->defaultCarrierCode, $options['type']);
|
||||
|
||||
return array_merge(
|
||||
$value,
|
||||
[
|
||||
'placeholder' => PhoneNumberUtil::getInstance()->format($examplePhoneNumber, $options['format']),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public function getParent()
|
||||
{
|
||||
return PhoneNumberType::class;
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ class ObjectToIdTransformer implements DataTransformerInterface
|
||||
*/
|
||||
public function reverseTransform($id)
|
||||
{
|
||||
if (!$id) {
|
||||
if (null === $id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ class ObjectToIdTransformer implements DataTransformerInterface
|
||||
->getRepository($this->class)
|
||||
->find($id);
|
||||
|
||||
if (!$object) {
|
||||
if (null === $object) {
|
||||
throw new TransformationFailedException();
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ class ObjectToIdTransformer implements DataTransformerInterface
|
||||
*/
|
||||
public function transform($object)
|
||||
{
|
||||
if (!$object) {
|
||||
if (null === $object) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ namespace Chill\MainBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Chill\MainBundle\Form\Type\PickUserDynamicType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
@ -21,8 +22,14 @@ use LogicException;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Callback;
|
||||
use Symfony\Component\Validator\Constraints\Email;
|
||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||
use Symfony\Component\Validator\Constraints\NotNull;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
use Symfony\Component\Workflow\Transition;
|
||||
use function array_key_exists;
|
||||
@ -146,6 +153,22 @@ class WorkflowStepType extends AbstractType
|
||||
'label' => 'workflow.dest for next steps',
|
||||
'multiple' => true,
|
||||
'mapped' => false,
|
||||
])
|
||||
->add('future_dest_emails', ChillCollectionType::class, [
|
||||
'label' => 'workflow.dest by email',
|
||||
'help' => 'workflow.dest by email help',
|
||||
'mapped' => false,
|
||||
'allow_add' => true,
|
||||
'entry_type' => EmailType::class,
|
||||
'button_add_label' => 'workflow.Add an email',
|
||||
'button_remove_label' => 'workflow.Remove an email',
|
||||
'empty_collection_explain' => 'workflow.Any email',
|
||||
'entry_options' => [
|
||||
'constraints' => [
|
||||
new NotNull(), new NotBlank(), new Email(['checkMX' => true]),
|
||||
],
|
||||
'label' => 'Email',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@ -175,6 +198,37 @@ class WorkflowStepType extends AbstractType
|
||||
->setRequired('transition')
|
||||
->setAllowedTypes('transition', 'bool')
|
||||
->setRequired('entity_workflow')
|
||||
->setAllowedTypes('entity_workflow', EntityWorkflow::class);
|
||||
->setAllowedTypes('entity_workflow', EntityWorkflow::class)
|
||||
->setDefault('constraints', [
|
||||
new Callback(
|
||||
function ($step, ExecutionContextInterface $context, $payload) {
|
||||
/** @var EntityWorkflowStep $step */
|
||||
$form = $context->getObject();
|
||||
$workflow = $this->registry->get($step->getEntityWorkflow(), $step->getEntityWorkflow()->getWorkflowName());
|
||||
$transition = $form['transition']->getData();
|
||||
$toFinal = true;
|
||||
|
||||
foreach ($transition->getTos() as $to) {
|
||||
$meta = $workflow->getMetadataStore()->getPlaceMetadata($to);
|
||||
|
||||
if (
|
||||
!array_key_exists('isFinal', $meta) || false === $meta['isFinal']
|
||||
) {
|
||||
$toFinal = false;
|
||||
}
|
||||
}
|
||||
|
||||
$destUsers = $form['future_dest_users']->getData();
|
||||
$destEmails = $form['future_dest_emails']->getData();
|
||||
|
||||
if (!$toFinal && [] === $destUsers && [] === $destEmails) {
|
||||
$context
|
||||
->buildViolation('workflow.You must add at least one dest user or email')
|
||||
->atPath('future_dest_users')
|
||||
->addViolation();
|
||||
}
|
||||
}
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
<?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\Phonenumber;
|
||||
|
||||
use libphonenumber\PhoneNumber;
|
||||
|
||||
/**
|
||||
* Helper to some task linked to phonenumber.
|
||||
*
|
||||
* Currently, only Twilio is supported (https://www.twilio.com/lookup). A method
|
||||
* allow to check if the helper is configured for validation. This should be used
|
||||
* before doing some validation.
|
||||
*/
|
||||
interface PhoneNumberHelperInterface
|
||||
{
|
||||
public function format(PhoneNumber $phoneNumber): string;
|
||||
|
||||
/**
|
||||
* Get type (mobile, landline, ...) for phone number.
|
||||
*/
|
||||
public function getType(string $phonenumber): string;
|
||||
|
||||
/**
|
||||
* Return true if the validation is configured and available.
|
||||
*/
|
||||
public function isPhonenumberValidationConfigured(): bool;
|
||||
|
||||
/**
|
||||
* Return true if the phonenumber is a landline or voip phone. Return always true
|
||||
* if the validation is not configured.
|
||||
*/
|
||||
public function isValidPhonenumberAny(string $phonenumber): bool;
|
||||
|
||||
/**
|
||||
* Return true if the phonenumber is a landline or voip phone. Return always true
|
||||
* if the validation is not configured.
|
||||
*/
|
||||
public function isValidPhonenumberLandOrVoip(string $phonenumber): bool;
|
||||
|
||||
/**
|
||||
* REturn true if the phoennumber is a mobile phone. Return always true
|
||||
* if the validation is not configured.
|
||||
*/
|
||||
public function isValidPhonenumberMobile(string $phonenumber): bool;
|
||||
}
|
@ -15,8 +15,12 @@ use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use GuzzleHttp\Exception\ServerException;
|
||||
use libphonenumber\NumberParseException;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
|
||||
use function array_key_exists;
|
||||
use function in_array;
|
||||
@ -24,40 +28,32 @@ use function json_decode;
|
||||
use function preg_replace;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Helper to some task linked to phonenumber.
|
||||
*
|
||||
* Currently, only Twilio is supported (https://www.twilio.com/lookup). A method
|
||||
* allow to check if the helper is configured for validation. This should be used
|
||||
* before doing some validation.
|
||||
*/
|
||||
class PhonenumberHelper
|
||||
final class PhonenumberHelper implements PhoneNumberHelperInterface
|
||||
{
|
||||
public const FORMAT_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s';
|
||||
|
||||
public const LOOKUP_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s';
|
||||
|
||||
protected CacheItemPoolInterface $cachePool;
|
||||
private CacheItemPoolInterface $cachePool;
|
||||
|
||||
/**
|
||||
* TRUE if the client is properly configured.
|
||||
*/
|
||||
protected bool $isConfigured = false;
|
||||
private array $config;
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
private bool $isConfigured = false;
|
||||
|
||||
/**
|
||||
* Twilio client.
|
||||
*/
|
||||
protected Client $twilioClient;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
private PhonenumberUtil $phoneNumberUtil;
|
||||
|
||||
private Client $twilioClient;
|
||||
|
||||
public function __construct(
|
||||
CacheItemPoolInterface $cachePool,
|
||||
$config,
|
||||
CacheItemPoolInterface $cacheUserData,
|
||||
ParameterBagInterface $parameterBag,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->logger = $logger;
|
||||
$this->cachePool = $cachePool;
|
||||
$this->cachePool = $cacheUserData;
|
||||
$this->config = $config = $parameterBag->get('chill_main.phone_helper');
|
||||
|
||||
if (
|
||||
array_key_exists('twilio_sid', $config)
|
||||
@ -72,11 +68,19 @@ class PhonenumberHelper
|
||||
]);
|
||||
$this->isConfigured = true;
|
||||
}
|
||||
|
||||
$this->phoneNumberUtil = PhoneNumberUtil::getInstance();
|
||||
}
|
||||
|
||||
public function format($phonenumber)
|
||||
/**
|
||||
* @param string $phoneNumber A national phone number starting with +
|
||||
*
|
||||
* @throws NumberParseException
|
||||
*/
|
||||
public function format(PhoneNumber $phoneNumber): string
|
||||
{
|
||||
return $this->performTwilioFormat($phonenumber);
|
||||
return $this->phoneNumberUtil
|
||||
->formatOutOfCountryCallingNumber($phoneNumber, $this->config['default_carrier_code']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,7 +141,7 @@ class PhonenumberHelper
|
||||
}
|
||||
|
||||
/**
|
||||
* REturn true if the phoennumber is a mobile phone. Return always true
|
||||
* REturn true if the phonenumber is a mobile phone. Return always true
|
||||
* if the validation is not configured.
|
||||
*
|
||||
* @param string $phonenumber
|
||||
@ -157,68 +161,7 @@ class PhonenumberHelper
|
||||
return 'mobile' === $validation;
|
||||
}
|
||||
|
||||
protected function performTwilioFormat($phonenumber)
|
||||
{
|
||||
if (false === $this->isPhonenumberValidationConfigured()) {
|
||||
return $phonenumber;
|
||||
}
|
||||
|
||||
// filter only number
|
||||
$filtered = preg_replace('/[^0-9]/', '', $phonenumber);
|
||||
|
||||
$item = $this->cachePool->getItem('pnum_format_nat_' . $filtered);
|
||||
|
||||
if ($item->isHit()) {
|
||||
return $item->get();
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->twilioClient->get(sprintf(self::FORMAT_URI, '+' . $filtered), [
|
||||
'http_errors' => true,
|
||||
]);
|
||||
} catch (ClientException $e) {
|
||||
$response = $e->getResponse();
|
||||
$this->logger->error('[phonenumber helper] Could not format number '
|
||||
. 'due to client error', [
|
||||
'message' => $response->getBody()->getContents(),
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'phonenumber' => $phonenumber,
|
||||
]);
|
||||
|
||||
return $phonenumber;
|
||||
} catch (ServerException $e) {
|
||||
$response = $e->getResponse();
|
||||
$this->logger->error('[phonenumber helper] Could not format number '
|
||||
. 'due to server error', [
|
||||
'message' => $response->getBody()->getContents(),
|
||||
'status_code' => $response->getStatusCode(),
|
||||
'phonenumber' => $phonenumber,
|
||||
]);
|
||||
|
||||
return null;
|
||||
} catch (ConnectException $e) {
|
||||
$this->logger->error('[phonenumber helper] Could not format number '
|
||||
. 'due to connect error', [
|
||||
'message' => $e->getMessage(),
|
||||
'phonenumber' => $phonenumber,
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$format = json_decode($response->getBody()->getContents())->national_format;
|
||||
|
||||
$item
|
||||
->set($format)
|
||||
// expires after 3d
|
||||
->expiresAfter(3600 * 24 * 3);
|
||||
|
||||
$this->cachePool->save($item);
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
protected function performTwilioLookup($phonenumber)
|
||||
private function performTwilioLookup($phonenumber)
|
||||
{
|
||||
if (false === $this->isPhonenumberValidationConfigured()) {
|
||||
return null;
|
||||
@ -230,7 +173,7 @@ class PhonenumberHelper
|
||||
$item = $this->cachePool->getItem('pnum_' . $filtered);
|
||||
|
||||
if ($item->isHit()) {
|
||||
//return $item->get();
|
||||
return $item->get();
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -16,10 +16,7 @@ use Twig\TwigFilter;
|
||||
|
||||
class Templating extends AbstractExtension
|
||||
{
|
||||
/**
|
||||
* @var PhonenumberHelper
|
||||
*/
|
||||
protected $phonenumberHelper;
|
||||
protected PhonenumberHelper $phonenumberHelper;
|
||||
|
||||
public function __construct(PhonenumberHelper $phonenumberHelper)
|
||||
{
|
||||
|
@ -34,6 +34,20 @@ class EntityWorkflowRepository implements ObjectRepository
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countByPreviousDestWithoutReaction(User $user): int
|
||||
{
|
||||
$qb = $this->buildQueryByPreviousDestWithoutReaction($user)->select('count(ew)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countByPreviousTransitionned(User $user): int
|
||||
{
|
||||
$qb = $this->buildQueryByPreviousTransitionned($user)->select('count(ew)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countBySubscriber(User $user): int
|
||||
{
|
||||
$qb = $this->buildQueryBySubscriber($user)->select('count(ew)');
|
||||
@ -78,6 +92,32 @@ class EntityWorkflowRepository implements ObjectRepository
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findByPreviousDestWithoutReaction(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
$qb = $this->buildQueryByPreviousDestWithoutReaction($user)->select('ew');
|
||||
|
||||
foreach ($orderBy as $key => $sort) {
|
||||
$qb->addOrderBy('ew.' . $key, $sort);
|
||||
}
|
||||
|
||||
$qb->setMaxResults($limit)->setFirstResult($offset);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findByPreviousTransitionned(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
$qb = $this->buildQueryByPreviousTransitionned($user)->select('ew')->distinct(true);
|
||||
|
||||
foreach ($orderBy as $key => $sort) {
|
||||
$qb->addOrderBy('ew.' . $key, $sort);
|
||||
}
|
||||
|
||||
$qb->setMaxResults($limit)->setFirstResult($offset);
|
||||
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findBySubscriber(User $user, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
$qb = $this->buildQueryBySubscriber($user)->select('ew');
|
||||
@ -120,6 +160,41 @@ class EntityWorkflowRepository implements ObjectRepository
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function buildQueryByPreviousDestWithoutReaction(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('ew');
|
||||
|
||||
$qb->join('ew.steps', 'step');
|
||||
|
||||
$qb->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->isMemberOf(':user', 'step.destUser'),
|
||||
$qb->expr()->neq('step.transitionBy', ':user'),
|
||||
)
|
||||
);
|
||||
|
||||
$qb->setParameter('user', $user);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function buildQueryByPreviousTransitionned(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('ew');
|
||||
|
||||
$qb->join('ew.steps', 'step');
|
||||
|
||||
$qb->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('step.transitionBy', ':user'),
|
||||
)
|
||||
);
|
||||
|
||||
$qb->setParameter('user', $user);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function buildQueryBySubscriber(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('ew');
|
||||
|
@ -278,6 +278,7 @@ table.table-bordered {
|
||||
}
|
||||
|
||||
/// meta-data
|
||||
div.createdBy,
|
||||
div.updatedBy,
|
||||
div.metadata {
|
||||
span.user, span.date {
|
||||
|
@ -279,6 +279,7 @@ div.wrap-header {
|
||||
|
||||
li {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -316,7 +317,7 @@ div.float-button {
|
||||
|
||||
div.action {
|
||||
height: calc(100% - 0em);
|
||||
shape-outside: inset(calc(100% - 4em) 0 0);
|
||||
shape-outside: inset(calc(100% - 2em) 0 0);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 0 0 0 1em;
|
||||
@ -328,6 +329,10 @@ div.float-button {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chill-entity.entity-address .address p {
|
||||
display: unset;
|
||||
}
|
||||
}
|
||||
&.debug {
|
||||
padding: 1em;
|
||||
|
@ -1,7 +1,7 @@
|
||||
ul.record_actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap-reverse;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
padding: 0.5em 0;
|
||||
|
||||
|
@ -72,9 +72,9 @@ section.chill-entity {
|
||||
}
|
||||
}
|
||||
p {
|
||||
// display: inline-block;
|
||||
margin: 0 0 0 1.5em;
|
||||
text-indent: -1.5em;
|
||||
display: inline-block;
|
||||
//margin: 0 0 0 1.5em;
|
||||
//text-indent: -1.5em;
|
||||
|
||||
&.street {
|
||||
span.streetnumber {
|
||||
|
@ -11,19 +11,20 @@
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
console.log('200 error')
|
||||
return response.json();
|
||||
}
|
||||
|
||||
if (response.status === 422) {
|
||||
console.log('422 error')
|
||||
return response.json().then(response => {
|
||||
throw ValidationException(response)
|
||||
});
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
return response.json().then(() => {
|
||||
throw AccessException();
|
||||
});
|
||||
console.log('403 error')
|
||||
throw AccessException(response);
|
||||
}
|
||||
|
||||
throw {
|
||||
@ -88,14 +89,13 @@ const ValidationException = (response) => {
|
||||
error.violations = response.violations.map((violation) => `${violation.title}: ${violation.propertyPath}`);
|
||||
error.titles = response.violations.map((violation) => violation.title);
|
||||
error.propertyPaths = response.violations.map((violation) => violation.propertyPath);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
const AccessException = () => {
|
||||
const AccessException = (response) => {
|
||||
const error = {};
|
||||
error.name = 'AccessException';
|
||||
error.violations = ['You are no longer permitted to perform this action'];
|
||||
error.violations = ['You are not allowed to perform this action'];
|
||||
|
||||
return error;
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ var handleAdd = function(button) {
|
||||
empty_explain = collection.querySelector('li[data-collection-empty-explain]'),
|
||||
entry = document.createElement('li'),
|
||||
event = new CustomEvent('collection-add-entry', { detail: { collection: collection, entry: entry } }),
|
||||
counter = collection.childNodes.length,
|
||||
counter = collection.childNodes.length + parseInt(Math.random() * 1000000)
|
||||
content
|
||||
;
|
||||
content = prototype.replace(new RegExp('__name__', 'g'), counter);
|
||||
|
@ -1,11 +1,39 @@
|
||||
.confidential {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
.toggle-far-twig {
|
||||
i {
|
||||
bottom: 0px;
|
||||
right: -30px;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-close-twig {
|
||||
i {
|
||||
bottom: 0px;
|
||||
right: -5px;
|
||||
}
|
||||
}
|
||||
.toggle{
|
||||
margin-left: 30px;
|
||||
margin-top: 5px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
right: -30px
|
||||
}
|
||||
|
||||
.toggle-far {
|
||||
bottom: 0px;
|
||||
right: 20px !important;
|
||||
}
|
||||
|
||||
.toggle-close {
|
||||
bottom: 125px;
|
||||
right: 15px !important;
|
||||
}
|
||||
|
||||
.blur {
|
||||
-webkit-filter: blur(5px);
|
||||
-moz-filter: blur(5px);
|
||||
|
@ -3,7 +3,7 @@ import {ShowHide} from 'ChillMainAssets/lib/show_hide/show_hide.js';
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
let
|
||||
divTransitions = document.querySelector('#transitions'),
|
||||
futureDestUsersContainer = document.querySelector('#futureDestUsers')
|
||||
futureDestUsersContainer = document.querySelector('#futureDests')
|
||||
;
|
||||
|
||||
if (null !== divTransitions) {
|
||||
@ -67,24 +67,4 @@ window.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
|
||||
// validate form
|
||||
let form = document.querySelector('form[name="workflow_step"]');
|
||||
|
||||
if (form === null) {
|
||||
console.error('form to validate not found');
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
const datas = new FormData(event.target);
|
||||
|
||||
if (datas.has('workflow_step[future_dest_users]')) {
|
||||
const dests = JSON.parse(datas.get('workflow_step[future_dest_users]'));
|
||||
if (dests === null || (dests instanceof Array && dests.length === 0)) {
|
||||
event.preventDefault();
|
||||
console.log('no users!');
|
||||
window.alert('Indiquez un utilisateur pour traiter la prochaine étape.');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -115,7 +115,6 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
hasResourceComment() {
|
||||
//console.log('hasResourceComment', this.parent);
|
||||
return (typeof this.parent !== 'undefined' && this.parent !== null)
|
||||
&& this.action === 'show'
|
||||
&& this.parent.type === 'accompanying_period_resource'
|
||||
|
@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div :class="classes">
|
||||
<div class="confidential-content" :class="{ 'blur': isBlurred }">
|
||||
<div class="confidential">
|
||||
<div :class="{ 'blur': isBlurred }">
|
||||
<slot name="confidential-content"></slot>
|
||||
</div>
|
||||
<div>
|
||||
<i class="fa fa-eye toggle" aria-hidden="true" @click="toggleBlur"></i>
|
||||
<i class="fa fa-eye toggle" :class="positionBtn" aria-hidden="true" @click="toggleBlur"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -12,6 +12,7 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "Confidential",
|
||||
props: ['positionBtnFar'],
|
||||
data() {
|
||||
return {
|
||||
isBlurred: true,
|
||||
@ -19,9 +20,14 @@ export default {
|
||||
},
|
||||
methods : {
|
||||
toggleBlur() {
|
||||
console.log('toggle blur');
|
||||
console.log(this.positionBtnFar);
|
||||
this.isBlurred = !this.isBlurred;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
positionBtn() {
|
||||
return this.positionBtnFar ? 'toggle-far' : 'toggle-close'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -29,18 +35,5 @@ export default {
|
||||
<style scoped lang='scss'>
|
||||
.confidential{
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.toggle{
|
||||
margin-top: 28px;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: right;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.blur {
|
||||
-webkit-filter: blur(5px);
|
||||
-moz-filter: blur(5px);
|
||||
filter: blur(5px);
|
||||
}
|
||||
</style>
|
||||
|
@ -5,7 +5,7 @@
|
||||
<component :is="component" class="address" :class="multiline">
|
||||
|
||||
<div v-if="isConfidential">
|
||||
<confidential>
|
||||
<confidential :positionBtnFar="true">
|
||||
<template v-slot:confidential-content>
|
||||
<div v-if="isMultiline === true">
|
||||
<p v-for="(l, i) in address.lines" :key="`line-${i}`">
|
||||
@ -27,7 +27,6 @@
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</confidential>
|
||||
</div>
|
||||
|
||||
@ -94,6 +93,7 @@ export default {
|
||||
return this.isMultiline === true ? "multiline" : "";
|
||||
},
|
||||
isConfidential() {
|
||||
console.log(this.address.confidential)
|
||||
return this.address.confidential;
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
<div>
|
||||
<div class="item-row col">
|
||||
<h2>Workflow</h2>
|
||||
<h2>{{ w.title }}</h2>
|
||||
<div class="flex-grow-1 ms-3 h3">
|
||||
<div class="visually-hidden">
|
||||
{{ w.relatedEntityClass }}
|
||||
|
@ -12,6 +12,8 @@
|
||||
:relatedEntityClass="this.relatedEntityClass"
|
||||
:relatedEntityId="this.relatedEntityId"
|
||||
:workflowsAvailables="workflowsAvailables"
|
||||
:preventDefaultMoveToGenerate="this.$props.preventDefaultMoveToGenerate"
|
||||
:goToGenerateWorkflowPayload="this.goToGenerateWorkflowPayload"
|
||||
@go-to-generate-workflow="goToGenerateWorkflow"
|
||||
></pick-workflow>
|
||||
|
||||
@ -36,6 +38,7 @@
|
||||
:relatedEntityId="this.relatedEntityId"
|
||||
:workflowsAvailables="workflowsAvailables"
|
||||
:preventDefaultMoveToGenerate="this.$props.preventDefaultMoveToGenerate"
|
||||
:goToGenerateWorkflowPayload="this.goToGenerateWorkflowPayload"
|
||||
@go-to-generate-workflow="this.goToGenerateWorkflow"
|
||||
></pick-workflow>
|
||||
</template>
|
||||
@ -83,6 +86,10 @@ export default {
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
goToGenerateWorkflowPayload: {
|
||||
required: false,
|
||||
default: {}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -105,7 +112,7 @@ export default {
|
||||
this.modal.showModal = true;
|
||||
},
|
||||
goToGenerateWorkflow(data) {
|
||||
console.log('go to generate workflow intercepted');
|
||||
console.log('go to generate workflow intercepted', data);
|
||||
this.$emit('goToGenerateWorkflow', data);
|
||||
}
|
||||
},
|
||||
|
@ -37,6 +37,10 @@ export default {
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
goToGenerateWorkflowPayload: {
|
||||
required: false,
|
||||
default: {}
|
||||
},
|
||||
},
|
||||
emits: ['goToGenerateWorkflow'],
|
||||
methods: {
|
||||
@ -51,7 +55,7 @@ export default {
|
||||
window.location.assign(this.makeLink(workflowName));
|
||||
}
|
||||
|
||||
this.$emit('goToGenerateWorkflow', {event, workflowName, link: this.makeLink(workflowName)});
|
||||
this.$emit('goToGenerateWorkflow', {event, workflowName, link: this.makeLink(workflowName), payload: this.goToGenerateWorkflowPayload});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@
|
||||
must be shown in such list
|
||||
#}
|
||||
{%- if render == 'list' -%}
|
||||
<li class="chill-entity entity-address {% if address.confidential %}confidential{% endif %}">
|
||||
<li class="chill-entity entity-address {% if address.confidential %} confidential toggle-far-twig {% endif %}">
|
||||
{% if options['with_picto'] %}
|
||||
<i class="fa fa-li fa-map-marker"></i>
|
||||
{% endif %}
|
||||
@ -68,7 +68,7 @@
|
||||
{%- endif -%}
|
||||
|
||||
{%- if render == 'inline' -%}
|
||||
<span class="chill-entity entity-address {% if address.confidential %}confidential{% endif %}">
|
||||
<span class="chill-entity entity-address {% if address.confidential %} confidential toggle-far-twig {% endif %}">
|
||||
{% if options['with_picto'] %}
|
||||
<i class="fa fa-fw fa-map-marker"></i>
|
||||
{% endif %}
|
||||
@ -77,7 +77,7 @@
|
||||
{%- endif -%}
|
||||
|
||||
{%- if render == 'bloc' -%}
|
||||
<div class="chill-entity entity-address {% if address.confidential %}confidential{% endif %}">
|
||||
<div class="chill-entity entity-address {% if address.confidential %} confidential toggle-close-twig {% endif %}">
|
||||
{% if options['has_no_address'] == true and address.isNoAddress == true %}
|
||||
{% if address.postCode is not empty %}
|
||||
<div class="address{% if options['multiline'] %} multiline{% endif %}{% if options['with_delimiter'] %} delimiter{% endif %}">
|
||||
|
@ -18,8 +18,10 @@
|
||||
{% for entity in entities %}
|
||||
<tr>
|
||||
<td>{{ entity.name }}</td>
|
||||
<td>{{ entity.phonenumber1 }}</td>
|
||||
<td>{{ entity.phonenumber2 }}</td>
|
||||
<td>
|
||||
{{ entity.phonenumber1|chill_format_phonenumber }}
|
||||
</td>
|
||||
<td>{{ entity.phonenumber2|chill_format_phonenumber }}</td>
|
||||
<td>{{ entity.email }}</td>
|
||||
<td>
|
||||
{% if entity.address is not null %}
|
||||
|
@ -3,6 +3,8 @@
|
||||
{% if transition_form is not null %}
|
||||
{{ form_start(transition_form) }}
|
||||
|
||||
{{ form_errors(transition_form) }}
|
||||
|
||||
{% set step = entity_workflow.currentStepChained %}
|
||||
{% set labels = workflow_metadata(entity_workflow, 'label', step.currentStep) %}
|
||||
{% set label = labels is null ? step.currentStep : labels|localize_translatable_string %}
|
||||
@ -60,8 +62,10 @@
|
||||
{{ form_row(transition_form.freezeAfter) }}
|
||||
{% endif %}
|
||||
|
||||
<div id="futureDestUsers">
|
||||
<div id="futureDests">
|
||||
{{ form_row(transition_form.future_dest_users) }}
|
||||
|
||||
{{ form_row(transition_form.future_dest_emails) }}
|
||||
</div>
|
||||
|
||||
<p>{{ form_label(transition_form.comment) }}</p>
|
||||
@ -82,14 +86,24 @@
|
||||
<p>{{ 'workflow.This workflow is finalized'|trans }}</p>
|
||||
{% else %}
|
||||
<p>{{ 'workflow.You are not allowed to apply a transition on this workflow'|trans }}</p>
|
||||
<p>{{ 'workflow.Only those users are allowed'|trans }}:</p>
|
||||
|
||||
{% if entity_workflow.currentStep.destUser|length > 0 %}
|
||||
<p><b>{{ 'workflow.Only those users are allowed'|trans }} :</b></p>
|
||||
<ul>
|
||||
{% for u in entity_workflow.currentStep.destUser -%}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if entity_workflow.currentStep.destUserByAccessKey|length > 0 %}
|
||||
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
||||
<ul>
|
||||
{% for u in entity_workflow.currentStep.destUserByAccessKey %}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div data-list-workflows="1"
|
||||
data-workflows="{{ entity_workflows_json|json_encode|e('html_attr') }}"
|
||||
data-allow-create="{{ acl }}"
|
||||
data-related-entity-class="{{ blank_workflow.relatedEntityClass }}"
|
||||
data-related-entity-id="{{ blank_workflow.relatedEntityId }}"
|
||||
data-workflows-availables="{{ workflows_availables|json_encode()|e('html_attr') }}"
|
||||
data-related-entity-class="{{ relatedEntityClass }}"
|
||||
data-related-entity-id="{{ relatedEntityId }}"
|
||||
data-workflows-availables="{{ workflows_available|json_encode()|e('html_attr') }}"
|
||||
></div>
|
||||
|
@ -69,15 +69,26 @@
|
||||
</blockquote>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if loop.last and step.destUser|length > 0 %}
|
||||
{% if loop.last and step.allDestUser|length > 0 %}
|
||||
<div class="item-row separator">
|
||||
<div>
|
||||
{% if step.destUser|length > 0 %}
|
||||
<p><b>{{ 'workflow.Users allowed to apply transition'|trans }} : </b></p>
|
||||
<ul>
|
||||
{% for u in step.destUser %}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if step.destUserByAccessKey|length > 0 %}
|
||||
<p><b>{{ 'workflow.Those users are also granted to apply a transition by using an access key'|trans }} :</b></p>
|
||||
<ul>
|
||||
{% for u in step.destUserByAccessKey %}
|
||||
<li>{{ u|chill_entity_render_box }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -0,0 +1,29 @@
|
||||
{% extends '@ChillMain/layout.html.twig' %}
|
||||
|
||||
{% block title 'workflow.Delete workflow ?'|trans %}
|
||||
|
||||
{% block display_content %}
|
||||
<h2>
|
||||
{{ handler.entityTitle(entityWorkflow) }}
|
||||
</h2>
|
||||
|
||||
{% include handler.template(entityWorkflow) with handler.templateData(entityWorkflow)|merge({
|
||||
'display_action': false
|
||||
}) %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container chill-md-10">
|
||||
{{ include('@ChillMain/Util/confirmation_template.html.twig',
|
||||
{
|
||||
'title' : 'workflow.Delete workflow ?'|trans,
|
||||
'confirm_question' : 'workflow.Are you sure you want to delete this workflow ?'|trans,
|
||||
'display_content' : block('display_content'),
|
||||
'cancel_route' : 'chill_main_workflow_show',
|
||||
'cancel_parameters' : {'id' : entityWorkflow.id},
|
||||
'form' : delete_form
|
||||
} ) }}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -21,6 +21,8 @@
|
||||
{{ encore_entry_link_tags('mod_wopi_link') }}
|
||||
{% endblock %}
|
||||
|
||||
{% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as macro %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-10 workflow">
|
||||
<h1 class="mb-5">{{ block('title') }}</h1>
|
||||
@ -32,9 +34,24 @@
|
||||
#}
|
||||
<section class="step my-4">
|
||||
<div class="mb-5">
|
||||
{% include handler_template_title with handler_template_data|merge({'breadcrumb': true }) %}
|
||||
<h2>{{ handler.entityTitle(entity_workflow) }}</h2>
|
||||
|
||||
{{ macro.breadcrumb({'entity_workflow': entity_workflow}) }}
|
||||
</div>
|
||||
|
||||
{% include handler_template with handler_template_data|merge({'display_action': true }) %}
|
||||
|
||||
{% if is_granted('CHILL_MAIN_WORKFLOW_DELETE', entity_workflow) %}
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<a class="btn btn-delete"
|
||||
href="{{ chill_path_add_return_path('chill_main_workflow_delete', {'id': entity_workflow.id}) }}"
|
||||
>
|
||||
{{ 'workflow.Delete workflow'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
<section class="step my-4">{% include '@ChillMain/Workflow/_follow.html.twig' %}</section>
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
<h1 class="mb-5">{{ block('title') }}</h1>
|
||||
|
||||
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li class="nav-item">
|
||||
<a href="{{ path('chill_main_workflow_list_subscribed') }}"
|
||||
@ -24,8 +25,24 @@
|
||||
{{ 'workflow.dest'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ path('chill_main_workflow_list_previous_without_reaction') }}"
|
||||
class="nav-link {% if step == 'previous_without_reaction' %}active{% endif %}">
|
||||
{{ 'workflow.Previous dest without reaction'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="{{ path('chill_main_workflow_list_previous_transitionned') }}"
|
||||
class="nav-link {% if step == 'previous_transitionned' %}active{% endif %}">
|
||||
{{ 'workflow.Previous transitionned'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% if help is defined %}
|
||||
<p style="margin-top: 2rem;">{{ help|trans }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if workflows|length == 0 %}
|
||||
<p class="chill-no-data-statement">{{ 'workflow.No workflow'|trans }}</p>
|
||||
{% else %}
|
||||
@ -39,16 +56,14 @@
|
||||
|
||||
<div class="item-row col">
|
||||
<h2>
|
||||
{{ 'workflow_'|trans }}
|
||||
{{ l.handler.entityTitle(l.entity_workflow) }}
|
||||
</h2>
|
||||
{% include l.handler.templateTitle(l.entity_workflow) with l.handler.templateTitleData(l.entity_workflow)|merge({
|
||||
'description': true,
|
||||
'add_classes': 'ms-3 h3'
|
||||
}) %}
|
||||
</div>
|
||||
|
||||
</button>
|
||||
<div>
|
||||
{{ macro.breadcrumb(l) }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="flush-collapse-{{ l.entity_workflow.id }}"
|
||||
@ -78,6 +93,13 @@
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_MAIN_WORKFLOW_DELETE', l.entity_workflow) %}
|
||||
<li>
|
||||
<a class="btn btn-delete"
|
||||
href="{{ chill_path_add_return_path('chill_main_workflow_delete', {'id': l.entity_workflow.id}) }}"
|
||||
></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{{ path('chill_main_workflow_show', {'id': l.entity_workflow.id}) }}"
|
||||
class="btn btn-show">
|
||||
|
@ -0,0 +1,15 @@
|
||||
Madame, Monsieur,
|
||||
|
||||
Un suivi "{{ workflow.text }}" a atteint une nouvelle étape: {{ workflow.text }}.
|
||||
|
||||
Titre du workflow: "{{ entityTitle }}".
|
||||
|
||||
Vous êtes invité·e à valider cette étape. Pour obtenir un accès, vous pouvez cliquer sur le lien suivant:
|
||||
|
||||
{{ absolute_url(path('chill_main_workflow_grant_access_by_key', {'id': entity_workflow.currentStep.id, 'accessKey': entity_workflow.currentStep.accessKey})) }}
|
||||
|
||||
Dès que vous aurez cliqué une fois sur le lien, vous serez autorisé à valider cette étape.
|
||||
|
||||
Notez que vous devez disposer d'un compte utilisateur valide dans Chill.
|
||||
|
||||
Cordialement,
|
@ -0,0 +1 @@
|
||||
Un suivi {{ workflow.text }} demande votre attention: {{ entityTitle }}
|
@ -11,8 +11,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Search\Utils;
|
||||
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use LogicException;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use function count;
|
||||
use function implode;
|
||||
use function preg_match;
|
||||
@ -24,6 +26,13 @@ class ExtractPhonenumberFromPattern
|
||||
{
|
||||
private const PATTERN = '([\\+]{0,1}[0-9\\ ]{5,})';
|
||||
|
||||
private string $defaultCarrierCode;
|
||||
|
||||
public function __construct(ParameterBagInterface $parameterBag)
|
||||
{
|
||||
$this->defaultCarrierCode = $parameterBag->get('chill_main')['phone_helper']['default_carrier_code'];
|
||||
}
|
||||
|
||||
public function extractPhonenumber(string $subject): SearchExtractionResult
|
||||
{
|
||||
$matches = [];
|
||||
@ -35,11 +44,21 @@ class ExtractPhonenumberFromPattern
|
||||
|
||||
foreach (str_split(trim($matches[0])) as $key => $char) {
|
||||
switch ($char) {
|
||||
case '+':
|
||||
if (0 === $key) {
|
||||
$phonenumber[] = $char;
|
||||
} else {
|
||||
throw new LogicException('should not match not alnum character');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '0':
|
||||
$length++;
|
||||
|
||||
if (0 === $key) {
|
||||
$phonenumber[] = '+32';
|
||||
$util = PhoneNumberUtil::getInstance();
|
||||
$phonenumber[] = '+' . $util->getCountryCodeForRegion($this->defaultCarrierCode);
|
||||
} else {
|
||||
$phonenumber[] = $char;
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ class EntityWorkflowVoter extends Voter
|
||||
{
|
||||
public const CREATE = 'CHILL_MAIN_WORKFLOW_CREATE';
|
||||
|
||||
public const DELETE = 'CHILL_MAIN_WORKFLOW_DELETE';
|
||||
|
||||
public const SEE = 'CHILL_MAIN_WORKFLOW_SEE';
|
||||
|
||||
private EntityWorkflowManager $manager;
|
||||
@ -40,7 +42,11 @@ class EntityWorkflowVoter extends Voter
|
||||
return $subject instanceof EntityWorkflow && in_array($attribute, self::getRoles(), true);
|
||||
}
|
||||
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||
/**
|
||||
* @param EntityWorkflow $subject
|
||||
* @param mixed $attribute
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
|
||||
{
|
||||
switch ($attribute) {
|
||||
case self::CREATE:
|
||||
@ -55,6 +61,9 @@ class EntityWorkflowVoter extends Voter
|
||||
|
||||
return $this->security->isGranted($entityAttribute, $handler->getRelatedEntity($subject));
|
||||
|
||||
case self::DELETE:
|
||||
return $subject->getStep() === 'initial';
|
||||
|
||||
default:
|
||||
throw new UnexpectedValueException("attribute {$attribute} not supported");
|
||||
}
|
||||
@ -65,6 +74,7 @@ class EntityWorkflowVoter extends Voter
|
||||
return [
|
||||
self::SEE,
|
||||
self::CREATE,
|
||||
self::DELETE,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
<?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\Serializer\Normalizer;
|
||||
|
||||
use libphonenumber\NumberParseException;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
|
||||
|
||||
class PhonenumberNormalizer implements NormalizerInterface, DenormalizerInterface
|
||||
{
|
||||
private string $defaultCarrierCode;
|
||||
|
||||
private PhoneNumberUtil $phoneNumberUtil;
|
||||
|
||||
public function __construct(ParameterBagInterface $parameterBag)
|
||||
{
|
||||
$this->defaultCarrierCode = $parameterBag->get('chill_main')['phone_helper']['default_carrier_code'];
|
||||
$this->phoneNumberUtil = PhoneNumberUtil::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @param mixed $type
|
||||
* @param null|mixed $format
|
||||
*
|
||||
* @throws UnexpectedValueException
|
||||
*/
|
||||
public function denormalize($data, $type, $format = null, array $context = [])
|
||||
{
|
||||
try {
|
||||
return $this->phoneNumberUtil->parse($data, $this->defaultCarrierCode);
|
||||
} catch (NumberParseException $e) {
|
||||
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function normalize($object, ?string $format = null, array $context = []): string
|
||||
{
|
||||
return $this->phoneNumberUtil->formatOutOfCountryCallingNumber($object, $this->defaultCarrierCode);
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, $type, $format = null)
|
||||
{
|
||||
return 'libphonenumber\PhoneNumber' === $type;
|
||||
}
|
||||
|
||||
public function supportsNormalization($data, ?string $format = null)
|
||||
{
|
||||
return $data instanceof PhoneNumber;
|
||||
}
|
||||
}
|
@ -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\MainBundle\Tests\Routing\Loader;
|
||||
|
||||
use Chill\MainBundle\Phonenumber\PhonenumberHelper;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @coversNothing
|
||||
*/
|
||||
final class PhonenumberHelperTest extends KernelTestCase
|
||||
{
|
||||
public function formatPhonenumbers()
|
||||
{
|
||||
yield [
|
||||
'BE',
|
||||
'+3281136917',
|
||||
'081 13 69 17',
|
||||
];
|
||||
|
||||
yield [
|
||||
'FR',
|
||||
'+33 6 23 12 45 54',
|
||||
'06 23 12 45 54',
|
||||
];
|
||||
|
||||
yield [
|
||||
'FR',
|
||||
'+32 81 13 69 17',
|
||||
'00 32 81 13 69 17',
|
||||
];
|
||||
|
||||
yield [
|
||||
'BE',
|
||||
'+33 6 23 12 45 54',
|
||||
'00 33 6 23 12 45 54',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider formatPhonenumbers
|
||||
*/
|
||||
public function testFormatPhonenumbers(string $defaultCarrierCode, string $phoneNumber, string $expected)
|
||||
{
|
||||
$util = PhoneNumberUtil::getInstance();
|
||||
$subject = new PhonenumberHelper(
|
||||
new ArrayAdapter(),
|
||||
new ParameterBag([
|
||||
'chill_main.phone_helper' => [
|
||||
'default_carrier_code' => $defaultCarrierCode,
|
||||
],
|
||||
]),
|
||||
new NullLogger()
|
||||
);
|
||||
|
||||
$this->assertEquals($expected, $subject->format($util->parse($phoneNumber)));
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ namespace Search\Utils;
|
||||
|
||||
use Chill\MainBundle\Search\Utils\ExtractPhonenumberFromPattern;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -22,17 +23,25 @@ final class ExtractPhonenumberFromPatternTest extends KernelTestCase
|
||||
{
|
||||
public function provideData()
|
||||
{
|
||||
yield ['Diallo', 0, [], 'Diallo', 'no phonenumber'];
|
||||
yield ['BE', 'Diallo', 0, [], 'Diallo', 'no phonenumber'];
|
||||
|
||||
yield ['Diallo 15/06/2021', 0, [], 'Diallo 15/06/2021', 'no phonenumber and a date'];
|
||||
yield ['BE', 'Diallo 15/06/2021', 0, [], 'Diallo 15/06/2021', 'no phonenumber and a date'];
|
||||
|
||||
yield ['Diallo 0486 123 456', 1, ['+32486123456'], 'Diallo', 'a phonenumber and a name'];
|
||||
yield ['BE', 'Diallo 0486 123 456', 1, ['+32486123456'], 'Diallo', 'a phonenumber and a name'];
|
||||
|
||||
yield ['Diallo 123 456', 1, ['123456'], 'Diallo', 'a number and a name, without leadiing 0'];
|
||||
yield ['BE', 'Diallo 123 456', 1, ['123456'], 'Diallo', 'a number and a name, without leadiing 0'];
|
||||
|
||||
yield ['123 456', 1, ['123456'], '', 'only phonenumber'];
|
||||
yield ['BE', '123 456', 1, ['123456'], '', 'only phonenumber'];
|
||||
|
||||
yield ['0123 456', 1, ['+32123456'], '', 'only phonenumber with a leading 0'];
|
||||
yield ['BE', '0123 456', 1, ['+32123456'], '', 'only phonenumber with a leading 0'];
|
||||
|
||||
yield ['FR', '123 456', 1, ['123456'], '', 'only phonenumber'];
|
||||
|
||||
yield ['FR', '0123 456', 1, ['+33123456'], '', 'only phonenumber with a leading 0'];
|
||||
|
||||
yield ['FR', 'Diallo 0486 123 456', 1, ['+33486123456'], 'Diallo', 'a phonenumber and a name'];
|
||||
|
||||
yield ['FR', 'Diallo +32486 123 456', 1, ['+32486123456'], 'Diallo', 'a phonenumber and a name'];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,9 +53,11 @@ final class ExtractPhonenumberFromPatternTest extends KernelTestCase
|
||||
* @param mixed $filteredSubject
|
||||
* @param mixed $msg
|
||||
*/
|
||||
public function testExtract($subject, $expectedCount, $expected, $filteredSubject, $msg)
|
||||
public function testExtract(string $defaultCarrierCode, $subject, $expectedCount, $expected, $filteredSubject, $msg)
|
||||
{
|
||||
$extractor = new ExtractPhonenumberFromPattern();
|
||||
$extractor = new ExtractPhonenumberFromPattern(new ParameterBag(['chill_main' => [
|
||||
'phone_helper' => ['default_carrier_code' => $defaultCarrierCode],
|
||||
]]));
|
||||
$result = $extractor->extractPhonenumber($subject);
|
||||
|
||||
$this->assertCount($expectedCount, $result->getFound());
|
||||
|
@ -11,24 +11,21 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Validation\Validator;
|
||||
|
||||
use Chill\MainBundle\Phonenumber\PhonenumberHelper;
|
||||
use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface;
|
||||
use LogicException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
class ValidPhonenumber extends ConstraintValidator
|
||||
final class ValidPhonenumber extends ConstraintValidator
|
||||
{
|
||||
protected $logger;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
/**
|
||||
* @var PhonenumberHelper
|
||||
*/
|
||||
protected $phonenumberHelper;
|
||||
private PhoneNumberHelperInterface $phonenumberHelper;
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
PhonenumberHelper $phonenumberHelper
|
||||
PhoneNumberHelperInterface $phonenumberHelper
|
||||
) {
|
||||
$this->phonenumberHelper = $phonenumberHelper;
|
||||
$this->logger = $logger;
|
||||
@ -46,7 +43,7 @@ class ValidPhonenumber extends ConstraintValidator
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($value)) {
|
||||
if ('' === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -33,10 +33,6 @@ interface EntityWorkflowHandlerInterface
|
||||
|
||||
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array;
|
||||
|
||||
public function getTemplateTitle(EntityWorkflow $entityWorkflow, array $options = []): string;
|
||||
|
||||
public function getTemplateTitleData(EntityWorkflow $entityWorkflow, array $options = []): array;
|
||||
|
||||
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool;
|
||||
|
||||
public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool;
|
||||
|
@ -72,7 +72,7 @@ class EntityWorkflowTransitionEventSubscriber implements EventSubscriberInterfac
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entityWorkflow->getCurrentStep()->getDestUser()->contains($this->security->getUser())) {
|
||||
if (!$entityWorkflow->getCurrentStep()->getAllDestUser()->contains($this->security->getUser())) {
|
||||
if (!$event->getMarking()->has('initial')) {
|
||||
$event->addTransitionBlocker(new TransitionBlocker(
|
||||
'workflow.You are not allowed to apply a transition on this workflow. Only those users are allowed: %users%',
|
||||
@ -80,7 +80,7 @@ class EntityWorkflowTransitionEventSubscriber implements EventSubscriberInterfac
|
||||
[
|
||||
'%users%' => implode(
|
||||
', ',
|
||||
$entityWorkflow->getCurrentStep()->getDestUser()->map(function (User $u) {
|
||||
$entityWorkflow->getCurrentStep()->getAllDestUser()->map(function (User $u) {
|
||||
return $this->userRender->renderString($u, []);
|
||||
})->toArray()
|
||||
),
|
||||
|
@ -52,11 +52,11 @@ class NotificationOnTransition implements EventSubscriberInterface
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'workflow.completed' => 'onCompleted',
|
||||
'workflow.completed' => 'onCompletedSendNotification',
|
||||
];
|
||||
}
|
||||
|
||||
public function onCompleted(Event $event): void
|
||||
public function onCompletedSendNotification(Event $event): void
|
||||
{
|
||||
if (!$event->getSubject() instanceof EntityWorkflow) {
|
||||
return;
|
||||
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Workflow\EventSubscriber;
|
||||
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Chill\MainBundle\Workflow\EntityWorkflowManager;
|
||||
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
|
||||
class SendAccessKeyEventSubscriber
|
||||
{
|
||||
private EngineInterface $engine;
|
||||
|
||||
private EntityWorkflowManager $entityWorkflowManager;
|
||||
|
||||
private MailerInterface $mailer;
|
||||
|
||||
private MetadataExtractor $metadataExtractor;
|
||||
|
||||
private Registry $registry;
|
||||
|
||||
public function __construct(EngineInterface $engine, MetadataExtractor $metadataExtractor, Registry $registry, EntityWorkflowManager $entityWorkflowManager, MailerInterface $mailer)
|
||||
{
|
||||
$this->engine = $engine;
|
||||
$this->metadataExtractor = $metadataExtractor;
|
||||
$this->registry = $registry;
|
||||
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||
$this->mailer = $mailer;
|
||||
}
|
||||
|
||||
public function postPersist(EntityWorkflowStep $step): void
|
||||
{
|
||||
$entityWorkflow = $step->getEntityWorkflow();
|
||||
|
||||
$place = $this->metadataExtractor->buildArrayPresentationForPlace($entityWorkflow);
|
||||
$workflow = $this->metadataExtractor->buildArrayPresentationForWorkflow(
|
||||
$this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName())
|
||||
);
|
||||
$handler = $this->entityWorkflowManager->getHandler($entityWorkflow);
|
||||
|
||||
foreach ($entityWorkflow->futureDestEmails as $emailAddress) {
|
||||
$context = [
|
||||
'entity_workflow' => $entityWorkflow,
|
||||
'dest' => $emailAddress,
|
||||
'place' => $place,
|
||||
'workflow' => $workflow,
|
||||
'entityTitle' => $handler->getEntityTitle($entityWorkflow),
|
||||
];
|
||||
|
||||
$email = new Email();
|
||||
$email
|
||||
->addTo($emailAddress)
|
||||
->subject($this->engine->render('@ChillMain/Workflow/workflow_send_access_key_title.fr.txt.twig', $context))
|
||||
->text($this->engine->render('@ChillMain/Workflow/workflow_send_access_key.fr.txt.twig', $context));
|
||||
|
||||
$this->mailer->send($email);
|
||||
}
|
||||
}
|
||||
}
|
@ -61,7 +61,38 @@ class WorkflowTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||
return null;
|
||||
}
|
||||
|
||||
public function listWorkflows(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
|
||||
/**
|
||||
* @param array{relatedEntityClass: string, relatedEntityId: int} $supplementaryRelated
|
||||
*
|
||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
|
||||
* @throws \Twig\Error\LoaderError
|
||||
* @throws \Twig\Error\RuntimeError
|
||||
* @throws \Twig\Error\SyntaxError
|
||||
*/
|
||||
public function listWorkflows(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = [], array $supplementaryRelated = []): string
|
||||
{
|
||||
[$blankEntityWorkflow, $workflowsAvailable, $entityWorkflows] = $this->getWorkflowsForRelated($relatedEntityClass, $relatedEntityId);
|
||||
|
||||
foreach ($supplementaryRelated as $supplementary) {
|
||||
[$supplementaryBlankEntityWorkflow, $supplementaryWorkflowsAvailable, $supplementaryEntityWorkflows]
|
||||
= $this->getWorkflowsForRelated($supplementary['relatedEntityClass'], $supplementary['relatedEntityId']);
|
||||
|
||||
$entityWorkflows = array_merge($entityWorkflows, $supplementaryEntityWorkflows);
|
||||
}
|
||||
|
||||
return $environment->render('@ChillMain/Workflow/_extension_list_workflow_for.html.twig', [
|
||||
'entity_workflows_json' => $this->normalizer->normalize($entityWorkflows, 'json', ['groups' => 'read']),
|
||||
'blank_workflow' => $blankEntityWorkflow,
|
||||
'workflows_available' => $workflowsAvailable,
|
||||
'relatedEntityClass' => $relatedEntityClass,
|
||||
'relatedEntityId' => $relatedEntityId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array where keys are: {0: blankWorkflow, 1: entityWorkflows, 2: workflowList}
|
||||
*/
|
||||
private function getWorkflowsForRelated(string $relatedEntityClass, int $relatedEntityId): array
|
||||
{
|
||||
$blankEntityWorkflow = new EntityWorkflow();
|
||||
$blankEntityWorkflow
|
||||
@ -70,25 +101,13 @@ class WorkflowTwigExtensionRuntime implements RuntimeExtensionInterface
|
||||
|
||||
$workflowsList = $this->metadataExtractor->availableWorkflowFor($relatedEntityClass, $relatedEntityId);
|
||||
|
||||
// get the related entity already created
|
||||
$entityWorkflows = [];
|
||||
|
||||
foreach ($entityWorkflowsNaked = $this->repository->findBy(
|
||||
return
|
||||
[
|
||||
$blankEntityWorkflow,
|
||||
$workflowsList,
|
||||
$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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@
|
||||
],
|
||||
"require": {
|
||||
"league/csv": "^9.6",
|
||||
"phpoffice/phpspreadsheet": "~1.2"
|
||||
"phpoffice/phpspreadsheet": "~1.2",
|
||||
"odolbeau/phone-number-bundle": "^3.6"
|
||||
},
|
||||
"require-dev": {
|
||||
},
|
||||
|
@ -38,6 +38,17 @@ services:
|
||||
arguments:
|
||||
$handlers: !tagged_iterator chill_main.workflow_handler
|
||||
|
||||
Chill\MainBundle\Workflow\EventSubscriber\SendAccessKeyEventSubscriber:
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
tags:
|
||||
-
|
||||
name: 'doctrine.orm.entity_listener'
|
||||
event: 'postPersist'
|
||||
entity: 'Chill\MainBundle\Entity\Workflow\EntityWorkflowStep'
|
||||
# set the 'lazy' option to TRUE to only instantiate listeners when they are used
|
||||
lazy: true
|
||||
|
||||
# other stuffes
|
||||
|
||||
chill.main.helper.translatable_string:
|
||||
@ -85,3 +96,4 @@ services:
|
||||
- "@security.token_storage"
|
||||
|
||||
Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverDispatcher'
|
||||
|
||||
|
@ -3,19 +3,12 @@ services:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\MainBundle\Phonenumber\PhonenumberHelper:
|
||||
arguments:
|
||||
$config: '%chill_main.phone_helper%'
|
||||
$cachePool: '@cache.user_data'
|
||||
Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface: '@Chill\MainBundle\Phonenumber\PhonenumberHelper'
|
||||
|
||||
Chill\MainBundle\Phonenumber\Templating:
|
||||
arguments:
|
||||
$phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper'
|
||||
tags:
|
||||
- { name: twig.extension }
|
||||
Chill\MainBundle\Phonenumber\PhonenumberHelper: ~
|
||||
|
||||
Chill\MainBundle\Phonenumber\Templating: ~
|
||||
|
||||
Chill\MainBundle\Validation\Validator\ValidPhonenumber:
|
||||
arguments:
|
||||
$phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper'
|
||||
tags:
|
||||
- { name: validator.constraint_validator }
|
||||
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20220223171457 extends AbstractMigration
|
||||
{
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step DROP accessKey');
|
||||
$this->addSql('DROP TABLE chill_main_workflow_entity_step_user_by_accesskey');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add access key to EntityWorkflowStep';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ADD accessKey TEXT DEFAULT NULL');
|
||||
$this->addSql('WITH randoms AS (select
|
||||
cmwes.id,
|
||||
string_agg(substr(characters, (random() * length(characters) + 0.5)::integer, 1), \'\') as random_word
|
||||
from (values(\'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\')) as symbols(characters)
|
||||
-- length of word
|
||||
join generate_series(1, 32) on 1 = 1
|
||||
JOIN chill_main_workflow_entity_step cmwes ON true
|
||||
GROUP BY cmwes.id)
|
||||
UPDATE chill_main_workflow_entity_step SET accessKey = randoms.random_word FROM randoms WHERE chill_main_workflow_entity_step.id = randoms.id');
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER accessKey DROP DEFAULT ');
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step ALTER accessKey SET NOT NULL');
|
||||
|
||||
$this->addSql('CREATE TABLE chill_main_workflow_entity_step_user_by_accesskey (entityworkflowstep_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(entityworkflowstep_id, user_id))');
|
||||
$this->addSql('CREATE INDEX IDX_8296D8397E6AF9D4 ON chill_main_workflow_entity_step_user_by_accesskey (entityworkflowstep_id)');
|
||||
$this->addSql('CREATE INDEX IDX_8296D839A76ED395 ON chill_main_workflow_entity_step_user_by_accesskey (user_id)');
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_user_by_accesskey ADD CONSTRAINT FK_8296D8397E6AF9D4 FOREIGN KEY (entityworkflowstep_id) REFERENCES chill_main_workflow_entity_step (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_main_workflow_entity_step_user_by_accesskey ADD CONSTRAINT FK_8296D839A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
}
|
||||
}
|
@ -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\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
|
||||
final class Version20220302132728 extends AbstractMigration implements ContainerAwareInterface
|
||||
{
|
||||
use ContainerAwareTrait;
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber1 TYPE VARCHAR(64)');
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber1 DROP DEFAULT');
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 TYPE VARCHAR(64)');
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 DROP DEFAULT');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_location.phonenumber1 IS NULL');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_location.phonenumber2 IS NULL');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Upgrade phonenumber on location';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$carrier_code = $this->container
|
||||
->getParameter('chill_main')['phone_helper']['default_carrier_code'];
|
||||
|
||||
if (null === $carrier_code) {
|
||||
throw new RuntimeException('no carrier code');
|
||||
}
|
||||
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber1 TYPE VARCHAR(35)');
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber1 DROP DEFAULT');
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber1 TYPE VARCHAR(35)');
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 TYPE VARCHAR(35)');
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 DROP DEFAULT');
|
||||
$this->addSql('ALTER TABLE chill_main_location ALTER phonenumber2 TYPE VARCHAR(35)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_location.phonenumber1 IS \'(DC2Type:phone_number)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_location.phonenumber2 IS \'(DC2Type:phone_number)\'');
|
||||
|
||||
$this->addSql(
|
||||
'UPDATE chill_main_location SET ' .
|
||||
$this->buildMigrationPhonenumberClause($carrier_code, 'phonenumber1') .
|
||||
', ' .
|
||||
$this->buildMigrationPhoneNumberClause($carrier_code, 'phonenumber2')
|
||||
);
|
||||
}
|
||||
|
||||
private function buildMigrationPhoneNumberClause(string $defaultCarriercode, string $field): string
|
||||
{
|
||||
$util = PhoneNumberUtil::getInstance();
|
||||
|
||||
$countryCode = $util->getCountryCodeForRegion($defaultCarriercode);
|
||||
|
||||
return sprintf('%s=CASE
|
||||
WHEN %s = \'\' THEN NULL
|
||||
WHEN LEFT(%s, 1) = \'0\'
|
||||
THEN \'+%s\' || replace(replace(substr(%s, 2), \'(0)\', \'\'), \' \', \'\')
|
||||
ELSE replace(replace(%s, \'(0)\', \'\'),\' \', \'\')
|
||||
END', $field, $field, $field, $countryCode, $field, $field);
|
||||
}
|
||||
}
|
@ -397,6 +397,23 @@ workflow:
|
||||
Current step: Étape actuelle
|
||||
Comment on last change: Commentaire à la transition précédente
|
||||
Users allowed to apply transition: Utilisateurs pouvant valider cette étape
|
||||
Workflow deleted with success: Le workflow a été supprimé
|
||||
Delete workflow ?: Supprimer le workflow ?
|
||||
Are you sure you want to delete this workflow ?: Êtes-vous sûr·e de vouloir supprimer ce workflow ?
|
||||
Delete workflow: Supprimer le workflow
|
||||
Steps is not waiting for transition. Maybe someone apply the transition before you ?: L'étape que vous cherchez a déjà été modifiée par un autre utilisateur. Peut-être quelqu'un a-t-il modifié cette étape avant vous ?
|
||||
You get access to this step: Vous avez acquis les droits pour appliquer une transition sur ce workflow.
|
||||
Those users are also granted to apply a transition by using an access key: Ces utilisateurs peuvent également valider cette étape, grâce à un lien d'accès
|
||||
dest by email: Liens d'autorisation par email
|
||||
dest by email help: Les adresses email mentionnées ici recevront un lien d'accès. Ce lien d'accès permettra à l'utilisateur de valider cette étape.
|
||||
Add an email: Ajouter une adresse email
|
||||
Remove an email: Enlever cette adresse email
|
||||
Any email: Aucune adresse email
|
||||
Previous dest without reaction: Workflows clotûrés après action d'un autre utilisateur
|
||||
Previous workflow without reaction help: Liste des workflows où vous avez été cité comme pouvant réagir à une étape, mais où un autre utilisateur a exécuté une action avant vous.
|
||||
Previous transitionned: Anciens workflows
|
||||
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
|
||||
|
||||
|
||||
Subscribe final: Recevoir une notification à l'étape finale
|
||||
Subscribe all steps: Recevoir une notification à chaque étape
|
||||
|
@ -28,3 +28,6 @@ notification:
|
||||
At least one addressee: Indiquez au moins un destinataire
|
||||
Title must be defined: Un titre doit être indiqué
|
||||
Comment content might not be blank: Le commentaire ne peut pas être vide
|
||||
|
||||
workflow:
|
||||
You must add at least one dest user or email: Indiquez au moins un destinataire ou une adresse email
|
||||
|
@ -322,19 +322,39 @@ final class AccompanyingCourseApiController extends ApiController
|
||||
/**
|
||||
* @Route("/api/1.0/person/accompanying-course/{id}/confidential.json", name="chill_api_person_accompanying_period_confidential")
|
||||
* @ParamConverter("accompanyingCourse", options={"id": "id"})
|
||||
*
|
||||
* @param mixed $id
|
||||
*/
|
||||
public function toggleConfidentialApi(AccompanyingPeriod $accompanyingCourse, Request $request)
|
||||
public function toggleConfidentialApi(AccompanyingPeriod $accompanyingCourse, $id, Request $request)
|
||||
{
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL, $accompanyingCourse);
|
||||
|
||||
$accompanyingCourse->setConfidential(!$accompanyingCourse->isConfidential());
|
||||
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
}
|
||||
|
||||
return $this->json($accompanyingCourse->isConfidential(), Response::HTTP_OK, [], ['groups' => ['read']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/api/1.0/person/accompanying-course/{id}/intensity.json", name="chill_api_person_accompanying_period_intensity")
|
||||
* @ParamConverter("accompanyingCourse", options={"id": "id"})
|
||||
*/
|
||||
public function toggleIntensityApi(AccompanyingPeriod $accompanyingCourse, Request $request)
|
||||
{
|
||||
if ($request->getMethod() === 'POST') {
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::TOGGLE_INTENSITY, $accompanyingCourse);
|
||||
|
||||
$status = $accompanyingCourse->getIntensity() === 'regular' ? 'occasional' : 'regular';
|
||||
$accompanyingCourse->setIntensity($status);
|
||||
$this->getDoctrine()->getManager()->flush();
|
||||
}
|
||||
|
||||
return $this->json($accompanyingCourse->getIntensity(), Response::HTTP_OK, [], ['groups' => ['read']]);
|
||||
}
|
||||
|
||||
public function workApi($id, Request $request, string $_format): Response
|
||||
{
|
||||
return $this->addRemoveSomething(
|
||||
|
@ -69,7 +69,7 @@ class AccompanyingPeriodWorkEvaluationApiController
|
||||
$evaluations =
|
||||
array_filter(
|
||||
$this->docGeneratorTemplateRepository
|
||||
->findByEntity(AccompanyingPeriodWorkEvaluation::class),
|
||||
->findByEntity(AccompanyingPeriodWorkEvaluation::class, 0, 500),
|
||||
static function (DocGeneratorTemplate $t) use ($evaluation) {
|
||||
$ids = $t->getOptions()['evaluations'] ?? [];
|
||||
|
||||
|
@ -395,16 +395,16 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
||||
Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::SEE,
|
||||
],
|
||||
],
|
||||
'confidential' => [
|
||||
'methods' => [
|
||||
Request::METHOD_POST => true,
|
||||
Request::METHOD_GET => true,
|
||||
],
|
||||
'controller_action' => 'toggleConfidentialApi',
|
||||
'roles' => [
|
||||
Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL,
|
||||
],
|
||||
],
|
||||
// 'confidential' => [
|
||||
// 'methods' => [
|
||||
// Request::METHOD_POST => true,
|
||||
// Request::METHOD_GET => true,
|
||||
// ],
|
||||
// 'controller_action' => 'toggleConfidentialApi',
|
||||
// 'roles' => [
|
||||
// Request::METHOD_POST => \Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL,
|
||||
// ],
|
||||
// ],
|
||||
'findAccompanyingPeriodsByPerson' => [
|
||||
'path' => '/by-person/{person_id}.{_format}',
|
||||
'controller_action' => 'getAccompanyingPeriodsByPerson',
|
||||
|
@ -30,6 +30,8 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\UserHistory;
|
||||
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
|
||||
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\AccompanyingPeriodValidity;
|
||||
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ConfidentialCourseMustHaveReferrer;
|
||||
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\LocationValidity;
|
||||
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ParticipationOverlap;
|
||||
use Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod\ResourceDuplicateCheck;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
@ -62,12 +64,9 @@ use const SORT_REGULAR;
|
||||
* "accompanying_period": AccompanyingPeriod::class
|
||||
* })
|
||||
* @Assert\GroupSequenceProvider
|
||||
* @Assert\Expression(
|
||||
* "this.isConfidential and this.getUser === NULL",
|
||||
* message="If the accompanying course is confirmed and confidential, a referrer must remain assigned."
|
||||
* )
|
||||
*
|
||||
* @AccompanyingPeriodValidity(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
|
||||
* @LocationValidity(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
|
||||
* @ConfidentialCourseMustHaveReferrer(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
|
||||
*/
|
||||
class AccompanyingPeriod implements
|
||||
GroupSequenceProviderInterface,
|
||||
@ -201,7 +200,7 @@ class AccompanyingPeriod implements
|
||||
/**
|
||||
* @var string
|
||||
* @ORM\Column(type="string", nullable=true)
|
||||
* @Groups({"read", "write"})
|
||||
* @Groups({"read"})
|
||||
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CONFIRMED})
|
||||
*/
|
||||
private $intensity = self::INTENSITY_OCCASIONAL;
|
||||
@ -1000,7 +999,7 @@ class AccompanyingPeriod implements
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation function.
|
||||
* Validation functions.
|
||||
*/
|
||||
public function isDateConsistent(ExecutionContextInterface $context)
|
||||
{
|
||||
|
@ -250,10 +250,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
|
||||
return $this->accompanyingPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getAccompanyingPeriodWorkEvaluations()
|
||||
public function getAccompanyingPeriodWorkEvaluations(): Collection
|
||||
{
|
||||
return $this->accompanyingPeriodWorkEvaluations;
|
||||
}
|
||||
|
@ -67,10 +67,15 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
|
||||
private ?User $createdBy = null;
|
||||
|
||||
/**
|
||||
* **Note on deserialization/denormalization**: denormalization of documents is handled by.
|
||||
*
|
||||
* @see{Chill\PersonBundle\Serializer\Normalizer\AccompanyingPeriodWorkEvaluationDenormalizer}
|
||||
*
|
||||
* @ORM\OneToMany(
|
||||
* targetEntity=AccompanyingPeriodWorkEvaluationDocument::class,
|
||||
* mappedBy="accompanyingPeriodWorkEvaluation",
|
||||
* cascade={"remove"}
|
||||
* cascade={"remove", "persist"},
|
||||
* orphanRemoval=true
|
||||
* )
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
@ -261,6 +266,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
|
||||
public function removeDocument(AccompanyingPeriodWorkEvaluationDocument $document): self
|
||||
{
|
||||
$this->documents->removeElement($document);
|
||||
$document->setAccompanyingPeriodWorkEvaluation(null);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
||||
/**
|
||||
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||
*/
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
@ -49,6 +50,7 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
||||
* targetEntity=User::class
|
||||
* )
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||
*/
|
||||
private ?User $createdBy = null;
|
||||
|
||||
@ -60,15 +62,32 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
||||
* @internal the default name exceeds 64 characters, we must set manually:
|
||||
* @ORM\SequenceGenerator(sequenceName="chill_person_social_work_eval_doc_id_seq", allocationSize=1, initialValue=1000)
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||
*/
|
||||
private ?int $id;
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* This is a workaround for client, to allow them to assign arbitrary data
|
||||
* dedicated to their job.
|
||||
*
|
||||
* This data is not persisted into database, but will appears on the data
|
||||
* normalized during the same request (like PUT/PATCH request)
|
||||
*
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"write"})
|
||||
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $key;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(
|
||||
* targetEntity=StoredObject::class,
|
||||
* cascade={"remove"},
|
||||
* )
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"write"})
|
||||
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||
*/
|
||||
private ?StoredObject $storedObject = null;
|
||||
|
||||
@ -77,12 +96,22 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
||||
* targetEntity=DocGeneratorTemplate::class
|
||||
* )
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||
*/
|
||||
private ?DocGeneratorTemplate $template = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text", nullable=false, options={"default": ""})
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"write"})
|
||||
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||
*/
|
||||
private ?string $title = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||
*/
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
@ -91,6 +120,7 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
||||
* targetEntity=User::class
|
||||
* )
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
|
||||
*/
|
||||
private ?User $updatedBy = null;
|
||||
|
||||
@ -117,6 +147,14 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getKey()
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
public function getStoredObject(): ?StoredObject
|
||||
{
|
||||
return $this->storedObject;
|
||||
@ -127,6 +165,11 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateTimeImmutable|null
|
||||
*/
|
||||
@ -171,6 +214,18 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $key
|
||||
*
|
||||
* @return AccompanyingPeriodWorkEvaluationDocument
|
||||
*/
|
||||
public function setKey($key)
|
||||
{
|
||||
$this->key = $key;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setStoredObject(?StoredObject $storedObject): AccompanyingPeriodWorkEvaluationDocument
|
||||
{
|
||||
$this->storedObject = $storedObject;
|
||||
@ -185,6 +240,13 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTitle(?string $title): AccompanyingPeriodWorkEvaluationDocument
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(DateTimeInterface $datetime): TrackUpdateInterface
|
||||
{
|
||||
$this->updatedAt = $datetime;
|
||||
|
@ -37,6 +37,7 @@ use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Exception;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
@ -371,15 +372,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
/**
|
||||
* The person's mobile phone number.
|
||||
*
|
||||
* @ORM\Column(type="text")
|
||||
* @Assert\Regex(
|
||||
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
|
||||
* )
|
||||
* @PhonenumberConstraint(
|
||||
* type="mobile",
|
||||
* )
|
||||
* @PhonenumberConstraint(type="mobile")
|
||||
* @ORM\Column(type="phone_number", nullable=true)
|
||||
*/
|
||||
private string $mobilenumber = '';
|
||||
private ?PhoneNumber $mobilenumber = null;
|
||||
|
||||
/**
|
||||
* The person's nationality.
|
||||
@ -429,15 +425,12 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
/**
|
||||
* The person's phonenumber.
|
||||
*
|
||||
* @ORM\Column(type="text")
|
||||
* @Assert\Regex(
|
||||
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
|
||||
* )
|
||||
* @ORM\Column(type="phone_number", nullable=true)
|
||||
* @PhonenumberConstraint(
|
||||
* type="landline",
|
||||
* )
|
||||
*/
|
||||
private string $phonenumber = '';
|
||||
private ?PhoneNumber $phonenumber = null;
|
||||
|
||||
/**
|
||||
* The person's place of birth.
|
||||
@ -1227,10 +1220,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
return $this->memo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mobilenumber.
|
||||
*/
|
||||
public function getMobilenumber(): string
|
||||
public function getMobilenumber(): ?PhoneNumber
|
||||
{
|
||||
return $this->mobilenumber;
|
||||
}
|
||||
@ -1295,10 +1285,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
return $this->otherPhoneNumbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get phonenumber.
|
||||
*/
|
||||
public function getPhonenumber(): string
|
||||
public function getPhonenumber(): ?PhoneNumber
|
||||
{
|
||||
return $this->phonenumber;
|
||||
}
|
||||
@ -1737,16 +1724,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mobilenumber.
|
||||
*
|
||||
* @param string $mobilenumber
|
||||
*
|
||||
* @return Person
|
||||
*/
|
||||
public function setMobilenumber(?string $mobilenumber = '')
|
||||
public function setMobilenumber(?PhoneNumber $mobilenumber)
|
||||
{
|
||||
$this->mobilenumber = (string) $mobilenumber;
|
||||
$this->mobilenumber = $mobilenumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -1782,16 +1762,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set phonenumber.
|
||||
*
|
||||
* @param string $phonenumber
|
||||
*
|
||||
* @return Person
|
||||
*/
|
||||
public function setPhonenumber(?string $phonenumber = '')
|
||||
public function setPhonenumber(?PhoneNumber $phonenumber)
|
||||
{
|
||||
$this->phonenumber = (string) $phonenumber;
|
||||
$this->phonenumber = $phonenumber;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -13,17 +13,18 @@ namespace Chill\PersonBundle\Form;
|
||||
|
||||
use Chill\MainBundle\Form\Event\CustomizeFormEvent;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
|
||||
use Chill\MainBundle\Form\Type\PickCenterType;
|
||||
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Form\Type\GenderType;
|
||||
use Chill\PersonBundle\Form\Type\PersonAltNameType;
|
||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||
use libphonenumber\PhoneNumberType;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TelType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
@ -60,11 +61,13 @@ final class CreationPersonType extends AbstractType
|
||||
->add('birthdate', ChillDateType::class, [
|
||||
'required' => false,
|
||||
])
|
||||
->add('phonenumber', TelType::class, [
|
||||
->add('phonenumber', ChillPhoneNumberType::class, [
|
||||
'required' => false,
|
||||
'type' => PhoneNumberType::FIXED_LINE,
|
||||
])
|
||||
->add('mobilenumber', TelType::class, [
|
||||
->add('mobilenumber', ChillPhoneNumberType::class, [
|
||||
'required' => false,
|
||||
'type' => PhoneNumberType::MOBILE,
|
||||
])
|
||||
->add('email', EmailType::class, [
|
||||
'required' => false,
|
||||
|
@ -14,26 +14,27 @@ namespace Chill\PersonBundle\Form;
|
||||
use Chill\CustomFieldsBundle\Form\Type\CustomFieldType;
|
||||
use Chill\MainBundle\Form\Type\ChillCollectionType;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Chill\MainBundle\Form\Type\CommentType;
|
||||
use Chill\MainBundle\Form\Type\PickCivilityType;
|
||||
use Chill\MainBundle\Form\Type\Select2CountryType;
|
||||
use Chill\MainBundle\Form\Type\Select2LanguageType;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\PersonBundle\Entity\PersonPhone;
|
||||
use Chill\PersonBundle\Form\Type\GenderType;
|
||||
use Chill\PersonBundle\Form\Type\PersonAltNameType;
|
||||
use Chill\PersonBundle\Form\Type\PersonPhoneType;
|
||||
use Chill\PersonBundle\Form\Type\Select2MaritalStatusType;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TelType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
@ -57,17 +58,21 @@ class PersonType extends AbstractType
|
||||
|
||||
protected TranslatableStringHelper $translatableStringHelper;
|
||||
|
||||
private ParameterBagInterface $parameterBag;
|
||||
|
||||
/**
|
||||
* @param string[] $personFieldsConfiguration configuration of visibility of some fields
|
||||
*/
|
||||
public function __construct(
|
||||
array $personFieldsConfiguration,
|
||||
ConfigPersonAltNamesHelper $configAltNamesHelper,
|
||||
TranslatableStringHelper $translatableStringHelper
|
||||
TranslatableStringHelperInterface $translatableStringHelper,
|
||||
ParameterBagInterface $parameterBag
|
||||
) {
|
||||
$this->config = $personFieldsConfiguration;
|
||||
$this->configAltNamesHelper = $configAltNamesHelper;
|
||||
$this->translatableStringHelper = $translatableStringHelper;
|
||||
$this->parameterBag = $parameterBag;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
@ -126,22 +131,34 @@ class PersonType extends AbstractType
|
||||
}
|
||||
|
||||
if ('visible' === $this->config['phonenumber']) {
|
||||
$builder->add('phonenumber', TelType::class, [
|
||||
$builder
|
||||
->add(
|
||||
'phonenumber',
|
||||
ChillPhoneNumberType::class,
|
||||
[
|
||||
'required' => false,
|
||||
// 'placeholder' => '+33623124554' //TODO placeholder for phone numbers
|
||||
]);
|
||||
'type' => \libphonenumber\PhoneNumberType::FIXED_LINE,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
if ('visible' === $this->config['mobilenumber']) {
|
||||
$builder
|
||||
->add('mobilenumber', TelType::class, ['required' => false])
|
||||
->add(
|
||||
'mobilenumber',
|
||||
ChillPhoneNumberType::class,
|
||||
[
|
||||
'type' => \libphonenumber\PhoneNumberType::MOBILE,
|
||||
'required' => false,
|
||||
]
|
||||
)
|
||||
->add('acceptSMS', CheckboxType::class, [
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
$builder->add('otherPhoneNumbers', ChillCollectionType::class, [
|
||||
'entry_type' => PersonPhoneType::class,
|
||||
'entry_type' => ChillPhoneNumberType::class,
|
||||
'button_add_label' => 'Add new phone',
|
||||
'button_remove_label' => 'Remove phone',
|
||||
'required' => false,
|
||||
|
@ -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\AccompanyingPeriodWorkEvaluationDocument;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
class AccompanyingPeriodWorkEvaluationDocumentRepository implements ObjectRepository
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->repository = $em->getRepository(AccompanyingPeriodWorkEvaluationDocument::class);
|
||||
}
|
||||
|
||||
public function find($id): ?AccompanyingPeriodWorkEvaluationDocument
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|object[]|AccompanyingPeriodWorkEvaluationDocument[]
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|mixed $limit
|
||||
* @param null|mixed $offset
|
||||
*
|
||||
* @return array|object[]|AccompanyingPeriodWorkEvaluationDocument[]
|
||||
*/
|
||||
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): ?AccompanyingPeriodWorkEvaluationDocument
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return AccompanyingPeriodWorkEvaluationDocument::class;
|
||||
}
|
||||
}
|
@ -58,7 +58,7 @@ export default {
|
||||
this.$store.dispatch('toggleIntensity', value)
|
||||
.catch(({name, violations}) => {
|
||||
if (name === 'ValidationException' || name === 'AccessException') {
|
||||
violations.forEach((violation) => this.$toast.open({message: violation}));
|
||||
this.$toast.open({message: this.$t('Only the referrer can toggle the intensity of an accompanying course')})
|
||||
} else {
|
||||
this.$toast.open({message: 'An error occurred'})
|
||||
}
|
||||
@ -75,16 +75,11 @@ export default {
|
||||
});
|
||||
},
|
||||
toggleConfidential() {
|
||||
this.$store.dispatch('fetchPermissions').then(() => {
|
||||
if (!this.$store.getters.canTogglePermission) {
|
||||
this.$toast.open({message: "Seul le référent peut modifier la confidentialité"});
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return this.$store.dispatch('toggleConfidential', (!this.isConfidential));
|
||||
}
|
||||
}).catch(({name, violations}) => {
|
||||
this.$store.dispatch('toggleConfidential')
|
||||
.catch(({name, violations}) => {
|
||||
console.log(name);
|
||||
if (name === 'ValidationException' || name === 'AccessException') {
|
||||
violations.forEach((violation) => this.$toast.open({message: violation}));
|
||||
this.$toast.open({message: this.$t('Only the referrer can toggle the confidentiality of an accompanying course')})
|
||||
} else {
|
||||
this.$toast.open({message: 'An error occurred'})
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<input type="checkbox" v-model="requestorIsAnonymous" class="me-2" />
|
||||
{{ $t('requestor.is_anonymous') }}
|
||||
</label>
|
||||
<confidential v-if="accompanyingCourse.requestor.type === 'thirdparty'">
|
||||
<confidential :positionBtn="false" v-if="accompanyingCourse.requestor.type === 'thirdparty'">
|
||||
<template v-slot:confidential-content>
|
||||
<third-party-render-box
|
||||
:thirdparty="accompanyingCourse.requestor"
|
||||
@ -33,7 +33,7 @@
|
||||
</template>
|
||||
</confidential>
|
||||
|
||||
<confidential v-else-if="accompanyingCourse.requestor.type === 'person'">
|
||||
<confidential :positionBtnFar="false" v-else-if="accompanyingCourse.requestor.type === 'person'">
|
||||
<template v-slot:confidential-content>
|
||||
<person-render-box render="bloc"
|
||||
:person="accompanyingCourse.requestor"
|
||||
|
@ -168,7 +168,7 @@ export default {
|
||||
}
|
||||
else if (payload.type === 'thirdparty') {
|
||||
console.log('data', payload.data)
|
||||
body.name = payload.data.text;
|
||||
body.name = payload.data.name;
|
||||
body.email = payload.data.email;
|
||||
body.telephone = payload.data.phonenumber;
|
||||
body.address = payload.data.address ? { id: payload.data.address.address_id } : null;
|
||||
|
@ -46,6 +46,12 @@ if (root === 'banner') {
|
||||
})
|
||||
.use(store)
|
||||
.use(i18n)
|
||||
.use(VueToast, {
|
||||
position: "bottom-right",
|
||||
type: "error",
|
||||
duration: 5000,
|
||||
dismissible: true
|
||||
})
|
||||
.component('banner', Banner)
|
||||
.mount('#banner-accompanying-course');
|
||||
});
|
||||
|
@ -167,6 +167,8 @@ const appMessages = {
|
||||
'Error while retriving users.': "Erreur du serveur lors du chargement de la liste des travailleurs.",
|
||||
'Error while getting whoami.': "Erreur du serveur lors de la requête 'qui suis-je ?'",
|
||||
'Error while retriving origin\'s list.': "Erreur du serveur lors du chargement de la liste des origines de la demande.",
|
||||
'Only the referrer can toggle the intensity of an accompanying course': "Seul le référent peut modifier l'intensité d'un parcours.",
|
||||
'Only the referrer can toggle the confidentiality of an accompanying course': "Seul le référent peut modifier la confidentialité d'un parcours."
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -432,12 +432,12 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
|
||||
* Update accompanying course intensity/emergency/confidentiality
|
||||
*/
|
||||
toggleIntensity({ commit }, payload) {
|
||||
const url = `/api/1.0/person/accompanying-course/${id}.json`
|
||||
const url = `/api/1.0/person/accompanying-course/${id}/intensity.json`
|
||||
const body = { type: "accompanying_period", 'intensity': payload }
|
||||
|
||||
return makeFetch('PATCH', url, body)
|
||||
return makeFetch('POST', url, body)
|
||||
.then((response) => {
|
||||
commit('toggleIntensity', response.intensity);
|
||||
commit('toggleIntensity', response);
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -459,14 +459,18 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
|
||||
})
|
||||
},
|
||||
toggleConfidential({ commit }, payload) {
|
||||
const url = `/api/1.0/person/accompanying-course/${id}.json`
|
||||
const url = `/api/1.0/person/accompanying-course/${id}/confidential.json`
|
||||
const body = { type: "accompanying_period", confidential: payload }
|
||||
|
||||
return makeFetch('PATCH', url, body)
|
||||
console.log('url', url, 'body', body);
|
||||
|
||||
return makeFetch('POST', url, body)
|
||||
.then((response) => {
|
||||
commit('toggleConfidential', response.confidential);
|
||||
console.log('response', response);
|
||||
commit('toggleConfidential', response);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('error', error)
|
||||
commit('catchError', error);
|
||||
throw error;
|
||||
})
|
||||
|
@ -251,6 +251,7 @@
|
||||
relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork"
|
||||
:relatedEntityId="this.work.id"
|
||||
:workflowsAvailables="this.work.workflows_availables"
|
||||
:preventDefaultMoveToGenerate="true"
|
||||
@go-to-generate-workflow="goToGenerateWorkflow"
|
||||
></list-workflow-modal>
|
||||
</li>
|
||||
|
@ -106,8 +106,6 @@ export default {
|
||||
this.toggleEditEvaluation();
|
||||
},
|
||||
goToGenerateWorkflow({event, link, workflowName}) {
|
||||
console.log('goToGenerate in evaluation', event, link, workflowName);
|
||||
|
||||
const callback = (data) => {
|
||||
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
|
||||
window.location.assign(buildLinkCreate(workflowName,
|
||||
|
@ -65,20 +65,44 @@
|
||||
<h5>{{ $t('Documents') }} :</h5>
|
||||
|
||||
<div class="flex-table">
|
||||
<div class="item-bloc" v-for="d in evaluation.documents">
|
||||
<div class="item-bloc" v-for="(d, i) in evaluation.documents" :key="d.key">
|
||||
<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/>
|
||||
<div class="input-group input-group-lg mb-3">
|
||||
<div>
|
||||
<input
|
||||
class="form-control form-control-lg"
|
||||
style="font-weight: bold;"
|
||||
type="text"
|
||||
:value="d.title"
|
||||
:id="d.id"
|
||||
@input="onInputDocumentTitle"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="item-col item-meta">
|
||||
<p v-if="d.createdBy" class="createdBy">Créé par {{ d.createdBy.text }}<br/>
|
||||
Le {{ $d(ISOToDatetime(d.createdAt.datetime), 'long') }}</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<ul class="record_actions" >
|
||||
<li v-if="d.workflows_availables.length > 0">
|
||||
<list-workflow-modal
|
||||
:workflows="d.workflows"
|
||||
:allowCreate="true"
|
||||
relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument"
|
||||
:relatedEntityId="d.id"
|
||||
:workflowsAvailables="d.workflows_availables"
|
||||
:preventDefaultMoveToGenerate="true"
|
||||
:goToGenerateWorkflowPayload="{doc: d}"
|
||||
@go-to-generate-workflow="goToGenerateWorkflowEvaluationDocument"
|
||||
></list-workflow-modal>
|
||||
</li>
|
||||
<li>
|
||||
<a :href="buildEditLink(d.storedObject)" class="btn btn-action btn-sm">
|
||||
<i class="fa fa-edit"></i>
|
||||
<a :href="buildEditLink(d.storedObject)" class="btn btn-wopilink"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="btn btn-delete" @click="removeDocument(d)">
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@ -86,8 +110,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<h6>{{ $t('document_add') }} :</h6>
|
||||
<pick-template
|
||||
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
|
||||
:id="evaluation.id"
|
||||
@ -99,6 +125,20 @@
|
||||
<label class="col-sm-4 col-form-label">{{ $t('evaluation_generate_a_document') }}</label>
|
||||
</template>
|
||||
</pick-template>
|
||||
<div>
|
||||
<label class="col-sm-4 col-form-label">{{ $t('document_upload') }}</label>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<add-async-upload
|
||||
:buttonTitle="$t('browse')"
|
||||
:options="asyncUploadOptions"
|
||||
@addDocument="addDocument"
|
||||
>
|
||||
</add-async-upload>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -111,6 +151,9 @@ import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
||||
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
|
||||
import AddAsyncUpload from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUpload.vue';
|
||||
import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue';
|
||||
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
@ -129,6 +172,11 @@ const i18n = {
|
||||
evaluation_add_a_document: "Ajouter un document",
|
||||
evaluation_add: "Ajouter une évaluation",
|
||||
Documents: "Documents",
|
||||
document_add: "Générer ou téléverser un document",
|
||||
document_upload: "Téléverser un document",
|
||||
document_title: "Titre du document",
|
||||
template_title: "Nom du template",
|
||||
browse: "Ajouter un document"
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -139,12 +187,19 @@ export default {
|
||||
components: {
|
||||
ckeditor: CKEditor.component,
|
||||
PickTemplate,
|
||||
AddAsyncUpload,
|
||||
ListWorkflowModal,
|
||||
},
|
||||
i18n,
|
||||
data() {
|
||||
return {
|
||||
editor: ClassicEditor,
|
||||
template: null,
|
||||
asyncUploadOptions: {
|
||||
maxFiles: 1,
|
||||
maxPostSize: 15000000,
|
||||
required: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@ -162,7 +217,6 @@ export default {
|
||||
return dateToISO(this.evaluation.startDate);
|
||||
},
|
||||
set(v) {
|
||||
console.log(v);
|
||||
this.$store.commit('setEvaluationStartDate', { key: this.evaluation.key, date: ISOToDate(v) });
|
||||
}
|
||||
},
|
||||
@ -217,7 +271,36 @@ export default {
|
||||
};
|
||||
|
||||
return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; });
|
||||
},
|
||||
onInputDocumentTitle(event) {
|
||||
const id = Number(event.target.id);
|
||||
const title = event.target.value;
|
||||
this.$store.commit('updateDocumentTitle', {id: id, evaluationKey: this.evaluation.key, title: title});
|
||||
},
|
||||
addDocument(storedObject) {
|
||||
let document = {
|
||||
type: 'accompanying_period_work_evaluation_document',
|
||||
storedObject: storedObject,
|
||||
title: 'Nouveau document',
|
||||
};
|
||||
this.$store.commit('addDocument', {key: this.evaluation.key, document: document});
|
||||
},
|
||||
removeDocument(document) {
|
||||
if (window.confirm("Êtes-vous sûr·e de vouloir supprimer le document qui a pour titre \"" + document.title +"\" ?")) {
|
||||
this.$store.commit('removeDocument', {key: this.evaluation.key, document: document});
|
||||
}
|
||||
},
|
||||
goToGenerateWorkflowEvaluationDocument({event, link, workflowName, payload}) {
|
||||
const callback = (data) => {
|
||||
let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key);
|
||||
let updatedDocument = evaluation.documents.find(d => d.key === payload.doc.key);
|
||||
window.location.assign(buildLinkCreate(workflowName,
|
||||
'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', updatedDocument.id));
|
||||
};
|
||||
|
||||
return this.$store.dispatch('submit', callback)
|
||||
.catch(e => { console.log(e); throw e; });
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -110,6 +110,7 @@ const store = createStore({
|
||||
maxDate: e.maxDate !== null ? { datetime: datetimeToISO(e.maxDate) } : null,
|
||||
warningInterval: intervalDaysToISO(e.warningInterval),
|
||||
comment: e.comment,
|
||||
documents: e.documents
|
||||
};
|
||||
if (e.id !== undefined) {
|
||||
o.id = e.id;
|
||||
@ -130,6 +131,11 @@ const store = createStore({
|
||||
endDate: e.endDate !== null ? ISOToDatetime(e.endDate.datetime) : null,
|
||||
maxDate: e.maxDate !== null ? ISOToDatetime(e.maxDate.datetime) : null,
|
||||
warningInterval: e.warningInterval !== null ? intervalISOToDays(e.warningInterval) : null,
|
||||
documents: e.documents.map((d, dindex) => {
|
||||
return Object.assign(d, {
|
||||
key: index
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
return k;
|
||||
@ -197,6 +203,24 @@ const store = createStore({
|
||||
|
||||
found.results = found.results.filter(r => r.id !== result.id);
|
||||
},
|
||||
addDocument(state, payload) {
|
||||
let evaluation = state.evaluationsPicked.find(e => e.key === payload.key);
|
||||
evaluation.documents.push(Object.assign(
|
||||
payload.document, {
|
||||
key: evaluation.documents.length + 1,
|
||||
workflows_availables: state.work.workflows_availables_evaluation_documents,
|
||||
workflows: [],
|
||||
}));
|
||||
},
|
||||
removeDocument(state, {key, document}) {
|
||||
let evaluations = state.evaluationsPicked.find(e => e.key === key);
|
||||
|
||||
if (evaluations === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
evaluations.documents = evaluations.documents.filter(d => d.key !== document.key);
|
||||
},
|
||||
addEvaluation(state, evaluation) {
|
||||
let e = {
|
||||
type: "accompanying_period_work_evaluation",
|
||||
@ -284,6 +308,10 @@ const store = createStore({
|
||||
setIsPosting(state, st) {
|
||||
state.isPosting = st;
|
||||
},
|
||||
updateDocumentTitle(state, payload) {
|
||||
state.evaluationsPicked.find(e => e.key === payload.evaluationKey)
|
||||
.documents.find(d => d.id === payload.id).title = payload.title;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
updateThirdParty({ commit }, payload) {
|
||||
@ -374,13 +402,18 @@ const store = createStore({
|
||||
});
|
||||
}
|
||||
},
|
||||
addDocument({commit}, payload) {
|
||||
commit('addDocument', payload);
|
||||
},
|
||||
removeDocument({commit}, payload) {
|
||||
commit('removeDocument', payload);
|
||||
},
|
||||
submit({ getters, state, commit }, callback) {
|
||||
let
|
||||
payload = getters.buildPayload,
|
||||
url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json`,
|
||||
errors = []
|
||||
;
|
||||
|
||||
commit('setIsPosting', true);
|
||||
|
||||
return makeFetch('PUT', url, payload)
|
||||
@ -397,6 +430,9 @@ const store = createStore({
|
||||
commit('setErrors', error.violations);
|
||||
});
|
||||
},
|
||||
updateDocumentTitle({commit}, payload) {
|
||||
commit('updateDocumentTitle', payload)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="container tpartycontainer">
|
||||
<div class="tparty-identification">
|
||||
<span v-if="item.result.profession" class="profession">{{ item.result.profession.name.fr }}</span>
|
||||
<span class="name">
|
||||
{{ item.result.text }}
|
||||
</span>
|
||||
@ -123,6 +124,11 @@ export default {
|
||||
font-size: 90%;
|
||||
font-style: italic;
|
||||
}
|
||||
.profession {
|
||||
font-weight: 800;
|
||||
color: black;
|
||||
font-style: normal !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -151,14 +151,14 @@
|
||||
{% if accompanyingCourse.requestorPerson is not null %}
|
||||
<h4 class="item-key">{{ 'Requestor'|trans }}</h4>
|
||||
{% if accompanyingCourse.requestorAnonymous %}
|
||||
<div class="confidential"><p>{{ _self.insert_onthefly('person', accompanyingCourse.requestorPerson) }}</p></div>
|
||||
<div class="confidential toggle-close-twig"><p>{{ _self.insert_onthefly('person', accompanyingCourse.requestorPerson) }}</p></div>
|
||||
{% else %}
|
||||
{{ _self.insert_onthefly('person', accompanyingCourse.requestorPerson) }}
|
||||
{% endif %}
|
||||
{% elseif accompanyingCourse.requestorThirdParty is not null %}
|
||||
<h4 class="item-key">{{ 'Requestor'|trans }}</h4>
|
||||
{% if accompanyingCourse.requestorAnonymous %}
|
||||
<div class="confidential"><p>{{ _self.insert_onthefly('thirdparty', accompanyingCourse.requestorThirdParty) }}</p></div>
|
||||
<div class="confidential toggle-close-twig"><p>{{ _self.insert_onthefly('thirdparty', accompanyingCourse.requestorThirdParty) }}</p></div>
|
||||
{% else %}
|
||||
{{ _self.insert_onthefly('thirdparty', accompanyingCourse.requestorThirdParty) }}
|
||||
{% endif %}
|
||||
|
@ -107,8 +107,24 @@
|
||||
</div>
|
||||
|
||||
{% if displayAction is defined and displayAction == true %}
|
||||
<div class="item-col">
|
||||
<ul class="record_actions">
|
||||
<ul class="item-col record_actions">
|
||||
{% set suppEvaluations = [] %}
|
||||
{% for e in w.accompanyingPeriodWorkEvaluations %}
|
||||
{% set suppEvaluations = suppEvaluations|merge([
|
||||
{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation', 'relatedEntityId': e.id }
|
||||
]) %}
|
||||
|
||||
{% for d in e.documents %}
|
||||
{% set suppEvaluations = suppEvaluations|merge([
|
||||
{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id }
|
||||
]) %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<li>
|
||||
{{ chill_entity_workflow_list(
|
||||
'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork',
|
||||
w.id, [], suppEvaluations) }}
|
||||
</li>
|
||||
<li>
|
||||
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
|
||||
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
|
||||
@ -120,7 +136,6 @@
|
||||
></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
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