Merge remote-tracking branch 'origin/master' into issue480_bug_householdrenderbox

This commit is contained in:
Julien Fastré 2022-03-14 09:53:23 +01:00
commit 0c9499dd58
72 changed files with 995 additions and 294 deletions

View File

@ -11,6 +11,10 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472)
* [person] Add url in accompanying period work evaluations entity and form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/476)
* [person] Add document generation in admin and in person/{id}/document (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/464)
* [activity] do not override location if already exist (when validating new activity) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/470)
* [parcours] Toggle emergency/intensity only by referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/442) * [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) * [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 the possibility of uploading existing documents to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
@ -19,7 +23,7 @@ and this project adheres to
* [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) * [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) * [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] 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) * [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) * [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 * [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) * [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)
@ -27,6 +31,13 @@ and this project adheres to
* [parcours] Fix edit of both thirdparty and contact name (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/474) * [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) * [template] do not list inactive templates (for doc generator)
* [household] bugfix if position of member is null, renderbox no longer throws an error (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/480) * [household] bugfix if position of member is null, renderbox no longer throws an error (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/480)
* [parcours] location cannot be removed if linked to a user (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/478)
* [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490)
* [person] Add link to current household in person banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/484)
* [person] Change 'personne' with 'usager' and '&' with 'ET' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/499)
* [thirdparty] Add parameter condition to display centers or not (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/500)
* [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496)
* [contact] add contact button color changed plus the pipe at the side removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/506)
## Test releases ## Test releases

View File

@ -350,11 +350,6 @@ parameters:
count: 6 count: 6
path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php path: src/Bundle/ChillPersonBundle/Command/ImportPeopleFromCSVCommand.php
-
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 2
path: src/Bundle/ChillPersonBundle/Entity/PersonPhone.php
- -
message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#"
count: 1 count: 1

View File

@ -110,10 +110,8 @@ export default function prepareLocations(store) {
console.log('default loation id', window.default_location_id); console.log('default loation id', window.default_location_id);
if (window.default_location_id) { if (window.default_location_id) {
for (let group of store.state.availableLocations) { for (let group of store.state.availableLocations) {
console.log(group);
let location = group.locations.find((l) => l.id === window.default_location_id); let location = group.locations.find((l) => l.id === window.default_location_id);
console.log(location); if (location !== undefined & store.state.activity.location === null) {
if (location !== undefined) {
store.dispatch('updateLocation', location); store.dispatch('updateLocation', location);
break; break;
} }

View File

@ -76,7 +76,7 @@ activity:
Insert a document: Insérer un document Insert a document: Insérer un document
Remove a document: Supprimer le document Remove a document: Supprimer le document
comment: Commentaire comment: Commentaire
No documents: Pas de documents No documents: Aucun document
#timeline #timeline
'%user% has done an %activity_type%': '%user% a effectué une activité de type "%activity_type%"' '%user% has done an %activity_type%': '%user% a effectué une activité de type "%activity_type%"'

View File

@ -207,21 +207,21 @@ final class DocGeneratorTemplateController extends AbstractController
$context instanceof DocGeneratorContextWithPublicFormInterface $context instanceof DocGeneratorContextWithPublicFormInterface
&& $context->hasPublicForm($template, $entity) || $isTest && $context->hasPublicForm($template, $entity) || $isTest
) { ) {
if ($context instanceof DocGeneratorContextWithPublicFormInterface) { if ($context instanceof DocGeneratorContextWithPublicFormInterface && $context->hasPublicForm($template, $entity)) {
$builder = $this->createFormBuilder( $builder = $this->createFormBuilder(
array_merge( array_merge(
$context->getFormData($template, $entity), $context->getFormData($template, $entity),
$isTest ? ['test_file' => null] : [] $isTest ? ['test_file' => null] : []
) )
); );
$context->buildPublicForm($builder, $template, $entity);
} else { } else {
$builder = $this->createFormBuilder( $builder = $this->createFormBuilder(
['test_file' => null] ['test_file' => null]
); );
} }
$context->buildPublicForm($builder, $template, $entity);
if ($isTest) { if ($isTest) {
$builder->add('test_file', FileType::class, [ $builder->add('test_file', FileType::class, [
'label' => 'Template file', 'label' => 'Template file',

View File

@ -21,6 +21,8 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
use DateTime; use DateTime;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
@ -59,21 +61,37 @@ class DocumentAccompanyingCourseController extends AbstractController
} }
/** /**
* @Route("/{id}", name="accompanying_course_document_delete", methods="DELETE") * @Route("/{id}/delete", name="chill_docstore_accompanying_course_document_delete")
*/ */
public function delete(Request $request, AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response public function delete(Request $request, AccompanyingPeriod $course, AccompanyingCourseDocument $document): Response
{ {
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::DELETE, $document); $this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::DELETE, $document);
if ($this->isCsrfTokenValid('delete' . $document->getId(), $request->request->get('_token'))) { $form = $this->createForm(FormType::class);
$em = $this->getDoctrine()->getManager(); $form->add('submit', SubmitType::class, ['label' => 'Delete']);
$em->remove($document);
$em->flush(); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->remove($document);
$this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->translator->trans('The document is successfully removed'));
if ($request->query->has('returnPath')) {
return $this->redirect($request->query->get('returnPath'));
}
return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]);
} }
return $this->redirectToRoute( return $this->render(
'accompanying_course_document_index', 'ChillDocStoreBundle:AccompanyingCourseDocument:delete.html.twig',
['accompanyingCourse' => $course->getId()] [
'document' => $document,
'delete_form' => $form->createView(),
'accompanyingCourse' => $course,
]
); );
} }

View File

@ -14,6 +14,7 @@ namespace Chill\DocStoreBundle\Controller;
use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Form\PersonDocumentType; use Chill\DocStoreBundle\Form\PersonDocumentType;
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface; use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
@ -22,6 +23,8 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTime; use DateTime;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
@ -64,22 +67,37 @@ class DocumentPersonController extends AbstractController
} }
/** /**
* @Route("/{id}", name="person_document_delete", methods="DELETE") * @Route("/{id}/delete", name="chill_docstore_person_document_delete")
*/ */
public function delete(Request $request, Person $person, PersonDocument $document): Response public function delete(Request $request, Person $person, PersonDocument $document): Response
{ {
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person); $this->denyAccessUnlessGranted(PersonDocumentVoter::DELETE, $document);
$this->denyAccessUnlessGranted('CHILL_PERSON_DOCUMENT_DELETE', $document);
if ($this->isCsrfTokenValid('delete' . $document->getId(), $request->request->get('_token'))) { $form = $this->createForm(FormType::class);
$em = $this->getDoctrine()->getManager(); $form->add('submit', SubmitType::class, ['label' => 'Delete']);
$em->remove($document);
$em->flush(); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->remove($document);
$this->getDoctrine()->getManager()->flush();
$this->addFlash('success', $this->translator->trans('The document is successfully removed'));
if ($request->query->has('returnPath')) {
return $this->redirect($request->query->get('returnPath'));
}
return $this->redirectToRoute('person_document_index', ['person' => $person->getId()]);
} }
return $this->redirectToRoute( return $this->render(
'person_document_index', 'ChillDocStoreBundle:PersonDocument:delete.html.twig',
['person' => $person->getId()] [
'document' => $document,
'delete_form' => $form->createView(),
'person' => $person,
]
); );
} }
@ -98,7 +116,6 @@ class DocumentPersonController extends AbstractController
PersonDocumentType::class, PersonDocumentType::class,
$document, $document,
[ [
'center' => $document->getCenter(),
'role' => 'CHILL_PERSON_DOCUMENT_UPDATE', 'role' => 'CHILL_PERSON_DOCUMENT_UPDATE',
] ]
); );
@ -199,7 +216,6 @@ class DocumentPersonController extends AbstractController
$document->setDate(new DateTime('Now')); $document->setDate(new DateTime('Now'));
$form = $this->createForm(PersonDocumentType::class, $document, [ $form = $this->createForm(PersonDocumentType::class, $document, [
'center' => $document->getCenter(),
'role' => 'CHILL_PERSON_DOCUMENT_CREATE', 'role' => 'CHILL_PERSON_DOCUMENT_CREATE',
]); ]);
$form->handleRequest($request); $form->handleRequest($request);

View File

@ -13,15 +13,13 @@ namespace Chill\DocStoreBundle\Form;
use Chill\DocStoreBundle\Entity\Document; use Chill\DocStoreBundle\Entity\Document;
use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\ScopePickerType; use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher; use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectManager;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
@ -31,34 +29,16 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class PersonDocumentType extends AbstractType class PersonDocumentType extends AbstractType
{ {
/** private CenterResolverDispatcher $centerResolverDispatcher;
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/**
* @var ObjectManager
*/
protected $om;
/**
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
/**
* the user running this form.
*
* @var User
*/
protected $user;
private ParameterBagInterface $parameterBag; private ParameterBagInterface $parameterBag;
private ScopeResolverDispatcher $scopeResolverDispatcher; private ScopeResolverDispatcher $scopeResolverDispatcher;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct( public function __construct(
TranslatableStringHelper $translatableStringHelper, TranslatableStringHelperInterface $translatableStringHelper,
ScopeResolverDispatcher $scopeResolverDispatcher, ScopeResolverDispatcher $scopeResolverDispatcher,
ParameterBagInterface $parameterBag ParameterBagInterface $parameterBag
) { ) {
@ -96,7 +76,7 @@ class PersonDocumentType extends AbstractType
if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) { if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) {
$builder->add('scope', ScopePickerType::class, [ $builder->add('scope', ScopePickerType::class, [
'center' => $options['center'], 'center' => $this->centerResolverDispatcher->resolveCenter($document),
'role' => $options['role'], 'role' => $options['role'],
]); ]);
} }
@ -108,8 +88,7 @@ class PersonDocumentType extends AbstractType
'data_class' => Document::class, 'data_class' => Document::class,
]); ]);
$resolver->setRequired(['role', 'center']) $resolver->setRequired(['role'])
->setAllowedTypes('role', ['string']) ->setAllowedTypes('role', ['string']);
->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class]);
} }
} }

