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

This commit is contained in:
Julien Fastré 2022-03-21 12:59:24 +01:00
commit f084078cf2
14 changed files with 266 additions and 55 deletions

View File

@ -11,6 +11,7 @@ and this project adheres to
## Unreleased ## Unreleased
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [person] AccompanyingPeriodWork: add referrers to work, add doctrine event listener to add logged user to referrers collection and display a referrers list in work list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/502)
* [person] AccompanyingPeriodWorkEvaluation: fix circular reference when serialising (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/495) * [person] AccompanyingPeriodWorkEvaluation: fix circular reference when serialising (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/495)
* [person] order accompanying period by opening date in search persons, person and household period lists (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/493) * [person] order accompanying period by opening date in search persons, person and household period lists (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/493)
* [parcours] autosave of the pinned comment for draft accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/477) * [parcours] autosave of the pinned comment for draft accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/477)

View File

@ -27,7 +27,7 @@
</a> </a>
{% for menu in menus %} {% for menu in menus %}
<a class="list-group-item list-group-item-action" href="{{ menu.uri }}"> <a class="list-group-item list-group-item-action" href="{{ menu.uri }}">
{{ menu.label|upper }} {{ menu.label|trans|upper }}
</a> </a>
{% endfor %} {% endfor %}
</div> </div>

View File

@ -1,60 +1,62 @@
{% extends '@ChillMain/Admin/Permission/layout_crud_permission_index.html.twig' %} {% extends '@ChillMain/Admin/Permission/layout_crud_permission_index.html.twig' %}
{% block admin_content -%} {% block admin_content -%}
{% embed '@ChillMain/CRUD/_index.html.twig' %}
{% block table_entities_thead_tr %} <h1>{{"Users"|trans}}</h1>
<th>{{ 'crud.admin_user.index.is_active'|trans }}</th> {% for entity in entities %}
<th>{{ 'crud.admin_user.index.usernames'|trans }}</th> <div class="flex-table">
<th>{{ 'crud.admin_user.index.mains'|trans }}</th> <div class="item-bloc">
<th>&nbsp;</th> <div class="item-row">
{% endblock %} <div class="item-col">
{% block table_entities_tbody %} {{ entity.label }}
{% for entity in entities %}
<tr data-username="{{ entity.username|e('html_attr') }}">
<td>
{% if entity.isEnabled %} {% if entity.isEnabled %}
<i class="fa fa-check chill-green"></i> <i class="fa fa-check chill-green"></i>
{% else %} {% else %}
<i class="fa fa-times chill-red"></i> <i class="fa fa-times chill-red"></i>
{% endif %} {% endif %}
</td> </div>
<td> <div class="item-col"><i>{{ entity.email }}</i></div>
{{ entity.username }} </div>
<br/> <div class="item-row">
{{ entity.label }} <div class="item-col">
<br/> login: {{ entity.username|e('html_attr') }}
{{ entity.email }} </div>
</td> <div class="item-col">
<td>
{% if entity.userJob %} {% if entity.userJob %}
{{ entity.userJob.label|localize_translatable_string }} {{ entity.userJob.label|localize_translatable_string }}
<br/>
{% endif %} {% endif %}
</div>
</div>
<div class="item-row">
<div class="item-col">
{% if entity.mainScope %} {% if entity.mainScope %}
{{ entity.mainScope.name|localize_translatable_string }} {{ entity.mainScope.name|localize_translatable_string }}
<br/>
{% endif %} {% endif %}
{% if entity.mainCenter %} {% if entity.mainCenter %}
{{ entity.mainCenter.name }} , {{ entity.mainCenter.name }}
{% endif %} {% endif %}
</td> </div>
<td> </div>
<ul class="record_actions"> <div class="item-row">
<ul class="record_actions">
<li>
<a class="btn btn-edit" href="{{ path('chill_crud_admin_user_edit', { 'id': entity.id }) }}"></a>
</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>
</li>
{% if is_granted('ROLE_ALLOWED_TO_SWITCH') %}
<li> <li>
<a class="btn btn-edit" href="{{ path('chill_crud_admin_user_edit', { 'id': entity.id }) }}"></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>
</li> </li>
<li> {% endif %}
<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> </ul>
</li> </div>
{% if is_granted('ROLE_ALLOWED_TO_SWITCH') %} </div>
<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> </div>
</li> {% endfor %}
{% endif %}
</ul> {{ chill_pagination(paginator) }}
</td>
</tr> {% endblock %}
{% endfor %}
{% endblock %}
{% endembed %}
{% endblock %}

