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

This commit is contained in:
Julien Fastré 2022-05-27 21:57:19 +02:00
commit e6da1be5f5
55 changed files with 953 additions and 144 deletions

View File

@ -18,3 +18,10 @@ max_line_length = 80
[COMMIT_EDITMSG] [COMMIT_EDITMSG]
max_line_length = 0 max_line_length = 0
<<<<<<< Updated upstream
=======
[*.{js, vue, ts}]
indent_size = 2
indent_style = space
>>>>>>> Stashed changes

View File

@ -12,6 +12,13 @@ and this project adheres to
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [person] add closing motive to closed acc course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/603) * [person] add closing motive to closed acc course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/603)
* [person] household filiation: fetch person info when unfolding person (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/586)
* [admin] repair edit of social action in the admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/601)
* [admin]: add select2 to Goal form type entity fields (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/702)
* [main] allow hide permissions group list menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
* [main] allow hide change user password menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
* [main] filter user jobs by active jobs (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
* [main] add civility to User (entity, migration and form type) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
* [admin] refactorisation of the admin section: reorganisation of the menu, translations, form types, new entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/592) * [admin] refactorisation of the admin section: reorganisation of the menu, translations, form types, new entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/592)
* [admin] add admin section for languages and countries (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/596) * [admin] add admin section for languages and countries (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/596)
* [activity] activity admin: translations + remove label field for comment on admin activity type (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/587) * [activity] activity admin: translations + remove label field for comment on admin activity type (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/587)
@ -33,6 +40,7 @@ and this project adheres to
* [person] add maritalStatusComment to PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/582) * [person] add maritalStatusComment to PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/582)
* Load relationships without gender in french fixtures * Load relationships without gender in french fixtures
* Add command to remove old draft accompanying periods * Add command to remove old draft accompanying periods
* [parcours]: If users assings him/herself as referrer and job is not null. Update parcours job (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/578)
### 2021-04-28 ### 2021-04-28

View File

@ -471,11 +471,6 @@ parameters:
count: 1 count: 1
path: src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php path: src/Bundle/ChillMainBundle/Form/Type/UserPickerType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 1
path: src/Bundle/ChillMainBundle/Form/UserType.php
- -
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#" message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2 count: 2

View File

@ -15,6 +15,7 @@ use Chill\ActivityBundle\Validator\Constraints as ActivityValidator;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface; use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Location;
@ -134,6 +135,12 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
*/ */
private ?Collection $persons = null; private ?Collection $persons = null;
/**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable", columnPrefix="privateComment_")
* @Groups({"docgen:read"})
*/
private PrivateCommentEmbeddable $privateComment;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason") * @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason")
* @Groups({"docgen:read"}) * @Groups({"docgen:read"})
@ -193,6 +200,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
{ {
$this->reasons = new ArrayCollection(); $this->reasons = new ArrayCollection();
$this->comment = new CommentEmbeddable(); $this->comment = new CommentEmbeddable();
$this->privateComment = new PrivateCommentEmbeddable();
$this->persons = new ArrayCollection(); $this->persons = new ArrayCollection();
$this->thirdParties = new ArrayCollection(); $this->thirdParties = new ArrayCollection();
$this->documents = new ArrayCollection(); $this->documents = new ArrayCollection();
@ -400,6 +408,11 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return []; return [];
} }
public function getPrivateComment(): PrivateCommentEmbeddable
{
return $this->privateComment;
}
public function getReasons(): Collection public function getReasons(): Collection
{ {
return $this->reasons; return $this->reasons;
@ -586,6 +599,13 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this; return $this;
} }
public function setPrivateComment(PrivateCommentEmbeddable $privateComment): self
{
$this->privateComment = $privateComment;
return $this;
}
public function setReasons(?ArrayCollection $reasons): self public function setReasons(?ArrayCollection $reasons): self
{ {
$this->reasons = $reasons; $this->reasons = $reasons;

View File

@ -167,6 +167,16 @@ class ActivityType
*/ */
private int $personVisible = self::FIELD_REQUIRED; private int $personVisible = self::FIELD_REQUIRED;
/**
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $privateCommentLabel = '';
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $privateCommentVisible = self::FIELD_OPTIONAL;
/** /**
* @ORM\Column(type="string", nullable=false, options={"default": ""}) * @ORM\Column(type="string", nullable=false, options={"default": ""})
*/ */
@ -416,6 +426,16 @@ class ActivityType
return $this->personVisible; return $this->personVisible;
} }
public function getPrivateCommentLabel(): string
{
return $this->privateCommentLabel;
}
public function getPrivateCommentVisible(): int
{
return $this->privateCommentVisible;
}
public function getReasonsLabel(): string public function getReasonsLabel(): string
{ {
return $this->reasonsLabel; return $this->reasonsLabel;
@ -688,6 +708,20 @@ class ActivityType
return $this; return $this;
} }
public function setPrivateCommentLabel(string $privateCommentLabel): self
{
$this->privateCommentLabel = $privateCommentLabel;
return $this;
}
public function setPrivateCommentVisible(int $privateCommentVisible): self
{
$this->privateCommentVisible = $privateCommentVisible;
return $this;
}
public function setReasonsLabel(string $reasonsLabel): self public function setReasonsLabel(string $reasonsLabel): self
{ {
$this->reasonsLabel = $reasonsLabel; $this->reasonsLabel = $reasonsLabel;

View File

@ -20,6 +20,7 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PrivateCommentType;
use Chill\MainBundle\Form\Type\ScopePickerType; use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Form\Type\UserPickerType; use Chill\MainBundle\Form\Type\UserPickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
@ -251,6 +252,13 @@ class ActivityType extends AbstractType
]); ]);
} }
if ($activityType->isVisible('privateComment')) {
$builder->add('privateComment', PrivateCommentType::class, [
'label' => '' === $activityType->getLabel('privateComment') ? 'private comment' : $activityType->getPrivateCommentLabel(),
'required' => false,
]);
}
if ($activityType->isVisible('persons')) { if ($activityType->isVisible('persons')) {
$builder->add('persons', HiddenType::class); $builder->add('persons', HiddenType::class);
$builder->get('persons') $builder->get('persons')

View File

@ -57,7 +57,7 @@ class ActivityTypeType extends AbstractType
$fields = [ $fields = [
'persons', 'user', 'date', 'location', 'persons', 'persons', 'user', 'date', 'location', 'persons',
'thirdParties', 'durationTime', 'travelTime', 'attendee', 'thirdParties', 'durationTime', 'travelTime', 'attendee',
'reasons', 'sentReceived', 'documents', 'reasons', 'comment', 'privateComment', 'sentReceived', 'documents',
'emergency', 'socialIssues', 'socialActions', 'users', 'emergency', 'socialIssues', 'socialActions', 'users',
]; ];

View File

@ -83,6 +83,10 @@
{{ form_row(edit_form.comment) }} {{ form_row(edit_form.comment) }}
{% endif %} {% endif %}
{%- if edit_form.privateComment is defined -%}
{{ form_row(edit_form.privateComment) }}
{% endif %}
{%- if edit_form.attendee is defined -%} {%- if edit_form.attendee is defined -%}
{{ form_row(edit_form.attendee) }} {{ form_row(edit_form.attendee) }}
{% endif %} {% endif %}

View File

@ -81,10 +81,13 @@
{% endif %} {% endif %}
{%- if form.comment is defined -%} {%- if form.comment is defined -%}
{# TODO .. public and private #}
{{ form_row(form.comment) }} {{ form_row(form.comment) }}
{% endif %} {% endif %}
{%- if form.privateComment is defined -%}
{{ form_row(form.privateComment) }}
{% endif %}
{%- if form.attendee is defined -%} {%- if form.attendee is defined -%}
{{ form_row(form.attendee) }} {{ form_row(form.attendee) }}
{% endif %} {% endif %}

View File

@ -1,4 +1,5 @@
{%- set t = entity.type -%} {%- set t = entity.type -%}
{% set userId = app.user.id %}
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%} {%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
<h1>{{ "Activity"|trans }}</h1> <h1>{{ "Activity"|trans }}</h1>
@ -146,6 +147,21 @@
</dd> </dd>
{% endif %} {% endif %}
{% if t.privateCommentVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) and entity.privateComment.hasCommentForUser(app.user) %}
{% if t.privateCommentLabel is not empty %}
<dt class="inline">{{ t.privateCommentLabel }}</dt>
{% else %}
<dt class="inline">{{ 'Private comment'|trans }}</dt>
{% endif %}
<dd>
<section class="chill-entity entity-comment-embeddable">
<blockquote class="chill-user-quote private-quote">
{{ entity.privateComment.comments[userId] }}
</blockquote>
</section>
</dd>
{% endif %}
{% if t.documentsVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %} {% if t.documentsVisible and is_granted('CHILL_ACTIVITY_SEE_DETAILS', entity) %}
<dt class="inline">{{ 'Documents'|trans }}</dt> <dt class="inline">{{ 'Documents'|trans }}</dt>
<dd> <dd>

View File

@ -0,0 +1,35 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Activity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220425133027 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE activitytype DROP privateCommentLabel');
$this->addSql('ALTER TABLE activitytype DROP privateCommentVisible');
}
public function getDescription(): string
{
return 'add private comment option to activity types';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE activitytype ADD privateCommentLabel VARCHAR(255) DEFAULT \'\' NOT NULL');
$this->addSql('ALTER TABLE activitytype ADD privateCommentVisible SMALLINT DEFAULT 1 NOT NULL');
}
}