View File

@ -1,5 +0,0 @@
<form method="post" action="{{ path('accompanying_course_document_delete', {'id': document.id, 'course': course.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_method" value="DELETE">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ document.id) }}">
<button class="btn btn-delete">{{ 'Delete' | trans }}</button>
</form>

View File

@ -0,0 +1,43 @@
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
{% set activeRouteKey = '' %}
{% block title %}{{ 'Delete document ?' }}{% endblock %}
{% block docdescription %}
<dl class="chill_view_data">
<dt>{{ 'Title'|trans }}</dt>
<dd>{{ document.title }}</dd>
{% if document.scope is not null %}
<dt>{{ 'Scope' | trans }}</dt>
<dd>{{ document.scope.name | localize_translatable_string }}</dd>
{% endif %}
<dt>{{ 'Category'|trans }}</dt>
<dd>{{ document.category.name|localize_translatable_string }}</dd>
<dt>{{ 'Description' | trans }}</dt>
<dd>
{% if document.description is empty %}
<span class="chill-no-data-statement">{{ 'Any description'|trans }}</span>
{% else %}
<blockquote class="chill-user-quote">
{{ document.description|chill_markdown_to_html }}
</blockquote>
{% endif %}
</dd>
</dl>
{% endblock %}
{% block content %}
{{ include('@ChillMain/Util/confirmation_template.html.twig',
{
'title' : 'Delete document ?'|trans,
'display_content' : block('docdescription'),
'confirm_question' : 'Are you sure you want to remove this document ?'|trans,
'cancel_route' : 'accompanying_course_document_index',
'cancel_parameters' : {'course' : accompanyingCourse.id, 'id': document.id},
'form' : delete_form
} ) }}
{% endblock %}

View File

@ -25,8 +25,13 @@
{{ 'Back to the list' | trans }} {{ 'Back to the list' | trans }}
</a> </a>
</li> </li>
<li class="edit"> {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', document) %}
<button class="btn btn-edit">{{ 'Edit'|trans }}</button> <li class="delete">
<a href="{{ chill_return_path_or('chill_docstore_accompanying_course_document_delete', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-delete"></a>
</li>
{% endif %}
<li class="edit">
<button class="btn btn-edit">{{ 'Edit'|trans }}</button>
</li> </li>
</ul> </ul>

View File

@ -49,12 +49,9 @@
{{ 'Back to the list' | trans }} {{ 'Back to the list' | trans }}
</a> </a>
</li> </li>
<li> {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', document) %}
{{ m.download_button(document.object, document.title) }} <li class="delete">
</li> <a href="{{ chill_return_path_or('chill_docstore_accompanying_course_document_delete', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-delete"></a>
{% if chill_document_is_editable(document.object) %}
<li>
{{ document.object|chill_document_edit_button }}
</li> </li>
{% endif %} {% endif %}
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %} {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
@ -63,6 +60,14 @@
class="btn btn-edit" title="{{ 'Edit attributes' | trans }}"></a> class="btn btn-edit" title="{{ 'Edit attributes' | trans }}"></a>
</li> </li>
{% endif %} {% endif %}
<li>
{{ m.download_button(document.object, document.title) }}
</li>
{% if chill_document_is_editable(document.object) %}
<li>
{{ document.object|chill_document_edit_button }}
</li>
{% endif %}
{% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %} {% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %}
{% if workflows_frame is not empty %} {% if workflows_frame is not empty %}
<li> <li>

View File

@ -44,6 +44,16 @@
</div> </div>
<ul class="item-col record_actions flex-shrink-1"> <ul class="item-col record_actions flex-shrink-1">
{% if document.course is defined %} {% if document.course is defined %}
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_DELETE', document) %}
<li class="delete">
<a href="{{ chill_return_path_or('chill_docstore_accompanying_course_document_delete', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-delete"></a>
</li>
{% endif %}
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
</li>
{% endif %}
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %} {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
<li> <li>
{{ m.download_button(document.object, document.title) }} {{ m.download_button(document.object, document.title) }}
@ -52,15 +62,20 @@
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a> <a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': accompanyingCourse.id, 'id': document.id}) }}" class="btn btn-show"></a>
</li> </li>
{% endif %} {% endif %}
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('accompanying_course_document_edit', {'course': accompanyingCourse.id, 'id': document.id }) }}" class="btn btn-update"></a>
</li>
{% endif %}
<li> <li>
{{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }} {{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }}
</li> </li>
{% else %} {% else %}
{% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
<li class="delete">
<a href="{{ chill_return_path_or('chill_docstore_person_document_delete', {'person': person.id, 'id': document.id}) }}" class="btn btn-delete"></a>
</li>
{% endif %}
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
</li>
{% endif %}
{% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %} {% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
<li> <li>
{{ m.download_button(document.object, document.title) }} {{ m.download_button(document.object, document.title) }}
@ -69,13 +84,8 @@
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a> <a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></a>
</li> </li>
{% endif %} {% endif %}
{% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
<li>
<a href="{{ path('person_document_edit', {'person': person.id, 'id': document.id}) }}" class="btn btn-update"></a>
</li>
{% endif %}
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -0,0 +1,43 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set activeRouteKey = '' %}
{% block title %}{{ 'Delete document ?' }}{% endblock %}
{% block docdescription %}
<dl class="chill_view_data">
<dt>{{ 'Title'|trans }}</dt>
<dd>{{ document.title }}</dd>
{% if document.scope is not null %}
<dt>{{ 'Scope' | trans }}</dt>
<dd>{{ document.scope.name | localize_translatable_string }}</dd>
{% endif %}
<dt>{{ 'Category'|trans }}</dt>
<dd>{{ document.category.name|localize_translatable_string }}</dd>
<dt>{{ 'Description' | trans }}</dt>
<dd>
{% if document.description is empty %}
<span class="chill-no-data-statement">{{ 'Any description'|trans }}</span>
{% else %}
<blockquote class="chill-user-quote">
{{ document.description|chill_markdown_to_html }}
</blockquote>
{% endif %}
</dd>
</dl>
{% endblock %}
{% block personcontent %}
{{ include('@ChillMain/Util/confirmation_template.html.twig',
{
'title' : 'Delete document ?'|trans,
'display_content' : block('docdescription'),
'confirm_question' : 'Are you sure you want to remove this document ?'|trans,
'cancel_route' : 'person_document_index',
'cancel_parameters' : {'person' : person.id, 'id': document.id},
'form' : delete_form
} ) }}
{% endblock %}

View File

@ -36,20 +36,20 @@
{{ form_row(form.description) }} {{ form_row(form.description) }}
{{ form_row(form.object, { 'label': 'Document', 'existing': document.object }) }} {{ form_row(form.object, { 'label': 'Document', 'existing': document.object }) }}
<ul class="record_actions"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path('person_document_index', {'person': person.id}) }}" class="btn btn-cancel"> <a href="{{ chill_return_path_or('person_document_index', {'person': person.id}) }}" class="btn btn-cancel">
{{ 'Back to the list' | trans }} {{ 'Back to the list' | trans }}
</a> </a>
</li> </li>
{% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
<li class="delete">
<a href="{{ chill_return_path_or('chill_docstore_person_document_delete', {'person': person.id, 'id': document.id}) }}" class="btn btn-delete"></a>
</li>
{% endif %}
<li class="edit"> <li class="edit">
<button class="btn btn-edit">{{ 'Edit'|trans }}</button> <button class="btn btn-edit">{{ 'Edit'|trans }}</button>
</li> </li>
{# {% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
<li class="delete">
{{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }}
</li>
{% endif %} #}
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}

View File

@ -26,7 +26,17 @@
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }} {{ 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 %} {% endblock %}
{% block personcontent %} {% block personcontent %}
@ -46,6 +56,8 @@
{{ chill_pagination(pagination) }} {{ chill_pagination(pagination) }}
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\PersonBundle\Entity\Person" data-entity-id="{{ person.id }}"></div>
{% if is_granted('CHILL_PERSON_DOCUMENT_CREATE', person) %} {% if is_granted('CHILL_PERSON_DOCUMENT_CREATE', person) %}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="create"> <li class="create">

View File

@ -40,9 +40,9 @@
{{ form_row(form.description) }} {{ form_row(form.description) }}
{{ form_row(form.object, { 'label': 'Document', 'existing': document.object }) }} {{ form_row(form.object, { 'label': 'Document', 'existing': document.object }) }}
<ul class="record_actions"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path('person_document_index', {'person': person.id}) }}" class="btn btn-cancel"> <a href="{{ chill_return_path_or('person_document_index', {'person': person.id}) }}" class="btn btn-cancel">
{{ 'Back to the list' | trans }} {{ 'Back to the list' | trans }}
</a> </a>
</li> </li>

View File

