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

This commit is contained in:
2023-03-24 17:51:37 +01:00
158 changed files with 2482 additions and 797 deletions

View File

@@ -11,11 +11,13 @@ declare(strict_types=1);
namespace Chill\PersonBundle\DataFixtures\ORM;
use Chill\MainBundle\DataFixtures\ORM\LoadCenters;
use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\PostalCode;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Household\MembersEditorFactory;
use DateInterval;
use DateTime;
@@ -192,14 +194,20 @@ class LoadHousehold extends Fixture implements DependentFixtureInterface
private function preparePersonIds()
{
$centers = LoadCenters::$centers;
// @TODO: Remove this and make this service stateless
$this->personIds = $this->em
->createQuery(
'SELECT p.id FROM ' . Person::class . ' p ' .
'JOIN p.center c ' .
'WHERE c.name = :center '
'WHERE EXISTS( ' .
'SELECT 1 FROM ' . PersonCenterHistory::class . ' pch ' .
'JOIN pch.center c ' .
'WHERE pch.person = p.id ' .
'AND c.name IN (:authorized_centers)' .
')'
)
->setParameter('center', 'Center A')
->setParameter('authorized_centers', $centers)
->getScalarResult();
shuffle($this->personIds);

View File

@@ -680,6 +680,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
$this->proxyAccompanyingPeriodOpenState = false;
}
public function countResources(): int
{
return $this->resources->count();
}
/**
* This public function is the same but return only true or false.
*/
@@ -764,6 +769,18 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $result;
}
public function countAccompanyingPeriodInvolved(
bool $asParticipantOpen = true,
bool $asRequestor = true
): int {
// TODO should be optimized to avoid loading accompanying period ?
return $this->getAccompanyingPeriodInvolved($asParticipantOpen, $asRequestor)
->filter(function (AccompanyingPeriod $p) {
return $p->getStep() !== AccompanyingPeriod::STEP_DRAFT;
})
->count();
}
/**
* Get AccompanyingPeriodParticipations Collection.
*

View File

@@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;

View File

@@ -18,21 +18,25 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerInterface;
/**
* Pick user dymically, using vuejs module "AddPerson".
* m* Pick user dymically, using vuejs module "AddPerson".
*/
class PickPersonDynamicType extends AbstractType
{
private DenormalizerInterface $denormalizer;
private DenormalizerInterface $normalizer;
private SerializerInterface $serializer;
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer)
public function __construct(DenormalizerInterface $denormalizer, SerializerInterface $serializer, NormalizerInterface $normalizer)
{
$this->denormalizer = $denormalizer;
$this->serializer = $serializer;
$this->normalizer = $normalizer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -45,6 +49,11 @@ class PickPersonDynamicType extends AbstractType
$view->vars['multiple'] = $options['multiple'];
$view->vars['types'] = ['person'];
$view->vars['uniqid'] = uniqid('pick_user_dyn');
$view->vars['suggested'] = [];
foreach ($options['suggested'] as $person) {
$view->vars['suggested'][] = $this->normalizer->normalize($person, 'json', ['groups' => 'read']);
}
}
public function configureOptions(OptionsResolver $resolver)
@@ -52,7 +61,8 @@ class PickPersonDynamicType extends AbstractType
$resolver
->setDefault('multiple', false)
->setAllowedTypes('multiple', ['bool'])
->setDefault('compound', false);
->setDefault('compound', false)
->setDefault('suggested', []);
}
public function getBlockPrefix()

View File

@@ -12,11 +12,14 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Knp\Menu\MenuItem;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
use function count;
/**
* Add menu entrie to person menu.
@@ -35,18 +38,28 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
protected TranslatorInterface $translator;
private ResidentialAddressRepository $residentialAddressRepo;
private Security $security;
public function __construct(
ParameterBagInterface $parameterBag,
Security $security,
TranslatorInterface $translator
TranslatorInterface $translator,
ResidentialAddressRepository $residentialAddressRepo
) {
$this->showAccompanyingPeriod = $parameterBag->get('chill_person.accompanying_period');
$this->security = $security;
$this->translator = $translator;
$this->residentialAddressRepo = $residentialAddressRepo;
}
/**
* @param $menuId
* @param MenuItem $menu
* @param array{person: Person} $parameters
* @return void
*/
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
$menu->addChild($this->translator->trans('Person details'), [
@@ -67,6 +80,8 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
])
->setExtras([
'order' => 60,
'counter' => 0 < ($nbResidentials = $this->residentialAddressRepo->countByPerson($parameters['person'])) ?
$nbResidentials : null,
]);
$menu->addChild($this->translator->trans('person_resources_menu'), [
@@ -77,6 +92,7 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
])
->setExtras([
'order' => 70,
'counter' => 0 < ($nbResources = $parameters['person']->countResources()) ? $nbResources : null,
]);
$menu->addChild($this->translator->trans('household.person history'), [
@@ -111,6 +127,8 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
])
->setExtras([
'order' => 100,
'counter' => 0 < ($nbAccompanyingPeriod = $parameters['person']->countAccompanyingPeriodInvolved())
? $nbAccompanyingPeriod : null,
]);
}
}