View File

@ -0,0 +1,33 @@
<?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\Activity;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220527124438 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_work DROP privateComment_comments');
}
public function getDescription(): string
{
return 'add private comment to activity';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE activity ADD privateComment_comments JSON DEFAULT \'{}\'');
}
}

View File

@ -44,6 +44,7 @@ Received: Recevoir
by: 'Par ' by: 'Par '
location: Lieu location: Lieu
Reasons: Sujets Reasons: Sujets
Private comment: Commentaire privé
#forms #forms
@ -175,6 +176,8 @@ Reasons visible: Visibilité du champ Sujet
Reasons label: Libellé du champ Sujet Reasons label: Libellé du champ Sujet
Comment visible: Visibilité du champ Commentaire Comment visible: Visibilité du champ Commentaire
Comment label: Libellé du champ Commentaire Comment label: Libellé du champ Commentaire
Private comment visible: Visibilité du champ Commentaire Privé
Private comment label: Libellé du champ Commentaire Privé
Emergency visible: Visibilité du champ Urgent Emergency visible: Visibilité du champ Urgent
Emergency label: Libellé du champ Urgent Emergency label: Libellé du champ Urgent
Accompanying period visible: Visibilité du champ Période d'accompagnement Accompanying period visible: Visibilité du champ Période d'accompagnement

View File

@ -14,6 +14,7 @@ namespace Chill\CalendarBundle\Entity;
use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\Activity;
use Chill\CalendarBundle\Repository\CalendarRepository; use Chill\CalendarBundle\Repository\CalendarRepository;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
@ -115,6 +116,12 @@ class Calendar
*/ */
private Collection $persons; private Collection $persons;
/**
* @ORM\Embedded(class=PrivateCommentEmbeddable::class, columnPrefix="privateComment_")
* @Serializer\Groups({"calendar:read"})
*/
private PrivateCommentEmbeddable $privateComment;
/** /**
* @ORM\ManyToMany( * @ORM\ManyToMany(
* targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty", * targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty",
@ -151,6 +158,7 @@ class Calendar
public function __construct() public function __construct()
{ {
$this->comment = new CommentEmbeddable(); $this->comment = new CommentEmbeddable();
$this->privateComment = new PrivateCommentEmbeddable();
$this->persons = new ArrayCollection(); $this->persons = new ArrayCollection();
$this->professionals = new ArrayCollection(); $this->professionals = new ArrayCollection();
$this->invites = new ArrayCollection(); $this->invites = new ArrayCollection();
@ -278,6 +286,11 @@ class Calendar
return []; return [];
} }
public function getPrivateComment(): PrivateCommentEmbeddable
{
return $this->privateComment;
}
/** /**
* @return Collection|ThirdParty[] * @return Collection|ThirdParty[]
*/ */
@ -407,6 +420,13 @@ class Calendar
return $this; return $this;
} }
public function setPrivateComment(PrivateCommentEmbeddable $privateComment): self
{
$this->privateComment = $privateComment;
return $this;
}
public function setSendSMS(?bool $sendSMS): self public function setSendSMS(?bool $sendSMS): self
{ {
$this->sendSMS = $sendSMS; $this->sendSMS = $sendSMS;

View File

@ -18,6 +18,7 @@ use Chill\CalendarBundle\Entity\Invite;
use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PrivateCommentType;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdParty;
@ -51,6 +52,10 @@ class CalendarType extends AbstractType
->add('comment', CommentType::class, [ ->add('comment', CommentType::class, [
'required' => false, 'required' => false,
]) ])
->add('privateComment', PrivateCommentType::class, [
'required' => false,
'label' => 'private comment',
])
// ->add('cancelReason', EntityType::class, [ // ->add('cancelReason', EntityType::class, [
// 'required' => false, // 'required' => false,
// 'class' => CancelReason::class, // 'class' => CancelReason::class,

View File

@ -47,6 +47,10 @@
{{ form_row(form.comment) }} {{ form_row(form.comment) }}
{% endif %} {% endif %}
{%- if form.privateComment is defined -%}
{{ form_row(form.privateComment) }}
{% endif %}
{%- if form.sendSMS is defined -%} {%- if form.sendSMS is defined -%}
{{ form_row(form.sendSMS) }} {{ form_row(form.sendSMS) }}
{% endif %} {% endif %}

View File

@ -43,6 +43,10 @@
{{ form_row(form.comment) }} {{ form_row(form.comment) }}
{% endif %} {% endif %}
{%- if form.privateComment is defined -%}
{{ form_row(form.privateComment) }}
{% endif %}
{%- if form.sendSMS is defined -%} {%- if form.sendSMS is defined -%}
{{ form_row(form.sendSMS) }} {{ form_row(form.sendSMS) }}
{% endif %} {% endif %}

View File

@ -0,0 +1,33 @@
<?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\Calendar;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220527124558 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_calendar.calendar DROP privateComment_comments');
}
public function getDescription(): string
{
return 'add private comment to calendar';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_calendar.calendar ADD privateComment_comments JSON DEFAULT NULL');
}
}