@ -64,13 +64,9 @@
</a> </a>
</li> </li>
<li> {% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
{{ m.download_button(document.object, document.title) }} <li class="delete">
</li> <a href="{{ chill_return_path_or('chill_docstore_person_document_delete', {'person': person.id, 'id': document.id}) }}" class="btn btn-delete"></a>
{% if chill_document_is_editable(document.object) %}
<li>
{{ document.object|chill_document_edit_button }}
</li> </li>
{% endif %} {% endif %}
@ -82,5 +78,15 @@
</li> </li>
{% endif %} {% endif %}
{# {{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }} #} <li>
{{ m.download_button(document.object, document.title) }}
</li>
{% if chill_document_is_editable(document.object) %}
<li>
{{ document.object|chill_document_edit_button }}
</li>
{% endif %}
{# {{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }} #}
{% endblock %} {% endblock %}

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Workflow; namespace Chill\DocStoreBundle\Workflow;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Security\Authorization\AccompanyingCourseDocumentVoter;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
@ -36,6 +37,13 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
$this->translator = $translator; $this->translator = $translator;
} }
public function getDeletionRoles(): array
{
return [
AccompanyingCourseDocumentVoter::DELETE,
];
}
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
{ {
$course = $this->getRelatedEntity($entityWorkflow) $course = $this->getRelatedEntity($entityWorkflow)
@ -66,6 +74,18 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
return $this->repository->find($entityWorkflow->getRelatedEntityId()); return $this->repository->find($entityWorkflow->getRelatedEntityId());
} }
/**
* @param AccompanyingCourseDocument $object
*
* @return array[]
*/
public function getRelatedObjects(object $object): array
{
return [
['entityClass' => AccompanyingCourseDocument::class, 'entityId' => $object->getId()],
];
}
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string public function getRoleShow(EntityWorkflow $entityWorkflow): ?string
{ {
return null; return null;
@ -84,6 +104,11 @@ class AccompanyingCourseDocumentWorkflowHandler implements EntityWorkflowHandler
]; ];
} }
public function isObjectSupported(object $object): bool
{
return $object instanceof AccompanyingCourseDocument;
}
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool
{ {
return $entityWorkflow->getRelatedEntityClass() === AccompanyingCourseDocument::class; return $entityWorkflow->getRelatedEntityClass() === AccompanyingCourseDocument::class;

View File

@ -19,6 +19,12 @@ The document is successfully registered: Le document est enregistré
The document is successfully updated: Le document est mis à jour The document is successfully updated: Le document est mis à jour
Any description: Aucune description Any description: Aucune description
# delete
Delete document ?: Supprimer le document ?
Are you sure you want to remove this document ?: Êtes-vous sûr·e de vouloir supprimer ce document ?
The document is successfully removed: Le document a été supprimé
# dropzone upload # dropzone upload
File too big: Fichier trop volumineux File too big: Fichier trop volumineux
Drop your file or click here: Cliquez ici ou faites glissez votre nouveau fichier dans cette zone Drop your file or click here: Cliquez ici ou faites glissez votre nouveau fichier dans cette zone

View File

@ -256,6 +256,13 @@ class ChillMainExtension extends Extension implements
'channels' => ['chill'], 'channels' => ['chill'],
]); ]);
$container->prependExtensionConfig('security', [
'access_decision_manager' => [
'strategy' => 'unanimous',
'allow_if_all_abstain' => false,
],
]);
//add crud api //add crud api
$this->prependCruds($container); $this->prependCruds($container);
} }

View File

@ -192,7 +192,7 @@ class EntityWorkflowStep
* You should **not** rely on this method to get all users which are able to * 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. * apply a transition on this step. Use @see{EntityWorkflowStep::getAllDestUser} instead.
*/ */
public function getDestUser(): collection public function getDestUser(): Collection
{ {
return $this->destUser; return $this->destUser;
} }

View File

@ -16,9 +16,7 @@ use libphonenumber\PhoneNumberUtil;
use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType; use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use function array_key_exists;
class ChillPhoneNumberType extends AbstractType class ChillPhoneNumberType extends AbstractType
{ {
@ -37,21 +35,7 @@ class ChillPhoneNumberType extends AbstractType
$resolver $resolver
->setDefault('default_region', $this->defaultCarrierCode) ->setDefault('default_region', $this->defaultCarrierCode)
->setDefault('format', PhoneNumberFormat::NATIONAL) ->setDefault('format', PhoneNumberFormat::NATIONAL)
->setDefault('type', \libphonenumber\PhoneNumberType::FIXED_LINE_OR_MOBILE) ->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() public function getParent()

View File

@ -27,7 +27,7 @@ interface PhoneNumberHelperInterface
/** /**
* Get type (mobile, landline, ...) for phone number. * Get type (mobile, landline, ...) for phone number.
*/ */
public function getType(string $phonenumber): string; public function getType(PhoneNumber $phonenumber): string;
/** /**
* Return true if the validation is configured and available. * Return true if the validation is configured and available.

View File

@ -17,6 +17,7 @@ use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException; use GuzzleHttp\Exception\ServerException;
use libphonenumber\NumberParseException; use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberType;
use libphonenumber\PhoneNumberUtil; use libphonenumber\PhoneNumberUtil;
use Psr\Cache\CacheItemPoolInterface; use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -86,9 +87,19 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
/** /**
* Get type (mobile, landline, ...) for phone number. * Get type (mobile, landline, ...) for phone number.
*/ */
public function getType(string $phonenumber): string public function getType(PhoneNumber $phonenumber): string
{ {
return $this->performTwilioLookup($phonenumber) ?? 'unknown'; switch ($this->phoneNumberUtil->getNumberType($phonenumber)) {
case PhoneNumberType::MOBILE:
return 'mobile';
case PhoneNumberType::FIXED_LINE:
case PhoneNumberType::VOIP:
return 'landline';
default:
return 'landline';
}
} }
/** /**

View File

@ -55,6 +55,30 @@ class EntityWorkflowRepository implements ObjectRepository
return (int) $qb->getQuery()->getSingleScalarResult(); return (int) $qb->getQuery()->getSingleScalarResult();
} }
public function countRelatedWorkflows(array $relateds): int
{
$qb = $this->repository->createQueryBuilder('w');
$orX = $qb->expr()->orX();
$i = 0;
foreach ($relateds as $related) {
$orX->add(
$qb->expr()->andX(
$qb->expr()->eq('w.relatedEntityClass', ':entity_class_' . $i),
$qb->expr()->eq('w.relatedEntityId', ':entity_id_' . $i)
)
);
$qb
->setParameter('entity_class_' . $i, $related['entityClass'])
->setParameter('entity_id_' . $i, $related['entityId']);
++$i;
}
$qb->where($orX);
return $qb->select('COUNT(w)')->getQuery()->getSingleScalarResult();
}
public function find($id): ?EntityWorkflow public function find($id): ?EntityWorkflow
{ {
return $this->repository->find($id); return $this->repository->find($id);

View File

@ -25,7 +25,7 @@ $chill-theme-buttons: (
"notify": $chill-blue, "notify": $chill-blue,
"search": $gray-300, "search": $gray-300,
"unlink": $chill-red, "unlink": $chill-red,
"tpchild": $chill-pink, "tpchild": $chill-green,
); );
@each $button, $color in $chill-theme-buttons { @each $button, $color in $chill-theme-buttons {

View File

@ -9,6 +9,12 @@
<span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span> <span class="item-key">{{ 'Le'|trans ~ ' : ' }}</span>
<b>{{ step.previous.transitionAt|format_datetime('short', 'short') }}</b> <b>{{ step.previous.transitionAt|format_datetime('short', 'short') }}</b>
</li> </li>
<li>
<span class="item-key">{{ 'workflow.For'|trans ~ ' : ' }}</span>
<b>
{% for d in step.destUser %}{{ d|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}
</b>
</li>
{% else %} {% else %}
<li> <li>
<span class="item-key">{{ 'workflow.Created by'|trans ~ ' : ' }}</span> <span class="item-key">{{ 'workflow.Created by'|trans ~ ' : ' }}</span>

View File

@ -0,0 +1,65 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Security\Authorization;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use RuntimeException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use function in_array;
use function is_object;
class WorkflowEntityDeletionVoter extends Voter
{
private EntityWorkflowRepository $entityWorkflowRepository;
/**
* @var iterable|EntityWorkflowHandlerInterface[]
*/
private iterable $handlers;
public function __construct($handlers, EntityWorkflowRepository $entityWorkflowRepository)
{
$this->handlers = $handlers;
$this->entityWorkflowRepository = $entityWorkflowRepository;
}
protected function supports($attribute, $subject)
{
if (!is_object($subject)) {
return false;
}
foreach ($this->handlers as $handler) {
if ($handler->isObjectSupported($subject)
&& in_array($attribute, $handler->getDeletionRoles($subject), true)) {
return true;
}
}
return false;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
foreach ($this->handlers as $handler) {
if ($handler->isObjectSupported($subject)) {
return 0 === $this->entityWorkflowRepository->countRelatedWorkflows(
$handler->getRelatedObjects($subject)
);
}
}
throw new RuntimeException('no handlers found');
}
}

View File

@ -113,6 +113,7 @@ class AddressNormalizer implements ContextAwareNormalizerInterface, NormalizerAw
[ [
'postcode' => $helper->normalize(self::NULL_POSTCODE_COUNTRY, $format, $context), 'postcode' => $helper->normalize(self::NULL_POSTCODE_COUNTRY, $format, $context),
'country' => $helper->normalize(self::NULL_POSTCODE_COUNTRY, $format, $context), 'country' => $helper->normalize(self::NULL_POSTCODE_COUNTRY, $format, $context),
'lines' => [],
] ]
); );
} }

View File