View File

@@ -32,6 +32,16 @@ class ResidentialAddressRepository extends ServiceEntityRepository
parent::__construct($registry, ResidentialAddress::class);
}
public function countByPerson(Person $person): int
{
return $this->createQueryBuilder('ra')
->select('COUNT(ra)')
->where('ra.person = :person')
->setParameter('person', $person)
->getQuery()
->getSingleScalarResult();
}
public function buildQueryFindCurrentResidentialAddresses(Person $person, ?DateTimeImmutable $at = null): QueryBuilder
{
$date = null === $at ? new DateTimeImmutable('today') : $at;

View File

@@ -20,9 +20,6 @@ div.accompanying-course-work {
margin: 0;
}
}
td {
font-size: 85%;
}
td.obj,
td.res {
width: 50%;
@@ -37,6 +34,10 @@ div.accompanying-course-work {
}
}
.smallfont table.obj-res-eval {
font-size: 85%;
}
ul {
&.goal_title,
&.result_list,

View File

@@ -22,7 +22,12 @@
<i>{{ $t('course.open_at') }}{{ $d(accompanyingCourse.openingDate.datetime, 'text') }}</i>
</span>
<span v-if="accompanyingCourse.user" class="d-md-block ms-3 ms-md-0">
<span class="item-key">{{ $t('course.referrer') }}:</span> <b>{{ accompanyingCourse.user.text }}</b>
<span class="item-key">{{ $t('course.referrer') }}:</span>&nbsp;
<b>{{ accompanyingCourse.user.text }}</b>
<template v-if="accompanyingCourse.user.isAbsent">
&nbsp;
<span class="badge bg-danger rounded-pill" title="Absent">A</span>
</template>
</span>
</span>
</span>
@@ -59,13 +64,15 @@
import ToggleFlags from './Banner/ToggleFlags';
import SocialIssue from './Banner/SocialIssue.vue';
import PersonsAssociated from './Banner/PersonsAssociated.vue';
import UserRenderBoxBadge from 'ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue';
export default {
name: 'Banner',
components: {
ToggleFlags,
SocialIssue,
PersonsAssociated
PersonsAssociated,
UserRenderBoxBadge,
},
computed: {
accompanyingCourse() {

View File

@@ -2,6 +2,29 @@
<div class="vue-component">
<h2><a id="section-80"></a>{{ $t('referrer.title') }}</h2>
<teleport to="body">
<modal v-if="modal.showModal"
:modalDialogClass="modal.modalDialogClass"
@close="cancelChange">
<template v-slot:header>
<h3 class="modal-title">{{ $t('confirm.title') }}</h3>
</template>
<template v-slot:body-head>
<div class="modal-body">
<p v-html="$t('confirm.sure_referrer', { referrer: this.value.text })"></p>
</div>
</template>
<template v-slot:footer>
<button class="btn btn-save"
@click.prevent="this.confirmReferrer">
{{ $t('confirm.ok_referrer')}}
</button>
</template>
</modal>
</teleport>
<div>
<label class="col-form-label" for="selectJob">
@@ -35,6 +58,8 @@
:searchable="true"
:placeholder="$t('referrer.placeholder')"
v-model="value"
@select="updateReferrer"
@remove="removeReferrer"
:options="users"
:select-label="$t('multiselect.select_label')"
:deselect-label="$t('multiselect.deselect_label')"
@@ -78,16 +103,24 @@ import VueMultiselect from 'vue-multiselect';
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods';
import {mapState, mapGetters} from 'vuex';
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge";
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
export default {
name: "Referrer",
components: {
UserRenderBoxBadge,
VueMultiselect,
Modal
},
data() {
return {
jobs: []
jobs: [],
modal: {
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl"
},
value: this.$store.state.accompanyingCourse.user,
confirmed: false
}
},
computed: {
@@ -118,22 +151,6 @@ export default {
});
}
},
value: {
get() {
return this.$store.state.accompanyingCourse.user;
},
set(value) {
console.log('set referrer', value);
this.$store.dispatch('updateReferrer', value)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
}
},
},
mounted() {
this.getJobs();
@@ -141,6 +158,7 @@ export default {
methods: {
updateReferrer(value) {
this.value = value;
this.toggleModal();
},
getJobs() {
const url = '/api/1.0/main/user-job.json';
@@ -159,12 +177,38 @@ export default {
const url = `/api/1.0/main/whoami.json`;
makeFetch('GET', url)
.then(user => {
this.value = user
// this.value = user
this.updateReferrer(user);
})
/*.catch((error) => {
commit('catchError', error);
this.$toast.open({message: error.body})
})*/
},
toggleModal() {
this.modal.showModal = !this.modal.showModal;
},
confirmReferrer() {
this.$store.dispatch('updateReferrer', this.value)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
this.toggleModal()
},
removeReferrer() {
console.log('remove option')
this.$store.dispatch('updateReferrer', null)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
},
cancelChange() {
this.value = this.$store.state.accompanyingCourse.user
this.toggleModal()
}
}
}

View File

@@ -178,7 +178,7 @@ export default {
body.civility = {type: 'chill_main_civility', id: payload.data.civility.id};
}
if (null !== payload.data.profession) {
body.profession = {type: 'third_party_profession', id: payload.data.profession.id};
body.profession = payload.data.profession;
}
// console.log('body', body);

View File

@@ -29,7 +29,8 @@ const appMessages = {
emergency: "urgent",
confidential: "confidentiel",
regular: "régulier",
occasional: "ponctuel"
occasional: "ponctuel",
absent: "Absent",
},
origin: {
title: "Origine de la demande",
@@ -138,8 +139,10 @@ const appMessages = {
set_a_scope: "indiquez au moins un service",
sure: "Êtes-vous sûr ?",
sure_description: "Une fois le changement confirmé, il ne sera plus possible de le remettre à l'état de brouillon !",
sure_referrer: "Êtes-vous sûr de vouloir assigner ce parcours à <b>{referrer}</b>",
ok: "Confirmer le parcours",
delete: "Supprimer le parcours",
ok_referrer: "Confirmer le référent",
no_suggested_referrer: "Il n'y a aucun référent qui puisse être suggéré pour ce parcours. Vérifiez la localisation du parcours, les métiers et service indiqués. Si les données sont correctes, vous pouvez confirmer ce parcours.",
one_suggested_referrer: "Un unique référent peut être suggéré pour ce parcours",
choose_suggested_referrer: "Voulez-vous le désigner directement ?",

View File

@@ -130,6 +130,7 @@
:filename="d.title"
:can-edit="true"
:execute-before-leave="submitBeforeLeaveToEditor"
@on-stored-object-status-change="onStatusDocumentChanged"
></document-action-buttons-group>
</li>
<li v-if="d.workflows.length === 0">
@@ -374,6 +375,10 @@ export default {
this.$store.commit('removeDocument', {key: this.evaluation.key, document: document});
}
},
onStatusDocumentChanged(newStatus) {
console.log('onStatusDocumentChanged', newStatus);
this.$store.commit('statusDocumentChanged', {key: this.evaluation.key, newStatus: newStatus});
},
goToGenerateWorkflowEvaluationDocument({event, link, workflowName, payload}) {
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key);

View File

@@ -367,7 +367,22 @@ const store = createStore({
state.evaluationsPicked.find(e => e.key === payload.evaluationKey)
.documents.find(d => d.id === payload.id).title = payload.title;
}
}
},
statusDocumentChanged(state, {newStatus, key}) {
const e = state.evaluationsPicked.find(e => e.key === key);
if (typeof e === 'undefined') {
console.error('evaluation not found for given key', {key});
}
const doc = e.documents.find(d => d.storedObject?.id === newStatus.id);
if (typeof doc === 'undefined') {
console.error('document not found', {newStatus});
}
doc.storedObject.status = newStatus.status;
doc.storedObject.type = newStatus.type;
doc.storedObject.filename = newStatus.filename;
},
},
actions: {
updateThirdParty({ commit }, payload) {

View File

@@ -1,7 +1,7 @@
<template>
<div class="container tpartycontainer">
<div class="tparty-identification">
<span v-if="item.result.profession" class="profession">{{ item.result.profession.name.fr }}</span>
<span v-if="item.result.profession" class="profession">{{ item.result.profession }}</span>
<span class="name">
{{ item.result.text }}&nbsp;
</span>

View File

@@ -1,9 +1,7 @@
<template>
<div class="container usercontainer">
<div class="user-identification">
<span class="name">
{{ item.result.text }}
</span>
<user-render-box-badge :user="item.result"></user-render-box-badge>
</div>
</div>
<div class="right_actions">
@@ -16,10 +14,12 @@
<script>
import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue';
import UserRenderBoxBadge from 'ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue';
export default {
name: 'SuggestionUser',
components: {
UserRenderBoxBadge,
BadgeEntity
},
props: ['item'],

View File

@@ -3,6 +3,7 @@
# - itemBlocClass: [uniq|colored|extended]
# - displayContent: [short|long] default: short
# - displayAction: [true|false] default: false
# - displayFontSmall: [true|false] default: false
#}
<div class="item-bloc{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
@@ -83,7 +84,7 @@
{%- if w.referrers|length > 0 -%}
{% for u in w.referrers %}
<span class="wl-item">
{{ u|chill_entity_render_box }}
<span class="badge-user">{{ u|chill_entity_render_box }}</span>
{% if not loop.last %}, {% endif %}
</span>
{% endfor %}
@@ -110,7 +111,7 @@
</div>
{% if displayContent is not defined or displayContent == 'short' %}
<div class="item-row column">
<div class="item-row column{% if displayFontSmall is defined and displayFontSmall == true %} smallfont{% endif %}">
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {
'displayContent': displayContent
} %}

View File

@@ -130,27 +130,20 @@
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
<div class="download mb-4 container">
{% if e.documents|length > 0 %}
{% if e.documents|length > 0 %}
<table class="table mt-4 mx-auto">
{% for d in e.documents %}
<div class="row">
<div class="col text-start">
{{ d.title }}
</div>
<div class="col-md-auto text-center">
{{ mm.mimeIcon(d.storedObject.type) }}
</div>
<div class="col col-lg-4 text-end">
{{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}
</div>
</div>
<tr class="border-0">
<td class="border-0">{{ d.title }}</td>
<td class="border-0">{{ mm.mimeIcon(d.storedObject.type) }}</td>
<td class="border-0 text-end">{{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}</td>
</tr>
{% endfor %}
{% else %}
<span class="chill-no-data-statement">{{ 'No document found'|trans }}</span>
{% endif %}
</div>
</table>
{% else %}
<span class="chill-no-data-statement">{{ 'No document found'|trans }}</span>
{% endif %}
{% endif %}
</td>

View File

@@ -25,6 +25,7 @@
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with {
'displayAction': true,
'displayContent': 'short',
'displayFontSmall': true,
'itemBlocClass': ''
} %}
{% endfor %}

View File

@@ -33,8 +33,10 @@
<span class="item-key">{{ 'Referrers'|trans ~ ' : ' }}</span>
{% for u in w.referrers %}
<span class="badge-user">{{ u|chill_entity_render_box }}</span>
{% if not loop.last %}, {% endif %}
{% endfor %}
{% if w.referrers|length == 0 %}
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
{% endif %}
</li>
{% endif %}
<li class="associated-persons">

View File

@@ -3,6 +3,7 @@
#}{{ period.user.label }},
L'usager {{ oldPersonLocation|chill_entity_render_string }} a déménagé.
{{ app.user|chill_entity_render_string }} a enregistré ce déménagement.
Son adresse était utilisée pour localiser le parcours n°{{ period.id }}, dont vous êtes
le référent.

View File

@@ -69,7 +69,7 @@
</div>
<div class="wl-col list">
<div class="user">
{{ acp.user|chill_entity_render_box }}
<span class="badge-user">{{ acp.user|chill_entity_render_box }}</span>
</div>
</div>
</div>

View File

@@ -21,7 +21,7 @@
</h2>
</div>
<div class="item-row column">
<table class="obj-res-eval my-3" style="font-size: 110% !important;">
<table class="obj-res-eval my-3">
<thead>
<tr>
<th class="eval">

View File

@@ -49,7 +49,7 @@
</div>
</div>
<div class="item-row column">
<table class="obj-res-eval my-3" style="font-size: 110% !important;">
<table class="obj-res-eval my-3">
<thead>
<tr>
<th class="eval">

View File

@@ -17,9 +17,13 @@
<div class="list-group vertical-menu {{ 'menu-' ~ menus.name }}">
{% for menu in menus %}
<a class="list-group-item list-group-item-action"
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
href="{{ menu.uri }}">
{{ menu.label|upper }}
{% if menu.extras.counter is defined and menu.extras.counter is not null %}
<span class="badge rounded-pill bg-secondary notification-counter">{{ menu.extras.counter }}</span>
{% endif %}
</a>
{% endfor %}
</div>

View File

@@ -115,23 +115,23 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
{
$string = '@person ';
$string .= empty($data['_default']) ? '' : $data['_default'] . ' ';
$string .= $data['_default'] ? '' : $data['_default'] . ' ';
foreach (['firstname', 'lastname', 'gender', 'city'] as $key) {
$string .= empty($data[$key]) ? '' : $key . ':' .
$string .= $data[$key] ? '' : $key . ':' .
// add quote if contains spaces
(strpos($data[$key], ' ') !== false ? '"' . $data[$key] . '"' : $data[$key])
. ' ';
}
foreach (['birthdate', 'birthdate-before', 'birthdate-after'] as $key) {
$string .= empty($data[$key]) ?
$string .= $data[$key] ?
''
:
$key . ':' . $data[$key]->format('Y-m-d') . ' ';
}
$string .= empty($data['phonenumber']) ? '' : 'phonenumber:' . $data['phonenumber']->getNationalNumber();
$string .= $data['phonenumber'] ? '' : 'phonenumber:' . $data['phonenumber']->getNationalNumber();
return $string;
}
@@ -162,13 +162,13 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
$phonenumber = new PhoneNumber();
$phonenumber->setNationalNumber($terms['phonenumber']);
} catch (Exception $ex) {
throw new ParsingException("The date for {$key} is "
throw new ParsingException('The date for phonenumber is '
. 'not parsable', 0, $ex);
}
$data['phonenumber'] = $phonenumber ?? null;
}
$data['phonenumber'] = $phonenumber ?? null;
return $data;
}

View File

@@ -76,8 +76,22 @@ class SimilarPersonMatcher
$qb->select('p')
->from(Person::class, 'p')
->join('p.centerHistory', 'center_history')
->where('SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) >= :precision')
->andWhere($qb->expr()->in('p.center', ':centers'));
->andWhere($qb->expr()->in('center_history.center', ':centers'))
->andWhere($qb->expr()->andX(
$qb->expr()->lte('center_history.startDate', 'CURRENT_DATE()'),
$qb->expr()->orX(
$qb->expr()->isNull('center_history.endDate'),
$qb->expr()->gt('center_history.endDate', 'CURRENT_DATE()')
)
))
;
$qb
->setParameter('fullName', $this->personRender->renderString($person, []))
->setParameter('centers', $centers)
->setParameter('precision', $precision);
if (null !== $person->getBirthdate()) {
$qb->andWhere($qb->expr()->orX(
@@ -90,13 +104,13 @@ class SimilarPersonMatcher
if ($person->getId() !== null) {
$qb->andWhere($qb->expr()->neq('p.id', ':personId'));
$query->setParameter('personId', $person->getId());
$qb->setParameter('personId', $person->getId());
$notDuplicatePersons = $this->personNotDuplicateRepository->findNotDuplicatePerson($person);
if (count($notDuplicatePersons)) {
$qb->andWhere($qb->expr()->notIn('p.id', ':notDuplicatePersons'));
$query->setParameter('notDuplicatePersons', $notDuplicatePersons);
$qb->setParameter('notDuplicatePersons', $notDuplicatePersons);
}
}
@@ -109,13 +123,9 @@ class SimilarPersonMatcher
case self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY:
default:
$qb->orderBy('SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName)))', 'DESC');
$qb->setParameter('fullName', $this->personRender->renderString($person, []));
}
$qb
->setParameter('fullName', $this->personRender->renderString($person, []))
->setParameter('centers', $centers)
->setParameter('precision', $precision);
return $qb->getQuery()->getResult();
}
}

View File

@@ -24,6 +24,7 @@ use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
@@ -51,6 +52,8 @@ class AccompanyingPeriodContext implements
private PersonRenderInterface $personRender;
private PersonRepository $personRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator;
@@ -61,6 +64,7 @@ class AccompanyingPeriodContext implements
TranslatableStringHelperInterface $translatableStringHelper,
EntityManagerInterface $em,
PersonRenderInterface $personRender,
PersonRepository $personRepository,
TranslatorInterface $translator,
BaseContextData $baseContextData
) {
@@ -69,6 +73,7 @@ class AccompanyingPeriodContext implements
$this->translatableStringHelper = $translatableStringHelper;
$this->em = $em;
$this->personRender = $personRender;
$this->personRepository = $personRepository;
$this->translator = $translator;
$this->baseContextData = $baseContextData;
}
@@ -199,7 +204,7 @@ class AccompanyingPeriodContext implements
$options = $template->getOptions();
$data = [];
$data = array_merge($data, $this->baseContextData->getData());
$data = array_merge($data, $this->baseContextData->getData($contextGenerationData['creator'] ?? null));
$data['course'] = $this->normalizer->normalize($entity, 'docgen', ['docgen:expects' => AccompanyingPeriod::class, 'groups' => 'docgen:read']);
foreach (['mainPerson', 'person1', 'person2'] as $k) {
@@ -256,6 +261,31 @@ class AccompanyingPeriodContext implements
return $options['mainPerson'] || $options['person1'] || $options['person2'];
}
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
$normalized = [];
foreach (['mainPerson', 'person1', 'person2'] as $k) {
$normalized[$k] = null !== ($data[$k] ?? null) ? $data[$k]->getId() : null;
}
return $normalized;
}
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
$denormalized = [];
foreach (['mainPerson', 'person1', 'person2'] as $k) {
if (null !== ($id = ($data[$k] ?? null))) {
$denormalized[$k] = $this->personRepository->find($id);
} else {
$denormalized[$k] = null;
}
}
return $denormalized;
}
/**
* @param AccompanyingPeriod $entity
*/

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Service\DocGenerator;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
@@ -18,7 +19,13 @@ use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class AccompanyingPeriodWorkContext
/**
* Generate a context for an @link{AccompanyingPeriodWork}.
*
* Although there isn't any document associated to AccompanyingPeriodWork, this context
* is use by @link{AccompanyingPeriodWorkEvaluationContext}.
*/
class AccompanyingPeriodWorkContext implements DocGeneratorContextWithPublicFormInterface
{
private NormalizerInterface $normalizer;
@@ -109,8 +116,18 @@ class AccompanyingPeriodWorkContext
return $this->periodContext->hasPublicForm($template, $entity);
}
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
return $this->periodContext->contextGenerationDataNormalize($template, $entity, $data);
}
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
return $this->periodContext->contextGenerationDataDenormalize($template, $entity, $data);
}
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
// TODO: Implement storeGenerated() method.
// currently, no document associated with a AccompanyingPeriodWork
}
}