View File

@ -26,6 +26,7 @@ The calendar item has been successfully removed.: Le rendez-vous a été supprim
From the day: Du From the day: Du
to the day: au to the day: au
Transform to activity: Transformer en échange Transform to activity: Transformer en échange
canceledBy: supprimé par canceledBy: supprimé par
Canceled by: supprimé par Canceled by: supprimé par
Calendar configuration: Gestion des rendez-vous Calendar configuration: Gestion des rendez-vous

View File

@ -23,6 +23,7 @@ use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper; use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use RuntimeException; use RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form; use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
@ -37,6 +38,8 @@ class UserController extends CRUDController
{ {
public const FORM_GROUP_CENTER_COMPOSED = 'composed_groupcenter'; public const FORM_GROUP_CENTER_COMPOSED = 'composed_groupcenter';
protected ParameterBagInterface $parameterBag;
private LoggerInterface $logger; private LoggerInterface $logger;
private UserPasswordEncoderInterface $passwordEncoder; private UserPasswordEncoderInterface $passwordEncoder;
@ -49,12 +52,14 @@ class UserController extends CRUDController
LoggerInterface $chillLogger, LoggerInterface $chillLogger,
ValidatorInterface $validator, ValidatorInterface $validator,
UserPasswordEncoderInterface $passwordEncoder, UserPasswordEncoderInterface $passwordEncoder,
UserRepository $userRepository UserRepository $userRepository,
ParameterBagInterface $parameterBag
) { ) {
$this->logger = $chillLogger; $this->logger = $chillLogger;
$this->userRepository = $userRepository; $this->userRepository = $userRepository;
$this->validator = $validator; $this->validator = $validator;
$this->passwordEncoder = $passwordEncoder; $this->passwordEncoder = $passwordEncoder;
$this->parameterBag = $parameterBag;
} }
/** /**
@ -104,6 +109,7 @@ class UserController extends CRUDController
return $this->render('@ChillMain/User/edit.html.twig', [ return $this->render('@ChillMain/User/edit.html.twig', [
'entity' => $user, 'entity' => $user,
'access_permissions_group_list' => $this->parameterBag->get('chill_main.access_permissions_group_list'),
'edit_form' => $this->createEditForm($user)->createView(), 'edit_form' => $this->createEditForm($user)->createView(),
'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user, $request)->createView(), 'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user, $request)->createView(),
'delete_groupcenter_form' => array_map( 'delete_groupcenter_form' => array_map(
@ -153,6 +159,73 @@ class UserController extends CRUDController
return $this->redirect($this->generateUrl('chill_crud_admin_user_edit', ['id' => $uid])); return $this->redirect($this->generateUrl('chill_crud_admin_user_edit', ['id' => $uid]));
} }
public function edit(Request $request, $id): Response
{
$action = 'edit';
$entity = $this->getEntity($action, $id, $request);
if (null === $entity) {
throw $this->createNotFoundException(
sprintf(
'The %s with id %s is not found',
$this->getCrudName(),
$id
)
);
}
$response = $this->checkACL($action, $entity);
if ($response instanceof Response) {
return $response;
}
$response = $this->onPostCheckACL($action, $request, $entity);
if ($response instanceof Response) {
return $response;
}
$form = $this->createFormFor($action, $entity);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->onFormValid($action, $entity, $form, $request);
$em = $this->getDoctrine()->getManager();
$this->onPreFlush($action, $entity, $form, $request);
$em->flush();
$this->onPostFlush($action, $entity, $form, $request);
$this->addFlash('success', $this->generateFormSuccessMessage($action, $entity));
$result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request);
if ($result instanceof Response) {
return $result;
}
return $this->redirectToRoute('chill_crud_' . $this->getCrudName() . '_index');
}
if ($form->isSubmitted()) {
$this->addFlash('error', $this->generateFormErrorMessage($action, $form));
}
$defaultTemplateParameters = [
'form' => $form->createView(),
'entity' => $entity,
'crud_name' => $this->getCrudName(),
'access_permissions_group_list' => $this->parameterBag->get('chill_main.access_permissions_group_list'),
];
return $this->render(
$this->getTemplateFor($action, $entity, $request),
$this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters)
);
}
/** /**
* Displays a form to edit the user current location. * Displays a form to edit the user current location.
* *
@ -271,6 +344,11 @@ class UserController extends CRUDController
), ),
] ]
); );
} elseif ('index' === $action) {
return array_merge(
['allow_change_password' => $this->parameterBag->get('chill_main.access_user_change_password')],
$defaultTemplateParameters
);
} }
// default behaviour // default behaviour
@ -307,7 +385,7 @@ class UserController extends CRUDController
protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request)
{ {
// for "new", encode the password // for "new", encode the password
if ('new' === $action) { if ('new' === $action && $this->parameterBag->get('chill_main.access_user_change_password')) {
$entity->setPassword($this->passwordEncoder $entity->setPassword($this->passwordEncoder
->encodePassword($entity, $form['plainPassword']->getData())); ->encodePassword($entity, $form['plainPassword']->getData()));
} }

View File

@ -129,6 +129,16 @@ class ChillMainExtension extends Extension implements
$config['access_global_history'] $config['access_global_history']
); );
$container->setParameter(
'chill_main.access_user_change_password',
$config['access_user_change_password']
);
$container->setParameter(
'chill_main.access_permissions_group_list',
$config['access_permissions_group_list']
);
$container->setParameter( $container->setParameter(
'chill_main.routing.resources', 'chill_main.routing.resources',
$config['routing']['resources'] $config['routing']['resources']

View File

@ -116,6 +116,12 @@ class Configuration implements ConfigurationInterface
->booleanNode('access_global_history') ->booleanNode('access_global_history')
->defaultTrue() ->defaultTrue()
->end() ->end()
->booleanNode('access_user_change_password')
->defaultTrue()
->end()
->booleanNode('access_permissions_group_list')
->defaultTrue()
->end()
->arrayNode('redis') ->arrayNode('redis')
->children() ->children()
->scalarNode('host') ->scalarNode('host')

View File

@ -0,0 +1,70 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Entity\Embeddable;
use Chill\MainBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;
use function array_key_exists;
/**
* @ORM\Embeddable
*/
class PrivateCommentEmbeddable
{
/**
* @ORM\Column(type="json", nullable=false, options={"default": "{}"})
*
* @var array<int, string>
*/
private array $comments = [];
public function getCommentForUser(User $user): string
{
return $this->comments[$user->getId()] ?? '';
}
public function getComments(): ?array
{
return $this->comments;
}
public function hasCommentForUser(User $user): bool
{
return array_key_exists($user->getId(), $this->comments)
&& '' !== $this->comments[$user->getId()];
}
public function merge(PrivateCommentEmbeddable $newComment): self
{
$currentComments = null === $this->getComments() ? [] : $this->getComments();
$mergedComments = $newComment->getComments() + $currentComments;
$this->setComments($mergedComments);
return $this;
}
public function setCommentForUser(User $user, string $content): self
{
$this->comments[$user->getId()] = trim($content);
return $this;
}
public function setComments($comments)
{
$this->comments = $comments;
return $this;
}
}