@ -40,6 +40,10 @@ class PhonenumberNormalizer implements NormalizerInterface, DenormalizerInterfac
*/ */
public function denormalize($data, $type, $format = null, array $context = []) public function denormalize($data, $type, $format = null, array $context = [])
{ {
if ('' === trim($data)) {
return null;
}
try { try {
return $this->phoneNumberUtil->parse($data, $this->defaultCarrierCode); return $this->phoneNumberUtil->parse($data, $this->defaultCarrierCode);
} catch (NumberParseException $e) { } catch (NumberParseException $e) {

View File

@ -43,7 +43,7 @@ final class ValidPhonenumber extends ConstraintValidator
return; return;
} }
if ('' === $value) { if (null === $value) {
return; return;
} }

View File

@ -15,12 +15,19 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
interface EntityWorkflowHandlerInterface interface EntityWorkflowHandlerInterface
{ {
/**
* @return array|string[]
*/
public function getDeletionRoles(): array;
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array; public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array;
public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string; public function getEntityTitle(EntityWorkflow $entityWorkflow, array $options = []): string;
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object; public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?object;
public function getRelatedObjects(object $object): array;
/** /**
* Return a string representing the role required for seeing the workflow. * Return a string representing the role required for seeing the workflow.
* *
@ -33,6 +40,8 @@ interface EntityWorkflowHandlerInterface
public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array; public function getTemplateData(EntityWorkflow $entityWorkflow, array $options = []): array;
public function isObjectSupported(object $object): bool;
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool; public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool;
public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool; public function supportsFreeze(EntityWorkflow $entityWorkflow, array $options = []): bool;

View File

@ -75,3 +75,9 @@ services:
$locker: '@Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker' $locker: '@Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker'
tags: tags:
- { name: security.voter } - { name: security.voter }
Chill\MainBundle\Security\Authorization\WorkflowEntityDeletionVoter:
autoconfigure: true
autowire: true
arguments:
$handlers: !tagged_iterator chill_main.workflow_handler

View File

@ -413,6 +413,7 @@ workflow:
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 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 transitionned: Anciens workflows
Previous workflow transitionned help: Workflows où vous avez exécuté une action. Previous workflow transitionned help: Workflows où vous avez exécuté une action.
For: Pour
Subscribe final: Recevoir une notification à l'étape finale Subscribe final: Recevoir une notification à l'étape finale

View File

@ -13,7 +13,7 @@ namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\ResidentialAddress; use Chill\PersonBundle\Entity\Person\ResidentialAddress;
use Chill\PersonBundle\Form\Type\ResidentialAddressType; use Chill\PersonBundle\Form\ResidentialAddressType;
use Chill\PersonBundle\Repository\ResidentialAddressRepository; use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

View File

@ -18,8 +18,8 @@ use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdParty;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\Context; use Symfony\Component\Serializer\Annotation\Context;
use Symfony\Component\Serializer\Annotation\Groups;
/** /**
* @ORM\Entity(repositoryClass=ResidentialAddressRepository::class) * @ORM\Entity(repositoryClass=ResidentialAddressRepository::class)
@ -49,7 +49,7 @@ class ResidentialAddress
* @ORM\ManyToOne(targetEntity=Person::class) * @ORM\ManyToOne(targetEntity=Person::class)
* @ORM\JoinColumn(nullable=true) * @ORM\JoinColumn(nullable=true)
* @Groups({"read"}) * @Groups({"read"})
* @Context(normalizationContext={"groups"={"minimal"}}) * @Context(normalizationContext={"groups": {"minimal"}})
*/ */
private ?Person $hostPerson = null; private ?Person $hostPerson = null;

View File

@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Entity;
use DateTime; use DateTime;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use libphonenumber\PhoneNumber;
/** /**
* Person Phones. * Person Phones.
@ -51,9 +52,9 @@ class PersonPhone
private Person $person; private Person $person;
/** /**
* @ORM\Column(type="text", length=40, nullable=false) * @ORM\Column(type="phone_number", nullable=false)
*/ */
private string $phonenumber = ''; private ?PhoneNumber $phonenumber = null;
/** /**
* @ORM\Column(type="text", length=40, nullable=true) * @ORM\Column(type="text", length=40, nullable=true)
@ -85,7 +86,7 @@ class PersonPhone
return $this->person; return $this->person;
} }
public function getPhonenumber(): string public function getPhonenumber(): ?PhoneNumber
{ {
return $this->phonenumber; return $this->phonenumber;
} }
@ -97,7 +98,8 @@ class PersonPhone
public function isEmpty(): bool public function isEmpty(): bool
{ {
return empty($this->getDescription()) && empty($this->getPhonenumber()); return ('' === $this->getDescription() || null === $this->getDescription())
&& null === $this->getPhonenumber();
} }
public function setDate(DateTime $date): void public function setDate(DateTime $date): void
@ -115,7 +117,7 @@ class PersonPhone
$this->person = $person; $this->person = $person;
} }
public function setPhonenumber(string $phonenumber): void public function setPhonenumber(?PhoneNumber $phonenumber): void
{ {
$this->phonenumber = $phonenumber; $this->phonenumber = $phonenumber;
} }

View File

@ -62,6 +62,12 @@ class Evaluation
*/ */
private array $title = []; private array $title = [];
/**
* @ORM\Column(type="text", nullable=true)
* @Serializer\Groups({"read", "docgen:read"})
*/
private ?string $url = null;
public function __construct() public function __construct()
{ {
$this->socialActions = new ArrayCollection(); $this->socialActions = new ArrayCollection();
@ -101,6 +107,11 @@ class Evaluation
return $this->title; return $this->title;
} }
public function getUrl(): ?string
{
return $this->url;
}
public function removeSocialAction(SocialAction $socialAction): self public function removeSocialAction(SocialAction $socialAction): self
{ {
if ($this->socialActions->contains($socialAction)) { if ($this->socialActions->contains($socialAction)) {
@ -130,4 +141,11 @@ class Evaluation
return $this; return $this;
} }
public function setUrl(?string $url): self
{
$this->url = $url;
return $this;
}
} }

View File

@ -27,6 +27,7 @@ use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Entity\PersonPhone;
use Chill\PersonBundle\Form\Type\GenderType; use Chill\PersonBundle\Form\Type\GenderType;
use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Form\Type\PersonAltNameType;
use Chill\PersonBundle\Form\Type\PersonPhoneType;
use Chill\PersonBundle\Form\Type\Select2MaritalStatusType; use Chill\PersonBundle\Form\Type\Select2MaritalStatusType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
@ -158,7 +159,7 @@ class PersonType extends AbstractType
} }
$builder->add('otherPhoneNumbers', ChillCollectionType::class, [ $builder->add('otherPhoneNumbers', ChillCollectionType::class, [
'entry_type' => ChillPhoneNumberType::class, 'entry_type' => PersonPhoneType::class,
'button_add_label' => 'Add new phone', 'button_add_label' => 'Add new phone',
'button_remove_label' => 'Remove phone', 'button_remove_label' => 'Remove phone',
'required' => false, 'required' => false,

View File

@ -9,11 +9,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace Chill\PersonBundle\Form\Type; namespace Chill\PersonBundle\Form;
use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickAddressType; use Chill\MainBundle\Form\Type\PickAddressType;
use Chill\PersonBundle\Entity\Person\ResidentialAddress; use Chill\PersonBundle\Entity\Person\ResidentialAddress;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType; use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\DateType;

View File

@ -11,11 +11,11 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Form\Type; namespace Chill\PersonBundle\Form\Type;
use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Chill\MainBundle\Phonenumber\PhonenumberHelper; use Chill\MainBundle\Phonenumber\PhonenumberHelper;
use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Entity\PersonPhone;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
@ -36,7 +36,7 @@ class PersonPhoneType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder->add('phonenumber', TelType::class, [ $builder->add('phonenumber', ChillPhoneNumberType::class, [
'label' => 'Other phonenumber', 'label' => 'Other phonenumber',
'required' => true, 'required' => true,
]); ]);

View File

@ -32,26 +32,11 @@ class ResidentialAddressRepository extends ServiceEntityRepository
parent::__construct($registry, ResidentialAddress::class); parent::__construct($registry, ResidentialAddress::class);
} }
/**
* @param Person $person
* @param DateTimeImmutable|null $at
* @return array|ResidentialAddress[]|null
*/
public function findCurrentResidentialAddressByPerson(Person $person, ?DateTimeImmutable $at = null): array
{
return $this->buildQueryFindCurrentResidentialAddresses($person, $at)
->select('ra')
->getQuery()
->getResult();
}
public function buildQueryFindCurrentResidentialAddresses(Person $person, ?DateTimeImmutable $at = null): QueryBuilder public function buildQueryFindCurrentResidentialAddresses(Person $person, ?DateTimeImmutable $at = null): QueryBuilder
{ {
$date = null === $at ? new DateTimeImmutable('today') : $at; $date = null === $at ? new DateTimeImmutable('today') : $at;
$qb = $this->createQueryBuilder('ra'); $qb = $this->createQueryBuilder('ra');
$dateFilter = $qb->expr()->andX( $dateFilter = $qb->expr()->andX(
$qb->expr()->lte('ra.startDate', ':dateIn'), $qb->expr()->lte('ra.startDate', ':dateIn'),
$qb->expr()->orX( $qb->expr()->orX(
@ -69,6 +54,17 @@ class ResidentialAddressRepository extends ServiceEntityRepository
return $qb; return $qb;
} }
/**
* @return array|ResidentialAddress[]|null
*/
public function findCurrentResidentialAddressByPerson(Person $person, ?DateTimeImmutable $at = null): array
{
return $this->buildQueryFindCurrentResidentialAddresses($person, $at)
->select('ra')
->getQuery()
->getResult();
}
// /** // /**
// * @return ResidentialAddress[] Returns an array of ResidentialAddress objects // * @return ResidentialAddress[] Returns an array of ResidentialAddress objects
// */ // */

View File

@ -37,57 +37,68 @@ div.banner {
margin-right: 1em; margin-right: 1em;
} }
} }
.household-link {
border: 1px solid white;
padding: .05rem .3rem;
border-radius: 5px;
color: white;
cursor: pointer;
&:hover {
background-color: white;
color: $chill-person-context
}
}
} }
} }
div.person-view { div.person-view {
figure.person-details { figure.person-details {
h2 { h2 {
font-family: 'Open Sans'; font-family: 'Open Sans';
font-weight: 600; font-weight: 600;
margin-bottom: 0.3em; margin-bottom: 0.3em;
font-variant: small-caps; font-variant: small-caps;
} }
dl { dl {
margin-top: 0.3em; margin-top: 0.3em;
} }
dt { dt {
font-family: 'Open Sans'; font-family: 'Open Sans';
font-weight: 600; font-weight: 600;
} }
dd { dd {
margin-left: 0; margin-left: 0;
} }
/* /*
a.sc-button { background-color: $black; padding-top: 0.2em; padding-bottom: 0.2em; } a.sc-button { background-color: $black; padding-top: 0.2em; padding-bottom: 0.2em; }
*/ */
} }
/* custom fields on the home page */ /* custom fields on the home page */
div.custom-fields { div.custom-fields {
figure.person-details { figure.person-details {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
div.cf_title_box:nth-child(4n+1) h2 { div.cf_title_box:nth-child(4n+1) h2 {
@extend .chill-red !optional; @extend .chill-red !optional;
} }
div.cf_title_box:nth-child(4n+2) h2 { div.cf_title_box:nth-child(4n+2) h2 {
@extend .chill-green !optional; @extend .chill-green !optional;
} }
div.cf_title_box:nth-child(4n+3) h2 { div.cf_title_box:nth-child(4n+3) h2 {
@extend .chill-orange !optional; @extend .chill-orange !optional;
} }
div.cf_title_box:nth-child(4n+4) h2 { div.cf_title_box:nth-child(4n+4) h2 {
@extend .chill-blue !optional; @extend .chill-blue !optional;
} }
div.cf_title_box:nth-child(2n+1) { div.cf_title_box:nth-child(2n+1) {
width: 50%; width: 50%;
margin-right: 40px; margin-right: 40px;
} }
div.cf_title_box:nth-child(2n+2) { div.cf_title_box:nth-child(2n+2) {
width: calc(50% - 40px); width: calc(50% - 40px);
} }
} }
} }
} }
/* /*

View File

@ -19,22 +19,21 @@ import {fetchResults} from 'ChillMainAssets/lib/api/apiMethods.js';
*/ */
document.querySelectorAll('[data-set-referrer-app]').forEach(function (el) { document.querySelectorAll('[data-set-referrer-app]').forEach(function (el) {
let const periodId = Number.parseInt(el.dataset.setReferrerAccompanyingPeriodId);
periodId = Number.parseInt(el.dataset.setReferrerAccompanyingPeriodId); const jobId = Number.parseInt(el.dataset.setReferrerJobId);
const url = `/api/1.0/person/accompanying-course/${periodId}/referrers-suggested.json`; const url = `/api/1.0/person/accompanying-course/${periodId}/referrers-suggested.json`;
fetchResults(url).then(suggested => { fetchResults(url).then(suggested => {
const filteredSuggested = suggested.filter((s) => s.user_job ? s.user_job.id === jobId : false);
const app = createApp({ const app = createApp({
components: { components: {
SetReferrer, SetReferrer,
}, },
template: template:
'<set-referrer :suggested="suggested" :periodId="periodId" @referrerSet="onReferrerSet"></set-referrer>', '<set-referrer :suggested="filteredSuggested" :periodId="periodId" @referrerSet="onReferrerSet"></set-referrer>',
data() { data() {
return { return {
periodId, suggested, original: suggested, periodId, filteredSuggested, original: filteredSuggested,
} }
}, },
methods: { methods: {
@ -56,7 +55,7 @@ document.querySelectorAll('[data-set-referrer-app]').forEach(function (el) {
label.textContent = ref.text; label.textContent = ref.text;
label.classList.remove('chill-no-data-statement'); label.classList.remove('chill-no-data-statement');
this.suggested = this.original.filter(user => user.id !== ref.id); this.filteredSuggested = this.original.filter(user => user.id !== ref.id);
} }
} }
}); });

View File

@ -59,14 +59,6 @@
ref="addAddress"> ref="addAddress">
</add-address> </add-address>
</li> </li>
<li v-if="isPersonLocation">
<button
class="btn btn-remove"
@click="removeAddress"
:title="$t('courselocation.remove_button')">
{{ $t('action.remove') }}
</button>
</li>
</ul> </ul>
</div> </div>
@ -180,22 +172,6 @@ export default {
} }
this.$store.commit('setAddressContext', context); this.$store.commit('setAddressContext', context);
}, },
removeAddress() {
let payload = {
target: this.context.target.name,
targetId: this.context.target.id,
locationStatusTo: 'none'
};
//console.log('remove address');
this.$store.dispatch('updateLocation', payload)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
},
displayErrors() { displayErrors() {
return this.$refs.addAddress.errorMsg; return this.$refs.addAddress.errorMsg;
}, },

View File

@ -170,7 +170,9 @@ export default {
console.log('data', payload.data) console.log('data', payload.data)
body.name = payload.data.name; body.name = payload.data.name;
body.email = payload.data.email; body.email = payload.data.email;
body.telephone = payload.data.phonenumber; body.telephone = payload.data.telephone;
body.civility = payload.data.civility;
body.profession = payload.data.profession;
body.address = payload.data.address ? { id: payload.data.address.address_id } : null; body.address = payload.data.address ? { id: payload.data.address.address_id } : null;
makeFetch('PATCH', `/api/1.0/thirdparty/thirdparty/${payload.data.id}.json`, body) makeFetch('PATCH', `/api/1.0/thirdparty/thirdparty/${payload.data.id}.json`, body)

View File

@ -5,6 +5,11 @@
<span>{{ evaluation.evaluation.title.fr }}</span> <span>{{ evaluation.evaluation.title.fr }}</span>
</div> </div>
<div class="item-url mt-3 mb-4" v-if="evaluation.evaluation.url">
<i class="fa fa-link fa-lg"></i>
<a :href="evaluation.evaluation.url" target="_blank">{{ evaluation.evaluation.url }}</a>
</div>
<div> <div>
<form-evaluation ref="FormEvaluation" :key="evaluation.key" :evaluation="evaluation"></form-evaluation> <form-evaluation ref="FormEvaluation" :key="evaluation.key" :evaluation="evaluation"></form-evaluation>
@ -21,7 +26,7 @@
></list-workflow-modal> ></list-workflow-modal>
</li> </li>
<li> <li v-if="canDelete">
<a class="btn btn-delete" @click="modal.showModal = true" :title="$t('action.delete')"></a> <a class="btn btn-delete" @click="modal.showModal = true" :title="$t('action.delete')"></a>
</li> </li>
</ul> </ul>
@ -93,6 +98,19 @@ export default {
pickedEvaluations() { pickedEvaluations() {
return this.$store.state.evaluationsPicked; return this.$store.state.evaluationsPicked;
}, },
canDelete() {
if (this.evaluation.workflows.length > 0) {
return false;
}
for (let doc of this.evaluation.documents) {
if (doc.workflows.length > 0) {
return false;
}
}
return true;
},
}, },
methods: { methods: {
removeEvaluation(e) { removeEvaluation(e) {
@ -128,4 +146,11 @@ export default {
} }
} }
} }
div.item-url {
i {
color: unset!important;
margin-left: 1rem;
margin-right: 0.5rem;
}
}
</style> </style>

View File

@ -101,7 +101,7 @@
<li> <li>
<a :href="buildEditLink(d.storedObject)" class="btn btn-wopilink"></a> <a :href="buildEditLink(d.storedObject)" class="btn btn-wopilink"></a>
</li> </li>
<li> <li v-if="d.workflows.length === 0">
<a class="btn btn-delete" @click="removeDocument(d)"> <a class="btn btn-delete" @click="removeDocument(d)">
</a> </a>
</li> </li>

View File

@ -1,12 +1,12 @@
<template> <template>
<ul class="list-suggest add-items" v-if="suggested.length > 0"> <ul class="list-suggest add-items" v-if="suggested.length > 0">
<li v-for="r in suggested" @click="setReferrer(r)"><span>{{ r.text }}</span></li> <li v-for="(r, i) in suggested" @click="setReferrer(r)" :key="i"><span>{{ r.text }}</span></li>
</ul> </ul>
</template> </template>
<script> <script>
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods.js'; import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods.js';
export default { export default {
name: "SetReferrer", name: "SetReferrer",

View File

@ -130,11 +130,13 @@
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}" href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
></a> ></a>
</li> </li>
<li> {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE', w) %}
<a class="btn btn-delete" title="{{ 'Delete'|trans }}" <li>
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}" <a class="btn btn-delete" title="{{ 'Delete'|trans }}"
></a> href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
</li> ></a>
</li>
{% endif %}
</ul> </ul>
{% endif %} {% endif %}
</div> </div>

View File

@ -117,12 +117,15 @@
</div> </div>
{% endif %} {% endif %}
<div class="item-row separator"> <div class="item-row separator">
<div class="item-col item-meta"> {% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %}
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) %} {% if notif_counter.total > 0 %}
{% if notif_counter.total > 0 %} <div class="item-col item-meta">
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }} {{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', period.id) }}
{% endif %} </div>
</div> {% endif %}
{% if itemMeta is defined %}
{{ itemMeta }}
{% endif %}
<div class="item-col"> <div class="item-col">
{% if recordAction is defined %} {% if recordAction is defined %}
<ul class="record_actions"> <ul class="record_actions">

View File

@ -167,6 +167,18 @@
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span> <span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
</li> </li>
{% endif %} {% endif %}
{% if person.email is not empty %}
<li>
<i class="fa fa-li fa-envelope-o"></i><a href="{{ 'mailto:' ~ person.email }}">
{{ person.email }}
</a>
</li>
{% else %}
<li>
<i class="fa fa-li fa-envelope-o"></i>
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
</li>
{% endif %}
{% if options['addCenter'] and person|chill_resolve_center|length > 0 %} {% if options['addCenter'] and person|chill_resolve_center|length > 0 %}
<li> <li>
<i class="fa fa-li fa-long-arrow-right"></i> <i class="fa fa-li fa-long-arrow-right"></i>

View File

@ -58,6 +58,13 @@
}) }} }) }}
</span> </span>
{%- endif -%} {%- endif -%}
{% if person.getCurrentHousehold is not null %}
<span>
<a class="household-link" href="{{ chill_path_add_return_path('chill_person_household_summary', { 'household_id' : person.getCurrentHousehold.id } ) }}">
<i class="fa fa-home"></i>
</a>
</span>
{% endif %}
</div> </div>
</div> </div>

