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

This commit is contained in:
Julien Fastré 2022-03-14 13:27:26 +01:00
commit 33acc645ea
34 changed files with 388 additions and 164 deletions

View File

@ -12,7 +12,8 @@ and this project adheres to
<!-- write down unreleased development here -->
* [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)
* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472)
* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472)
* [person] Add url in accompanying period work evaluations entity and form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/476)
* [person] Add document generation in admin and in person/{id}/document (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/464)
@ -32,9 +33,16 @@ and this project adheres to
* [confidential] Fix position of toggle button so it does not cover text nor fall outside of box (no issue)
* [parcours] Fix edit of both thirdparty and contact name (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/474)
* [template] do not list inactive templates (for doc generator)
* [household] bugfix if position of member is null, renderbox no longer throws an error (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/480)
* [parcours] location cannot be removed if linked to a user (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/478)
* [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490)
* [person] Add link to current household in person banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/484)
* [address] person badge in address history changed to open OnTheFly with all person info (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/489)
* [person] Change 'personne' with 'usager' and '&' with 'ET' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/499)
* [thirdparty] Add parameter condition to display centers or not (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/500)
* [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496)
* [contact] add contact button color changed plus the pipe at the side removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/506)
* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486)
## Test releases

View File

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

View File

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

View File

@ -16,9 +16,7 @@ use libphonenumber\PhoneNumberUtil;
use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use function array_key_exists;
class ChillPhoneNumberType extends AbstractType
{
@ -37,21 +35,7 @@ class ChillPhoneNumberType extends AbstractType
$resolver
->setDefault('default_region', $this->defaultCarrierCode)
->setDefault('format', PhoneNumberFormat::NATIONAL)
->setDefault('type', \libphonenumber\PhoneNumberType::FIXED_LINE_OR_MOBILE)
->setNormalizer('attr', function (Options $options, $value) {
if (array_key_exists('placeholder', $value)) {
return $value;
}
$examplePhoneNumber = $this->phoneNumberUtil->getExampleNumberForType($this->defaultCarrierCode, $options['type']);
return array_merge(
$value,
[
'placeholder' => PhoneNumberUtil::getInstance()->format($examplePhoneNumber, $options['format']),
]
);
});
->setDefault('type', \libphonenumber\PhoneNumberType::FIXED_LINE_OR_MOBILE);
}
public function getParent()

View File

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

View File