View File

@ -47,6 +47,11 @@ class User implements AdvancedUserInterface
*/ */
private array $attributes = []; private array $attributes = [];
/**
* @ORM\ManyToOne(targetEntity=Civility::class)
*/
private ?Civility $civility = null;
/** /**
* @ORM\ManyToOne(targetEntity=Location::class) * @ORM\ManyToOne(targetEntity=Location::class)
*/ */
@ -184,6 +189,11 @@ class User implements AdvancedUserInterface
return $this->attributes; return $this->attributes;
} }
public function getCivility(): ?Civility
{
return $this->civility;
}
public function getCurrentLocation(): ?Location public function getCurrentLocation(): ?Location
{ {
return $this->currentLocation; return $this->currentLocation;
@ -363,6 +373,13 @@ class User implements AdvancedUserInterface
return $this; return $this;
} }
public function setCivility(?Civility $civility): User
{
$this->civility = $civility;
return $this;
}
public function setCurrentLocation(?Location $currentLocation): User public function setCurrentLocation(?Location $currentLocation): User
{ {
$this->currentLocation = $currentLocation; $this->currentLocation = $currentLocation;

View File

@ -0,0 +1,50 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Form\DataMapper;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Security\Core\Security;
final class PrivateCommentDataMapper extends AbstractType implements DataMapperInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function mapDataToForms($viewData, $forms)
{
if (null === $viewData) {
return null;
}
if (!$viewData instanceof PrivateCommentEmbeddable) {
throw new UnexpectedTypeException($viewData, PrivateCommentEmbeddable::class);
}
$forms = iterator_to_array($forms);
$forms['comments']->setData($viewData->getCommentForUser($this->security->getUser()));
}
public function mapFormsToData($forms, &$viewData)
{
$forms = iterator_to_array($forms);
$viewData->setCommentForUser($this->security->getUser(), $forms['comments']->getData());
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Form\Type;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
use Chill\MainBundle\Form\DataMapper\PrivateCommentDataMapper;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class PrivateCommentType extends AbstractType
{
protected PrivateCommentDataMapper $dataMapper;
protected UserInterface $user;
public function __construct(TokenStorageInterface $tokenStorage, PrivateCommentDataMapper $dataMapper)
{
$this->user = $tokenStorage->getToken()->getUser();
$this->dataMapper = $dataMapper;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('comments', ChillTextareaType::class, [
'disable_editor' => $options['disable_editor'],
'label' => false,
])
->setDataMapper($this->dataMapper);
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['hideLabel'] = true;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefined('disable_editor')
->setAllowedTypes('disable_editor', 'bool')
->setDefaults([
'data_class' => PrivateCommentEmbeddable::class,
'disable_editor' => false,
]);
}
}

View File

@ -15,9 +15,11 @@ use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Form\Type\PickCivilityType;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\EmailType;
@ -32,11 +34,16 @@ use Symfony\Component\Validator\Constraints\Regex;
class UserType extends AbstractType class UserType extends AbstractType
{ {
protected ParameterBagInterface $parameterBag;
private TranslatableStringHelper $translatableStringHelper; private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper) public function __construct(
{ TranslatableStringHelper $translatableStringHelper,
ParameterBagInterface $parameterBag
) {
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
$this->parameterBag = $parameterBag;
} }
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
@ -47,6 +54,11 @@ class UserType extends AbstractType
'required' => true, 'required' => true,
]) ])
->add('label', TextType::class) ->add('label', TextType::class)
->add('civility', PickCivilityType::class, [
'required' => false,
'label' => 'Civility',
'placeholder' => 'choose civility',
])
->add('mainCenter', EntityType::class, [ ->add('mainCenter', EntityType::class, [
'label' => 'Main center', 'label' => 'Main center',
'required' => false, 'required' => false,
@ -76,6 +88,12 @@ class UserType extends AbstractType
'choice_label' => function (UserJob $c) { 'choice_label' => function (UserJob $c) {
return $this->translatableStringHelper->localize($c->getLabel()); return $this->translatableStringHelper->localize($c->getLabel());
}, },
'query_builder' => static function (EntityRepository $er) {
$qb = $er->createQueryBuilder('uj');
$qb->where('uj.active = TRUE');
return $qb;
},
]) ])
->add('mainLocation', EntityType::class, [ ->add('mainLocation', EntityType::class, [
'label' => 'Main location', 'label' => 'Main location',
@ -94,7 +112,8 @@ class UserType extends AbstractType
}, },
]); ]);
if ($options['is_creation']) { // @phpstan-ignore-next-line
if ($options['is_creation'] && $this->parameterBag->get('chill_main.access_user_change_password')) {
$builder->add('plainPassword', RepeatedType::class, [ $builder->add('plainPassword', RepeatedType::class, [
'mapped' => false, 'mapped' => false,
'type' => PasswordType::class, 'type' => PasswordType::class,

View File

@ -298,6 +298,10 @@ table.table-bordered {
} }
} }
.private-quote {
border-left: 10px solid $pink;
}
/// meta-data /// meta-data
div.createdBy, div.createdBy,
div.updatedBy, div.updatedBy,

View File

@ -198,6 +198,23 @@
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
{% block private_comment_row %}
{{ form_label(form) }}
{{ form_row(form) }}
{% endblock %}
{% block private_comment_widget %}
{% for entry in form %}
{{ form_widget(entry) }}
{% endfor %}
{% endblock %}
{% block comment_row %}
{{ form_label(form) }}
{{ form_row(form) }}
{% endblock %}
{% block comment_widget %} {% block comment_widget %}
{% for entry in form %} {% for entry in form %}
{{ form_widget(entry) }} {{ form_widget(entry) }}

View File

@ -3,6 +3,7 @@
{% block admin_content -%} {% block admin_content -%}
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %} {% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% block crud_content_after_form %} {% block crud_content_after_form %}
{% if access_permissions_group_list %}
<h2>{{ 'Permissions granted'|trans }}</h2> <h2>{{ 'Permissions granted'|trans }}</h2>
{% if entity.groupcenters|length > 0 %} {% if entity.groupcenters|length > 0 %}
@ -49,8 +50,9 @@
{{ form_row(add_groupcenter_form.submit, { 'attr' : { 'class': 'btn btn-chill-green' } } ) }} {{ form_row(add_groupcenter_form.submit, { 'attr' : { 'class': 'btn btn-chill-green' } } ) }}
{{ form_end(add_groupcenter_form) }} {{ form_end(add_groupcenter_form) }}
{% endif %}
{% endblock %} {% endblock %}
{% block content_form_actions_save_and_show %}{% endblock %} {% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %} {% endembed %}
{% endblock %} {% endblock %}

View File

@ -11,6 +11,11 @@
<div class="item-bloc"> <div class="item-bloc">
<div class="item-row"> <div class="item-row">
<div class="item-col"> <div class="item-col">
{% if entity.civility is not null %}
{% if entity.civility.name|length > 0 %}
{{ entity.civility.name|first }}
{% endif %}
{% endif %}
{{ entity.label }} {{ entity.label }}
{% if entity.isEnabled %} {% if entity.isEnabled %}
<i class="fa fa-check chill-green"></i> <i class="fa fa-check chill-green"></i>
@ -45,9 +50,13 @@
<li> <li>
<a class="btn btn-edit" href="{{ path('chill_crud_admin_user_edit', { 'id': entity.id }) }}"></a> <a class="btn btn-edit" href="{{ path('chill_crud_admin_user_edit', { 'id': entity.id }) }}"></a>
</li> </li>
{% if allow_change_password is same as(true) %}
<li> <li>
<a class="btn btn-chill-red" href="{{ path('admin_user_edit_password', { 'id' : entity.id }) }}" title="{{ 'Edit password'|trans|e('html_attr') }}"><i class="fa fa-ellipsis-h"></i></a> <a class="btn btn-chill-red" href="{{ path('admin_user_edit_password', { 'id' : entity.id }) }}" title="{{ 'Edit password'|trans|e('html_attr') }}"><i class="fa fa-ellipsis-h"></i></a>
</li> </li>
{% endif %}
{% if is_granted('ROLE_ALLOWED_TO_SWITCH') %} {% if is_granted('ROLE_ALLOWED_TO_SWITCH') %}
<li> <li>
<a class="btn btn-chill-blue" href="{{ path('chill_main_homepage', {'_switch_user': entity.username }) }}" title="{{ "Impersonate"|trans|e('html_attr') }}"><i class="fa fa-user-secret"></i></a> <a class="btn btn-chill-blue" href="{{ path('chill_main_homepage', {'_switch_user': entity.username }) }}" title="{{ "Impersonate"|trans|e('html_attr') }}"><i class="fa fa-user-secret"></i></a>

View File

@ -13,6 +13,7 @@ namespace Chill\MainBundle\Routing\MenuBuilder;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem; use Knp\Menu\MenuItem;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class AdminUserMenuBuilder implements LocalMenuBuilderInterface class AdminUserMenuBuilder implements LocalMenuBuilderInterface
@ -22,9 +23,14 @@ class AdminUserMenuBuilder implements LocalMenuBuilderInterface
*/ */
protected $authorizationChecker; protected $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker) protected ParameterBagInterface $parameterBag;
{
public function __construct(
AuthorizationCheckerInterface $authorizationChecker,
ParameterBagInterface $parameterBag
) {
$this->authorizationChecker = $authorizationChecker; $this->authorizationChecker = $authorizationChecker;
$this->parameterBag = $parameterBag;
} }
public function buildMenu($menuId, MenuItem $menu, array $parameters) public function buildMenu($menuId, MenuItem $menu, array $parameters)
@ -51,9 +57,11 @@ class AdminUserMenuBuilder implements LocalMenuBuilderInterface
'route' => 'admin_scope', 'route' => 'admin_scope',
])->setExtras(['order' => 1020]); ])->setExtras(['order' => 1020]);
if ($this->parameterBag->get('chill_main.access_permissions_group_list')) {
$menu->addChild('Permissions group list', [ $menu->addChild('Permissions group list', [
'route' => 'admin_permissionsgroup', 'route' => 'admin_permissionsgroup',
])->setExtras(['order' => 1030]); ])->setExtras(['order' => 1030]);
}
$menu->addChild('crud.admin_user.index.title', [ $menu->addChild('crud.admin_user.index.title', [
'route' => 'chill_crud_admin_user_index', 'route' => 'chill_crud_admin_user_index',

View File

@ -15,11 +15,14 @@ use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Notification\Counter\NotificationByUserCounter; use Chill\MainBundle\Notification\Counter\NotificationByUserCounter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\MainBundle\Workflow\Counter\WorkflowByUserCounter; use Chill\MainBundle\Workflow\Counter\WorkflowByUserCounter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class UserMenuBuilder implements LocalMenuBuilderInterface class UserMenuBuilder implements LocalMenuBuilderInterface
{ {
protected ParameterBagInterface $parameterBag;
private NotificationByUserCounter $notificationByUserCounter; private NotificationByUserCounter $notificationByUserCounter;
private Security $security; private Security $security;
@ -32,12 +35,14 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
NotificationByUserCounter $notificationByUserCounter, NotificationByUserCounter $notificationByUserCounter,
WorkflowByUserCounter $workflowByUserCounter, WorkflowByUserCounter $workflowByUserCounter,
Security $security, Security $security,
TranslatorInterface $translator TranslatorInterface $translator,
ParameterBagInterface $parameterBag
) { ) {
$this->notificationByUserCounter = $notificationByUserCounter; $this->notificationByUserCounter = $notificationByUserCounter;
$this->workflowByUserCounter = $workflowByUserCounter; $this->workflowByUserCounter = $workflowByUserCounter;
$this->security = $security; $this->security = $security;
$this->translator = $translator; $this->translator = $translator;
$this->parameterBag = $parameterBag;
} }
public function buildMenu($menuId, \Knp\Menu\MenuItem $menu, array $parameters) public function buildMenu($menuId, \Knp\Menu\MenuItem $menu, array $parameters)
@ -85,6 +90,7 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
'order' => 700, 'order' => 700,
]); ]);
if ($this->parameterBag->get('chill_main.access_user_change_password')) {
$menu $menu
->addChild( ->addChild(
'Change password', 'Change password',
@ -94,6 +100,7 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
'order' => 99999999998, 'order' => 99999999998,
]); ]);
} }
}
$menu $menu
->addChild( ->addChild(

View File

@ -0,0 +1,55 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\MainBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class PrivateCommentEmbeddableNormalizer implements NormalizerInterface, DenormalizerInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function denormalize($data, string $type, ?string $format = null, array $context = []): PrivateCommentEmbeddable
{
$comment = new PrivateCommentEmbeddable();
if (null === $data) {
return $comment;
}
$comment->setCommentForUser($this->security->getUser(), $data);
return $comment;
}
public function normalize($object, $format = null, array $context = []): string
{
return $object->getCommentForUser($this->security->getUser());
}
public function supportsDenormalization($data, string $type, ?string $format = null): bool
{
return PrivateCommentEmbeddable::class === $type;
}
public function supportsNormalization($data, ?string $format = null): bool
{
return $data instanceof PrivateCommentEmbeddable;
}
}

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Serializer\Normalizer; namespace Chill\MainBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
@ -60,9 +61,14 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
$context, $context,
['docgen:expects' => Location::class, 'groups' => 'docgen:read'] ['docgen:expects' => Location::class, 'groups' => 'docgen:read']
); );
$civilityContext = array_merge(
$context,
['docgen:expects' => Civility::class, 'groups' => 'docgen:read']
);
if (null === $user && 'docgen' === $format) { if (null === $user && 'docgen' === $format) {
return array_merge(self::NULL_USER, [ return array_merge(self::NULL_USER, [
'civility' => $this->normalizer->normalize(null, $format, $civilityContext),
'user_job' => $this->normalizer->normalize(null, $format, $userJobContext), 'user_job' => $this->normalizer->normalize(null, $format, $userJobContext),
'main_center' => $this->normalizer->normalize(null, $format, $centerContext), 'main_center' => $this->normalizer->normalize(null, $format, $centerContext),
'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext), 'main_scope' => $this->normalizer->normalize(null, $format, $scopeContext),
@ -84,6 +90,7 @@ class UserNormalizer implements ContextAwareNormalizerInterface, NormalizerAware
]; ];
if ('docgen' === $format) { if ('docgen' === $format) {
$data['civility'] = $this->normalizer->normalize($user->getCivility(), $format, $civilityContext);
$data['current_location'] = $this->normalizer->normalize($user->getCurrentLocation(), $format, $locationContext); $data['current_location'] = $this->normalizer->normalize($user->getCurrentLocation(), $format, $locationContext);
$data['main_location'] = $this->normalizer->normalize($user->getMainLocation(), $format, $locationContext); $data['main_location'] = $this->normalizer->normalize($user->getMainLocation(), $format, $locationContext);
} }

View File

@ -143,3 +143,7 @@ services:
Chill\MainBundle\Form\Type\LocationFormType: ~ Chill\MainBundle\Form\Type\LocationFormType: ~
Chill\MainBundle\Form\WorkflowStepType: ~ Chill\MainBundle\Form\WorkflowStepType: ~
Chill\MainBundle\Form\DataMapper\PrivateCommentDataMapper:
autowire: true
autoconfigure: true

View File

@ -10,4 +10,9 @@ services:
Chill\MainBundle\Serializer\Normalizer\DoctrineExistingEntityNormalizer: Chill\MainBundle\Serializer\Normalizer\DoctrineExistingEntityNormalizer:
tags: tags:
- { name: 'serializer.normalizer', priority: 8 } - { name: 'serializer.normalizer', priority: 8 }
Chill\MainBundle\Serializer\Normalizer\PrivateCommentEmbeddableNormalizer:
autowire: true
autoconfigure: true
tags:
- { name: 'serializer.normalizer', priority: 64 }

View File

@ -1,5 +1,12 @@
<?php <?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); declare(strict_types=1);
namespace Chill\Migrations\Main; namespace Chill\Migrations\Main;
@ -9,6 +16,10 @@ use Doctrine\Migrations\AbstractMigration;
final class Version20220513151853 extends AbstractMigration final class Version20220513151853 extends AbstractMigration
{ {
public function down(Schema $schema): void
{
}
public function getDescription(): string public function getDescription(): string
{ {
return 'set default on attributes'; return 'set default on attributes';
@ -18,9 +29,4 @@ final class Version20220513151853 extends AbstractMigration
{ {
$this->addSql('update users set attributes = \'[]\'::json where attributes IS NULL'); $this->addSql('update users set attributes = \'[]\'::json where attributes IS NULL');
} }
public function down(Schema $schema): void
{
}
} }

View File

@ -0,0 +1,40 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add civility to User.
*/
final class Version20220516085659 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E923D6A298');
$this->addSql('DROP INDEX IDX_1483A5E923D6A298');
$this->addSql('ALTER TABLE users DROP civility_id');
}
public function getDescription(): string
{
return 'Add civility to User';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE users ADD civility_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE users ADD CONSTRAINT FK_1483A5E923D6A298 FOREIGN KEY (civility_id) REFERENCES chill_main_civility (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_1483A5E923D6A298 ON users (civility_id)');
}
}

View File

@ -65,6 +65,7 @@ Read more: Lire la suite
# comment embeddable # comment embeddable
No comment associated: Aucun commentaire No comment associated: Aucun commentaire
private comment: Notes privées
#pagination #pagination
Previous: Précédent Previous: Précédent

View File

@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface; use Chill\PersonBundle\AccompanyingPeriod\SocialIssueConsistency\AccompanyingPeriodLinkedWithSocialIssuesEntityInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
@ -143,6 +144,12 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
*/ */
private Collection $persons; private Collection $persons;
/**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable", columnPrefix="privateComment_")
* @Serializer\Groups({"read", "accompanying_period_work:edit"})
*/
private PrivateCommentEmbeddable $privateComment;
/** /**
* @ORM\ManyToMany(targetEntity=User::class) * @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_referrer") * @ORM\JoinTable(name="chill_person_accompanying_period_work_referrer")
@ -202,6 +209,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
public function __construct() public function __construct()
{ {
$this->goals = new ArrayCollection(); $this->goals = new ArrayCollection();
$this->privateComment = new PrivateCommentEmbeddable();
$this->results = new ArrayCollection(); $this->results = new ArrayCollection();
$this->thirdParties = new ArrayCollection(); $this->thirdParties = new ArrayCollection();
$this->persons = new ArrayCollection(); $this->persons = new ArrayCollection();
@ -328,6 +336,11 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this->persons; return $this->persons;
} }
public function getPrivateComment(): PrivateCommentEmbeddable
{
return $this->privateComment;
}
/** /**
* @return Collection|User[] * @return Collection|User[]
*/ */
@ -505,6 +518,13 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this; return $this;
} }
public function setPrivateComment(PrivateCommentEmbeddable $privateComment): self
{
$this->privateComment->merge($privateComment);
return $this;
}
public function setSocialAction(?SocialAction $socialAction): self public function setSocialAction(?SocialAction $socialAction): self
{ {
$this->socialAction = $socialAction; $this->socialAction = $socialAction;

View File

@ -46,10 +46,8 @@ class SocialAction
private $desactivationDate; private $desactivationDate;
/** /**
* @ORM\ManyToMany( * @ORM\ManyToMany(targetEntity=Evaluation::class, inversedBy="socialActions")
* targetEntity=Evaluation::class, * @ORM\JoinTable(name="chill_person_social_work_evaluation_action")
* mappedBy="socialActions"
* )
*/ */
private Collection $evaluations; private Collection $evaluations;
@ -110,6 +108,15 @@ class SocialAction
return $this; return $this;
} }
public function addEvaluation(Evaluation $evaluation): self
{
if (!$this->evaluations->contains($evaluation)) {
$this->evaluations[] = $evaluation;
}
return $this;
}
public function addGoal(Goal $goal): self public function addGoal(Goal $goal): self
{ {
if (!$this->goals->contains($goal)) { if (!$this->goals->contains($goal)) {
@ -300,6 +307,13 @@ class SocialAction
return $this; return $this;
} }
public function removeEvaluation(Evaluation $evaluation): self
{
$this->evaluations->removeElement($evaluation);
return $this;
}
public function removeGoal(Goal $goal): self public function removeGoal(Goal $goal): self
{ {
$this->goals->removeElement($goal); $this->goals->removeElement($goal);

View File

@ -43,16 +43,6 @@ class GoalType extends AbstractType
->add('title', TranslatableStringFormType::class, [ ->add('title', TranslatableStringFormType::class, [
'label' => 'Nom', 'label' => 'Nom',
]) ])
->add('socialActions', EntityType::class, [
'class' => SocialAction::class,
'required' => false,
'multiple' => true,
'choice_label' => function (SocialAction $issue) {
return $this->translatableStringHelper->localize($issue->getTitle());
},
])
->add('results', EntityType::class, [ ->add('results', EntityType::class, [
'class' => Result::class, 'class' => Result::class,
'required' => false, 'required' => false,
@ -60,8 +50,8 @@ class GoalType extends AbstractType
'choice_label' => function (Result $r) { 'choice_label' => function (Result $r) {
return $this->translatableStringHelper->localize($r->getTitle()); return $this->translatableStringHelper->localize($r->getTitle());
}, },
'attr' => ['class' => 'select2 '],
]) ])
->add('desactivationDate', ChillDateType::class, [ ->add('desactivationDate', ChillDateType::class, [
'required' => false, 'required' => false,
'label' => 'goal.desactivationDate', 'label' => 'goal.desactivationDate',

View File

@ -253,7 +253,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
state.accompanyingCourse.administrativeLocation = value; state.accompanyingCourse.administrativeLocation = value;
}, },
updateReferrer(state, value) { updateReferrer(state, value) {
//console.log('value', value);
state.accompanyingCourse.user = value; state.accompanyingCourse.user = value;
}, },
updateJob(state, value) { updateJob(state, value) {
@ -775,9 +774,7 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
throw error; throw error;
}) })
}, },
updateReferrer({ commit }, payload) { updateReferrer({ commit, state }, payload) {
console.log('update referrer', payload);
console.log('payload !== null', payload !== null);
const url = `/api/1.0/person/accompanying-course/${id}.json`; const url = `/api/1.0/person/accompanying-course/${id}.json`;
let body = { type: "accompanying_period", user: null }; let body = { type: "accompanying_period", user: null };
@ -788,7 +785,10 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
return makeFetch('PATCH', url, body) return makeFetch('PATCH', url, body)
.then((response) => { .then((response) => {
commit('updateReferrer', response.user); commit('updateReferrer', response.user);
commit('setFilteredReferrersSuggested'); if (null !== payload.user_job && payload.user_job !== state.accompanyingCourse.job) {
this.dispatch('updateJob', payload.user_job);
}
// commit('setFilteredReferrersSuggested'); // this mutation doesn't exist?
}) })
.catch((error) => { .catch((error) => {
commit('catchError', error); commit('catchError', error);
@ -799,6 +799,8 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
const url = `/api/1.0/person/accompanying-course/${id}.json`; const url = `/api/1.0/person/accompanying-course/${id}.json`;
let body = { type: "accompanying_period", job: null }; let body = { type: "accompanying_period", job: null };
console.log('update job', payload);
if (payload !== null) { if (payload !== null) {
body = { type: "accompanying_period", job: { id: payload.id, type: payload.type } }; body = { type: "accompanying_period", job: { id: payload.id, type: payload.type } };
} }

View File

@ -24,6 +24,15 @@
<input v-model="endDate" type="date" class="form-control" /> <input v-model="endDate" type="date" class="form-control" />
</div> </div>
<div id="privateComment" class="action-row">
<label class="col-form-label">{{ $t('private_comment') }}</label>
<ckeditor
v-model="privateComment"
:editor="editor"
tag-name="textarea"
></ckeditor>
</div>
<div id="comment" class="action-row"> <div id="comment" class="action-row">
<label class="col-form-label">{{ $t('comments') }}</label> <label class="col-form-label">{{ $t('comments') }}</label>
<ckeditor <ckeditor
@ -33,6 +42,7 @@
></ckeditor> ></ckeditor>
</div> </div>
<div id="objectives" class="action-row"> <div id="objectives" class="action-row">
<div aria="hidden" class="title"> <div aria="hidden" class="title">
<div><h3>{{ $t('goals_title') }}</h3></div> <div><h3>{{ $t('goals_title') }}</h3></div>
@ -355,7 +365,8 @@ const i18n = {
referrers: "Agents traitants", referrers: "Agents traitants",
no_referrers: "Aucun agent traitant", no_referrers: "Aucun agent traitant",
choose_referrers: "Choisir des agents traitants", choose_referrers: "Choisir des agents traitants",
remove_referrer: "Enlever l'agent" remove_referrer: "Enlever l'agent",
private_comment: "Commentaire privé"
} }
} }
}; };
@ -461,6 +472,14 @@ export default {
this.$store.commit('setNote', v); this.$store.commit('setNote', v);
} }
}, },
privateComment: {
get() {
return this.$store.state.privateComment;
},
set(v) {
this.$store.commit('setPrivateComment', v);
}
},
availableForCheckGoal() { availableForCheckGoal() {
let pickedIds = this.$store.state.goalsPicked.map(g => g.goal.id); let pickedIds = this.$store.state.goalsPicked.map(g => g.goal.id);
@ -573,6 +592,7 @@ div#workEditor {
"title title" "title title"
"startDate endDate" "startDate endDate"
"comment comment" "comment comment"
"privateComment privateComment"
"objectives objectives" "objectives objectives"
"evaluations evaluations" "evaluations evaluations"
"persons persons" "persons persons"
@ -589,6 +609,8 @@ div#workEditor {
grid-area: endDate; } grid-area: endDate; }
#comment { #comment {
grid-area: comment; } grid-area: comment; }
#privateComment {
grid-area: privateComment; }
#objectives { #objectives {
grid-area: objectives; } grid-area: objectives; }
#evaluations { #evaluations {

View File

@ -17,6 +17,7 @@ const store = createStore({
endDate: window.accompanyingCourseWork.endDate !== null ? endDate: window.accompanyingCourseWork.endDate !== null ?
dateToISO(new Date(window.accompanyingCourseWork.endDate.datetime)) : null, dateToISO(new Date(window.accompanyingCourseWork.endDate.datetime)) : null,
note: window.accompanyingCourseWork.note, note: window.accompanyingCourseWork.note,
privateComment: window.accompanyingCourseWork.privateComment,
goalsPicked: window.accompanyingCourseWork.goals, goalsPicked: window.accompanyingCourseWork.goals,
goalsForAction: [], goalsForAction: [],
resultsPicked: window.accompanyingCourseWork.results, resultsPicked: window.accompanyingCourseWork.results,
@ -79,6 +80,7 @@ const store = createStore({
datetime: datetimeToISO(ISOToDate(state.endDate)) datetime: datetimeToISO(ISOToDate(state.endDate))
}, },
note: state.note, note: state.note,
privateComment: state.privateComment,
persons: state.personsPicked.map(p => ({id: p.id, type: p.type})), persons: state.personsPicked.map(p => ({id: p.id, type: p.type})),
handlingThierParty: state.handlingThirdParty === null ? null : { handlingThierParty: state.handlingThirdParty === null ? null : {
id: state.handlingThirdParty.id, id: state.handlingThirdParty.id,
@ -297,6 +299,9 @@ const store = createStore({
setNote(state, note) { setNote(state, note) {
state.note = note; state.note = note;
}, },
setPrivateComment(state, privateComment) {
state.privateComment = privateComment;
},
setHandlingThirdParty(state, thirdParty) { setHandlingThirdParty(state, thirdParty) {
state.handlingThirdParty = thirdParty; state.handlingThirdParty = thirdParty;
}, },
@ -454,6 +459,8 @@ const store = createStore({
; ;
commit('setIsPosting', true); commit('setIsPosting', true);
console.log('payload', payload);
return makeFetch('PUT', url, payload) return makeFetch('PUT', url, payload)
.then(data => { .then(data => {
if (typeof(callback) !== 'undefined') { if (typeof(callback) !== 'undefined') {

View File

@ -305,7 +305,8 @@ export default {
if (this.listenPersonFlag === 'normal') { if (this.listenPersonFlag === 'normal') {
if (person.folded === true) { if (person.folded === true) {
//console.log(' @@> expand mode event') //console.log(' @@> expand mode event')
this.$store.commit('unfoldPerson', person) this.$store.commit('unfoldPerson', person);
this.$store.dispatch('fetchInfoForPerson', person);
} }
} else { } else {
//console.log(' @@> create link mode event') //console.log(' @@> create link mode event')

View File

@ -252,7 +252,6 @@ const store = createStore({
if (!person.folded) { if (!person.folded) {
return; return;
} }
//console.log('unfoldPerson', person)
person.label = person._label person.label = person._label
delete person._label delete person._label
delete person.title delete person.title

View File

@ -5,7 +5,6 @@
{% block table_entities_thead_tr %} {% block table_entities_thead_tr %}
<th>{{ 'Id'|trans }}</th> <th>{{ 'Id'|trans }}</th>
<th>{{ 'Title'|trans }}</th> <th>{{ 'Title'|trans }}</th>
<th>{{ 'Social actions'|trans }}</th>
<th>{{ 'goal.results'|trans }}</th> <th>{{ 'goal.results'|trans }}</th>
<th>{{ 'goal.desactivationDate'|trans }}</th> <th>{{ 'goal.desactivationDate'|trans }}</th>
<th>&nbsp;</th> <th>&nbsp;</th>
@ -17,11 +16,6 @@
<td>{{ entity.id }}</td> <td>{{ entity.id }}</td>
<td>{{ entity.title|localize_translatable_string }}</td> <td>{{ entity.title|localize_translatable_string }}</td>
<td> <td>
{% for sa in entity.socialActions %}
{{ sa.title|localize_translatable_string }}{% if not loop.last %},{% else %}.{% endif %}
{% endfor %}
</td>
<td>
{% for sa in entity.results %} {% for sa in entity.results %}
{{ sa.title|localize_translatable_string }}{% if not loop.last %},{% else %}.{% endif %} {{ sa.title|localize_translatable_string }}{% if not loop.last %},{% else %}.{% endif %}
{% endfor %} {% endfor %}

View File

@ -8,9 +8,20 @@
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %} {% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% block crud_content_form_rows %} {% block crud_content_form_rows %}
<div class="mb-3 row">
<label class="col-form-label col-sm-4 required" for="social_action_ordering">{{ 'Parent'|trans }}</label>
<div class="col-sm-8">{{ entity.parent|chill_entity_render_box }}</div>
</div>
<div class="mb-3 row">
<label class="col-form-label col-sm-4 required" for="social_action_ordering">{{ 'Social issue'|trans }}</label>
<div class="col-sm-8">{{ entity.issue|chill_entity_render_box }}</div>
</div>
{% for f in form %} {% for f in form %}
{% if f.vars.name == 'parent' or f.vars.name == 'issue' %} {% if f.vars.name == 'parent' or f.vars.name == 'issue' %}
{{ form_row(f, { 'attr':{'disabled':'disabled'}}) }} {{ form_widget(f, { 'attr':{'hidden':'hidden'}}) }}
{% else %} {% else %}
{{ form_row(f) }} {{ form_row(f) }}
{% endif %} {% endif %}

View File

@ -17,6 +17,7 @@ use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData; use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument; use Chill\DocStoreBundle\Entity\AccompanyingCourseDocument;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository; use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
@ -132,7 +133,7 @@ class AccompanyingPeriodContext implements
]) ])
->add('category', EntityType::class, [ ->add('category', EntityType::class, [
'placeholder' => 'Choose a document category', 'placeholder' => 'Choose a document category',
'class' => 'ChillDocStoreBundle:DocumentCategory', 'class' => DocumentCategory::class,
'query_builder' => static function (EntityRepository $er) { 'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('c') return $er->createQueryBuilder('c')
->where('c.documentClass = :docClass') ->where('c.documentClass = :docClass')

View File

@ -15,6 +15,7 @@ use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithAdminFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException; use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocGeneratorBundle\Service\Context\BaseContextData; use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
use Chill\DocStoreBundle\Entity\DocumentCategory;
use Chill\DocStoreBundle\Entity\PersonDocument; use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository; use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
@ -92,7 +93,7 @@ class PersonContext implements DocGeneratorContextWithAdminFormInterface
$builder $builder
->add('category', EntityType::class, [ ->add('category', EntityType::class, [
'placeholder' => 'Choose a document category', 'placeholder' => 'Choose a document category',
'class' => 'ChillDocStoreBundle:DocumentCategory', 'class' => DocumentCategory::class,
'query_builder' => static function (EntityRepository $er) { 'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('c') return $er->createQueryBuilder('c')
->where('c.documentClass = :docClass') ->where('c.documentClass = :docClass')

View File

@ -223,6 +223,8 @@ components:
- "accompanying_period_work" - "accompanying_period_work"
note: note:
type: string type: string
privateComment:
type: string
startDate: startDate:
$ref: "#/components/schemas/Date" $ref: "#/components/schemas/Date"
endDate: endDate:

View File

@ -0,0 +1,33 @@
<?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;
final class Version20220527124737 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE activity DROP privateComment_comments');
}
public function getDescription(): string
{
return 'add private comment on accompanying period work';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period_work ADD privateComment_comments JSON DEFAULT \'{}\'');
}
}

View File

@ -609,6 +609,8 @@ accompanying_course_work:
Any work: Aucune action d'accompagnement Any work: Aucune action d'accompagnement
remove: Supprimer une action d'accompagnement remove: Supprimer une action d'accompagnement
social_evaluation: Évaluation social_evaluation: Évaluation
private_comment: Commentaire privé
# #
Person addresses: Adresses de résidence Person addresses: Adresses de résidence