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

This commit is contained in:
Julien Fastré 2022-03-06 22:33:16 +01:00
commit 739c7e9a77
40 changed files with 543 additions and 145 deletions

View File

@ -12,8 +12,8 @@ and this project adheres to
<!-- write down unreleased development here -->
* [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)
* [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)
@ -29,6 +29,7 @@ and this project adheres to
* [confidential] Fix position of toggle button so it does not cover text nor fall outside of box (no issue)
* [parcours] Fix edit of both thirdparty and contact name (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/474)
* [template] do not list inactive templates (for doc generator)
* [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490)
## Test releases

View File

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

View File

@ -76,7 +76,7 @@ activity:
Insert a document: Insérer un document
Remove a document: Supprimer le document
comment: Commentaire
No documents: Pas de documents
No documents: Aucun document
#timeline
'%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->hasPublicForm($template, $entity) || $isTest
) {
if ($context instanceof DocGeneratorContextWithPublicFormInterface) {
if ($context instanceof DocGeneratorContextWithPublicFormInterface && $context->hasPublicForm($template, $entity)) {
$builder = $this->createFormBuilder(
array_merge(
$context->getFormData($template, $entity),
$isTest ? ['test_file' => null] : []
)
);
$context->buildPublicForm($builder, $template, $entity);
} else {
$builder = $this->createFormBuilder(
['test_file' => null]
);
}
$context->buildPublicForm($builder, $template, $entity);
if ($isTest) {
$builder->add('test_file', FileType::class, [
'label' => 'Template file',

View File

@ -21,6 +21,8 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
use DateTime;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
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\Response;
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
{
$this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::DELETE, $document);
if ($this->isCsrfTokenValid('delete' . $document->getId(), $request->request->get('_token'))) {
$em = $this->getDoctrine()->getManager();
$em->remove($document);
$em->flush();
$form = $this->createForm(FormType::class);
$form->add('submit', SubmitType::class, ['label' => 'Delete']);
$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',
['accompanyingCourse' => $course->getId()]
return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]);
}
return $this->render(
'ChillDocStoreBundle:AccompanyingCourseDocument:delete.html.twig',
[
'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\Form\PersonDocumentType;
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\Person;
@ -22,6 +23,8 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTime;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
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\Response;
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
{
$this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
$this->denyAccessUnlessGranted('CHILL_PERSON_DOCUMENT_DELETE', $document);
$this->denyAccessUnlessGranted(PersonDocumentVoter::DELETE, $document);
if ($this->isCsrfTokenValid('delete' . $document->getId(), $request->request->get('_token'))) {
$em = $this->getDoctrine()->getManager();
$em->remove($document);
$em->flush();
$form = $this->createForm(FormType::class);
$form->add('submit', SubmitType::class, ['label' => 'Delete']);
$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('person_document_index', ['person' => $person->getId()]);
}
return $this->render(
'ChillDocStoreBundle:PersonDocument:delete.html.twig',
[
'document' => $document,
'delete_form' => $form->createView(),
'person' => $person,
]
);
}
@ -98,7 +116,6 @@ class DocumentPersonController extends AbstractController
PersonDocumentType::class,
$document,
[
'center' => $document->getCenter(),
'role' => 'CHILL_PERSON_DOCUMENT_UPDATE',
]
);
@ -199,7 +216,6 @@ class DocumentPersonController extends AbstractController
$document->setDate(new DateTime('Now'));
$form = $this->createForm(PersonDocumentType::class, $document, [
'center' => $document->getCenter(),
'role' => 'CHILL_PERSON_DOCUMENT_CREATE',
]);
$form->handleRequest($request);

View File

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

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,6 +25,11 @@
{{ 'Back to the list' | trans }}
</a>
</li>
{% 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 %}
<li class="edit">
<button class="btn btn-edit">{{ 'Edit'|trans }}</button>
</li>

View File

@ -49,12 +49,9 @@
{{ 'Back to the list' | trans }}
</a>
</li>
<li>
{{ m.download_button(document.object, document.title) }}
</li>
{% if chill_document_is_editable(document.object) %}
<li>
{{ document.object|chill_document_edit_button }}
{% 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) %}
@ -63,6 +60,14 @@
class="btn btn-edit" title="{{ 'Edit attributes' | trans }}"></a>
</li>
{% 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) %}
{% if workflows_frame is not empty %}
<li>

View File

@ -44,6 +44,16 @@
</div>
<ul class="item-col record_actions flex-shrink-1">
{% 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) %}
<li>
{{ 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>
</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 %}
<li>
{{ chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) }}
</li>
{% 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) %}
<li>
{{ m.download_button(document.object, document.title) }}
@ -69,11 +84,6 @@
<a href="{{ path('person_document_show', {'person': person.id, 'id': document.id}) }}" class="btn btn-show"></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 %}
{% endif %}
</ul>

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.object, { 'label': 'Document', 'existing': document.object }) }}
<ul class="record_actions">
<ul class="record_actions sticky-form-buttons">
<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 }}
</a>
</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">
<button class="btn btn-edit">{{ 'Edit'|trans }}</button>
</li>
{# {% if is_granted('CHILL_PERSON_DOCUMENT_DELETE', document) %}
<li class="delete">
{{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }}
</li>
{% endif %} #}
</ul>
{{ form_end(form) }}

View File

@ -26,7 +26,17 @@
{% endblock %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('mod_async_upload') }}
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
{{ encore_entry_script_tags('mod_entity_workflow_pick') }}
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('mod_async_upload') }}
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
{{ encore_entry_link_tags('mod_entity_workflow_pick') }}
{% endblock %}
{% block personcontent %}
@ -46,6 +56,8 @@
{{ 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) %}
<ul class="record_actions sticky-form-buttons">
<li class="create">