View File

@ -24,6 +24,8 @@ class AccompanyingPeriodWorkVoter extends Voter
{ {
public const CREATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE'; public const CREATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE';
public const DELETE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE';
public const SEE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE'; public const SEE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE';
public const UPDATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE'; public const UPDATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE';
@ -60,6 +62,7 @@ class AccompanyingPeriodWorkVoter extends Voter
case self::CREATE: case self::CREATE:
case self::UPDATE: case self::UPDATE:
case self::DELETE:
return $this->security->isGranted(AccompanyingPeriodVoter::EDIT, $subject->getAccompanyingPeriod()); return $this->security->isGranted(AccompanyingPeriodVoter::EDIT, $subject->getAccompanyingPeriod());
default: default:
@ -86,6 +89,6 @@ class AccompanyingPeriodWorkVoter extends Voter
private function getRoles(): array private function getRoles(): array
{ {
return [self::SEE, self::CREATE, self::UPDATE]; return [self::SEE, self::CREATE, self::UPDATE, self::DELETE];
} }
} }

View File

@ -74,6 +74,7 @@ class PersonDocGenNormalizer implements
$data = [ $data = [
'type' => 'person', 'type' => 'person',
'id' => $person->getId(),
'isNull' => false, 'isNull' => false,
'civility' => $this->normalizer->normalize($person->getCivility(), $format, array_merge($context, ['docgen:expects' => Civility::class])), 'civility' => $this->normalizer->normalize($person->getCivility(), $format, array_merge($context, ['docgen:expects' => Civility::class])),
'firstName' => $person->getFirstName(), 'firstName' => $person->getFirstName(),
@ -151,7 +152,7 @@ class PersonDocGenNormalizer implements
$normalizer = new NormalizeNullValueHelper($this->normalizer, 'type', 'person'); $normalizer = new NormalizeNullValueHelper($this->normalizer, 'type', 'person');
$attributes = [ $attributes = [
'firstName', 'lastName', 'age', 'altNames', 'text', 'id', 'firstName', 'lastName', 'age', 'altNames', 'text',
'civility' => Civility::class, 'civility' => Civility::class,
'birthdate' => DateTimeInterface::class, 'birthdate' => DateTimeInterface::class,
'deathdate' => DateTimeInterface::class, 'deathdate' => DateTimeInterface::class,

View File

@ -31,6 +31,9 @@ use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait; use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
use function array_key_exists; use function array_key_exists;
use function count;
use function in_array;
use function is_string;
/** /**
* Serialize a Person entity. * Serialize a Person entity.
@ -54,11 +57,11 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
private ResidentialAddressRepository $residentialAddressRepository; private ResidentialAddressRepository $residentialAddressRepository;
public function __construct( public function __construct(
ChillEntityRenderExtension $render, ChillEntityRenderExtension $render, /* TODO: replace by PersonRenderInterface, as sthis is the only one required */
PersonRepository $repository, PersonRepository $repository,
CenterResolverManagerInterface $centerResolverManager, CenterResolverManagerInterface $centerResolverManager,
ResidentialAddressRepository $residentialAddressRepository, ResidentialAddressRepository $residentialAddressRepository,
PhoneNumberHelperInterface $phoneNumberHelper PhoneNumberHelperInterface $phoneNumberHelper /* TODO maybe not necessayr any more */
) { ) {
$this->render = $render; $this->render = $render;
$this->repository = $repository; $this->repository = $repository;
@ -187,6 +190,10 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
public function normalize($person, $format = null, array $context = []) public function normalize($person, $format = null, array $context = [])
{ {
$groups = $context[AbstractNormalizer::GROUPS] ?? []; $groups = $context[AbstractNormalizer::GROUPS] ?? [];
if (is_string($groups)) {
$groups = [$groups];
}
$household = $person->getCurrentHousehold(); $household = $person->getCurrentHousehold();
$currentResidentialAddresses = $this->residentialAddressRepository->findCurrentResidentialAddressByPerson($person); $currentResidentialAddresses = $this->residentialAddressRepository->findCurrentResidentialAddressByPerson($person);
@ -207,8 +214,8 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
'gender' => $person->getGender(), 'gender' => $person->getGender(),
]; ];
if (in_array("minimal", $groups) && 1 === count($groups)) { if (in_array('minimal', $groups, true) && 1 === count($groups)) {
return $data; return $data;
} }
return array_merge($data, [ return array_merge($data, [

View File

@ -179,7 +179,8 @@ class AccompanyingPeriodWorkEvaluationContext implements
$doc = new AccompanyingPeriodWorkEvaluationDocument(); $doc = new AccompanyingPeriodWorkEvaluationDocument();
$doc->setStoredObject($storedObject) $doc->setStoredObject($storedObject)
->setAccompanyingPeriodWorkEvaluation($entity) ->setAccompanyingPeriodWorkEvaluation($entity)
->setTemplate($template); ->setTemplate($template)
->setTitle($this->translatableStringHelper->localize($template->getName()));
$this->em->persist($doc); $this->em->persist($doc);
} }
} }

View File

@ -0,0 +1,179 @@
<?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\Service\DocGenerator;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Person;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists;
class PersonContext implements DocGeneratorContextWithAdminFormInterface
{
private BaseContextData $baseContextData;
private DocumentCategoryRepository $documentCategoryRepository;
private EntityManagerInterface $em;
private NormalizerInterface $normalizer;
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator;
public function __construct(
DocumentCategoryRepository $documentCategoryRepository,
NormalizerInterface $normalizer,
TranslatableStringHelperInterface $translatableStringHelper,
EntityManagerInterface $em,
TranslatorInterface $translator,
BaseContextData $baseContextData
) {
$this->documentCategoryRepository = $documentCategoryRepository;
$this->normalizer = $normalizer;
$this->translatableStringHelper = $translatableStringHelper;
$this->em = $em;
$this->baseContextData = $baseContextData;
$this->translator = $translator;
}
public function adminFormReverseTransform(array $data): array
{
if (array_key_exists('category', $data)) {
$data['category'] = [
'idInsideBundle' => $data['category']->getIdInsideBundle(),
'bundleId' => $data['category']->getBundleId(),
];
}
return $data;
}
public function adminFormTransform(array $data): array
{
$r = [
'mainPerson' => $data['mainPerson'] ?? false,
'mainPersonLabel' => $data['mainPersonLabel'] ?? $this->translator->trans('docgen.Main person'),
];
if (array_key_exists('category', $data)) {
$r['category'] = array_key_exists('category', $data) ?
$this->documentCategoryRepository->find($data['category']) : null;
}
return $r;
}
public function buildAdminForm(FormBuilderInterface $builder): void
{
$builder
->add('category', EntityType::class, [
'placeholder' => 'Choose a document category',
'class' => 'ChillDocStoreBundle:DocumentCategory',
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('c')
->where('c.documentClass = :docClass')
->setParameter('docClass', PersonDocument::class);
},
'choice_label' => function ($entity = null) {
return $entity ? $this->translatableStringHelper->localize($entity->getName()) : '';
},
]);
}
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
{
if (!$entity instanceof Person) {
throw new UnexpectedTypeException($entity, Person::class);
}
$data = [];
$data = array_merge($data, $this->baseContextData->getData());
$data['person'] = $this->normalizer->normalize($entity, 'docgen', [
'docgen:expects' => Person::class,
'groups' => ['docgen:read', 'docgen:person:with-household', 'docgen:person:with-relations'],
]);
return $data;
}
public function getDescription(): string
{
return 'docgen.A basic context for person';
}
public function getEntityClass(): string
{
return Person::class;
}
public function getFormData(DocGeneratorTemplate $template, $entity): array
{
return [
'person' => $entity,
];
}
public static function getKey(): string
{
return self::class;
}
public function getName(): string
{
return 'docgen.Person basic';
}
public function hasAdminForm(): bool
{
return true;
}
/**
* @param Person $entity
*/
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
$doc = new PersonDocument();
$doc->setTemplate($template)
->setTitle($this->translatableStringHelper->localize($template->getName()))
->setDate(new DateTime())
->setDescription($this->translatableStringHelper->localize($template->getName()))
->setPerson($entity)
->setObject($storedObject);
if (array_key_exists('category', $template->getOptions())) {
$doc
->setCategory(
$this->documentCategoryRepository->find(
$template->getOptions()['category']
)
);
}
$this->em->persist($doc);
}
}

View File

@ -38,6 +38,7 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
use ProphecyTrait; use ProphecyTrait;
private const BLANK = [ private const BLANK = [
'id' => '',
'firstName' => '', 'firstName' => '',
'lastName' => '', 'lastName' => '',
'altNames' => '', 'altNames' => '',

View File

@ -11,7 +11,15 @@ declare(strict_types=1);
namespace Serializer\Normalizer; namespace Serializer\Normalizer;
use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\PersonBundle\Serializer\Normalizer\PersonJsonNormalizer;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@ -22,12 +30,27 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
*/ */
final class PersonJsonNormalizerTest extends KernelTestCase final class PersonJsonNormalizerTest extends KernelTestCase
{ {
private NormalizerInterface $normalizer; use ProphecyTrait;
private PersonJsonNormalizer $normalizer;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->normalizer = self::$container->get(NormalizerInterface::class);
$residentialAddressRepository = $this->prophesize(ResidentialAddressRepository::class);
$residentialAddressRepository
->findCurrentResidentialAddressByPerson(Argument::type(Person::class), Argument::any())
->willReturn([]);
$this->normalizer = $this->buildPersonJsonNormalizer(
self::$container->get(ChillEntityRenderExtension::class),
self::$container->get(PersonRepository::class),
self::$container->get(CenterResolverManagerInterface::class),
$residentialAddressRepository->reveal(),
self::$container->get(PhoneNumberHelperInterface::class),
self::$container->get(NormalizerInterface::class)
);
} }
public function testNormalization() public function testNormalization()
@ -37,4 +60,24 @@ final class PersonJsonNormalizerTest extends KernelTestCase
$this->assertIsArray($result); $this->assertIsArray($result);
} }
private function buildPersonJsonNormalizer(
ChillEntityRenderExtension $render,
PersonRepository $repository,
CenterResolverManagerInterface $centerResolverManager,
ResidentialAddressRepository $residentialAddressRepository,
PhoneNumberHelperInterface $phoneNumberHelper,
NormalizerInterface $normalizer
): PersonJsonNormalizer {
$personJsonNormalizer = new PersonJsonNormalizer(
$render,
$repository,
$centerResolverManager,
$residentialAddressRepository,
$phoneNumberHelper
);
$personJsonNormalizer->setNormalizer($normalizer);
return $personJsonNormalizer;
}
} }