@ -1,17 +1,22 @@
/**
* Generic api method that can be adapted to any fetch request
*/
const makeFetch = (method, url, body) => {
return fetch(url, {
const makeFetch = (method, url, body, options) => {
let opts = {
method: method,
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: (body !== null) ? JSON.stringify(body) : null
})
};
if (typeof options !== 'undefined') {
opts = Object.assign(opts, options);
}
return fetch(url, opts)
.then(response => {
if (response.ok) {
console.log('200 error')
return response.json();
}

View File

@ -1,37 +1,26 @@
.confidential {
display: flex;
position: relative;
}
.toggle-far-twig {
i {
bottom: 0px;
right: -30px;
}
margin-right: 20px
}
.toggle-close-twig {
i {
bottom: 0px;
right: -5px;
}
.toggle-container {
position: absolute;
width: 100%;
top: 0;
left: 0;
cursor: pointer;
z-index: 5;
padding-right: 1rem;
}
.toggle{
margin-left: 30px;
margin-top: 5px;
cursor: pointer;
position: absolute;
z-index: 5;
right: -30px
}
.toggle-far {
bottom: 0px;
right: 20px !important;
}
.toggle-close {
bottom: 125px;
right: 15px !important;
right: 4px;
&-twig {
position: absolute;
right: -25px;
bottom: 20px;
}
}
.blur {

View File

@ -2,18 +2,19 @@ require('./blur.scss');
document.querySelectorAll('.confidential').forEach(function (el) {
let i = document.createElement('i');
const classes = ['fa', 'fa-eye', 'toggle'];
const classes = ['fa', 'fa-eye-slash', 'toggle-twig'];
i.classList.add(...classes);
el.appendChild(i);
const toggleBlur = function(e) {
for (let child of el.children) {
if (!child.classList.contains('toggle')) {
if (!child.classList.contains('toggle-twig')) {
child.classList.toggle('blur');
}
}
i.classList.toggle('fa-eye');
i.classList.toggle('fa-eye-slash');
i.classList.toggle('fa-eye');
}
i.addEventListener('click', toggleBlur);
toggleBlur();
});
});

View File

@ -22,6 +22,7 @@ const fetchCountries = () => {
*/
const fetchCities = (country) => {
//console.log('<<< fetching cities for', country);
// warning: do not use fetchResults (in apiMethods): we need only a **part** of the results in the db
const url = `/api/1.0/main/postal-code.json?item_per_page=1000&country=${country.id}`;
return fetch(url)
.then(response => {

View File

@ -3,8 +3,8 @@
<div :class="{ 'blur': isBlurred }">
<slot name="confidential-content"></slot>
</div>
<div>
<i class="fa fa-eye toggle" :class="positionBtn" aria-hidden="true" @click="toggleBlur"></i>
<div class="toggle-container">
<i class="fa toggle" :class="toggleIcon" aria-hidden="true" @click="toggleBlur"></i>
</div>
</div>
</template>
@ -12,28 +12,24 @@
<script>
export default {
name: "Confidential",
props: ['positionBtnFar'],
data() {
return {
isBlurred: true,
toggleIcon: 'fa-eye',
};
},
methods : {
toggleBlur() {
console.log(this.positionBtnFar);
this.isBlurred = !this.isBlurred;
this.toggleIcon = this.isBlurred ? 'fa-eye' : 'fa-eye-slash';
},
},
computed: {
positionBtn() {
return this.positionBtnFar ? 'toggle-far' : 'toggle-close'
}
}
}
</script>
<style scoped lang='scss'>
.confidential{
align-items: center;
align-content: center;
}
</style>

View File

@ -59,7 +59,7 @@
must be shown in such list
#}
{%- if render == 'list' -%}
<li class="chill-entity entity-address {% if address.confidential %} confidential toggle-far-twig {% endif %}">
<li class="chill-entity entity-address {% if address.confidential %} confidential {% endif %}">
{% if options['with_picto'] %}
<i class="fa fa-li fa-map-marker"></i>
{% endif %}
@ -68,7 +68,7 @@
{%- endif -%}
{%- if render == 'inline' -%}
<span class="chill-entity entity-address {% if address.confidential %} confidential toggle-far-twig {% endif %}">
<span class="chill-entity entity-address {% if address.confidential %} confidential {% endif %}">
{% if options['with_picto'] %}
<i class="fa fa-fw fa-map-marker"></i>
{% endif %}
@ -77,7 +77,7 @@
{%- endif -%}
{%- if render == 'bloc' -%}
<div class="chill-entity entity-address {% if address.confidential %} confidential toggle-close-twig {% endif %}">
<div class="chill-entity entity-address {% if address.confidential %} confidential {% endif %}">
{% if options['has_no_address'] == true and address.isNoAddress == true %}
{% if address.postCode is not empty %}
<div class="address{% if options['multiline'] %} multiline{% endif %}{% if options['with_delimiter'] %} delimiter{% endif %}">

View File

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

View File

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

View File

@ -413,6 +413,7 @@ workflow:
Previous workflow without reaction help: Liste des workflows où vous avez été cité comme pouvant réagir à une étape, mais où un autre utilisateur a exécuté une action avant vous.
Previous transitionned: Anciens workflows
Previous workflow transitionned help: Workflows où vous avez exécuté une action.
For: Pour
Subscribe final: Recevoir une notification à l'étape finale

View File

@ -15,6 +15,7 @@ use Chill\MainBundle\DependencyInjection\MissingBundleException;
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController;
use Chill\PersonBundle\Doctrine\DQL\AddressPart;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodCommentVoter;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
@ -415,6 +416,25 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
],
],
],
[
'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Comment::class,
'name' => 'accompanying_period_comment',
'base_path' => '/api/1.0/person/accompanying-period/comment',
'base_role' => 'ROLE_USER',
'actions' => [
'_entity' => [
'methods' => [
Request::METHOD_GET => false,
Request::METHOD_PATCH => true,
Request::METHOD_HEAD => false,
Request::METHOD_DELETE => false,
],
'roles' => [
Request::METHOD_PATCH => AccompanyingPeriodCommentVoter::EDIT,
],
],
],
],
[
'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Resource::class,
'name' => 'accompanying_period_resource',

View File

@ -257,9 +257,11 @@ class AccompanyingPeriod implements
/**
* @ORM\ManyToOne(
* targetEntity=Comment::class
* targetEntity=Comment::class,
* cascade={"persist"},
* )
* @Groups({"read"})
* @ORM\JoinColumn(onDelete="SET NULL")
*/
private ?Comment $pinnedComment = null;

View File

@ -33,6 +33,8 @@ div.banner {
padding-top: 1em;
padding-bottom: 1em;
div.contact {
display: flex;
align-content: center;
& > * {
margin-right: 1em;
}

View File

@ -14,24 +14,27 @@
<ckeditor
name="content"
v-bind:placeholder="$t('comment.content')"
:placeholder="$t('comment.content')"
:editor="editor"
v-model="content"
tag-name="textarea">
</ckeditor>
<div v-if="pinnedComment" class="metadata">
{{ $t('comment.created_by', [
pinnedComment.creator.text,
$d(pinnedComment.createdAt.datetime, 'long')
]) }}
<div class="sub-comment">
<div v-if="pinnedComment !== null && typeof pinnedComment.creator !== 'undefined'" class="metadata">
{{ $t('comment.created_by', [
pinnedComment.creator.text,
$d(pinnedComment.updatedAt.datetime, 'long')
])
}}
</div>
<div class="loading">
<i v-if="loading" class="fa fa-circle-o-notch fa-spin" :title="$t('loading')"></i>
</div>
</div>
<div>
<ul class="record_actions">
<li>
<button type="submit" class="btn btn-save">{{ $t('action.save') }}</button>
</li>
<li v-if="pinnedComment !== null">
<a class="btn btn-delete"
@click="removeComment">
@ -50,6 +53,7 @@
<script>
import CKEditor from '@ckeditor/ckeditor5-vue';
import ClassicEditor from "ChillMainAssets/module/ckeditor5";
import { mapState } from "vuex";
export default {
name: "Comment",
@ -59,22 +63,58 @@ export default {
data() {
return {
editor: ClassicEditor,
formdata: {
type: "accompanying_period_comment",
content: ''
}
loading: false,
lastRecordedContent: null,
}
},
computed: {
pinnedComment() {
return this.$store.state.accompanyingCourse.pinnedComment;
},
...mapState({
pinnedComment: state => state.accompanyingCourse.pinnedComment,
}),
content: {
set(value) {
this.formdata.content = value;
console.log('new comment value', value);
console.log('previous value', this.lastRecordedContent);
this.lastRecordedContent = value;
setTimeout(() => {
console.log('performing test on ', value);
if (this.lastRecordedContent === value) {
this.loading = true;
if (value !== '') {
this.$store.dispatch('updatePinnedComment', value)
.then(() => {
this.loading = false;
})
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
} else {
if (this.$store.state.accompanyingCourse.pinnedComment !== null) {
this.$store.dispatch('removePinnedComment', {id: this.pinnedComment.id})
.then(() => {
this.loading = false;
this.lastRecoredContent = 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'})
}
});
}
}
}
}, 3000);
},
get() {
return (this.pinnedComment)? this.pinnedComment.content : {};
get() {
return this.pinnedComment ? this.pinnedComment.content : '';
}
},
errors() {
@ -82,18 +122,11 @@ export default {
}
},
methods: {
submitform() {
this.$store.dispatch('postFirstComment', this.formdata)
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
} else {
this.$toast.open({message: 'An error occurred'})
}
});
onContentChange() {
let lastRecordedContent = this.formData.content;
},
removeComment() {
this.$store.dispatch('postFirstComment', {})
this.$store.dispatch('removePinnedComment', {id: this.pinnedComment.id})
.catch(({name, violations}) => {
if (name === 'ValidationException' || name === 'AccessException') {
violations.forEach((violation) => this.$toast.open({message: violation}));
@ -104,15 +137,18 @@ export default {
}
}
}
/*
* TODO
* - [x] delete button in ul record_actions, but not in form
* - [ ] display updatedAt => pinnedComment fetch PATCH content changes MUST NOT change object id !!
*/
</script>
<style lang="scss">
div.ck-editor.ck-reset {
margin: 0.6em 0;
}
div.sub-comment {
display: flex;
justify-content: space-between;
div.loading {
margin-right: 6px;
margin-left: 6px;
}
}
</style>

View File

@ -9,7 +9,7 @@
<input type="checkbox" v-model="requestorIsAnonymous" class="me-2" />
{{ $t('requestor.is_anonymous') }}
</label>
<confidential :positionBtn="false" v-if="accompanyingCourse.requestor.type === 'thirdparty'">
<confidential v-if="accompanyingCourse.requestor.type === 'thirdparty'">
<template v-slot:confidential-content>
<third-party-render-box
:thirdparty="accompanyingCourse.requestor"
@ -33,7 +33,7 @@
</template>
</confidential>
<confidential :positionBtnFar="false" v-else-if="accompanyingCourse.requestor.type === 'person'">
<confidential v-else-if="accompanyingCourse.requestor.type === 'person'">
<template v-slot:confidential-content>
<person-render-box render="bloc"
:person="accompanyingCourse.requestor"
@ -339,5 +339,6 @@ div.flex-table {
.confidential {
display: block;
margin-right: 0px !important;
}
</style>

View File

@ -122,7 +122,7 @@ const appMessages = {
title: "Observations",
label: "Ajout d'une note",
content: "Rédigez une première note…",
created_by: "créé par {0}, le {1}"
created_by: "créé par {0}, mis à jour le {1}"
},
confirm: {
title: "Confirmation",

View File

@ -42,7 +42,11 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
referrersSuggested: [],
// all the users available
users: [],
permissions: {}
permissions: {},
// controller for updating comment
updateCommentAbortController: null,
// waiting response for inserting first comment
postFirstPinnedCommentResponse: null,
},
getters: {
isParticipationValid(state) {
@ -203,9 +207,35 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
//console.log('### mutation: toggleConfidential');
state.accompanyingCourse.confidential = value;
},
postFirstComment(state, comment) {
//console.log('### mutation: postFirstComment', comment);
state.accompanyingCourse.pinnedComment = comment;
setPinnedComment(state, content) {
if (null === state.accompanyingCourse.pinnedComment) {
state.accompanyingCourse.pinnedComment = {
id: -1,
content,
type: "accompanying_period_comment",
};
} else {
state.accompanyingCourse.pinnedComment.content = content;
}
},
setPinnedCommentDetails(state, value) {
state.accompanyingCourse.pinnedComment.id = value.id;
if (typeof value.creator !== 'undefined') {
state.accompanyingCourse.pinnedComment.creator = value.creator;
state.accompanyingCourse.pinnedComment.updatedBy = value.updatedBy;
state.accompanyingCourse.pinnedComment.updatedAt = value.updatedAt;
state.accompanyingCourse.pinnedComment.createdAt = value.createdAt;
}
},
removePinnedComment(state, value) {
state.accompanyingCourse.pinnedComment = null;
},
setPinCommentAbortController(state, value) {
state.updateCommentAbortController = value;
},
setPostFirstPinnedCommentResponse(state, value) {
state.postFirstPinnedCommentResponse = value;
},
updateSocialIssues(state, value) {
console.log('updateSocialIssues', value);
@ -337,6 +367,87 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
throw error;
})
},
/**
* Add/remove pinnedComment
*/
removePinnedComment({ commit }, payload) {
const body = {type: "accompanying_period_comment", id: payload.id};
const url = `/api/1.0/person/accompanying-course/${id}/comment.json`;
return makeFetch('DELETE', url, body)
.then((response) => {
commit('removePinnedComment');
})
.catch((error) => {
commit('catchError', error);
throw error;
})
},
/**
* internal method to insert a new comment
*
* @param commit
* @param dispatch
* @param content
* @returns {*}
*/
addPinnedComment({ commit, dispatch }, content) {
const url = `/api/1.0/person/accompanying-course/${id}/comment.json`;
return makeFetch('POST', url, {type: "accompanying_period_comment", content})
.then((response) => {
commit('setPinnedCommentDetails', response);
return dispatch('patchFirstComment', response);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
},
updatePinnedComment({ commit, state, dispatch }, content) {
commit('setPinnedComment', content);
if (state.accompanyingCourse.pinnedComment.id === -1 && state.postFirstPinnedCommentResponse === null) {
let r = dispatch('addPinnedComment', content).then(() => {
commit('setPostFirstPinnedCommentResponse', null);
});
commit('setPostFirstPinnedCommentResponse', r);
} else {
(state.postFirstPinnedCommentResponse === null ? Promise.resolve() : state.postFirstPinnedCommentResponse).then(() => {
dispatch('updateExistingPinnedComment', content);
});
}
},
/**
* internal method to patch an existing comment
*
* @param commit
* @param state
* @param comment
* @returns {*}
*/
updateExistingPinnedComment({ commit, state }, content) {
const payload = {type: "accompanying_period_comment", content, id: state.accompanyingCourse.pinnedComment.id};
const url = `/api/1.0/person/accompanying-period/comment/${payload.id}.json`;
if (state.updateCommentAbortController !== null) {
state.updateCommentAbortController.abort();
commit('setPinCommentAbortController', null);
}
let controller = new AbortController();
commit('setPinCommentAbortController', controller);
return makeFetch('PATCH', url, payload, { signal: controller.signal })
.then((response) => {
commit('setPinCommentAbortController', null);
commit('setPinnedCommentDetails', response);
})
.catch((error) => {
commit('catchError', error);
commit('setPinCommentAbortController', null);
throw error;
})
},
/**
* Add/remove/display anonymous requestor
*/
@ -606,18 +717,20 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
return Promise.all(promises);
},
postFirstComment({ commit }, payload) {
const url = `/api/1.0/person/accompanying-course/${id}.json`
const body = { type: "accompanying_period", pinnedComment: payload }
patchFirstComment({ commit }, payload) {
const url = `/api/1.0/person/accompanying-course/${id}.json`;
const body = {
type: "accompanying_period",
pinnedComment: {
type: "accompanying_period_comment",
id: payload.id
}
};
return makeFetch('PATCH', url, body)
.then((response) => {
commit('postFirstComment', response.pinnedComment);
})
.catch((error) => {
commit('catchError', error);
throw error;
})
});
},
updateSocialIssues({ state, commit, dispatch }, { payload, body, method }) {
const url = `/api/1.0/person/accompanying-course/${id}/socialissue.json`;

View File

@ -87,10 +87,14 @@ export default {
currentMembers() {
let members = this.household.members.filter(m => this.household.current_members_id.includes(m.id))
.sort((a, b) => {
if (a.position.ordering < b.position.ordering) {
const orderA = a.position ? a.position.ordering : 0;
const orderB = b.position ? b.position.ordering : 0;
if (orderA < orderB) {
return -1;
}
if (a.position.ordering > b.position.ordering) {
if (orderA > orderB) {
return 1;
}
if (a.holder && !b.holder) {

View File

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

View File

@ -167,7 +167,7 @@
<span class="chill-no-data-statement">{{ 'No data given'|trans }}</span>
</li>
{% endif %}
{% if person.email is not null %}
{% if person.email is not empty %}
<li>
<i class="fa fa-li fa-envelope-o"></i><a href="{{ 'mailto:' ~ person.email }}">
{{ person.email }}

View File

@ -49,7 +49,12 @@
<li>
<i class="fa fa-li fa-home"></i>
<span class="item-key">{{ "Address of"|trans}} </span>
<span class="chill-entity entity-person badge-person">{{ a.hostPerson|chill_entity_render_box }}</span>
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'person', id: a.hostPerson.id },
buttonText: a.hostPerson|chill_entity_render_string,
isDead: a.hostPerson.deathdate is not null
} %}
</li>
<li>
{% set address_date = date(a.startDate|date("m/d/Y")) %}
@ -62,7 +67,12 @@
<li>
<i class="fa fa-li fa-home"></i>
<span class="item-key">{{ "Address of"|trans}}</span>
<span class="chill-entity entity-person badge-thirdparty">{{ a.hostThirdParty|chill_entity_render_box }}</span>
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
action: 'show', displayBadge: true,
targetEntity: { name: 'thirdparty', id: a.hostThirdParty.id },
buttonText: a.hostThirdParty|chill_entity_render_string,
isDead: false,
} %}
</li>
<li>
{% if a.hostThirdParty.address is not null %}

View File

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

View File

@ -72,6 +72,7 @@ class AccompanyingPeriodWorkEvaluationWorkflowHandler implements EntityWorkflowH
*/
public function getRelatedObjects(object $object): array
{
$relateds = [];
$relateds[] = ['entityClass' => AccompanyingPeriodWorkEvaluation::class, 'entityId' => $object->getId()];
foreach ($object->getDocuments() as $doc) {

View File

@ -71,6 +71,7 @@ class AccompanyingPeriodWorkWorkflowHandler implements EntityWorkflowHandlerInte
*/
public function getRelatedObjects(object $object): array
{
$relateds = [];
$relateds[] = ['entityClass' => AccompanyingPeriodWork::class, 'entityId' => $object->getId()];
foreach ($object->getAccompanyingPeriodWorkEvaluations() as $evaluation) {

View File

@ -0,0 +1,38 @@
<?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;
/**
* Change constraint on pinnedComment in Accompanying period.
*/
final class Version20220310124318 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period DROP CONSTRAINT fk_e260a868b0804e90');
$this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT fk_e260a868b0804e90 FOREIGN KEY (pinnedcomment_id) REFERENCES chill_person_accompanying_period_comment (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function getDescription(): string
{
return 'Change constraint on pinnedComment in Accompanying period';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period DROP CONSTRAINT FK_E260A868B0804E90');
$this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT FK_E260A868B0804E90 FOREIGN KEY (pinnedComment_id) REFERENCES chill_person_accompanying_period_comment (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE');
}
}

View File

@ -124,8 +124,8 @@ address_country_code: Code pays
'Alreay existing person': 'Dossiers déjà encodés'
'Add the person': 'Ajouter la personne'
'Add the person and create an accompanying period': "Créer la personne & créer une période d'accompagnement"
'Add the person and create a household': "Créer la personne & créer un ménage"
'Add the person and create an accompanying period': "Créer l'usager ET créer une période d'accompagnement"
'Add the person and create a household': "Créer l'usager' ET créer un ménage"
Show person: Voir le dossier de la personne
'Confirm the creation': 'Confirmer la création'
'You will create this person': 'Vous allez créer le dossier suivant'

View File

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

View File

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

View File

@ -36,7 +36,7 @@
<ul class="list-content fa-ul">
<li v-if="getProfession.length > 0">
<i class="fa fa-li fa-id-card"></i>
<p><span v-for="p in getProfession" :key="p" class="list-item">{{ p[0].toUpperCase() + p.slice(1).toLowerCase() }}</span></p>
<p><span v-for="p in getProfession" :key="p" class="list-item list-professions">{{ p[0].toUpperCase() + p.slice(1).toLowerCase() }}</span></p>
</li>
<li v-if="hasParent">
<i class="fa fa-li fa-hand-o-right"></i>
@ -165,7 +165,7 @@ export default {
}
}
.list-item {
.list-professions {
&::after {
content: " | ";
}

View File

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