View File

@ -40,9 +40,9 @@
{{ form_row(form.description) }}
{{ form_row(form.object, { 'label': 'Document', 'existing': document.object }) }}
<ul class="record_actions">
<ul class="record_actions sticky-form-buttons">
<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 }}
</a>
</li>

View File

@ -64,13 +64,9 @@
</a>
</li>
<li>
{{ m.download_button(document.object, document.title) }}
</li>
{% if chill_document_is_editable(document.object) %}
<li>
{{ document.object|chill_document_edit_button }}
{% 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 %}
@ -82,5 +78,15 @@
</li>
{% 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 %}
{# {{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }} #}
{% endblock %}

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
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
File too big: Fichier trop volumineux
Drop your file or click here: Cliquez ici ou faites glissez votre nouveau fichier dans cette zone

View File

@ -27,7 +27,7 @@ interface PhoneNumberHelperInterface
/**
* 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.

View File

@ -17,6 +17,7 @@ use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberType;
use libphonenumber\PhoneNumberUtil;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;
@ -86,9 +87,17 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface
/**
* 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

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

View File

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

View File

@ -13,7 +13,7 @@ namespace Chill\PersonBundle\Controller;
use Chill\PersonBundle\Entity\Person;
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\Security\Authorization\PersonVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

View File

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

View File

@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use libphonenumber\PhoneNumber;
/**
* Person Phones.
@ -51,9 +52,9 @@ class PersonPhone
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)
@ -85,7 +86,7 @@ class PersonPhone
return $this->person;
}
public function getPhonenumber(): string
public function getPhonenumber(): ?PhoneNumber
{
return $this->phonenumber;
}
@ -97,7 +98,8 @@ class PersonPhone
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
@ -115,7 +117,7 @@ class PersonPhone
$this->person = $person;
}
public function setPhonenumber(string $phonenumber): void
public function setPhonenumber(?PhoneNumber $phonenumber): void
{
$this->phonenumber = $phonenumber;
}

View File

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

View File

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

View File

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

View File

@ -32,26 +32,11 @@ class ResidentialAddressRepository extends ServiceEntityRepository
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
{
$date = null === $at ? new DateTimeImmutable('today') : $at;
$qb = $this->createQueryBuilder('ra');
$dateFilter = $qb->expr()->andX(
$qb->expr()->lte('ra.startDate', ':dateIn'),
$qb->expr()->orX(
@ -69,6 +54,17 @@ class ResidentialAddressRepository extends ServiceEntityRepository
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
// */

View File

@ -170,7 +170,9 @@ export default {
console.log('data', payload.data)
body.name = payload.data.name;
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;
makeFetch('PATCH', `/api/1.0/thirdparty/thirdparty/${payload.data.id}.json`, body)

View File

@ -167,6 +167,18 @@
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
</li>
{% endif %}
{% if person.email is not null %}
<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 %}
<li>
<i class="fa fa-li fa-long-arrow-right"></i>

View File

@ -74,6 +74,7 @@ class PersonDocGenNormalizer implements
$data = [
'type' => 'person',
'id' => $person->getId(),
'isNull' => false,
'civility' => $this->normalizer->normalize($person->getCivility(), $format, array_merge($context, ['docgen:expects' => Civility::class])),
'firstName' => $person->getFirstName(),
@ -151,7 +152,7 @@ class PersonDocGenNormalizer implements
$normalizer = new NormalizeNullValueHelper($this->normalizer, 'type', 'person');
$attributes = [
'firstName', 'lastName', 'age', 'altNames', 'text',
'id', 'firstName', 'lastName', 'age', 'altNames', 'text',
'civility' => Civility::class,
'birthdate' => 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\ObjectToPopulateTrait;
use function array_key_exists;
use function count;
use function in_array;
use function is_string;
/**
* Serialize a Person entity.
@ -54,11 +57,11 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
private ResidentialAddressRepository $residentialAddressRepository;
public function __construct(
ChillEntityRenderExtension $render,
ChillEntityRenderExtension $render, /* TODO: replace by PersonRenderInterface, as sthis is the only one required */
PersonRepository $repository,
CenterResolverManagerInterface $centerResolverManager,
ResidentialAddressRepository $residentialAddressRepository,
PhoneNumberHelperInterface $phoneNumberHelper
PhoneNumberHelperInterface $phoneNumberHelper /* TODO maybe not necessayr any more */
) {
$this->render = $render;
$this->repository = $repository;
@ -187,6 +190,10 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
public function normalize($person, $format = null, array $context = [])
{
$groups = $context[AbstractNormalizer::GROUPS] ?? [];
if (is_string($groups)) {
$groups = [$groups];
}
$household = $person->getCurrentHousehold();
$currentResidentialAddresses = $this->residentialAddressRepository->findCurrentResidentialAddressByPerson($person);
@ -207,7 +214,7 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
'gender' => $person->getGender(),
];
if (in_array("minimal", $groups) && 1 === count($groups)) {
if (in_array('minimal', $groups, true) && 1 === count($groups)) {
return $data;
}

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;
private const BLANK = [
'id' => '',
'firstName' => '',
'lastName' => '',
'altNames' => '',

View File

@ -11,7 +11,15 @@ declare(strict_types=1);
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\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\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@ -22,12 +30,27 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
*/
final class PersonJsonNormalizerTest extends KernelTestCase
{
private NormalizerInterface $normalizer;
use ProphecyTrait;
private PersonJsonNormalizer $normalizer;
protected function setUp(): void
{
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()
@ -37,4 +60,24 @@ final class PersonJsonNormalizerTest extends KernelTestCase
$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

@ -541,6 +541,8 @@ docgen:
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 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_designated_subject: Vous êtes référent d'un parcours d'accompagnement

View File

@ -61,9 +61,9 @@
<i class="fa fa-li fa-map-marker"></i>
<address-render-box :address="thirdparty.address" :isMultiline="isMultiline"></address-render-box>
</li>
<li v-if="thirdparty.phonenumber">
<li v-if="thirdparty.telephone">
<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 v-if="thirdparty.email">
<i class="fa fa-li fa-envelope-o"></i>
@ -78,9 +78,9 @@
<i class="fa fa-li fa-map-marker"></i>
<address-render-box :address="thirdparty.address" :isMultiline="isMultiline"></address-render-box>
</li>
<li v-if="thirdparty.phonenumber">
<li v-if="thirdparty.telephone">
<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 v-if="thirdparty.email">
<i class="fa fa-li fa-envelope-o"></i>

View File

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

View File

@ -62,7 +62,7 @@ class ThirdPartyNormalizer implements NormalizerAwareInterface, NormalizerInterf
}, $thirdParty->getTypesAndCategories()),
'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context),
'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(),
'isChild' => $thirdParty->isChild(),
'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context),