View File

@@ -174,6 +174,18 @@ class AccompanyingPeriodWorkEvaluationContext implements
->hasPublicForm($template, $entity->getAccompanyingPeriodWork());
}
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
return $this->accompanyingPeriodWorkContext
->contextGenerationDataNormalize($template, $entity, $data);
}
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
return $this->accompanyingPeriodWorkContext
->contextGenerationDataDenormalize($template, $entity, $data);
}
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
$doc = new AccompanyingPeriodWorkEvaluationDocument();

View File

@@ -21,11 +21,13 @@ use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\PersonRepository;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
@@ -55,6 +57,8 @@ final class PersonContext implements PersonContextInterface
private NormalizerInterface $normalizer;
private ScopeRepositoryInterface $scopeRepository;
private Security $security;
private bool $showScopes;
@@ -71,6 +75,7 @@ final class PersonContext implements PersonContextInterface
EntityManagerInterface $em,
NormalizerInterface $normalizer,
ParameterBagInterface $parameterBag,
ScopeRepositoryInterface $scopeRepository,
Security $security,
TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper
@@ -81,6 +86,7 @@ final class PersonContext implements PersonContextInterface
$this->documentCategoryRepository = $documentCategoryRepository;
$this->em = $em;
$this->normalizer = $normalizer;
$this->scopeRepository = $scopeRepository;
$this->security = $security;
$this->showScopes = $parameterBag->get('chill_main')['acl']['form_show_scopes'];
$this->translator = $translator;
@@ -159,7 +165,7 @@ final class PersonContext implements PersonContextInterface
}
$data = [];
$data = array_merge($data, $this->baseContextData->getData());
$data = array_merge($data, $this->baseContextData->getData($contextGenerationData['creator'] ?? null));
$data['person'] = $this->normalizer->normalize($entity, 'docgen', [
'docgen:expects' => Person::class,
'groups' => ['docgen:read'],
@@ -211,6 +217,38 @@ final class PersonContext implements PersonContextInterface
return true;
}
/**
* @param Person $entity
*/
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
$scope = $data['scope'] ?? null;
return [
'title' => $data['title'] ?? '',
'scope_id' => $scope instanceof Scope ? $scope->getId() : null,
];
}
/**
* @param Person $entity
*/
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
if (!isset($data['scope'])) {
$scope = null;
} else {
if (null === $scope = $this->scopeRepository->find($data['scope'])) {
throw new \UnexpectedValueException('scope not found');
}
}
return [
'title' => $data['title'] ?? '',
'scope' => $scope,
];
}
/**
* @param Person $entity
*/

View File

@@ -48,6 +48,10 @@ interface PersonContextInterface extends DocGeneratorContextWithAdminFormInterfa
*/
public function hasPublicForm(DocGeneratorTemplate $template, $entity): bool;
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array;
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array;
/**
* @param Person $entity
*/

View File

@@ -17,6 +17,7 @@ use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
@@ -30,12 +31,16 @@ class PersonContextWithThirdParty implements DocGeneratorContextWithAdminFormInt
private PersonContextInterface $personContext;
private ThirdPartyRepository $thirdPartyRepository;
public function __construct(
PersonContextInterface $personContext,
NormalizerInterface $normalizer
NormalizerInterface $normalizer,
ThirdPartyRepository $thirdPartyRepository
) {
$this->personContext = $personContext;
$this->normalizer = $normalizer;
$this->thirdPartyRepository = $thirdPartyRepository;
}
public function adminFormReverseTransform(array $data): array
@@ -123,6 +128,26 @@ class PersonContextWithThirdParty implements DocGeneratorContextWithAdminFormInt
return true;
}
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
return array_merge(
[
'thirdParty' => null === $data['thirdParty'] ? null : $data['thirdParty']->getId(),
],
$this->personContext->contextGenerationDataNormalize($template, $entity, $data),
);
}
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
{
return array_merge(
[
'thirdParty' => null === $data['thirdParty'] ? null : $this->thirdPartyRepository->find($data['thirdParty']),
],
$this->personContext->contextGenerationDataDenormalize($template, $entity, $data),
);
}
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
{
$this->personContext->storeGenerated($template, $storedObject, $entity, $contextGenerationData);

View File

@@ -24,10 +24,36 @@ use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Validator\Test\ConstraintValidatorTestCase;
class ParticipationOverlapValidatorTest extends ConstraintValidatorTestCase
/**
* @internal
* @coversNothing
*/
final class ParticipationOverlapValidatorTest extends ConstraintValidatorTestCase
{
use ProphecyTrait;
/**
* @return mixed
*/
public function getConstraint()
{
return new ParticipationOverlap();
}
public function testOneParticipation()
{
$period = new AccompanyingPeriod();
$person = new Person();
$collection = new ArrayCollection([
new AccompanyingPeriodParticipation($period, $person),
]);
$this->validator->validate($collection, $this->getConstraint());
$this->assertNoViolation();
}
protected function createValidator()
{
$personRender = $this->prophesize(PersonRenderInterface::class);
@@ -37,26 +63,4 @@ class ParticipationOverlapValidatorTest extends ConstraintValidatorTestCase
return new ParticipationOverlapValidator($personRender->reveal(), $thirdPartyRender->reveal());
}
public function testOneParticipation()
{
$period = new AccompanyingPeriod();
$person = new Person();
$collection = new ArrayCollection([
new AccompanyingPeriodParticipation($period, $person)
]);
$this->validator->validate($collection, $this->getConstraint());
$this->assertNoViolation();
}
/**
* @return mixed
*/
public function getConstraint()
{
return new ParticipationOverlap();
}
}

View File

@@ -54,7 +54,7 @@ class AccompanyingPeriodValidityValidator extends ConstraintValidator
$activities = $this->activityRepository->findBy(['accompanyingPeriod' => $period]);
foreach ($activities as $activity) {
$socialIssues = $activity->getSocialIssues()->toArray();
$socialIssues = array_merge($socialIssues, $activity->getSocialIssues()->toArray());
}
foreach ($period->getWorks() as $work) {
@@ -64,7 +64,7 @@ class AccompanyingPeriodValidityValidator extends ConstraintValidator
$socialIssuesByKey = [];
foreach ($socialIssues as $si) {
$socialIssuesByKey[$si->getId()] = $si;
$socialIssuesByKey[spl_object_hash($si)] = $si;
}
$periodIssuesWithAncestors = [];
@@ -75,7 +75,7 @@ class AccompanyingPeriodValidityValidator extends ConstraintValidator
$periodIssuesWithAncestors,
array_map(
static function (SocialIssue $si) {
return $si->getId();
return spl_object_hash($si);
},
$si->getAncestors(true)
)

View File

@@ -97,6 +97,21 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW
return AccompanyingPeriodWorkEvaluationDocumentVoter::SEE;
}
public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array
{
$suggestedUsers = $entityWorkflow->getUsersInvolved();
$referrer = $this->getRelatedEntity($entityWorkflow)
->getAccompanyingPeriodWorkEvaluation()
->getAccompanyingPeriodWork()
->getAccompanyingPeriod()
->getUser();
$suggestedUsers[spl_object_hash($referrer)] = $referrer;
return $suggestedUsers;
}
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string
{
return '@ChillPerson/Workflow/_evaluation_document.html.twig';

View File

@@ -87,6 +87,20 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH
return AccompanyingPeriodWorkEvaluationVoter::SEE;
}
public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array
{
$suggestedUsers = $entityWorkflow->getUsersInvolved();
$referrer = $this->getRelatedEntity($entityWorkflow)
->getAccompanyingPeriodWork()
->getAccompanyingPeriod()
->getUser();
$suggestedUsers[spl_object_hash($referrer)] = $referrer;
return $suggestedUsers;
}
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string
{
return '@ChillPerson/Workflow/_evaluation.html.twig';

View File

@@ -94,6 +94,19 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte
return null;
}
public function getSuggestedUsers(EntityWorkflow $entityWorkflow): array
{
$suggestedUsers = $entityWorkflow->getUsersInvolved();
$referrer = $this->getRelatedEntity($entityWorkflow)
->getAccompanyingPeriod()
->getUser();
$suggestedUsers[spl_object_hash($referrer)] = $referrer;
return $suggestedUsers;
}
public function getTemplate(EntityWorkflow $entityWorkflow, array $options = []): string
{
return '@ChillPerson/Workflow/_accompanying_period_work.html.twig';

View File

@@ -125,7 +125,7 @@ address_country_code: Landscode
'Alreay existing person': 'Reeds bestaand persoonsdossier'
'Add the person': 'Persoon toevoegen'
'Add the person and create an accompanying period': "Persoon & hulpverleningstraject aanmaken"
'Add the person and create a household': "Persoon & gezin aanmaken"
'Add the person and create a household': "Persoon & huishouden aanmaken"
Show person: Toon persoonsdossier
'Confirm the creation': 'Aanmaak dossier bevestigen'
'You will create this person': 'U zal het volgende dossier aanmaken'
@@ -177,9 +177,9 @@ An accompanying period ends: Een hulpverleningstraject loopt op zijn einde
An accompanying period starts: Een hulpverleningstraject gaat van start
Any accompanying periods are open: Geen enkel hulpverleningstraject open
An accompanying period is open: Een hulpverleningstraject staat open
Accompanying period list: Hulpverleningstraject
Accompanying period list: Hulpverleningstrajecten
Accompanying period list for person: Hulpverleningstrajecten van persoon
Accompanying period: Hulpverleningstraject
Accompanying period: Hulpverleningstrajecten
Any accompanying period: Geen enkel hulpverleningstraject
period: Hulpverleningstraject
New accompanying course: Nieuw hulpverleningstraject
@@ -215,8 +215,8 @@ No resources: "Geen hulpverlening partners"
Persons associated: Betrokken personen
Referrer: Doorverwijzer
Referrers: Doorverwijzers
Some peoples does not belong to any household currently. Add them to an household soon: Sommige personen maken nog geen deel uit van een gezin. Voeg ze zo snel mogelijk aan gezin toe.
Add to household now: Toevoegen aan een gezin
Some peoples does not belong to any household currently. Add them to an household soon: Sommige personen maken nog geen deel uit van een huishouden. Voeg ze zo snel mogelijk aan huishouden toe.
Add to household now: Toevoegen aan een huishouden
Any resource for this accompanying course: Geen enkele hulpverlening partner
course.draft: Ontwerp
course.closed: Afgesloten
@@ -458,7 +458,7 @@ Accompanying course location: Locatie van hulpverleningstraject
This course is located by: Locatie bij
This course has a temporarily location: Voorlopige locatie
Choose a person to locate by: Adres van persoon toewijzen
Associate at least one member with an household, and set an address to this household: Associeer minstens één betrokken persoon in dit hulpverleningstraject met een gezin en wijs een adres toe aan dit gezin.
Associate at least one member with an household, and set an address to this household: Associeer minstens één betrokken persoon in dit hulpverleningstraject met een huishouden en wijs een adres toe aan dit huishouden.
Locate by: Adres toewijzen
fix it: Aanvullen
accompanying_course:
@@ -480,23 +480,23 @@ accompanying_course_comment:
Read more: Meer lezen..
# Household
Household: Gezin
Household: Huishouden
Summary: Samenvatting
Members: Gezinsleden
Members: Leden huishouden
Addresses: Addressen
Move household: Nieuwe verhuis
Addresses history for household: Historiek adressen
Household accompanying period: Hulpverleningstrajecten van gezin
Household summary: Samenvatting gezin
Edit household address: Adres gezin bijwerken
Show household: Gezin bekijken
Back to household: Terugkeren naar gezin
Remove household composition: Gezinssamenstelling verwijderen
Are you sure you want to remove this composition?: Bent u zeker deze gezinssamenstelling te willen verwijderen?
Concerns household n°%id%: Betrokken gezin n°%id%
Composition: Gezinssamenstelling
Household accompanying period: Hulpverleningstrajecten van huishouden
Household summary: Samenvatting huishouden
Edit household address: Adres huishouden bijwerken
Show household: huishouden bekijken
Back to household: Terugkeren naar huishouden
Remove household composition: huishoudenssamenstelling verwijderen
Are you sure you want to remove this composition?: Bent u zeker deze huishoudenssamenstelling te willen verwijderen?
Concerns household n°%id%: Betrokken huishouden n°%id%
Composition: huishoudenssamenstelling
Budget: Budget
The composition has been successfully removed.: De gezinssamenstelling werd verwijdert.
The composition has been successfully removed.: De huishoudenssamenstelling werd verwijdert.
# accompanying course work
Accompanying Course Actions: Hulpverleningsmaatregelen
@@ -560,16 +560,16 @@ You are getting a notification for a period you are not allowed to see: De notif
This is the minimal period details: Hulpverleningstraject n°
household_composition:
No composition yet: Geen enkele gezinssamenstelling toegewezen
Compositions: Gezinssamenstelling
No composition yet: Geen enkele huishoudenssamenstelling toegewezen
Compositions: huishoudenssamenstelling
endDate: Einddatum
numberOfChildren: Aantal kinderen in het gezin
Household composition: Gezinssamenstelling
Composition added: Informatie over de gezinssamenstelling toegevoegd
Currently no composition: Geen enkele gezinssamenstelling toegewezen
Add a composition: Een gezinssamenstelling toevoegen
Update composition: Gezinssamenstelling bijwerken
Create: Een nieuwe gezinssamenstelling toewijzen
numberOfChildren: Aantal kinderen in het huishouden
Household composition: huishoudenssamenstelling
Composition added: Informatie over de huishoudenssamenstelling toegevoegd
Currently no composition: Geen enkele huishoudenssamenstelling toegewezen
Add a composition: Een huishoudenssamenstelling toevoegen
Update composition: huishoudenssamenstelling bijwerken
Create: Een nieuwe huishoudenssamenstelling toewijzen
# docgen
Linked evaluations: Gerelateerde evaluaties