View File

@ -37,6 +37,13 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW
$this->translator = $translator; $this->translator = $translator;
} }
public function getDeletionRoles(): array
{
return [
'_',
];
}
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
{ {
$doc = $this->getRelatedEntity($entityWorkflow); $doc = $this->getRelatedEntity($entityWorkflow);
@ -63,6 +70,18 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW
return $this->repository->find($entityWorkflow->getRelatedEntityId()); return $this->repository->find($entityWorkflow->getRelatedEntityId());
} }
/**
* @param AccompanyingPeriodWorkEvaluationDocument $object
*
* @return array[]
*/
public function getRelatedObjects(object $object): array
{
return [
['entityClass' => AccompanyingPeriodWorkEvaluationDocument::class, $object->getId()],
];
}
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string public function getRoleShow(EntityWorkflow $entityWorkflow): ?string
{ {
return AccompanyingPeriodWorkEvaluationDocumentVoter::SEE; return AccompanyingPeriodWorkEvaluationDocumentVoter::SEE;
@ -84,6 +103,11 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW
]; ];
} }
public function isObjectSupported(object $object): bool
{
return $object instanceof AccompanyingPeriodWorkEvaluationDocument;
}
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool
{ {
return $entityWorkflow->getRelatedEntityClass() === AccompanyingPeriodWorkEvaluationDocument::class; return $entityWorkflow->getRelatedEntityClass() === AccompanyingPeriodWorkEvaluationDocument::class;

View File

@ -15,6 +15,7 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationRepository; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkEvaluationVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkEvaluationVoter;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
@ -37,6 +38,11 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH
$this->translator = $translator; $this->translator = $translator;
} }
public function getDeletionRoles(): array
{
return ['_'];
}
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
{ {
$evaluation = $this->getRelatedEntity($entityWorkflow); $evaluation = $this->getRelatedEntity($entityWorkflow);
@ -61,6 +67,21 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH
return $this->repository->find($entityWorkflow->getRelatedEntityId()); return $this->repository->find($entityWorkflow->getRelatedEntityId());
} }
/**
* @param AccompanyingPeriodWorkEvaluation $object
*/
public function getRelatedObjects(object $object): array
{
$relateds = [];
$relateds[] = ['entityClass' => AccompanyingPeriodWorkEvaluation::class, 'entityId' => $object->getId()];
foreach ($object->getDocuments() as $doc) {
$relateds[] = ['entityClass' => AccompanyingPeriodWorkEvaluationDocument::class, 'entityId' => $doc->getId()];
}
return $relateds;
}
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string public function getRoleShow(EntityWorkflow $entityWorkflow): ?string
{ {
return AccompanyingPeriodWorkEvaluationVoter::SEE; return AccompanyingPeriodWorkEvaluationVoter::SEE;
@ -79,6 +100,11 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH
]; ];
} }
public function isObjectSupported(object $object): bool
{
return $object instanceof AccompanyingPeriodWorkEvaluation;
}
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool
{ {
return $entityWorkflow->getRelatedEntityClass() === AccompanyingPeriodWorkEvaluation::class; return $entityWorkflow->getRelatedEntityClass() === AccompanyingPeriodWorkEvaluation::class;

View File

@ -15,7 +15,10 @@ use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository; use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInterface class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInterface
@ -36,6 +39,11 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte
$this->translator = $translator; $this->translator = $translator;
} }
public function getDeletionRoles(): array
{
return [AccompanyingPeriodWorkVoter::DELETE];
}
public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array public function getEntityData(EntityWorkflow $entityWorkflow, array $options = []): array
{ {
return [ return [
@ -58,6 +66,25 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte
return $this->repository->find($entityWorkflow->getRelatedEntityId()); return $this->repository->find($entityWorkflow->getRelatedEntityId());
} }
/**
* @param AccompanyingPeriodWork $object
*/
public function getRelatedObjects(object $object): array
{
$relateds = [];
$relateds[] = ['entityClass' => AccompanyingPeriodWork::class, 'entityId' => $object->getId()];
foreach ($object->getAccompanyingPeriodWorkEvaluations() as $evaluation) {
$relateds[] = ['entityClass' => AccompanyingPeriodWorkEvaluation::class, 'entityId' => $evaluation->getId()];
foreach ($evaluation->getDocuments() as $doc) {
$relateds[] = ['entityClass' => AccompanyingPeriodWorkEvaluationDocument::class, 'entityId' => $doc->getId()];
}
}
return $relateds;
}
public function getRoleShow(EntityWorkflow $entityWorkflow): ?string public function getRoleShow(EntityWorkflow $entityWorkflow): ?string
{ {
return null; return null;
@ -76,6 +103,11 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte
]; ];
} }
public function isObjectSupported(object $object): bool
{
return $object instanceof AccompanyingPeriodWork;
}
public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool public function supports(EntityWorkflow $entityWorkflow, array $options = []): bool
{ {
return $entityWorkflow->getRelatedEntityClass() === AccompanyingPeriodWork::class; return $entityWorkflow->getRelatedEntityClass() === AccompanyingPeriodWork::class;

View File

@ -0,0 +1,37 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add url to SocialWork Evaluation.
*/
final class Version20220303113855 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_social_work_evaluation DROP url');
}
public function getDescription(): string
{
return 'Add url to SocialWork Evaluation';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_social_work_evaluation ADD url TEXT DEFAULT NULL');
$this->addSql("UPDATE chill_person_social_work_evaluation SET url = CASE WHEN title->>'fr' LIKE 'http%' THEN title->>'fr' ELSE null END;");
}
}