View File

@ -142,6 +142,15 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
*/ */
private Collection $persons; private Collection $persons;
/**
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_referrer")
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
* @Serializer\Groups({"accompanying_period_work:edit"})
* @Serializer\Groups({"accompanying_period_work:create"})
*/
private Collection $referrers;
/** /**
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorks") * @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorks")
* @ORM\JoinTable(name="chill_person_accompanying_period_work_result") * @ORM\JoinTable(name="chill_person_accompanying_period_work_result")
@ -196,6 +205,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
$this->thirdParties = new ArrayCollection(); $this->thirdParties = new ArrayCollection();
$this->persons = new ArrayCollection(); $this->persons = new ArrayCollection();
$this->accompanyingPeriodWorkEvaluations = new ArrayCollection(); $this->accompanyingPeriodWorkEvaluations = new ArrayCollection();
$this->referrers = new ArrayCollection();
} }
public function addAccompanyingPeriodWorkEvaluation(AccompanyingPeriodWorkEvaluation $evaluation): self public function addAccompanyingPeriodWorkEvaluation(AccompanyingPeriodWorkEvaluation $evaluation): self
@ -227,6 +237,15 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this; return $this;
} }
public function addReferrer(User $referrer): self
{
if (!$this->referrers->contains($referrer)) {
$this->referrers[] = $referrer;
}
return $this;
}
public function addResult(Result $result): self public function addResult(Result $result): self
{ {
if (!$this->results->contains($result)) { if (!$this->results->contains($result)) {
@ -308,6 +327,14 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this->persons; return $this->persons;
} }
/**
* @return Collection|User[]
*/
public function getReferrers(): Collection
{
return $this->referrers;
}
/** /**
* @return Collection|Result[] * @return Collection|Result[]
*/ */
@ -382,6 +409,13 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this; return $this;
} }
public function removeReferrer(User $referrer): self
{
$this->referrers->removeElement($referrer);
return $this;
}
public function removeResult(Result $result): self public function removeResult(Result $result): self
{ {
$this->results->removeElement($result); $this->results->removeElement($result);

View File

@ -0,0 +1,30 @@
<?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\EventListener;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Symfony\Component\Security\Core\Security;
class AccompanyingPeriodWorkEventListener
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function prePersistAccompanyingPeriodWork(AccompanyingPeriodWork $work): void
{
$work->addReferrer($this->security->getUser());
}
}

View File

@ -87,9 +87,12 @@ class AccompanyingPeriodWorkEvaluationRepository implements ObjectRepository
->join('work.accompanyingPeriod', 'period') ->join('work.accompanyingPeriod', 'period')
->where( ->where(
$qb->expr()->andX( $qb->expr()->andX(
$qb->expr()->eq('period.user', ':user'),
$qb->expr()->isNull('e.endDate'), $qb->expr()->isNull('e.endDate'),
$qb->expr()->gte(':now', $qb->expr()->diff('e.maxDate', 'e.warningInterval')) $qb->expr()->gte(':now', $qb->expr()->diff('e.maxDate', 'e.warningInterval')),
$qb->expr()->orX(
$qb->expr()->eq('period.user', ':user'),
$qb->expr()->isMemberOf(':user', 'work.referrers')
)
) )
) )
->setParameters([ ->setParameters([

View File

@ -159,9 +159,12 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository
->join('w.accompanyingPeriod', 'period') ->join('w.accompanyingPeriod', 'period')
->where( ->where(
$qb->expr()->andX( $qb->expr()->andX(
$qb->expr()->eq('period.user', ':user'),
$qb->expr()->gte('w.endDate', ':since'), $qb->expr()->gte('w.endDate', ':since'),
$qb->expr()->lte('w.startDate', ':until') $qb->expr()->lte('w.startDate', ':until'),
$qb->expr()->orX(
$qb->expr()->eq('period.user', ':user'),
$qb->expr()->isMemberOf(':user', 'w.referrers')
)
) )
) )
->setParameters([ ->setParameters([

View File

@ -151,6 +151,37 @@
</ul> </ul>
</div> </div>
<div id="referrers" class="action-row">
<h3>{{ $t('referrers') }}</h3>
<div v-if="!hasReferrers">
<p class="chill-no-data-statement">{{ $t('no_referrers') }}</p>
</div>
<div v-else>
<ul class="list-suggest remove-items inline">
<li v-for="u in referrers" :key="u.id" :title="$t('remove_referrer')" @click="removeReferrer(u)">
<span>
{{ u.text }}
</span>
</li>
</ul>
</div>
<ul class="record_actions">
<li class="add-persons">
<add-persons
ref="referrerPicker"
:key="referrerPicker.key"
:buttonTitle="$t('add_referrers')"
:modalTitle="$t('choose_referrers')"
:options="referrerPicker.options"
@addNewPersons="addReferrers">
</add-persons>
</li>
</ul>
</div>
<div id="handlingThirdParty" class="action-row"> <div id="handlingThirdParty" class="action-row">
<h3>{{ $t('handling_thirdparty') }}</h3> <h3>{{ $t('handling_thirdparty') }}</h3>
@ -289,7 +320,6 @@ import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vu
import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js'; import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js';
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods'; import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
const i18n = { const i18n = {
messages: { messages: {
fr: { fr: {
@ -322,6 +352,10 @@ const i18n = {
available_evaluations_text: "Documents disponibles pour ajout :", available_evaluations_text: "Documents disponibles pour ajout :",
no_evaluations_available: "Aucune évaluation disponible", no_evaluations_available: "Aucune évaluation disponible",
no_goals_available: "Aucun objectif disponible", no_goals_available: "Aucun objectif disponible",
referrers: "Agents traitants",
no_referrers: "Aucun agent traitant",
choose_referrers: "Choisir des agents traitants",
remove_referrer: "Enlever l'agent"
} }
} }
}; };
@ -370,6 +404,17 @@ export default {
} }
}, },
}, },
referrerPicker: {
key: 'referrer',
options: {
type: ['user'],
priority: null,
uniq: false,
button: {
display: false
}
},
},
}; };
}, },
computed: { computed: {
@ -381,6 +426,7 @@ export default {
'personsReachables', 'personsReachables',
'handlingThirdParty', 'handlingThirdParty',
'thirdParties', 'thirdParties',
'referrers',
'isPosting', 'isPosting',
'errors', 'errors',
'templatesAvailablesForAction', 'templatesAvailablesForAction',
@ -389,6 +435,7 @@ export default {
'hasResultsForAction', 'hasResultsForAction',
'hasHandlingThirdParty', 'hasHandlingThirdParty',
'hasThirdParties', 'hasThirdParties',
'hasReferrers'
]), ]),
startDate: { startDate: {
get() { get() {
@ -465,6 +512,14 @@ export default {
removeThirdParty(t) { removeThirdParty(t) {
this.$store.commit('removeThirdParty', t); this.$store.commit('removeThirdParty', t);
}, },
addReferrers({selected, modal}) {
this.$store.commit('addReferrers', selected.map(r => r.result));
this.$refs.referrerPicker.resetSearch();
modal.showModal = false;
},
removeReferrer(u) {
this.$store.commit('removeReferrer', u);
},
goToGenerateWorkflow({link}) { goToGenerateWorkflow({link}) {
console.log('save before leave to generate workflow') console.log('save before leave to generate workflow')
const callback = (data) => { const callback = (data) => {
@ -521,6 +576,7 @@ div#workEditor {
"objectives objectives" "objectives objectives"
"evaluations evaluations" "evaluations evaluations"
"persons persons" "persons persons"
"referrers referrers"
"handling handling" "handling handling"
"tparties tparties" "tparties tparties"
"errors errors"; "errors errors";
@ -543,6 +599,8 @@ div#workEditor {
grid-area: handling; } grid-area: handling; }
#thirdParties { #thirdParties {
grid-area: tparties; } grid-area: tparties; }
#referrers {
grid-area: referrers; }
#errors { #errors {
grid-area: errors; } grid-area: errors; }
@ -657,5 +715,4 @@ div#workEditor {
} }
} }
</style> </style>