View File

@ -124,8 +124,8 @@ address_country_code: Code pays
'Alreay existing person': 'Dossiers déjà encodés' 'Alreay existing person': 'Dossiers déjà encodés'
'Add the person': 'Ajouter la personne' 'Add the person': 'Ajouter la personne'
'Add the person and create an accompanying period': "Créer la personne & créer une période d'accompagnement" 'Add the person and create an accompanying period': "Créer l'usager ET créer une période d'accompagnement"
'Add the person and create a household': "Créer la personne & créer un ménage" 'Add the person and create a household': "Créer l'usager' ET créer un ménage"
Show person: Voir le dossier de la personne Show person: Voir le dossier de la personne
'Confirm the creation': 'Confirmer la création' 'Confirm the creation': 'Confirmer la création'
'You will create this person': 'Vous allez créer le dossier suivant' 'You will create this person': 'Vous allez créer le dossier suivant'
@ -541,6 +541,8 @@ docgen:
A basic context for accompanying period: Contexte pour les parcours A basic context for accompanying period: Contexte pour les parcours
A context for accompanying period work: Contexte pour les actions d'accompagnement A context for accompanying period work: Contexte pour les actions d'accompagnement
A context for accompanying period work evaluation: Contexte pour les évaluations dans les actions d'accompagnement A context for accompanying period work evaluation: Contexte pour les évaluations dans les actions d'accompagnement
Person basic: Personne (basique)
A basic context for person: Contexte pour les personnes
period_notification: period_notification:
period_designated_subject: Vous êtes référent d'un parcours d'accompagnement period_designated_subject: Vous êtes référent d'un parcours d'accompagnement

View File

@ -46,6 +46,7 @@ User: Utilisateur
"Associated tasks": "Tâches associées" "Associated tasks": "Tâches associées"
"My tasks": "Mes tâches" "My tasks": "Mes tâches"
"Tasks for this accompanying period": "Tâches pour ce parcours d'accompagnement" "Tasks for this accompanying period": "Tâches pour ce parcours d'accompagnement"
"Tasks for {{ name }}": "Tâches pour {{ name }}"
"No description": "Pas de description" "No description": "Pas de description"
"No dates specified": "Dates non spécifiées" "No dates specified": "Dates non spécifiées"
"No one assignee": "Aucune personne assignée" "No one assignee": "Aucune personne assignée"

View File

@ -20,6 +20,7 @@ use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepositoryInterface; use Chill\ThirdPartyBundle\Repository\ThirdPartyACLAwareRepositoryInterface;
use Chill\ThirdPartyBundle\Security\Voter\ThirdPartyVoter; use Chill\ThirdPartyBundle\Security\Voter\ThirdPartyVoter;
use LogicException; use LogicException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
@ -31,37 +32,39 @@ use function array_merge;
final class ThirdPartyController extends CRUDController final class ThirdPartyController extends CRUDController
{ {
/** protected AuthorizationHelper $authorizationHelper;
* @var AuthorizationHelper
*/
protected $authorizationHelper;
/** protected PaginatorFactory $paginatorFactory;
* @var PaginatorFactory
*/
protected $paginatorFactory;
protected RequestStack $requestStack; protected RequestStack $requestStack;
protected ThirdPartyACLAwareRepositoryInterface $thirdPartyACLAwareRepository; protected ThirdPartyACLAwareRepositoryInterface $thirdPartyACLAwareRepository;
/** protected TranslatorInterface $translator;
* @var TranslatorInterface
*/ private bool $askCenter;
protected $translator;
public function __construct( public function __construct(
AuthorizationHelper $authorizationHelper, AuthorizationHelper $authorizationHelper,
TranslatorInterface $translator, TranslatorInterface $translator,
PaginatorFactory $paginatorFactory, PaginatorFactory $paginatorFactory,
RequestStack $requestStack, RequestStack $requestStack,
ThirdPartyACLAwareRepositoryInterface $thirdPartyACLAwareRepository ThirdPartyACLAwareRepositoryInterface $thirdPartyACLAwareRepository,
ParameterBagInterface $parameterBag
) { ) {
$this->authorizationHelper = $authorizationHelper; $this->authorizationHelper = $authorizationHelper;
$this->translator = $translator; $this->translator = $translator;
$this->paginatorFactory = $paginatorFactory; $this->paginatorFactory = $paginatorFactory;
$this->requestStack = $requestStack; $this->requestStack = $requestStack;
$this->thirdPartyACLAwareRepository = $thirdPartyACLAwareRepository; $this->thirdPartyACLAwareRepository = $thirdPartyACLAwareRepository;
$this->askCenter = $parameterBag->get('chill_main')['acl']['form_show_centers'];
}
public function generateTemplateParameter(string $action, $entity, Request $request, array $defaultTemplateParameters = [])
{
$defaultTemplateParameters['askCenter'] = $this->askCenter;
return $defaultTemplateParameters;
} }
protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper protected function buildFilterOrderHelper(string $action, Request $request): ?FilterOrderHelper

View File

@ -36,7 +36,7 @@
<ul class="list-content fa-ul"> <ul class="list-content fa-ul">
<li v-if="getProfession.length > 0"> <li v-if="getProfession.length > 0">
<i class="fa fa-li fa-id-card"></i> <i class="fa fa-li fa-id-card"></i>
<p><span v-for="p in getProfession" :key="p" class="list-item">{{ p[0].toUpperCase() + p.slice(1).toLowerCase() }}</span></p> <p><span v-for="p in getProfession" :key="p" class="list-item list-professions">{{ p[0].toUpperCase() + p.slice(1).toLowerCase() }}</span></p>
</li> </li>
<li v-if="hasParent"> <li v-if="hasParent">
<i class="fa fa-li fa-hand-o-right"></i> <i class="fa fa-li fa-hand-o-right"></i>
@ -61,9 +61,9 @@
<i class="fa fa-li fa-map-marker"></i> <i class="fa fa-li fa-map-marker"></i>
<address-render-box :address="thirdparty.address" :isMultiline="isMultiline"></address-render-box> <address-render-box :address="thirdparty.address" :isMultiline="isMultiline"></address-render-box>
</li> </li>
<li v-if="thirdparty.phonenumber"> <li v-if="thirdparty.telephone">
<i class="fa fa-li fa-mobile"></i> <i class="fa fa-li fa-mobile"></i>
<a :href="'tel: ' + thirdparty.phonenumber">{{ thirdparty.phonenumber }}</a> <a :href="'tel: ' + thirdparty.telephone">{{ thirdparty.telephone }}</a>
</li> </li>
<li v-if="thirdparty.email"> <li v-if="thirdparty.email">
<i class="fa fa-li fa-envelope-o"></i> <i class="fa fa-li fa-envelope-o"></i>
@ -78,9 +78,9 @@
<i class="fa fa-li fa-map-marker"></i> <i class="fa fa-li fa-map-marker"></i>
<address-render-box :address="thirdparty.address" :isMultiline="isMultiline"></address-render-box> <address-render-box :address="thirdparty.address" :isMultiline="isMultiline"></address-render-box>
</li> </li>
<li v-if="thirdparty.phonenumber"> <li v-if="thirdparty.telephone">
<i class="fa fa-li fa-mobile"></i> <i class="fa fa-li fa-mobile"></i>
<a :href="'tel: ' + thirdparty.phonenumber">{{ thirdparty.phonenumber }}</a> <a :href="'tel: ' + thirdparty.telephone">{{ thirdparty.telephone }}</a>
</li> </li>
<li v-if="thirdparty.email"> <li v-if="thirdparty.email">
<i class="fa fa-li fa-envelope-o"></i> <i class="fa fa-li fa-envelope-o"></i>
@ -165,7 +165,7 @@ export default {
} }
} }
.list-item { .list-professions {
&::after { &::after {
content: " | "; content: " | ";
} }

View File

@ -118,7 +118,7 @@
<div class="input-group mb-3"> <div class="input-group mb-3">
<span class="input-group-text" id="phonenumber"><i class="fa fa-fw fa-phone"></i></span> <span class="input-group-text" id="phonenumber"><i class="fa fa-fw fa-phone"></i></span>
<input class="form-control form-control-lg" <input class="form-control form-control-lg"
v-model="thirdparty.phonenumber" v-model="thirdparty.telephone"
v-bind:placeholder="$t('thirdparty.phonenumber')" v-bind:placeholder="$t('thirdparty.phonenumber')"
v-bind:aria-label="$t('thirdparty.phonenumber')" v-bind:aria-label="$t('thirdparty.phonenumber')"
aria-describedby="phonenumber" /> aria-describedby="phonenumber" />

View File

@ -13,7 +13,7 @@
<h1> <h1>
{{ title_ }} {{ title_ }}
<span class="badge bg-{{ thirdParty.active ? 'success' : 'danger' }} float-end" <span class="badge bg-{{ thirdParty.active ? 'success' : 'danger' }} float-end"
title="{{ (thirdParty.active ? 'shown to users' : 'not shown to users')|trans }}"> title="{{ (thirdParty.active ? 'shown to users' : 'not shown to users')|trans }}">
{{ (thirdParty.active ? 'Active' : 'Inactive')|trans }} {{ (thirdParty.active ? 'Active' : 'Inactive')|trans }}
</span> </span>
</h1> </h1>
@ -123,19 +123,20 @@
</dd> </dd>
{% endif %} {% endif %}
<dt>{{ 'Centers'|trans }}</dt> {% if askCenter %}
<dd> <dt>{{ 'Centers'|trans }}</dt>
{% set centers = thirdParty|chill_resolve_center %} <dd>
{% if centers is iterable %} {% set centers = thirdParty|chill_resolve_center %}
{{ 'The party is visible in those centers'|trans }}&nbsp;: {% if centers is iterable %}
{{ centers|join(', ') }} {{ 'The party is visible in those centers'|trans }}&nbsp;:
{% elseif centers is null %} {{ centers|join(', ') }}
{{ 'The party is not visible in any center'|trans }} {% elseif centers is null %}
{% else %} {{ 'The party is not visible in any center'|trans }}
{{ 'The party is visible in those centers'|trans }}&nbsp;: {{ centers }} {% else %}
{% endif %} {{ 'The party is visible in those centers'|trans }}&nbsp;: {{ centers }}
</dd> {% endif %}
</dd>
{% endif %}
</dl> </dl>
{% endblock %} {% endblock %}
{% block content_form_actions_delete %}{% endblock %} {% block content_form_actions_delete %}{% endblock %}

View File

@ -62,7 +62,7 @@ class ThirdPartyNormalizer implements NormalizerAwareInterface, NormalizerInterf
}, $thirdParty->getTypesAndCategories()), }, $thirdParty->getTypesAndCategories()),
'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context), 'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context),
'address' => $this->normalizer->normalize($thirdParty->getAddress(), $format, ['address_rendering' => 'short']), 'address' => $this->normalizer->normalize($thirdParty->getAddress(), $format, ['address_rendering' => 'short']),
'phonenumber' => $this->normalizer->normalize($thirdParty->getTelephone()), 'telephone' => $this->normalizer->normalize($thirdParty->getTelephone()),
'email' => $thirdParty->getEmail(), 'email' => $thirdParty->getEmail(),
'isChild' => $thirdParty->isChild(), 'isChild' => $thirdParty->isChild(),
'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context), 'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context),