View File

@ -31,6 +31,7 @@ const store = createStore({
.map(p => p.person), .map(p => p.person),
handlingThirdParty: window.accompanyingCourseWork.handlingThierParty, handlingThirdParty: window.accompanyingCourseWork.handlingThierParty,
thirdParties: window.accompanyingCourseWork.thirdParties, thirdParties: window.accompanyingCourseWork.thirdParties,
referrers: window.accompanyingCourseWork.referrers,
isPosting: false, isPosting: false,
errors: [], errors: [],
}, },
@ -54,6 +55,9 @@ const store = createStore({
hasHandlingThirdParty(state) { hasHandlingThirdParty(state) {
return state.handlingThirdParty !== null; return state.handlingThirdParty !== null;
}, },
hasReferrers(state) {
return state.referrers.length > 0;
},
hasThirdParties(state) { hasThirdParties(state) {
return state.thirdParties.length > 0; return state.thirdParties.length > 0;
}, },
@ -82,6 +86,7 @@ const store = createStore({
}, },
results: state.resultsPicked.map(r => ({id: r.id, type: r.type})), results: state.resultsPicked.map(r => ({id: r.id, type: r.type})),
thirdParties: state.thirdParties.map(t => ({id: t.id, type: t.type})), thirdParties: state.thirdParties.map(t => ({id: t.id, type: t.type})),
referrers: state.referrers.map(t => ({id: t.id, type: t.type})),
goals: state.goalsPicked.map(g => { goals: state.goalsPicked.map(g => {
let o = { let o = {
type: g.type, type: g.type,
@ -302,6 +307,18 @@ const store = createStore({
state.thirdParties = state.thirdParties state.thirdParties = state.thirdParties
.filter(t => t.id !== thirdParty.id); .filter(t => t.id !== thirdParty.id);
}, },
addReferrers(state, referrers) {
let ids = state.referrers.map(t => t.id);
let unexistings = referrers.filter(t => !ids.includes(t.id));
for (let i in unexistings) {
state.referrers.push(unexistings[i]);
}
},
removeReferrer(state, user) {
state.referrers = state.referrers
.filter(u => u.id !== user.id);
},
setErrors(state, errors) { setErrors(state, errors) {
state.errors = errors; state.errors = errors;
}, },
@ -315,7 +332,6 @@ const store = createStore({
}, },
actions: { actions: {
updateThirdParty({ commit }, payload) { updateThirdParty({ commit }, payload) {
console.log(payload);
commit('updateThirdParty', payload); commit('updateThirdParty', payload);
}, },
getReachablesGoalsForAction({ getters, commit, dispatch }) { getReachablesGoalsForAction({ getters, commit, dispatch }) {

View File

@ -28,11 +28,14 @@
{% if w.createdBy %} {% if w.createdBy %}
<div class="wl-row"> <div class="wl-row">
<div class="wl-col title"> <div class="wl-col title">
<h3>{{ 'Referrer'|trans }}</h3> <h3>{{ 'Referrers'|trans }}</h3>
</div> </div>
<div class="wl-col list"> <div class="wl-col list">
<p class="wl-item"> <p class="wl-item">
{{ w.createdBy|chill_entity_render_box }} {% for u in w.referrers %}
{{ u|chill_entity_render_box }}
{% if not loop.last %}, {% endif %}
{% endfor %}
</p> </p>
</div> </div>
</div> </div>

View File

@ -16,6 +16,7 @@ use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Household\Position; use Chill\PersonBundle\Entity\Household\Position;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@ -25,17 +26,24 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
*/ */
final class HouseholdNormalizerTest extends KernelTestCase final class HouseholdNormalizerTest extends KernelTestCase
{ {
private EntityManagerInterface $entityManager;
private ?NormalizerInterface $normalizer; private ?NormalizerInterface $normalizer;
private array $toDelete;
protected function setUp(): void protected function setUp(): void
{ {
self::bootKernel(); self::bootKernel();
$this->normalizer = self::$container->get(NormalizerInterface::class); $this->normalizer = self::$container->get(NormalizerInterface::class);
$this->entityManager = self::$container->get(EntityManagerInterface::class);
} }
public function testNormalizationRecursive() public function testNormalizationRecursive()
{ {
$person = new Person(); $person = new Person();
$person->setFirstName('ok')->setLastName('ok');
$this->entityManager->persist($person);
$member = new HouseholdMember(); $member = new HouseholdMember();
$household = new Household(); $household = new Household();
$position = (new Position()) $position = (new Position())
@ -44,7 +52,8 @@ final class HouseholdNormalizerTest extends KernelTestCase
$member->setPerson($person) $member->setPerson($person)
->setStartDate(new DateTimeImmutable('1 year ago')) ->setStartDate(new DateTimeImmutable('1 year ago'))
->setEndDate(new DateTimeImmutable('1 month ago')); ->setEndDate(new DateTimeImmutable('1 month ago'))
->setPosition($position);
$household->addMember($member); $household->addMember($member);

View File

@ -12,3 +12,13 @@ services:
event: 'prePersist' event: 'prePersist'
entity: 'Chill\PersonBundle\Entity\PersonAltName' entity: 'Chill\PersonBundle\Entity\PersonAltName'
method: 'prePersistAltName' method: 'prePersistAltName'
Chill\PersonBundle\EventListener\AccompanyingPeriodWorkEventListener:
autoconfigure: true
autowire: true
tags:
-
name: 'doctrine.orm.entity_listener'
event: 'prePersist'
entity: 'Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork'
method: 'prePersistAccompanyingPeriodWork'

View File

@ -0,0 +1,42 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add referrers to AccompanyingPeriodWork.
*/
final class Version20220310063629 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE chill_person_accompanying_period_work_referrer');
}
public function getDescription(): string
{
return 'Add referrers to AccompanyingPeriodWork';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE chill_person_accompanying_period_work_referrer (accompanyingperiodwork_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(accompanyingperiodwork_id, user_id))');
$this->addSql('CREATE INDEX IDX_3619F5EBB99F6060 ON chill_person_accompanying_period_work_referrer (accompanyingperiodwork_id)');
$this->addSql('CREATE INDEX IDX_3619F5EBA76ED395 ON chill_person_accompanying_period_work_referrer (user_id)');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_referrer ADD CONSTRAINT FK_3619F5EBB99F6060 FOREIGN KEY (accompanyingperiodwork_id) REFERENCES chill_person_accompanying_period_work (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_person_accompanying_period_work_referrer ADD CONSTRAINT FK_3619F5EBA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('INSERT INTO chill_person_accompanying_period_work_referrer (accompanyingperiodwork_id, user_id)
SELECT id, createdby_id FROM postgres.public.chill_person_accompanying_period_work');
}
}

View File

@ -214,6 +214,7 @@ No requestor: Pas de demandeur
No resources: "Pas d'interlocuteurs privilégiés" No resources: "Pas d'interlocuteurs privilégiés"
Persons associated: Usagers concernés Persons associated: Usagers concernés
Referrer: Référent Referrer: Référent
Referrers: Référents
Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur ménage dès que possible. Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur ménage dès que possible.
Add to household now: Ajouter à un ménage Add to household now: Ajouter à un ménage
Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours