diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 295eb3e0e..68b1bcf08 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,7 +15,9 @@ before_script:
- curl -sS https://getcomposer.org/installer | php
- php -d memory_limit=2G composer.phar install
- php tests/app/bin/console doctrine:migrations:migrate -n
- - php -d memory_limit=2G tests/app/bin/console doctrine:fixtures:load -n
+ - php -d memory_limit=2G tests/app/bin/console cache:clear --env=dev
+ - php -d memory_limit=3G tests/app/bin/console doctrine:fixtures:load -n
+ - php -d memory_limit=2G tests/app/bin/console cache:clear --env=test
- echo "before_script finished"
# Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
diff --git a/.gitlab/merge_request_templates/Default merge request.md b/.gitlab/merge_request_templates/Default merge request.md
new file mode 100644
index 000000000..5d62f91eb
--- /dev/null
+++ b/.gitlab/merge_request_templates/Default merge request.md
@@ -0,0 +1,24 @@
+
+# Description of changes
+
+
+
+
+# Issues related
+
+
+
+* ...
+* ...
+
+# Tests
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index de73e3467..8e6252d4d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,57 @@ and this project adheres to
## Unreleased
+* unnecessary whitespace removed from person banner after person-id + double parentheses removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/290)
+* [person]: delete accompanying period work, including related objects (cascade) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/36)
+* [address]: Display of incomplete address adjusted.
+* [household]: improve relationship graph
+ * add form to create/edit/delete relationship link,
+ * improve graph refresh mechanism
+ * add feature to export canvas as image (png)
+
+## Test releases
+
+### Test release 2021-11-08
+
+* [person]: Display the name of a user when searching after a User (TMS)
+* [person]: Add civility to the person
+* [person]: Various improvements on the edit person form
+* [person]: Set available_languages and available_countries as parameters for use in the edit person form
+* [activity] Bugfix: documents can now be added to an activity.
+* [tasks] improve tasks with filter order
+* [tasks] refactor singleControllerTasks: limit the number of conditions from the context
+* [validations] validation of accompanying period added: no duplicate participations or resources (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/60).
+* [renderbox] If gender of person is not defined, no icon is displayed instead of neuter-icon (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/129).
+* [confidential information] module added to blur confidential information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/248).
+* refactor `AuthorizationHelper` and `UserACLAwareRepository` to fix constructor, and separate logic for parent role helper into `ParentRoleHelper`
+* [main]: filter location and locationType in backend: exclude NULL names, only active and availableToUsers
+* [activity]: perform client-side validation & show/hide fields in the "new location" modal
+* [person]: normalize person with CenterResolverDispatcher and handle case where center is null or multiple in PersonRenderBox
+* [docstore] voter for PersonDocument and AccompanyingCourseDocument on the 2.0 way (using VoterHelperFactory)
+* [docstore] add authorization check inside controller and menu
+* [activity]: fix inheritance for role `ACTIVITY FULL` and add missing acl in menu
+* [person] show current address in search results
+* [person] show alt names in search results
+* [admin]: links to activity admin section added again.
+* [household]: endDate field deleted from household edit form.
+* [household]: View accompanying periods of current and old household members.
+* [tasks]: different layout for task list / my tasks, and fix link to tasks in alert or in warning
+* [admin]: links to activity admin section added again.
+* [household]: household addresses ordered by ValidFrom date and by id to show the last created address on top.
+* [socialWorkAction]: display of social issue and parent issues + banner context added.
+* [DBAL dependencies] Upgrade to DBAL 3.1
+* [person]: double parentheses removed around age in banner + whitespace
+
+### Test release 2021-10-27
+
+* [person]: delete double actions buttons on search person page
+* [person]: accompanying course work: remove creation date display the list of work + handle case when end date is null
+* [main]: Add new pages with a menu for managing location and location type in the admin
+* [main]: Add some fixtures for location type
+* [calendar]: Pass the location when transforming a calendar item (rdv) into an activity
+* [calendar]: Add a user menu for "my calendar"
+
+### Test release 2021-10-18
* [3party]: french translation of contact and company
* [3party]: show parent in list
@@ -21,13 +72,14 @@ and this project adheres to
* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location.
* [household]: add relationship page with dynamic data visualisation graph
-
## Test releases
### Test release 2021-10-11
* Address: zoom on postal code geometry + fix origin of manually entered postal code
+* in the Address vue component, order the postal code and street address by alphabetic and numeric order
+
* add 3 new fields to PostalCode and adapt postal code command and fixtures
* [Aside activity] Fixes for aside activity
@@ -87,7 +139,7 @@ and this project adheres to
## Test released
-{% endblock %}
+ {{ encore_entry_link_tags('mod_blur') }}
+ {% block css %}{% endblock %}
@@ -88,6 +89,7 @@
{{ encore_entry_script_tags('mod_bootstrap') }}
{{ encore_entry_script_tags('mod_forkawesome') }}
{{ encore_entry_script_tags('mod_ckeditor5') }}
+ {{ encore_entry_script_tags('mod_blur') }}
{{ encore_entry_script_tags('chill') }}
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js
index ee4c3e11b..c372ac7a7 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/api.js
@@ -1,3 +1,5 @@
+import { fetchResults } from 'ChillMainAssets/lib/api/download.js';
+
/*
* Endpoint v.2 chill_api_single_accompanying_course__entity
* method GET/HEAD, get AccompanyingCourse Instance
@@ -84,7 +86,8 @@ const postParticipation = (id, payload, method) => {
})
.then(response => {
if (response.ok) { return response.json(); }
- throw { msg: 'Error while sending AccompanyingPeriod Course participation.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
+ // TODO: adjust message according to status code? Or how to access the message from the violation array?
+ throw { msg: 'Error while sending AccompanyingPeriod Course participation', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
});
};
@@ -168,11 +171,8 @@ const postSocialIssue = (id, body, method) => {
const getUsers = () => {
const url = `/api/1.0/main/user.json`;
- return fetch(url)
- .then(response => {
- if (response.ok) { return response.json(); }
- throw { msg: 'Error while retriving users.', sta: response.status, txt: response.statusText, err: new Error(), body: response.body };
- });
+
+ return fetchResults(url);
};
const whoami = () => {
@@ -216,8 +216,6 @@ const addScope = (id, scope) => {
const removeScope = (id, scope) => {
const url = `/api/1.0/person/accompanying-course/${id}/scope.json`;
- console.log(url);
- console.log(scope);
return fetch(url, {
method: 'DELETE',
@@ -235,6 +233,12 @@ const removeScope = (id, scope) => {
});
};
+const getReferrersSuggested = (course) => {
+ const url = `/api/1.0/person/accompanying-course/${course.id}/referrers-suggested.json`;
+
+ return fetchResults(url);
+}
+
export {
getAccompanyingCourse,
patchAccompanyingCourse,
@@ -249,4 +253,5 @@ export {
postSocialIssue,
addScope,
removeScope,
+ getReferrersSuggested,
};
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue
index 30001028c..30ad6afe0 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue
@@ -10,13 +10,13 @@
@@ -41,30 +52,29 @@
import VueMultiselect from 'vue-multiselect';
import { getUsers, whoami } from '../api';
import { mapState } from 'vuex';
+import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge";
export default {
name: "Referrer",
- components: { VueMultiselect },
- data() {
- return {
- options: []
- }
+ components: {
+ UserRenderBoxBadge,
+ VueMultiselect,
},
computed: {
...mapState({
value: state => state.accompanyingCourse.user,
+ users: state => state.users,
+ referrersSuggested: state => {
+ return state.referrersSuggested.filter(u => {
+ if (null === state.accompanyingCourse.user) {
+ return true;
+ }
+ return state.accompanyingCourse.user.id !== u.id;
+ })
+ },
}),
},
- mounted() {
- this.getOptions();
- },
methods: {
- getOptions() {
- getUsers().then(response => new Promise((resolve, reject) => {
- this.options = response.results;
- resolve();
- }));
- },
updateReferrer(value) {
//console.log('value', value);
this.$store.dispatch('updateReferrer', value);
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue
index cb4b243bb..6aedd373f 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue
@@ -3,51 +3,60 @@
{{ $t('requestor.title') }}
-
+
{{ $t('requestor.is_anonymous') }}
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('requestor.is_anonymous') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t('requestor.counter') }}
@@ -82,6 +142,7 @@ import AddPersons from 'ChillPersonAssets/vuejs/_components/AddPersons.vue';
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
import PersonRenderBox from '../../_components/Entity/PersonRenderBox.vue';
import ThirdPartyRenderBox from 'ChillThirdPartyAssets/vuejs/_components/Entity/ThirdPartyRenderBox.vue';
+import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue';
export default {
name: 'Requestor',
@@ -90,7 +151,9 @@ export default {
OnTheFly,
PersonRenderBox,
ThirdPartyRenderBox,
+ Confidential
},
+ props: ['isAnonymous'],
data() {
return {
addPersons: {
@@ -149,4 +212,8 @@ div.flex-table {
margin-top: 1em;
}
}
+
+.confidential {
+ display: block;
+}
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue
index bd70fa090..7f209f0d4 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue
@@ -2,7 +2,7 @@
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js
index e3fdec4a3..ace482d79 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js
@@ -2,6 +2,8 @@ import { createApp } from 'vue'
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
import { appMessages } from './js/i18n'
import { initPromise } from './store'
+import VueToast from 'vue-toast-notification';
+import 'vue-toast-notification/dist/theme-sugar.css';
import App from './App.vue';
import Banner from './components/Banner.vue';
@@ -21,6 +23,7 @@ if (root === 'app') {
})
.use(store)
.use(i18n)
+ .use(VueToast)
.component('app', App)
.mount('#accompanying-course');
});
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js
index ca1a89aa9..41e341dd8 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/store/index.js
@@ -10,6 +10,8 @@ import { getAccompanyingCourse,
postSocialIssue,
addScope,
removeScope,
+ getReferrersSuggested,
+ getUsers,
} from '../api';
import { patchPerson } from "ChillPersonAssets/vuejs/_api/OnTheFly";
import { patchThirdparty } from "ChillThirdPartyAssets/vuejs/_api/OnTheFly";
@@ -38,6 +40,10 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
scopesAtStart: accompanyingCourse.scopes.map(scope => scope),
// the scope states at server side
scopesAtBackend: accompanyingCourse.scopes.map(scope => scope),
+ // the users which are available for referrer
+ referrersSuggested: [],
+ // all the users available
+ users: [],
},
getters: {
isParticipationValid(state) {
@@ -71,7 +77,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
},
mutations: {
catchError(state, error) {
- console.log('### mutation: a new error have been catched and pushed in store !', error);
+ // console.log('### mutation: a new error have been catched and pushed in store !', error);
state.errorMsg.push(error);
},
removeParticipation(state, participation) {
@@ -170,6 +176,16 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
//console.log('value', value);
state.accompanyingCourse.user = value;
},
+ setReferrersSuggested(state, users) {
+ state.referrersSuggested = users.map(u => {
+ if (state.accompanyingCourse.user !== null) {
+ if (state.accompanyingCourse.user.id === u.id) {
+ return state.accompanyingCourse.user;
+ }
+ }
+ return u;
+ });
+ },
confirmAccompanyingCourse(state, response) {
//console.log('### mutation: confirmAccompanyingCourse: response', response);
state.accompanyingCourse.step = response.step;
@@ -178,6 +194,16 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
//console.log('define location context');
state.addressContext = context;
},
+ setUsers(state, users) {
+ state.users = users.map(u => {
+ if (state.accompanyingCourse.user !== null) {
+ if (state.accompanyingCourse.user.id === u.id) {
+ return state.accompanyingCourse.user;
+ }
+ }
+ return u;
+ });
+ },
updateLocation(state, r) {
//console.log('### mutation: set location attributes', r);
state.accompanyingCourse.locationStatus = r.locationStatus;
@@ -272,6 +298,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
})).catch((error) => { commit('catchError', error) });
},
patchOnTheFly({ commit }, payload) {
+ // TODO should be into the dedicated component, no ? JF
console.log('## action: patch OnTheFly', payload);
let body = { type: payload.type };
if (payload.type === 'person') {
@@ -313,10 +340,12 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
},
toggleEmergency({ commit }, payload) {
patchAccompanyingCourse(id, { type: "accompanying_period", emergency: payload })
- .then(course => new Promise((resolve, reject) => {
- commit('toggleEmergency', course.emergency);
- resolve();
- })).catch((error) => { commit('catchError', error) });
+ .then(course => new Promise((resolve, reject) => {
+ commit('toggleEmergency', course.emergency);
+ return dispatch('setRefererresAvailable');
+ }))
+ .then(() => Promise.resolve())
+ .catch((error) => { commit('catchError', error) });
},
toggleConfidential({ commit }, payload) {
patchAccompanyingCourse(id, { type: "accompanying_period", confidential: payload })
@@ -387,11 +416,15 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
// check/uncheck in the UI. I do not know of to avoid it.
commit('setScopes', scopes);
return Promise.resolve();
+ }).then(() => {
+ return dispatch('fetchReferrersSuggested');
});
} else {
return dispatch('setScopes', state.scopesAtStart).then(() => {
commit('setScopes', scopes);
return Promise.resolve();
+ }).then(() => {
+ return dispatch('fetchReferrersSuggested');
});
}
},
@@ -434,7 +467,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
resolve();
})).catch((error) => { commit('catchError', error) });
},
- updateSocialIssues({ state, commit }, { payload, body, method }) {
+ updateSocialIssues({ state, commit, dispatch }, { payload, body, method }) {
//console.log('## action: payload', { payload, body, method });
postSocialIssue(id, body, method)
.then(response => new Promise((resolve, reject) => {
@@ -445,6 +478,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
return getAccompanyingCourse(state.accompanyingCourse.id);
}).then(accompanying_course => {
commit('refreshSocialIssues', accompanying_course.socialIssues);
+ dispatch('fetchReferrersSuggested');
})
.catch((error) => { commit('catchError', error) });
},
@@ -462,7 +496,24 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
resolve();
})).catch((error) => { commit('catchError', error) });
},
- updateLocation({ commit }, payload) {
+ async fetchReferrersSuggested({ state, commit}) {
+ let users = await getReferrersSuggested(state.accompanyingCourse);
+ commit('setReferrersSuggested', users);
+ if (
+ null === state.accompanyingCourse.user
+ && !state.accompanyingCourse.confidential
+ && !state.accompanyingCourse.step === 'DRAFT'
+ && users.length === 1
+ ) {
+ // set the user if unique
+ commit('updateReferrer', users[0]);
+ }
+ },
+ async fetchUsers({commit}) {
+ let users = await getUsers();
+ commit('setUsers', users);
+ },
+ updateLocation({ commit, dispatch }, payload) {
//console.log('## action: updateLocation', payload.locationStatusTo);
let body = { 'type': payload.target, 'id': payload.targetId };
let location = {};
@@ -484,6 +535,7 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
personLocation: accompanyingCourse.personLocation
});
resolve();
+ dispatch('fetchReferrersSuggested');
})).catch((error) => { commit('catchError', error) });
},
confirmAccompanyingCourse({ commit }) {
@@ -498,6 +550,9 @@ let initPromise = Promise.all([scopesPromise, accompanyingCoursePromise])
}
}
});
+
+ store.dispatch('fetchReferrersSuggested');
+ store.dispatch('fetchUsers');
resolve(store);
}));
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue
index b6333b426..7931a2a0d 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue
@@ -9,7 +9,7 @@
{{ $t('pick_social_issue_linked_with_action') }}
- {{ si.title.fr }}
+ {{ si.text }}
@@ -26,7 +26,7 @@
@@ -72,7 +72,7 @@
{{ $t('action.save') }}
- {{ $t('Save') }}
+ {{ $t('action.save') }}
@@ -222,3 +222,16 @@ export default {
}
+
+
\ No newline at end of file
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue
index c99685680..5bb52358b 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue
@@ -84,7 +84,7 @@
-
{{ $t('Evaluations') }}
+
{{ $t('Evaluations') }} - {{ $t('Forms') }} - {{ $t('Post') }}
@@ -247,6 +247,8 @@ const i18n = {
add_objectif: "Ajouter un motif - objectif - dispositif",
add_an_objective: "Ajouter un objectif",
Evaluations: "Évaluations",
+ Forms: "Formulaires",
+ Post: "Courriers",
add_an_evaluation: "Ajouter une évaluation",
persons_involved: "Usagers concernés",
handling_thirdparty: "Tiers traitant",
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue
index 931830fd3..e3b4901d7 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue
@@ -20,18 +20,25 @@
v-bind:item="item">
+
+
+
+
+
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue
index 530a14e95..eb7793839 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue
@@ -35,7 +35,7 @@
- {{ birthdate }} - {{ deathdate }}
+ {{ $d(birthdate) }} - {{ $d(deathdate) }}
@@ -97,8 +97,13 @@
-
- {{ person.center.name }}
+
+
+ {{ person.center.name }}
+
+
+ {{ c.name }}
+
@@ -138,11 +143,13 @@
+ {{ parent() }}
{{ encore_entry_script_tags('vue_accourse_work_create') }}
{% endblock %}
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig
new file mode 100644
index 000000000..c66e5aa3d
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/delete.html.twig
@@ -0,0 +1,34 @@
+{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_person_accompanying_period_work_list' %}
+
+{% block title 'accompanying_course_work.remove'|trans %}
+
+{% block content %}
+
+
+
+ {{ 'accompanying_course_work.action'|trans }}
+ {{ work.socialAction|chill_entity_render_string }}
+
+
+
+
{{ "Associated peoples"|trans }}
+
+ {% for p in work.persons %}
+ {{ p|chill_entity_render_box }}
+ {% endfor %}
+
+
+
+
+
+ {{ include('@ChillMain/Util/confirmation_template.html.twig',
+ {
+ 'title' : 'accompanying_course_work.remove'|trans,
+ 'confirm_question' : 'Are you sure you want to remove this work of the accompanying period %name% ?'|trans({ '%name%' : accompanyingCourse.id } ),
+ 'cancel_route' : 'chill_person_accompanying_period_work_list',
+ 'cancel_parameters' : {'id' : accompanyingCourse.id},
+ 'form' : delete_form
+ } ) }}
+{% endblock %}
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig
index f0f0a051e..797f1bcc8 100644
--- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig
+++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/list_by_accompanying_period.html.twig
@@ -20,14 +20,6 @@
-
-
- {{ w.createdAt|format_date('long') }}
-
-
- {{ 'accompanying_course_work.create_date'|trans }}
-
-
{{ w.startDate|format_date('long') }}
@@ -36,6 +28,11 @@
{{ 'accompanying_course_work.start_date'|trans }}
+ {% if w.endDate == null %}
+
+
+
+ {% else %}
{{ w.endDate|format_date('long') }}
@@ -44,6 +41,7 @@
{{ 'accompanying_course_work.end_date'|trans }}
+ {% endif %}
@@ -105,6 +103,11 @@
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
>{% if buttonText is not defined or buttonText == true %}{{ 'Edit'|trans }}{% endif %}
+
+ {% if buttonText is not defined or buttonText == true %}{{ 'Delete'|trans }}{% endif %}
+
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list.html.twig
index a53a7c0d0..c5797a4b2 100644
--- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list.html.twig
+++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/_list.html.twig
@@ -77,7 +77,7 @@
{{ 'Participants'|trans }}
{%- if options['addInfo'] -%}
{% set gender = (person.gender == 'woman') ? 'fa-venus' :
- (person.gender == 'man') ? 'fa-mars' : 'fa-neuter' %}
+ (person.gender == 'man') ? 'fa-mars' : (person.gender == 'neuter') ? 'fa-neuter' : 'fa-genderless' %}
{% set genderTitle = (person.gender == 'woman') ? 'woman' :
- (person.gender == 'man') ? 'man' : 'neuter' %}
+ (person.gender == 'man') ? 'man' : (person.gender == 'neuter') ? 'neuter' : 'Not given'|trans %}
@@ -95,7 +95,7 @@
{%- if options['addAge'] -%}
- ({{ 'years_old'|trans({ 'age': person.age }) }})
+ {{- 'years_old'|trans({ 'age': person.age }) -}}
{%- endif -%}
{%- endif -%}
@@ -123,13 +123,15 @@
-
-
- {% else %}
-
-
{% endif %}
{% if preview == false %}
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig
index a67723f27..0351d0ba1 100644
--- a/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig
+++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/view.html.twig
@@ -51,6 +51,15 @@ This view should receive those arguments:
{{ 'General information'|trans }}
+ {% if person.civility is not null %}
+ {{ 'Civility'|trans }} :
+
+ {% if person.civility.name|length > 0 %}
+ {{ person.civility.name|first }}
+ {% endif %}
+
+ {% endif %}
+
{{ 'First name'|trans }} :
{{ person.firstName }}
@@ -93,14 +102,14 @@ This view should receive those arguments:
{%- endif -%}
{%- if chill_person.fields.country_of_birth == 'visible' -%}
- {{ 'Country of birth'|trans }} :
- {% apply spaceless %}
- {% if person.countryOfBirth is not null %}
- {{ person.countryOfBirth.name|localize_translatable_string }}
- {% else %}
- {{ 'Unknown country of birth'|trans }}
- {% endif %}
- {% endapply %}
+ {{ 'Country of birth'|trans }} :
+ {% apply spaceless %}
+ {% if person.countryOfBirth is not null %}
+ {{ person.countryOfBirth.name|localize_translatable_string }}
+ {% else %}
+ {{ 'Unknown country of birth'|trans }}
+ {% endif %}
+ {% endapply %}
{%- endif -%}
@@ -198,10 +207,10 @@ This view should receive those arguments:
{%- endif -%}
{%- if chill_person.fields.phonenumber == 'visible' -%}
-
- {{ 'Phonenumber'|trans }} :
- {% if person.phonenumber is not empty %}{{ person.phonenumber|chill_format_phonenumber }} {% else %}{{ 'No data given'|trans }}{% endif %}
-
+
+ {{ 'Phonenumber'|trans }} :
+ {% if person.phonenumber is not empty %}{{ person.phonenumber|chill_format_phonenumber }} {% else %}{{ 'No data given'|trans }}{% endif %}
+
{% endif %}
{%- if chill_person.fields.mobilenumber == 'visible' -%}
@@ -261,4 +270,4 @@ This view should receive those arguments:
{% endif %}
-{% endblock %}
+{% endblock %}
\ No newline at end of file
diff --git a/src/Bundle/ChillPersonBundle/Search/PersonSearch.php b/src/Bundle/ChillPersonBundle/Search/PersonSearch.php
index f62b90006..7fff34d7d 100644
--- a/src/Bundle/ChillPersonBundle/Search/PersonSearch.php
+++ b/src/Bundle/ChillPersonBundle/Search/PersonSearch.php
@@ -263,8 +263,9 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
public function convertTermsToFormData(array $terms)
{
- foreach(['firstname', 'lastname', 'gender', '_default']
- as $key) {
+ $data = [];
+
+ foreach(['firstname', 'lastname', 'gender', '_default'] as $key) {
$data[$key] = $terms[$key] ?? null;
}
diff --git a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php
index 380112a12..019f3989e 100644
--- a/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php
+++ b/src/Bundle/ChillPersonBundle/Serializer/Normalizer/PersonNormalizer.php
@@ -19,6 +19,7 @@
namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Center;
+use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
@@ -48,16 +49,22 @@ class PersonNormalizer implements
private PersonRepository $repository;
+ private CenterResolverDispatcher $centerResolverDispatcher;
+
use NormalizerAwareTrait;
use ObjectToPopulateTrait;
use DenormalizerAwareTrait;
- public function __construct(ChillEntityRenderExtension $render, PersonRepository $repository)
- {
+ public function __construct(
+ ChillEntityRenderExtension $render,
+ PersonRepository $repository,
+ CenterResolverDispatcher $centerResolverDispatcher
+ ) {
$this->render = $render;
$this->repository = $repository;
+ $this->centerResolverDispatcher = $centerResolverDispatcher;
}
public function normalize($person, string $format = null, array $context = array())
@@ -74,7 +81,7 @@ class PersonNormalizer implements
'lastName' => $person->getLastName(),
'birthdate' => $this->normalizer->normalize($person->getBirthdate()),
'deathdate' => $this->normalizer->normalize($person->getDeathdate()),
- 'center' => $this->normalizer->normalize($person->getCenter()),
+ 'center' => $this->normalizer->normalize($this->centerResolverDispatcher->resolveCenter($person)),
'phonenumber' => $person->getPhonenumber(),
'mobilenumber' => $person->getMobilenumber(),
'altNames' => $this->normalizeAltNames($person->getAltNames()),
diff --git a/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php
index 7da1a8cad..c68e300cf 100644
--- a/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php
+++ b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php
@@ -38,7 +38,7 @@ class SocialActionRender implements ChillEntityRenderInterface
{
/** @var $socialAction SocialAction */
$options = \array_merge(self::DEFAULT_ARGS, $options);
- $titles[] = $this->translatableStringHelper->localize($socialAction->getTitle());
+ $titles = [$this->translatableStringHelper->localize($socialAction->getTitle())];
while ($socialAction->hasParent()) {
$socialAction = $socialAction->getParent();
diff --git a/src/Bundle/ChillPersonBundle/Templating/Entity/SocialIssueRender.php b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialIssueRender.php
index 5ed80182c..80254d989 100644
--- a/src/Bundle/ChillPersonBundle/Templating/Entity/SocialIssueRender.php
+++ b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialIssueRender.php
@@ -38,8 +38,7 @@ final class SocialIssueRender implements ChillEntityRenderInterface
/** @var $socialIssue SocialIssue */
$options = array_merge(self::DEFAULT_ARGS, $options);
- $titles[] = $this->translatableStringHelper
- ->localize($socialIssue->getTitle());
+ $titles = [$this->translatableStringHelper->localize($socialIssue->getTitle())];
// loop to parent, until root
while ($socialIssue->hasParent()) {
diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php
index a24e94bfa..78376b8bd 100644
--- a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php
+++ b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php
@@ -325,6 +325,19 @@ class AccompanyingCourseApiControllerTest extends WebTestCase
$this->period = $period;
}
+ /**
+ * @dataProvider dataGenerateRandomAccompanyingCourse
+ */
+ public function testReferralAvailable(int $personId, int $periodId)
+ {
+ $this->client->request(
+ Request::METHOD_POST,
+ sprintf('/api/1.0/person/accompanying-course/%d/referrers-suggested.json', $periodId)
+ );
+
+ $this->assertEquals(200, $this->client->getResponse()->getStatusCode());
+ }
+
/**
*
* @dataProvider dataGenerateRandomAccompanyingCourse
diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php
index 3da325a9e..9b7b97a4f 100644
--- a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php
+++ b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php
@@ -193,8 +193,6 @@ class HouseholdMemberControllerTest extends WebTestCase
$form = $crawler->selectButton('Enregistrer')
->form();
- $form['household_member[endDate]'] = (new \DateTime('tomorrow'))
- ->format('Y-m-d');
$crawler = $client->submit($form);
diff --git a/src/Bundle/ChillPersonBundle/Tests/Entity/PersonTest.php b/src/Bundle/ChillPersonBundle/Tests/Entity/PersonTest.php
index dbef739d1..5b8dce6b2 100644
--- a/src/Bundle/ChillPersonBundle/Tests/Entity/PersonTest.php
+++ b/src/Bundle/ChillPersonBundle/Tests/Entity/PersonTest.php
@@ -2,8 +2,8 @@
/*
* Chill is a software for social workers
- *
- * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
+ *
+ * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
*
,
*
* This program is free software: you can redistribute it and/or modify
@@ -38,57 +38,57 @@ use Generator;
class PersonTest extends \PHPUnit\Framework\TestCase
{
/**
- * Test the creation of an accompanying, its closure and the access to
+ * Test the creation of an accompanying, its closure and the access to
* the current accompaniying period via the getCurrentAccompanyingPeriod
* function.
*/
public function testGetCurrentAccompanyingPeriod()
{
- $d = new \DateTime('yesterday');
+ $d = new \DateTime('yesterday');
$p = new Person();
$p->addAccompanyingPeriod(new AccompanyingPeriod($d));
-
+
$period = $p->getCurrentAccompanyingPeriod();
-
+
$this->assertInstanceOf(AccompanyingPeriod::class, $period);
$this->assertTrue($period->isOpen());
$this->assertEquals($d, $period->getOpeningDate());
-
+
//close and test
$period->setClosingDate(new \DateTime('tomorrow'));
-
+
$shouldBeNull = $p->getCurrentAccompanyingPeriod();
$this->assertNull($shouldBeNull);
}
-
+
/**
* Test if the getAccompanyingPeriodsOrdered function return a list of
* periods ordered ascendency.
*/
public function testAccompanyingPeriodOrderWithUnorderedAccompanyingPeriod()
- {
+ {
$d = new \DateTime("2013/2/1");
$p = new Person();
$p->addAccompanyingPeriod(new AccompanyingPeriod($d));
-
+
$e = new \DateTime("2013/3/1");
$period = $p->getCurrentAccompanyingPeriod()->setClosingDate($e);
$p->close($period);
-
+
$f = new \DateTime("2013/1/1");
$p->open(new AccompanyingPeriod($f));
-
- $g = new \DateTime("2013/4/1");
+
+ $g = new \DateTime("2013/4/1");
$period = $p->getCurrentAccompanyingPeriod()->setClosingDate($g);
$p->close($period);
-
+
$r = $p->getAccompanyingPeriodsOrdered();
-
+
$date = $r[0]->getOpeningDate()->format('Y-m-d');
-
+
$this->assertEquals($date, '2013-01-01');
}
-
+
/**
* Test if the getAccompanyingPeriodsOrdered function, for periods
* starting at the same time order regarding to the closing date.
@@ -97,25 +97,25 @@ class PersonTest extends \PHPUnit\Framework\TestCase
$d = new \DateTime("2013/2/1");
$p = new Person();
$p->addAccompanyingPeriod(new AccompanyingPeriod($d));
-
- $g = new \DateTime("2013/4/1");
+
+ $g = new \DateTime("2013/4/1");
$period = $p->getCurrentAccompanyingPeriod()->setClosingDate($g);
$p->close($period);
-
+
$f = new \DateTime("2013/2/1");
$p->open(new AccompanyingPeriod($f));
-
+
$e = new \DateTime("2013/3/1");
$period = $p->getCurrentAccompanyingPeriod()->setClosingDate($e);
$p->close($period);
$r = $p->getAccompanyingPeriodsOrdered();
-
+
$date = $r[0]->getClosingDate()->format('Y-m-d');
-
+
$this->assertEquals($date, '2013-03-01');
}
-
+
/**
* Test if the function checkAccompanyingPeriodIsNotCovering returns
* the good constant when two periods are collapsing : a period
@@ -125,23 +125,23 @@ class PersonTest extends \PHPUnit\Framework\TestCase
$d = new \DateTime("2013/2/1");
$p = new Person();
$p->addAccompanyingPeriod(new AccompanyingPeriod($d));
-
+
$e = new \DateTime("2013/3/1");
$period = $p->getCurrentAccompanyingPeriod()->setClosingDate($e);
$p->close($period);
-
+
$f = new \DateTime("2013/1/1");
$p->open(new AccompanyingPeriod($f));
-
- $g = new \DateTime("2013/4/1");
+
+ $g = new \DateTime("2013/4/1");
$period = $p->getCurrentAccompanyingPeriod()->setClosingDate($g);
$p->close($period);
-
+
$r = $p->checkAccompanyingPeriodsAreNotCollapsing();
-
+
$this->assertEquals($r['result'], Person::ERROR_PERIODS_ARE_COLLAPSING);
}
-
+
/**
* Test if the function checkAccompanyingPeriodIsNotCovering returns
* the good constant when two periods are collapsing : a period is open
@@ -151,68 +151,19 @@ class PersonTest extends \PHPUnit\Framework\TestCase
$d = new \DateTime("2013/2/1");
$p = new Person();
$p->addAccompanyingPeriod(new AccompanyingPeriod($d));
-
+
$e = new \DateTime("2013/3/1");
$period = $p->getCurrentAccompanyingPeriod()->setClosingDate($e);
$p->close($period);
-
+
$f = new \DateTime("2013/1/1");
$p->open(new AccompanyingPeriod($f));
-
+
$r = $p->checkAccompanyingPeriodsAreNotCollapsing();
-
+
$this->assertEquals($r['result'], Person::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD);
}
- public function dateProvider(): Generator
- {
- yield [(DateTime::createFromFormat('Y-m-d', '2021-01-05'))->settime(0, 0)];
- yield [(DateTime::createFromFormat('Y-m-d', '2021-02-05'))->settime(0, 0)];
- yield [(DateTime::createFromFormat('Y-m-d', '2021-03-05'))->settime(0, 0)];
- }
-
- /**
- * @dataProvider dateProvider
- */
- public function testGetLastAddress(DateTime $date)
- {
- $p = new Person($date);
-
- // Make sure that there is no last address.
- $this::assertNull($p->getLastAddress());
-
- // Take an arbitrary date before the $date in parameter.
- $addressDate = clone $date;
-
- // 1. Smoke test: Test that the first address added is the last one.
- $address1 = (new Address())->setValidFrom($addressDate->sub(new DateInterval('PT180M')));
- $p->addAddress($address1);
-
- $this::assertCount(1, $p->getAddresses());
- $this::assertSame($address1, $p->getLastAddress());
-
- // 2. Add an older address, which should not be the last address.
- $addressDate2 = clone $addressDate;
- $address2 = (new Address())->setValidFrom($addressDate2->sub(new DateInterval('PT30M')));
- $p->addAddress($address2);
-
- $this::assertCount(2, $p->getAddresses());
- $this::assertSame($address1, $p->getLastAddress());
-
- // 3. Add a newer address, which should be the last address.
- $addressDate3 = clone $addressDate;
- $address3 = (new Address())->setValidFrom($addressDate3->add(new DateInterval('PT30M')));
- $p->addAddress($address3);
-
- $this::assertCount(3, $p->getAddresses());
- $this::assertSame($address3, $p->getLastAddress());
-
- // 4. Get the last address from a specific date.
- $this::assertEquals($address1, $p->getLastAddress($addressDate));
- $this::assertEquals($address2, $p->getLastAddress($addressDate2));
- $this::assertEquals($address3, $p->getLastAddress($addressDate3));
- }
-
public function testIsSharingHousehold()
{
$person = new Person();
diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php
index 75c10ca75..b1b660596 100644
--- a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php
+++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php
@@ -11,7 +11,7 @@ class LocationValidity extends Constraint
{
public $messagePersonLocatedMustBeAssociated = "The person where the course is located must be associated to the course. Change course's location before removing the person.";
- public $messagePeriodMustRemainsLocated = "The period must remains located";
+ public $messagePeriodMustRemainsLocated = "The period must remain located";
public function getTargets()
{
diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlap.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlap.php
new file mode 100644
index 000000000..38d5516b5
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ParticipationOverlap.php
@@ -0,0 +1,15 @@
+getStartDate()->getTimezone());
+
+ foreach ($participations as $participation) {
+
+ if (!$participation instanceof AccompanyingPeriodParticipation) {
+ throw new UnexpectedTypeException($participation, AccompanyingPeriodParticipation::class);
+ }
+
+ $personId = $participation->getPerson()->getId();
+
+ $particpationList[$personId][] = $participation;
+
+ }
+
+ foreach ($particpationList as $group) {
+ if (count($group) > 1) {
+ foreach ($group as $p) {
+ $overlaps->add($p->getStartDate(), $p->getEndDate(), $p->getId());
+ }
+ }
+ }
+
+ $overlaps->compute();
+
+ if ($overlaps->hasIntersections()) {
+ foreach ($overlaps->getIntersections() as list($start, $end, $ids)) {
+ $msg = $end === null ? $constraint->message :
+ $constraint->message;
+
+ $this->context->buildViolation($msg)
+ ->setParameters([
+ '{{ start }}' => $start->format('d-m-Y'),
+ '{{ end }}' => $end === null ? null : $end->format('d-m-Y'),
+ '{{ ids }}' => $ids,
+ ])
+ ->addViolation();
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ResourceDuplicateCheck.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ResourceDuplicateCheck.php
new file mode 100644
index 000000000..bfc9d62af
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ResourceDuplicateCheck.php
@@ -0,0 +1,16 @@
+personRender = $personRender;
+ $this->thirdpartyRender = $thirdPartyRender;
+ }
+
+ public function validate($resources, Constraint $constraint)
+ {
+ if (!$constraint instanceof ResourceDuplicateCheck) {
+ throw new UnexpectedTypeException($constraint, ParticipationOverlap::class);
+ }
+
+ if (!$resources instanceof Collection) {
+ throw new UnexpectedTypeException($resources, Collection::class);
+ }
+
+ $resourceList = [];
+
+ foreach ($resources as $resource) {
+ $id = ($resource->getResource() instanceof Person ? 'p' :
+ 't').$resource->getResource()->getId();
+
+ if (\in_array($id, $resourceList, true)) {
+ $this->context->buildViolation($constraint->message)
+ ->setParameter('{{ name }}', $resource->getResource() instanceof Person ? $this->personRender->renderString($resource->getResource(), []) :
+ $this->thirdpartyRender->renderString($resource->getResource(), []))
+ ->addViolation();
+ }
+
+ $resourceList[] = $id;
+
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php b/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php
index 2dcbb8230..d8e61f499 100644
--- a/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php
+++ b/src/Bundle/ChillPersonBundle/Widget/PersonListWidget.php
@@ -22,7 +22,7 @@ namespace Chill\PersonBundle\Widget;
use Chill\MainBundle\Templating\Widget\WidgetInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr;
-use Doctrine\DBAL\Types\Type;
+use Doctrine\DBAL\Types\Types;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
@@ -33,11 +33,11 @@ use Chill\CustomFieldsBundle\Entity\CustomField;
use Twig\Environment;
/**
- * add a widget with person list.
- *
+ * add a widget with person list.
+ *
* The configuration is defined by `PersonListWidgetFactory`
- *
- * If the options 'custom_fields' is used, the custom fields entity will be
+ *
+ * If the options 'custom_fields' is used, the custom fields entity will be
* queried from the db and transmitted to the view under the `customFields` variable.
*/
class PersonListWidget implements WidgetInterface
@@ -48,33 +48,33 @@ class PersonListWidget implements WidgetInterface
* @var PersonRepository
*/
protected $personRepository;
-
+
/**
* The entity manager
*
* @var EntityManager
*/
protected $entityManager;
-
+
/**
* the authorization helper
- *
+ *
* @var AuthorizationHelper;
*/
protected $authorizationHelper;
-
+
/**
*
* @var TokenStorage
*/
protected $tokenStorage;
-
+
/**
*
* @var UserInterface
*/
protected $user;
-
+
public function __construct(
PersonRepository $personRepostory,
EntityManager $em,
@@ -88,26 +88,26 @@ class PersonListWidget implements WidgetInterface
}
/**
- *
+ *
* @param type $place
* @param array $context
* @param array $config
* @return string
*/
public function render(Environment $env, $place, array $context, array $config)
- {
+ {
$numberOfItems = $config['number_of_items'] ?? 20;
-
+
$qb = $this->personRepository
->createQueryBuilder('person');
-
+
// show only the person from the authorized centers
$and = $qb->expr()->andX();
$centers = $this->authorizationHelper
->getReachableCenters($this->getUser(), new Role(PersonVoter::SEE));
$and->add($qb->expr()->in('person.center', ':centers'));
$qb->setParameter('centers', $centers);
-
+
// add the "only active" query
if (\array_key_exists('only_active', $config) && $config['only_active'] === true) {
@@ -123,37 +123,37 @@ class PersonListWidget implements WidgetInterface
(new Expr())->between(':now', 'ap.openingDate', 'ap.closingDate')
);
$and->add($or);
- $qb->setParameter('now', new \DateTime(), Type::DATE);
+ $qb->setParameter('now', new \DateTime(), Types::DATE_MUTABLE);
}
-
+
if (\array_key_exists('filtering_class', $config) && $config['filtering_class'] !== NULL) {
$filteringClass = new $config['filtering_class'];
if ( ! $filteringClass instanceof PersonListWidget\PersonFilteringInterface) {
throw new \UnexpectedValueException(sprintf("the class %s does not "
- . "implements %s", $config['filtering_class'],
+ . "implements %s", $config['filtering_class'],
PersonListWidget\PersonFilteringInterface::class));
}
- $ids = $filteringClass->getPersonIds($this->entityManager,
+ $ids = $filteringClass->getPersonIds($this->entityManager,
$this->getUser());
$in = (new Expr())->in('person.id', ':ids');
$and->add($in);
$qb->setParameter('ids', $ids);
}
-
+
// adding the where clause to the query
$qb->where($and);
-
+
// ordering the query by lastname, firstname
$qb->addOrderBy('person.lastName', 'ASC')
->addOrderBy('person.firstName', 'ASC');
-
-
+
+
$qb->setFirstResult(0)->setMaxResults($numberOfItems);
-
+
$persons = $qb->getQuery()->getResult();
-
- // get some custom field when the view is overriden and we want to
+
+ // get some custom field when the view is overriden and we want to
// show some custom field in the overriden view.
$cfields = array();
if (isset($config['custom_fields'])) {
@@ -166,39 +166,39 @@ class PersonListWidget implements WidgetInterface
$cfields[$cf->getSlug()] = $cf;
}
}
- }
+ }
return $env->render(
'ChillPersonBundle:Widget:homepage_person_list.html.twig',
array(
'persons' => $persons,
'customFields' => $cfields
-
+
)
);
}
-
+
/**
- *
+ *
* @return UserInterface
* @throws \RuntimeException
*/
private function getUser()
{
$token = $this->tokenStorage->getToken();
-
+
if ($token === null) {
throw new \RuntimeException("the token should not be null");
}
-
+
$user = $token->getUser();
-
+
if (!$user instanceof UserInterface || $user == null) {
throw new \RuntimeException("the user should implement UserInterface. "
. "Are you logged in ?");
}
-
+
return $user;
}
-}
\ No newline at end of file
+}
diff --git a/src/Bundle/ChillPersonBundle/Widget/PersonListWidget/PersonFilteringInterface.php b/src/Bundle/ChillPersonBundle/Widget/PersonListWidget/PersonFilteringInterface.php
index 555884cc1..d1de741ab 100644
--- a/src/Bundle/ChillPersonBundle/Widget/PersonListWidget/PersonFilteringInterface.php
+++ b/src/Bundle/ChillPersonBundle/Widget/PersonListWidget/PersonFilteringInterface.php
@@ -27,7 +27,7 @@ use Chill\MainBundle\Entity\User;
/**
* Interface to implement on classes called in configuration for
* PersonListWidget (`person_list`), under the key `filtering_class` :
- *
+ *
* ```
* widgets:
* homepage:
@@ -35,41 +35,41 @@ use Chill\MainBundle\Entity\User;
* # where \FQDN\To\Class implements PersonFiltering
* class_filtering: \FQDN\To\Class
* ```
- *
+ *
*/
-interface PersonFilteringInterface
+interface PersonFilteringInterface
{
/**
* Return an array of persons id to show.
- *
- * Those ids are inserted into the query like this (where ids is the array
+ *
+ * Those ids are inserted into the query like this (where ids is the array
* returned by this class) :
- *
+ *
* ```
- * SELECT p FROM ChillPersonBundle:Persons p
+ * SELECT p FROM ChillPersonBundle:Persons p
* WHERE p.id IN (:ids)
- * AND
+ * AND
* -- security/authorization statement: restraint person to authorized centers
* p.center in :authorized_centers
* ```
- *
+ *
* Example of use : filtering based on custom field data :
* ```
-
+
class HomepagePersonFiltering implements PersonFilteringInterface {
public function getPersonIds(EntityManager $em, User $user)
{
$rsmBuilder = new ResultSetMappingBuilder($em);
- $rsmBuilder->addScalarResult('id', 'id', Type::BIGINT);
-
+ $rsmBuilder->addScalarResult('id', 'id', Types::BIGINT);
+
$personTable = $em->getClassMetadata('ChillPersonBundle:Person')
->getTableName();
$personIdColumn = $em->getClassMetadata('ChillPersonBundle:Person')
->getColumnName('id');
$personCfDataColumn = $em->getClassMetadata('ChillPersonBundle:Person')
->getColumnName('cfData');
-
+
return $em->createNativeQuery(sprintf("SELECT %s FROM %s WHERE "
. "jsonb_exists(%s, 'school-2fb5440e-192c-11e6-b2fd-74d02b0c9b55')",
$personIdColumn, $personTable, $personCfDataColumn), $rsmBuilder)
@@ -77,7 +77,7 @@ interface PersonFilteringInterface
}
}
* ```
- *
+ *
* @param EntityManager $em
* @return int[] an array of persons id to show
*/
diff --git a/src/Bundle/ChillPersonBundle/chill.api.specs.yaml b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml
index e4a44fdc0..5e1f0d937 100644
--- a/src/Bundle/ChillPersonBundle/chill.api.specs.yaml
+++ b/src/Bundle/ChillPersonBundle/chill.api.specs.yaml
@@ -480,7 +480,7 @@ paths:
/1.0/person/accompanying-course/{id}.json:
get:
tags:
- - person
+ - accompanying-course
summary: "Return the description for an accompanying course (accompanying period)"
parameters:
- name: id
@@ -567,7 +567,7 @@ paths:
/1.0/person/accompanying-course/{id}/requestor.json:
post:
tags:
- - person
+ - accompanying-course
summary: "Add a requestor to the accompanying course"
parameters:
- name: id
@@ -609,7 +609,7 @@ paths:
description: "object with validation errors"
delete:
tags:
- - person
+ - accompanying-course
summary: "Remove the requestor for the accompanying course"
parameters:
- name: id
@@ -633,7 +633,7 @@ paths:
/1.0/person/accompanying-course/{id}/participation.json:
post:
tags:
- - person
+ - accompanying-course
summary: "Add a participant to the accompanying course"
parameters:
- name: id
@@ -662,7 +662,7 @@ paths:
description: "object with validation errors"
delete:
tags:
- - person
+ - accompanying-course
summary: "Remove the participant for the accompanying course"
parameters:
- name: id
@@ -693,7 +693,7 @@ paths:
/1.0/person/accompanying-course/{id}/resource.json:
post:
tags:
- - person
+ - accompanying-course
summary: "Add a resource to the accompanying course"
parameters:
- name: id
@@ -738,7 +738,7 @@ paths:
description: "object with validation errors"
delete:
tags:
- - person
+ - accompanying-course
summary: "Remove the resource"
parameters:
- name: id
@@ -769,7 +769,7 @@ paths:
/1.0/person/accompanying-course/{id}/comment.json:
post:
tags:
- - person
+ - accompanying-course
summary: "Add a comment to the accompanying course"
parameters:
- name: id
@@ -807,7 +807,7 @@ paths:
description: "object with validation errors"
delete:
tags:
- - person
+ - accompanying-course
summary: "Remove the comment"
parameters:
- name: id
@@ -838,7 +838,7 @@ paths:
/1.0/person/accompanying-course/{id}/scope.json:
post:
tags:
- - person
+ - accompanying-course
summary: "Add a scope to the accompanying course"
parameters:
- name: id
@@ -872,7 +872,7 @@ paths:
description: "object with validation errors"
delete:
tags:
- - person
+ - accompanying-course
summary: "Remove the scope"
parameters:
- name: id
@@ -903,7 +903,7 @@ paths:
/1.0/person/accompanying-course/{id}/socialissue.json:
post:
tags:
- - person
+ - accompanying-course
summary: "Add a social issue to the accompanying course"
parameters:
- name: id
@@ -937,7 +937,7 @@ paths:
description: "object with validation errors"
delete:
tags:
- - person
+ - accompanying-course
summary: "Remove the social issue"
parameters:
- name: id
@@ -964,11 +964,31 @@ paths:
description: "OK"
422:
description: "object with validation errors"
+ /1.0/person/accompanying-course/{id}/referrers-suggested.json:
+ get:
+ tags:
+ - accompanying-course
+ summary: "get a list of available referral for a given accompanying cours"
+ parameters:
+ - name: id
+ in: path
+ required: true
+ description: The accompanying period's id
+ schema:
+ type: integer
+ format: integer
+ minimum: 1
+ responses:
+ 401:
+ description: "Unauthorized"
+ 404:
+ description: "Not found"
+ 200:
+ description: "OK"
/1.0/person/accompanying-course/{id}/work.json:
post:
tags:
- - person
- accompanying-course-work
summary: "Add a work (AccompanyingPeriodwork) to the accompanying course"
parameters:
diff --git a/src/Bundle/ChillPersonBundle/config/services/accompanyingPeriod.yaml b/src/Bundle/ChillPersonBundle/config/services/accompanyingPeriod.yaml
index 88e70c9a8..d9e784239 100644
--- a/src/Bundle/ChillPersonBundle/config/services/accompanyingPeriod.yaml
+++ b/src/Bundle/ChillPersonBundle/config/services/accompanyingPeriod.yaml
@@ -13,3 +13,10 @@ services:
entity: 'Chill\PersonBundle\Entity\AccompanyingPeriod'
lazy: true
method: preUpdateAccompanyingPeriod
+
+ Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestion:
+ autowire: true
+ autoconfigure: true
+
+ Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestionInterface: '@Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestion'
+
diff --git a/src/Bundle/ChillPersonBundle/config/services/controller.yaml b/src/Bundle/ChillPersonBundle/config/services/controller.yaml
index 45a656e89..b03ccf966 100644
--- a/src/Bundle/ChillPersonBundle/config/services/controller.yaml
+++ b/src/Bundle/ChillPersonBundle/config/services/controller.yaml
@@ -41,6 +41,7 @@ services:
tags: ['controller.service_arguments']
Chill\PersonBundle\Controller\AccompanyingCourseApiController:
+ autoconfigure: true
autowire: true
tags: ['controller.service_arguments']
diff --git a/src/Bundle/ChillPersonBundle/config/services/form.yaml b/src/Bundle/ChillPersonBundle/config/services/form.yaml
index dacfa41de..2390005cf 100644
--- a/src/Bundle/ChillPersonBundle/config/services/form.yaml
+++ b/src/Bundle/ChillPersonBundle/config/services/form.yaml
@@ -7,8 +7,9 @@ services:
Chill\PersonBundle\Form\PersonType:
arguments:
- - '%chill_person.person_fields%'
- - '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper'
+ $personFieldsConfiguration: '%chill_person.person_fields%'
+ $configAltNamesHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper'
+ $translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper'
tags:
- { name: form.type, alias: '@chill.person.form.person_creation' }
diff --git a/src/Bundle/ChillPersonBundle/config/services/validator.yaml b/src/Bundle/ChillPersonBundle/config/services/validator.yaml
new file mode 100644
index 000000000..435f2f5b5
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/config/services/validator.yaml
@@ -0,0 +1,6 @@
+services:
+ Chill\PersonBundle\Validator\Constraints\AccompanyingPeriod:
+ autowire: true
+ autoconfigure: true
+ tags: ['validator.service_arguments']
+
\ No newline at end of file
diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20150607231010.php b/src/Bundle/ChillPersonBundle/migrations/Version20150607231010.php
index 71a603a2f..a7384e2f4 100644
--- a/src/Bundle/ChillPersonBundle/migrations/Version20150607231010.php
+++ b/src/Bundle/ChillPersonBundle/migrations/Version20150607231010.php
@@ -17,7 +17,6 @@ class Version20150607231010 extends AbstractMigration
. 'recorded.';
}
-
/**
* @param Schema $schema
*/
@@ -28,36 +27,26 @@ class Version20150607231010 extends AbstractMigration
'Migration can only be executed safely on \'postgresql\'.'
);
- // retrieve center for setting a default center
- $centers = $this->connection->fetchAll('SELECT id FROM centers');
-
- if (count($centers) > 0) {
- $defaultCenterId = $centers[0]['id'];
- } else { // if no center, performs other checks
- //check if there are data in person table
- $nbPeople = $this->connection->fetchColumn('SELECT count(*) FROM person');
-
- if ($nbPeople > 0) {
- // we have data ! We have to create a center !
- $newCenterId = $this->connection->fetchColumn('SELECT nextval(\'centers_id_seq\');');
- $this->addSql(
- 'INSERT INTO centers (id, name) VALUES (:id, :name)',
- ['id' => $newCenterId, 'name' => 'Auto-created center']
- );
- $defaultCenterId = $newCenterId;
- }
- }
-
$this->addSql('ALTER TABLE person ADD center_id INT');
+ // retrieve center for setting a default center
+ $stmt = $this->connection->executeQuery("SELECT id FROM centers ORDER BY id ASC LIMIT 1");
+ $center = $stmt->fetchOne();
+
+ if ($center !== false) {
+ $defaultCenterId = $center['id'];
+ }
+
if (isset($defaultCenterId)) {
$this->addSql('UPDATE person SET center_id = :id', array('id' => $defaultCenterId));
}
+ // this will fail if a person is defined, which should not happen any more at this
+ // time of writing (2021-11-09)
+ $this->addSql('ALTER TABLE person ALTER center_id SET NOT NULL');
$this->addSql('ALTER TABLE person '
. 'ADD CONSTRAINT FK_person_center FOREIGN KEY (center_id) '
. 'REFERENCES centers (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
- $this->addSql('ALTER TABLE person ALTER center_id SET NOT NULL');
$this->addSql('CREATE INDEX IDX_person_center ON person (center_id)');
}
diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20200310090632.php b/src/Bundle/ChillPersonBundle/migrations/Version20200310090632.php
index 3a32f4bfc..52d4b0e88 100644
--- a/src/Bundle/ChillPersonBundle/migrations/Version20200310090632.php
+++ b/src/Bundle/ChillPersonBundle/migrations/Version20200310090632.php
@@ -15,7 +15,6 @@ final class Version20200310090632 extends AbstractMigration
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_person_closingmotive ADD parent_id INT DEFAULT NULL');
- $this->addSql('COMMENT ON COLUMN chill_person_closingmotive.name IS \'(DC2Type:json_array)\'');
$this->addSql('ALTER TABLE chill_person_closingmotive ADD CONSTRAINT FK_92351ECE727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_person_closingmotive (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_92351ECE727ACA70 ON chill_person_closingmotive (parent_id)');
$this->addsql("ALTER TABLE chill_person_closingmotive ADD ordering DOUBLE PRECISION DEFAULT '0' NOT NULL;");
diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20210419112619.php b/src/Bundle/ChillPersonBundle/migrations/Version20210419112619.php
index 919fe4cff..89828f862 100644
--- a/src/Bundle/ChillPersonBundle/migrations/Version20210419112619.php
+++ b/src/Bundle/ChillPersonBundle/migrations/Version20210419112619.php
@@ -24,6 +24,5 @@ final class Version20210419112619 extends AbstractMigration
public function down(Schema $schema) : void
{
- $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_closingmotive.name IS \'(DC2Type:json_array)\'');
}
}
diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20211020131133.php b/src/Bundle/ChillPersonBundle/migrations/Version20211020131133.php
new file mode 100644
index 000000000..031356cd3
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/migrations/Version20211020131133.php
@@ -0,0 +1,31 @@
+addSql('CREATE UNIQUE INDEX person_unique ON chill_person_accompanying_period_resource (person_id, accompanyingperiod_id) WHERE person_id IS NOT NULL');
+ $this->addSql('CREATE UNIQUE INDEX thirdparty_unique ON chill_person_accompanying_period_resource (thirdparty_id, accompanyingperiod_id) WHERE thirdparty_id IS NOT NULL');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('DROP INDEX person_unique');
+ $this->addSql('DROP INDEX thirdparty_unique');
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20211021125359.php b/src/Bundle/ChillPersonBundle/migrations/Version20211021125359.php
new file mode 100644
index 000000000..a4ed54254
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/migrations/Version20211021125359.php
@@ -0,0 +1,36 @@
+addSql('ALTER TABLE chill_person_accompanying_period_participation ADD CONSTRAINT '.
+ "participations_no_overlap EXCLUDE USING GIST(
+ -- extension btree_gist required to include comparaison with integer
+ person_id WITH =, accompanyingperiod_id WITH =,
+ daterange(startdate, enddate) WITH &&
+ )
+ INITIALLY DEFERRED");
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('CREATE UNIQUE INDEX participation_unique ON chill_person_accompanying_period_participation (accompanyingperiod_id, person_id)');
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20211108100849.php b/src/Bundle/ChillPersonBundle/migrations/Version20211108100849.php
new file mode 100644
index 000000000..49395dfae
--- /dev/null
+++ b/src/Bundle/ChillPersonBundle/migrations/Version20211108100849.php
@@ -0,0 +1,33 @@
+addSql('ALTER TABLE chill_person_person ADD civility_id INT DEFAULT NULL');
+ $this->addSql('ALTER TABLE chill_person_person ADD CONSTRAINT FK_BF210A1423D6A298 FOREIGN KEY (civility_id) REFERENCES chill_main_civility (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('CREATE INDEX IDX_BF210A1423D6A298 ON chill_person_person (civility_id)');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE chill_person_person DROP CONSTRAINT FK_BF210A1423D6A298');
+ $this->addSql('DROP INDEX IDX_BF210A1423D6A298');
+ $this->addSql('ALTER TABLE chill_person_person DROP civility_id');
+ }
+}
diff --git a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml
index ba7133b2a..e815153c8 100644
--- a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml
+++ b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml
@@ -26,6 +26,12 @@ household:
many {Montrer # anciennes ou futures appartenances}
other {Montrer # anciennes ou futures appartenances}
}
+ Show accompanying periods of past or future memberships: >-
+ {length, plural,
+ one {Montrer les parcours d'une ancienne appartenance}
+ many {Montrer # parcours des anciennes ou futures appartenances}
+ other {Montrer # parcours des anciennes ou futures appartenances}
+ }
Hide memberships: Masquer
Those members does not share address: Ces usagers ne partagent pas l'adresse du ménage.
Any persons into this position: Aucune personne n'appartient au ménage à cette position.
@@ -77,6 +83,7 @@ household:
Household history for %name%: Historique des ménages pour {name}
Household shared: Ménages domiciliés
Household not shared: Ménage non domiciliés
+ Members without position: Membres non positionnés
Never in any household: Membre d'aucun ménage
Membership currently running: En cours
from: Depuis
diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
index a17bbc55f..ef8e58bb1 100644
--- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
@@ -47,8 +47,8 @@ Phonenumber: 'Numéro de téléphone'
phonenumber: numéro de téléphone
Mobilenumber: 'Numéro de téléphone portable'
mobilenumber: numéro de téléphone portable
-Accept short text message ?: Accepte les SMS?
-Accept short text message: Accepte les SMS
+Accept short text message ?: La personne a donné l'autorisation d'utiliser ce no de téléphone pour l'envoi de rappel par SMS
+Accept short text message: La personne a donné l'autorisation d'utiliser ce no de téléphone pour l'envoi de rappel par SMS
Other phonenumber: Autre numéro de téléphone
Description: description
Add new phone: Ajouter un numéro de téléphone
@@ -80,6 +80,8 @@ Married: Marié(e)
'Contact information': 'Informations de contact'
'Administrative information': Administratif
File number: Dossier n°
+Civility: Civilité
+choose civility: --
# dédoublonnage
Old person: Doublon
@@ -403,6 +405,8 @@ Back to household: Revenir au ménage
# accompanying course work
Accompanying Course Actions: Actions d'accompagnements
Accompanying Course Action: Action d'accompagnement
+Are you sure you want to remove this work of the accompanying period %name% ?: Êtes-vous sûr de vouloir supprimer cette action de la période d'accompagnement %name% ?
+The accompanying period work has been successfully removed.: L'action d'accompagnement a été supprimée.
accompanying_course_work:
create: Créer une action
Create accompanying course work: Créer une action d'accompagnement
@@ -417,6 +421,7 @@ accompanying_course_work:
results: Résultats - orientations
goal: Objectif - motif - dispositif
Any work: Aucune action d'accompagnement
+ remove: Supprimer une action d'accompagnement
#
Person addresses: Adresses de résidence
diff --git a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml
index 0e77dae0c..6d2ccb3cb 100644
--- a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml
+++ b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml
@@ -41,3 +41,6 @@ household:
household_membership:
The end date must be after start date: La date de la fin de l'appartenance doit être postérieure à la date de début.
Person with membership covering: Une personne ne peut pas appartenir à deux ménages simultanément. Or, avec cette modification, %person_name% appartiendrait à %nbHousehold% ménages à partir du %from%.
+
+# Accompanying period
+'{{ name }} is already associated to this accompanying course.': '{{ name }} est déjà associé avec ce parcours.'
\ No newline at end of file
diff --git a/src/Bundle/ChillReportBundle/Entity/Report.php b/src/Bundle/ChillReportBundle/Entity/Report.php
index fa81d9283..a88de85e9 100644
--- a/src/Bundle/ChillReportBundle/Entity/Report.php
+++ b/src/Bundle/ChillReportBundle/Entity/Report.php
@@ -1,19 +1,19 @@
- *
+ *
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
- *
+ *
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
@@ -74,7 +74,7 @@ class Report implements HasCenterInterface, HasScopeInterface
/**
* @var array
- * @ORM\Column(type="json_array")
+ * @ORM\Column(type="json")
*/
private $cFData;
@@ -233,7 +233,7 @@ class Report implements HasCenterInterface, HasScopeInterface
{
return $this->cFGroup;
}
-
+
/**
* @return Center
*/
diff --git a/src/Bundle/ChillReportBundle/Export/Export/ReportList.php b/src/Bundle/ChillReportBundle/Export/Export/ReportList.php
index 583db8f4b..9524f860a 100644
--- a/src/Bundle/ChillReportBundle/Export/Export/ReportList.php
+++ b/src/Bundle/ChillReportBundle/Export/Export/ReportList.php
@@ -1,6 +1,6 @@
*/
@@ -38,27 +38,27 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
* @var CustomFieldsGroup
*/
protected $customfieldsGroup;
-
+
/**
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
-
+
/**
*
* @var TranslatorInterface
*/
protected $translator;
-
+
/**
*
* @var CustomFieldProvider
*/
protected $customFieldProvider;
-
+
protected $em;
-
+
protected $fields = array(
'person_id', 'person_firstName', 'person_lastName', 'person_birthdate',
'person_placeOfBirth', 'person_gender', 'person_memo', 'person_email', 'person_phonenumber',
@@ -67,11 +67,11 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
'person_address_postcode_code', 'person_address_country_name', 'person_address_country_code',
'report_id', 'report_user', 'report_date', 'report_scope'
);
-
+
protected $slugs = [];
-
+
function __construct(
- CustomFieldsGroup $customfieldsGroup,
+ CustomFieldsGroup $customfieldsGroup,
TranslatableStringHelper $translatableStringHelper,
TranslatorInterface $translator,
CustomFieldProvider $customFieldProvider,
@@ -84,18 +84,18 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
$this->em = $em;
}
-
+
public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder)
{
$choices = array_combine($this->fields, $this->fields);
-
+
foreach ($this->getCustomFields() as $cf) {
$choices
- [$this->translatableStringHelper->localize($cf->getName())]
- =
+ [$this->translatableStringHelper->localize($cf->getName())]
+ =
$cf->getSlug();
}
-
+
// Add a checkbox to select fields
$builder->add('fields', ChoiceType::class, array(
'multiple' => true,
@@ -133,7 +133,7 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
}
))]
));
-
+
// add a date field for addresses
$builder->add('address_date', ChillDateType::class, array(
'label' => "Address valid at this date",
@@ -142,14 +142,14 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
'block_name' => 'list_export_form_address_date'
));
}
-
+
public function validateForm($data, ExecutionContextInterface $context)
{
// get the field starting with address_
$addressFields = array_filter(function($el) {
return substr($el, 0, 8) === 'address_';
}, $this->fields);
-
+
// check if there is one field starting with address in data
if (count(array_intersect($data['fields'], $addressFields)) > 0) {
// if a field address is checked, the date must not be empty
@@ -161,10 +161,10 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
}
}
}
-
+
/**
* Get custom fields associated with person
- *
+ *
* @return CustomField[]
*/
private function getCustomFields()
@@ -184,7 +184,7 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
{
return $this->translator->trans(
"Generate list of report '%type%'",
- [
+ [
'%type%' => $this->translatableStringHelper->localize($this->customfieldsGroup->getName())
]
);
@@ -192,13 +192,12 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
/**
* {@inheritDoc}
- *
+ *
* @param type $key
* @param array $values
* @param type $data
- * @return type
*/
- public function getLabels($key, array $values, $data)
+ public function getLabels($key, array $values, $data): \Closure
{
switch ($key) {
case 'person_birthdate':
@@ -206,27 +205,27 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
// for birthdate or report date, we have to transform the string into a date
// to format the date correctly.
return function($value) use ($key) {
- if ($value === '_header') {
- return $key === 'person_birthdate' ? 'birthdate' : 'report_date';
+ if ($value === '_header') {
+ return $key === 'person_birthdate' ? 'birthdate' : 'report_date';
}
-
+
if (empty($value))
{
return "";
}
-
+
if ($key === 'person_birthdate') {
$date = \DateTime::createFromFormat('Y-m-d', $value);
} else {
$date = \DateTime::createFromFormat('Y-m-d H:i:s', $value);
}
// check that the creation could occurs.
- if ($date === false) {
+ if ($date === false) {
throw new \Exception(sprintf("The value %s could "
. "not be converted to %s", $value, \DateTime::class));
}
-
- return $date->format('d-m-Y');
+
+ return $date->format('d-m-Y');
};
case 'report_scope':
$qb = $this->em->getRepository(Scope::class)
@@ -236,17 +235,18 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
->where($qb->expr()->in('s.id', $values))
;
$rows = $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
-
+
+ $scopes = [];
+
foreach($rows as $row) {
- $scopes[$row['id']] = $this->translatableStringHelper
- ->localize($row['name']);
+ $scopes[$row['id']] = $this->translatableStringHelper->localize($row['name']);
}
-
- return function($value) use ($scopes) {
+
+ return function($value) use ($scopes): string {
if ($value === '_header') {
return 'circle';
}
-
+
return $scopes[$value];
};
case 'report_user':
@@ -257,71 +257,73 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
->where($qb->expr()->in('u.id', $values))
;
$rows = $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
-
+
+ $users = [];
+
foreach($rows as $row) {
$users[$row['id']] = $row['username'];
}
-
- return function($value) use ($users) {
+
+ return function($value) use ($users): string {
if ($value === '_header') {
return 'user';
}
-
+
return $users[$value];
};
case 'person_gender' :
// for gender, we have to translate men/women statement
return function($value) {
if ($value === '_header') { return 'gender'; }
-
+
return $this->translator->trans($value);
};
case 'person_countryOfBirth':
case 'person_nationality':
$countryRepository = $this->em
->getRepository('ChillMainBundle:Country');
-
+
// load all countries in a single query
$countryRepository->findBy(array('countryCode' => $values));
-
+
return function($value) use ($key, $countryRepository) {
if ($value === '_header') { return \strtolower($key); }
-
- if ($value === NULL) {
+
+ if ($value === NULL) {
return $this->translator->trans('no data');
}
-
+
$country = $countryRepository->find($value);
-
+
return $this->translatableStringHelper->localize(
$country->getName());
};
case 'person_address_country_name':
return function($value) use ($key) {
if ($value === '_header') { return \strtolower($key); }
-
+
if ($value === NULL) {
return '';
}
-
+
return $this->translatableStringHelper->localize(json_decode($value, true));
};
default:
// for fields which are associated with person
if (in_array($key, $this->fields)) {
return function($value) use ($key) {
- if ($value === '_header') { return \strtolower($key); }
+ if ($value === '_header') { return \strtolower($key); }
- return $value;
+ return $value;
};
} else {
return $this->getLabelForCustomField($key, $values, $data);
}
}
-
+
}
-
+
private function getLabelForCustomField($key, array $values, $data)
{
// for fields which are custom fields
@@ -329,38 +331,38 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
$cf = $this->em
->getRepository(CustomField::class)
->findOneBy(array('slug' => $this->DQLToSlug($key)));
-
+
$cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType());
$defaultFunction = function($value) use ($cf) {
if ($value === '_header') {
- return $this->translatableStringHelper->localize($cf->getName());
+ return $this->translatableStringHelper->localize($cf->getName());
}
return $this->customFieldProvider
->getCustomFieldByType($cf->getType())
->render(json_decode($value, true), $cf, 'csv');
};
-
+
if ($cfType instanceof CustomFieldChoice and $cfType->isMultiple($cf)) {
return function($value) use ($cf, $cfType, $key) {
$slugChoice = $this->extractInfosFromSlug($key)['additionnalInfos']['choiceSlug'];
$decoded = \json_decode($value, true);
-
+
if ($value === '_header') {
-
+
$label = $cfType->getChoices($cf)[$slugChoice];
-
+
return $this->translatableStringHelper->localize($cf->getName())
.' | '.$label;
}
-
+
if ($slugChoice === '_other' and $cfType->isChecked($cf, $choiceSlug, $decoded)) {
return $cfType->extractOtherValue($cf, $decoded);
} else {
return $cfType->isChecked($cf, $slugChoice, $decoded);
}
};
-
+
} else {
return $defaultFunction;
}
@@ -369,44 +371,44 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
public function getQueryKeys($data)
{
$fields = array();
-
+
foreach ($data['fields'] as $key) {
if (in_array($key, $this->fields)) {
$fields[] = $key;
}
}
-
+
// add the key from slugs and return
return \array_merge($fields, \array_keys($this->slugs));
}
-
+
/**
* clean a slug to be usable by DQL
- *
- * @param string $slugsanitize
+ *
+ * @param string $slugsanitize
* @param string $type the type of the customfield, if required (currently only for choices)
* @return string
*/
private function slugToDQL($slug, $type = "default", array $additionalInfos = [])
{
$uid = 'slug_'.\uniqid();
-
+
$this->slugs[$uid] = [
'slug' => $slug,
'type' => $type,
'additionnalInfos' => $additionalInfos
];
-
+
return $uid;
}
-
+
private function DQLToSlug($cleanedSlug)
- {
+ {
return $this->slugs[$cleanedSlug]['slug'];
}
-
+
/**
- *
+ *
* @param type $cleanedSlug
* @return an array with keys = 'slug', 'type', 'additionnalInfo'
*/
@@ -424,7 +426,7 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
{
return $this->translator->trans(
"List for report '%type%'",
- [
+ [
'%type%' => $this->translatableStringHelper->localize($this->customfieldsGroup->getName())
]
);
@@ -438,22 +440,22 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = array())
{
$centers = array_map(function($el) { return $el['center']; }, $acl);
-
+
// throw an error if any fields are present
if (!\array_key_exists('fields', $data)) {
throw new \Doctrine\DBAL\Exception\InvalidArgumentException("any fields "
. "have been checked");
}
-
+
$qb = $this->em->createQueryBuilder();
-
+
// process fields which are not custom fields
foreach ($this->fields as $f) {
// do not add fields which are not selected
if (!\in_array($f, $data['fields'])) {
continue;
}
-
+
// add a column to the query for each field
switch ($f) {
case 'person_countryOfBirth':
@@ -470,11 +472,11 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
case 'person_address_country_code':
// remove 'person_'
$suffix = \substr($f, 7);
-
+
$qb->addSelect(sprintf(
'GET_PERSON_ADDRESS_%s(person.id, :address_date) AS %s',
// get the part after address_
- strtoupper(substr($suffix, 8)),
+ strtoupper(substr($suffix, 8)),
$f));
$qb->setParameter('address_date', $data['address_date']);
break;
@@ -487,7 +489,7 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
default:
$prefix = \substr($f, 0, 7);
$suffix = \substr($f, 7);
-
+
switch($prefix) {
case 'person_':
$qb->addSelect(sprintf('person.%s as %s', $suffix, $f));
@@ -502,22 +504,22 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
}
}
-
+
// process fields which are custom fields
foreach ($this->getCustomFields() as $cf) {
// do not add custom fields which are not selected
if (!\in_array($cf->getSlug(), $data['fields'])) {
continue;
}
-
+
$cfType = $this->customFieldProvider->getCustomFieldByType($cf->getType());
-
+
// if is multiple, split into multiple columns
if ($cfType instanceof CustomFieldChoice and $cfType->isMultiple($cf)) {
foreach($cfType->getChoices($cf) as $choiceSlug => $label) {
$slug = $this->slugToDQL($cf->getSlug(), 'choice', [ 'choiceSlug' => $choiceSlug ]);
$qb->addSelect(
- sprintf('GET_JSON_FIELD_BY_KEY(report.cFData, :slug%s) AS %s',
+ sprintf('GET_JSON_FIELD_BY_KEY(report.cFData, :slug%s) AS %s',
$slug, $slug));
$qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug());
}
@@ -525,12 +527,12 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
// not multiple, add a single column
$slug = $this->slugToDQL($cf->getSlug());
$qb->addSelect(
- sprintf('GET_JSON_FIELD_BY_KEY(report.cFData, :slug%s) AS %s',
+ sprintf('GET_JSON_FIELD_BY_KEY(report.cFData, :slug%s) AS %s',
$slug, $slug));
$qb->setParameter(sprintf('slug%s', $slug), $cf->getSlug());
}
}
-
+
$qb
->from(Report::class, 'report')
->leftJoin('report.person', 'person')
@@ -540,7 +542,7 @@ class ReportList implements ListInterface, ExportElementValidatedInterface
->andWhere('center IN (:authorized_centers)')
->setParameter('authorized_centers', $centers);
;
-
+
return $qb;
}
diff --git a/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php b/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php
index bc76dacd4..d8944ae93 100644
--- a/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php
+++ b/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php
@@ -2,11 +2,17 @@
namespace Chill\TaskBundle\Controller;
+use Chill\MainBundle\Entity\Scope;
+use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
+use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
+use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
+use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Chill\PersonBundle\Privacy\PrivacyEvent;
+use Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
-use Doctrine\ORM\EntityManager;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -22,97 +28,129 @@ use Chill\TaskBundle\Repository\SingleTaskRepository;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\PersonBundle\Repository\PersonRepository;
-use Chill\MainBundle\Entity\UserRepository;
use Chill\TaskBundle\Event\TaskEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\Translation\TranslatorInterface;
use Chill\TaskBundle\Event\UI\UIEvent;
use Chill\MainBundle\Repository\CenterRepository;
use Chill\MainBundle\Timeline\TimelineBuilder;
+use Chill\PersonBundle\Entity\AccompanyingPeriod;
+use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
+use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
+use Doctrine\ORM\EntityManagerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Contracts\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorInterface as TranslationTranslatorInterface;
/**
* Class SingleTaskController
*
* @package Chill\TaskBundle\Controller
*/
-class SingleTaskController extends AbstractController
+final class SingleTaskController extends AbstractController
{
-
- /**
- * @var EventDispatcherInterface
- */
- protected $eventDispatcher;
-
- /**
- *
- * @var TimelineBuilder
- */
- protected $timelineBuilder;
-
- /**
- * @var LoggerInterface
- */
- protected $logger;
-
- /**
- * SingleTaskController constructor.
- *
- * @param EventDispatcherInterface $eventDispatcher
- */
+
+ private EventDispatcherInterface $eventDispatcher;
+ private TimelineBuilder $timelineBuilder;
+ private LoggerInterface $logger;
+ private CenterResolverDispatcher $centerResolverDispatcher;
+ private TranslatorInterface $translator;
+ private PaginatorFactory $paginatorFactory;
+ private SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository;
+ private FilterOrderHelperFactoryInterface $filterOrderHelperFactory;
+
public function __construct(
+ CenterResolverDispatcher $centerResolverDispatcher,
+ PaginatorFactory $paginatorFactory,
+ SingleTaskAclAwareRepositoryInterface $singleTaskAclAwareRepository,
+ TranslatorInterface $translator,
EventDispatcherInterface $eventDispatcher,
TimelineBuilder $timelineBuilder,
- LoggerInterface $logger
+ LoggerInterface $logger,
+ FilterOrderHelperFactoryInterface $filterOrderHelperFactory
) {
$this->eventDispatcher = $eventDispatcher;
$this->timelineBuilder = $timelineBuilder;
$this->logger = $logger;
+ $this->translator = $translator;
+ $this->centerResolverDispatcher = $centerResolverDispatcher;
+ $this->paginatorFactory = $paginatorFactory;
+ $this->singleTaskAclAwareRepository = $singleTaskAclAwareRepository;
+ $this->filterOrderHelperFactory = $filterOrderHelperFactory;
}
-
-
+
+ private function getEntityContext(Request $request)
+ {
+ if ($request->query->has('person_id')) {
+ return 'person';
+ } else if ($request->query->has('course_id')) {
+ return 'course';
+ } else {
+ return null;
+ }
+ }
+
+
/**
* @Route(
* "/{_locale}/task/single-task/new",
* name="chill_task_single_task_new"
* )
*/
- public function newAction(
- Request $request,
- TranslatorInterface $translator
- ) {
+ public function newAction(Request $request) {
$task = (new SingleTask())
->setAssignee($this->getUser())
->setType('task_default')
;
- if ($request->query->has('person_id')) {
-
- $personId = $request->query->getInt('person_id', 0); // sf4 check:
- // prevent error: `Argument 2 passed to ::getInt() must be of the type int, null given`
+ $entityType = $this->getEntityContext($request);
- if ($personId === null) {
- return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST);
- }
-
- $person = $this->getDoctrine()->getManager()
- ->getRepository(Person::class)
- ->find($personId);
-
- if ($person === null) {
- $this->createNotFoundException("Invalid person id");
- }
-
- $task->setPerson($person);
+ if (NULL === $entityType) {
+ throw new BadRequestHttpException("You must provide a entity_type");
}
- $this->denyAccessUnlessGranted(TaskVoter::CREATE, $task, 'You are not '
- . 'allowed to create this task');
+ $entityId = $request->query->getInt("{$entityType}_id", 0);
- $form = $this->setCreateForm($task, new Role(TaskVoter::CREATE));
+ if ($entityId === null) {
+ return new BadRequestHttpException("You must provide a {$entityType}_id");
+ }
+
+ switch ($entityType) {
+ case 'person':
+ $person = $this->getDoctrine()->getManager()
+ ->getRepository(Person::class)
+ ->find($entityId);
+
+ if ($person === null) {
+ $this->createNotFoundException("Invalid person id");
+ }
+
+ $task->setPerson($person);
+ $role = TaskVoter::CREATE_PERSON;
+ break;
+ case 'course':
+ $course = $this->getDoctrine()->getManager()
+ ->getRepository(AccompanyingPeriod::class)
+ ->find($entityId);
+
+ if ($course === null) {
+ $this->createNotFoundException("Invalid accompanying course id");
+ }
+
+ $task->setCourse($course);
+ $role = TaskVoter::CREATE_COURSE;
+ break;
+ default:
+ return new BadRequestHttpException("context with {$entityType} is not supported");
+ }
+
+ $this->denyAccessUnlessGranted($role, $task, 'You are not '
+ . 'allowed to create this task');
+
+ $form = $this->setCreateForm($task, new Role($role));
$form->handleRequest($request);
-
+
if ($form->isSubmitted()) {
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
@@ -122,72 +160,78 @@ class SingleTaskController extends AbstractController
$em->flush();
- $this->addFlash('success', $translator->trans("The task is created"));
+ $this->addFlash('success', $this->translator->trans("The task is created"));
- return $this->redirectToRoute('chill_task_singletask_list', [
- 'person_id' => $task->getPerson()->getId()
- ]);
+ if ($request->query->has('returnPath')) {
+ return $this->redirect($request->query->get('returnPath'));
+ }
+ if ($entityType === 'person') {
+ return $this->redirectToRoute('chill_task_singletask_by-person_list', [
+ 'id' => $task->getPerson()->getId()
+ ]);
+ } elseif ($entityType === 'course') {
+ return $this->redirectToRoute('chill_task_singletask_by-course_list', [
+ 'id' => $task->getCourse()->getId()
+ ]);
+ }
} else {
- $this->addFlash('error', $translator->trans("This form contains errors"));
+ $this->addFlash('error', $this->translator->trans("This form contains errors"));
}
}
- return $this->render('ChillTaskBundle:SingleTask:new.html.twig', array(
- 'form' => $form->createView(),
- 'task' => $task
- ));
+ switch ($entityType) {
+ case 'person':
+ return $this->render('@ChillTask/SingleTask/Person/new.html.twig', array(
+ 'form' => $form->createView(),
+ 'task' => $task,
+ 'person' => $task->getPerson(),
+ ));
+ case 'course':
+ return $this->render('@ChillTask/SingleTask/AccompanyingCourse/new.html.twig', array(
+ 'form' => $form->createView(),
+ 'task' => $task,
+ 'accompanyingCourse' => $task->getCourse(),
+ ));
+ default:
+ throw new \LogicException("entity context not supported");
+ }
}
-
/**
* @Route(
* "/{_locale}/task/single-task/{id}/show",
* name="chill_task_single_task_show"
* )
*/
- public function showAction(Request $request, $id)
+ public function showAction(SingleTask $task, Request $request)
{
-
- $em = $this->getDoctrine()->getManager();
- $task = $em->getRepository(SingleTask::class)->find($id);
+ $this->denyAccessUnlessGranted(TaskVoter::SHOW, $task);
- if ($task->getPerson() !== null) {
- $personId = $task->getPerson()->getId();
-
- if ($personId === null) {
- return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST);
- }
-
- $person = $this->getDoctrine()->getManager()
- ->getRepository(Person::class)
- ->find($personId);
-
- if ($person === null) {
- throw $this->createNotFoundException("Invalid person id");
- }
- }
- $this->denyAccessUnlessGranted(TaskVoter::SHOW, $task, 'You are not '
- . 'allowed to view this task');
-
- if (!$task) {
- throw $this->createNotFoundException('Unable to find Task entity.');
+ if ($person = $task->getContext() instanceof Person) {
+ $event = new PrivacyEvent($person, array(
+ 'element_class' => SingleTask::class,
+ 'element_id' => $task->getId(),
+ 'action' => 'show'
+ ));
+ $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
}
$timeline = $this->timelineBuilder
->getTimelineHTML('task', array('task' => $task));
-
- $event = new PrivacyEvent($person, array(
- 'element_class' => SingleTask::class,
- 'element_id' => $task->getId(),
- 'action' => 'show'
- ));
- $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
-
- return $this->render('ChillTaskBundle:SingleTask:show.html.twig', array(
- 'task' => $task,
- 'timeline' => $timeline
- ));
+
+ if ($task->getContext() instanceof Person) {
+ return $this->render('@ChillTask/SingleTask/Person/show.html.twig', array(
+ 'task' => $task,
+ 'timeline' => $timeline
+ ));
+ } else {
+ return $this->render('@ChillTask/SingleTask/AccompanyingCourse/show.html.twig', array(
+ 'task' => $task,
+ 'timeline' => $timeline
+ ));
+ }
+
}
@@ -198,41 +242,18 @@ class SingleTaskController extends AbstractController
* )
*/
public function editAction(
- Request $request,
- $id,
- TranslatorInterface $translator
+ SingleTask $task,
+ Request $request
) {
-
- $em = $this->getDoctrine()->getManager();
- $task = $em->getRepository(SingleTask::class)->find($id);
- if ($task->getPerson() !== null) {
- $personId = $task->getPerson()->getId();
- if ($personId === null) {
- return new Response("You must provide a person_id", Response::HTTP_BAD_REQUEST);
- }
-
- $person = $this->getDoctrine()->getManager()
- ->getRepository(Person::class)
- ->find($personId);
-
- if ($person === null) {
- throw $this->createNotFoundException("Invalid person id");
- }
- }
$this->denyAccessUnlessGranted(TaskVoter::UPDATE, $task, 'You are not '
. 'allowed to edit this task');
- if (!$task) {
- throw $this->createNotFoundException('Unable to find Task entity.');
- }
-
$event = (new UIEvent('single-task', $task))
->setForm($this->setCreateForm($task, new Role(TaskVoter::UPDATE)))
;
-
$this->eventDispatcher->dispatch(UIEvent::EDIT_FORM, $event);
-
+
$form = $event->getForm();
$form->handleRequest($request);
@@ -244,43 +265,64 @@ class SingleTaskController extends AbstractController
$em->flush();
- $this->addFlash('success', $translator
+ $this->addFlash('success', $this->translator
->trans("The task has been updated"));
-
- $event = new PrivacyEvent($person, array(
+
+ if ($task->getContext() instanceof Person) {
+ $event = new PrivacyEvent($task->getPerson(), array(
'element_class' => SingleTask::class,
'element_id' => $task->getId(),
'action' => 'update'
- ));
- $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
+ ));
+ $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
- return $this->redirectToRoute(
- 'chill_task_singletask_list',
- $request->query->get('list_params', [])
- );
+ if ($request->query->has('returnPath')) {
+ return $this->redirect($request->query->get('returnPath'));
+ }
+ return $this->redirectToRoute(
+ 'chill_task_singletask_list',
+ );
+ } else {
+ if ($request->query->has('returnPath')) {
+ return $this->redirect($request->query->get('returnPath'));
+ }
+
+ return $this->redirectToRoute(
+ 'chill_task_singletask_by-course_list', ['id' => $task->getCourse()->getId()]
+ );
+ }
} else {
- $this->addFlash('error', $translator->trans("This form contains errors"));
+ $this->addFlash('error', $this->translator->trans("This form contains errors"));
}
}
-
+
$this->eventDispatcher->dispatch(UIEvent::EDIT_PAGE, $event);
-
+
if ($event->hasResponse()) {
return $event->getResponse();
}
-
- $event = new PrivacyEvent($person, array(
- 'element_class' => SingleTask::class,
- 'element_id' => $task->getId(),
- 'action' => 'edit'
- ));
- $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
-
- return $this->render('ChillTaskBundle:SingleTask:edit.html.twig', array(
- 'task' => $task,
- 'form' => $form->createView()
- ));
+
+ if ($task->getContext() instanceof Person) {
+ $event = new PrivacyEvent($task->getPerson(), array(
+ 'element_class' => SingleTask::class,
+ 'element_id' => $task->getId(),
+ 'action' => 'edit'
+ ));
+ $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
+
+ return $this->render('@ChillTask/SingleTask/Person/edit.html.twig', array(
+ 'task' => $task,
+ 'form' => $form->createView()
+ ));
+ } else {
+ return $this->render('@ChillTask/SingleTask/AccompanyingCourse/edit.html.twig', array(
+ 'task' => $task,
+ 'form' => $form->createView(),
+ 'accompanyingCourse' => $task->getCourse()
+ ));
+ }
+
}
@@ -292,10 +334,9 @@ class SingleTaskController extends AbstractController
*/
public function deleteAction(
Request $request,
- $id,
- TranslatorInterface $translator
+ $id
) {
-
+
$em = $this->getDoctrine()->getManager();
$task = $em->getRepository(SingleTask::class)->find($id);
@@ -317,10 +358,25 @@ class SingleTaskController extends AbstractController
throw $this->createNotFoundException("Invalid person id");
}
+ } else {
+ $courseId = $task->getCourse()->getId();
+ if ($courseId === null){
+ return new Response("You must provide a course_id", Response::HTTP_BAD_REQUEST);
+ }
+
+ $course = $this->getDoctrine()->getManager()
+ ->getRepository(AccompanyingPeriod::class)
+ ->find($courseId);
+
+ if($course === null){
+ throw $this->createNotFoundException("Invalid accompanying period id");
+ }
}
- $this->denyAccessUnlessGranted(TaskVoter::DELETE, $task, 'You are not '
- . 'allowed to delete this task');
+ // TODO: reactivate right to delete
+
+ // $this->denyAccessUnlessGranted(TaskVoter::DELETE, $task, 'You are not '
+ // . 'allowed to delete this task');
$form = $this->createDeleteForm($id);
@@ -328,38 +384,50 @@ class SingleTaskController extends AbstractController
$form->handleRequest($request);
if ($form->isValid()) {
-
+
$this->logger->notice("A task has been removed", array(
'by_user' => $this->getUser()->getUsername(),
'task_id' => $task->getId(),
'description' => $task->getDescription(),
'assignee' => $task->getAssignee(),
- 'scope_id' => $task->getScope()->getId(),
- //'start_date' => $task->getStartDate()->format('Y-m-d'),
- //'end_date' => $task->getEndDate()->format('Y-m-d'),
- //'warning_interval' => $task->getWarningInterval()->format('Y-m-d')
+ // TODO reimplement scope
+ // 'scope_id' => $task->getScope()->getId(),
));
$em = $this->getDoctrine()->getManager();
$em->remove($task);
$em->flush();
- $this->addFlash('success', $translator
+ $this->addFlash('success', $this->translator
->trans("The task has been successfully removed."));
- return $this->redirect($this->generateUrl(
- 'chill_task_singletask_list',
- $request->query->get('list_params', [
- 'person_id' => $person->getId()
- ])));
+ if ($task->getContext() instanceof Person) {
+ return $this->redirect($this->generateUrl(
+ 'chill_task_singletask_by-person_list',
+ [ 'id' => $task->getPerson()->getId() ]
+ ));
+ } else {
+ return $this->redirect($this->generateUrl(
+ 'chill_task_singletask_by-course_list',
+ ['id' => $task->getCourse()->getId()]
+ ));
+ }
}
}
+ if($task->getContext() instanceof Person){
+ return $this->render('@ChillTask/SingleTask/Person/confirm_delete.html.twig', array(
+ 'task' => $task,
+ 'delete_form' => $form->createView()
+ ));
+ } else {
+ return $this->render('@ChillTask/SingleTask/AccompanyingCourse/confirm_delete.html.twig', array(
+ 'task' => $task,
+ 'delete_form' => $form->createView(),
+ 'accompanyingCourse' => $course
+ ));
+ }
- return $this->render('ChillTaskBundle:SingleTask:confirm_delete.html.twig', array(
- 'task' => $task,
- 'delete_form' => $form->createView()
- ));
}
/**
@@ -371,8 +439,7 @@ class SingleTaskController extends AbstractController
protected function setCreateForm(SingleTask $task, Role $role)
{
$form = $this->createForm(SingleTaskType::class, $task, [
- 'center' => $task->getCenter(),
- 'role' => $role
+ 'role' => $role,
]);
$form->add('submit', SubmitType::class);
@@ -385,223 +452,114 @@ class SingleTaskController extends AbstractController
* @return Response
* @Route(
* "/{_locale}/task/single-task/list/my",
- * name="chill_task_single_my_tasks"
+ * name="chill_task_singletask_my_tasks"
* )
*/
- public function myTasksAction(TranslatorInterface $translator)
+ public function myTasksAction()
{
- return $this->redirectToRoute('chill_task_singletask_list', [
- 'user_id' => $this->getUser()->getId(),
- 'hide_form' => true,
- 'title' => $translator->trans('My tasks')
+ $this->denyAccessUnlessGranted('ROLE_USER');
+
+ $filterOrder = $this->buildFilterOrder();
+ $flags = \array_merge(
+ $filterOrder->getCheckboxData('status'),
+ \array_map(fn ($i) => 'state_'.$i, $filterOrder->getCheckboxData('states'))
+ );
+ $nb = $this->singleTaskAclAwareRepository->countByCurrentUsersTasks(
+ $filterOrder->getQueryString(),
+ $flags
+ );
+ $paginator = $this->paginatorFactory->create($nb);
+ $tasks = $this->singleTaskAclAwareRepository->findByCurrentUsersTasks(
+ $filterOrder->getQueryString(),
+ $flags,
+ $paginator->getCurrentPageFirstItemNumber(),
+ $paginator->getItemsPerPage(),
+ [
+ 'startDate' => 'DESC',
+ 'endDate' => 'DESC',
+ ]
+ );
+
+ return $this->render('@ChillTask/SingleTask/List/index_my_tasks.html.twig', [
+ 'tasks' => $tasks,
+ 'paginator' => $paginator,
+ 'filter_order' => $filterOrder,
]);
}
+ private function buildFilterOrder(): FilterOrderHelper
+ {
+ $statuses = ['no-alert', 'warning', 'alert'];
+ $statusTrans = [
+ 'Tasks without alert',
+ 'Tasks near deadline',
+ 'Tasks over deadline',
+ ];
+ $states = [
+ // todo: get a list of possible states dynamically
+ 'new', 'in_progress', 'closed', 'canceled'
+ ];
+ return $this->filterOrderHelperFactory
+ ->create(self::class)
+ ->addSearchBox()
+ ->addCheckbox('status', $statuses, $statuses, $statusTrans)
+ ->addCheckbox('states', $states, ['new', 'in_progress'])
+ ->build()
+ ;
+ }
+
/**
*
* Arguments:
* - user_id
* - scope_id
+ * - s
* - person_id
* - hide_form (hide the form to filter the tasks)
* - status: date state, amongst SingleTaskRepository::DATE_STATUSES, or 'closed'
*
* @Route(
- * "/{_locale}/task/singletask/list",
+ * "/{_locale}/task/single-task/list",
* name="chill_task_singletask_list"
* )
*/
public function listAction(
- Request $request,
- PaginatorFactory $paginatorFactory,
- SingleTaskRepository $taskRepository,
- PersonRepository $personRepository,
- CenterRepository $centerRepository,
- FormFactoryInterface $formFactory
+ Request $request
) {
- /* @var $viewParams array The parameters for the view */
- /* @var $params array The parameters for the query */
+ $this->denyAccessUnlessGranted(TaskVoter::SHOW, null);
- $viewParams['person'] = null;
- $params['person'] = null;
- $viewParams['user'] = null;
- $params['user'] = null;
- $viewParams['center'] = null;
- $params['types'] = null;
+ $filterOrder = $this->buildFilterOrder();
+ $flags = \array_merge(
+ $filterOrder->getCheckboxData('status'),
+ \array_map(fn ($i) => 'state_'.$i, $filterOrder->getCheckboxData('states'))
+ );
+ $nb = $this->singleTaskAclAwareRepository->countByAllViewable(
+ $filterOrder->getQueryString(),
+ $flags
+ );
+ $paginator = $this->paginatorFactory->create($nb);
-
- // Get parameters from url
- if (!empty($request->query->get('person_id', NULL))) {
-
- $personId = $request->query->getInt('person_id', 0);
- $person = $personRepository->find($personId);
-
- if ($person === null) {
- throw $this->createNotFoundException("This person ' $personId ' does not exist.");
- }
- $this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
-
- $viewParams['person'] = $person;
- $params['person'] = $person;
- }
-
- if (!empty($request->query->get('center_id', NULL))) {
- $center = $centerRepository->find($request->query->getInt('center_id'));
- if ($center === null) {
- throw $this->createNotFoundException('center not found');
- }
- $params['center'] = $center;
- }
-
- if(!empty($request->query->get('types', []))) {
- $types = $request->query->get('types', []);
- if (count($types) > 0) {
- $params['types'] = $types;
- }
- }
-
- if (!empty($request->query->get('user_id', null))) {
- if ($request->query->get('user_id') === '_unassigned') {
- $params['unassigned'] = true;
- } else {
-
- $userId = $request->query->getInt('user_id', 0);
- $user = $this->getDoctrine()->getManager()
- ->getRepository('ChillMainBundle:User')
- ->find($userId);
-
- if ($user === null) {
- throw $this->createNotFoundException("This user ' $userId ' does not exist.");
- }
-
- $viewParams['user'] = $user;
- $params['user'] = $user;
- }
- }
-
- if (!empty($request->query->get('scope_id'))) {
-
- $scopeId = $request->query->getInt('scope_id', 0);
-
- $scope = $this->getDoctrine()->getManager()
- ->getRepository('ChillMainBundle:Scope')
- ->find($scopeId);
-
- if ($scope === null) {
- throw $this->createNotFoundException("This scope' $scopeId 'does not exist.");
- }
-
- $viewParams['scope'] = $scope;
- $params['scope'] = $scope;
- }
-
- // collect parameters for filter
- $possibleStatuses = \array_merge(SingleTaskRepository::DATE_STATUSES, [ 'closed' ]);
- $statuses = $request->query->get('status', $possibleStatuses);
-
- // check for invalid statuses
- $diff = \array_diff($statuses, $possibleStatuses);
- if (count($diff) > 0) {
- return new Response(
- 'date_status not allowed: '. \implode(', ', $diff),
- Response::HTTP_BAD_REQUEST
- );
- }
-
- $viewParams['isSingleStatus'] = $singleStatus = count($statuses) === 1;
-
- $tasks_count = 0;
-
- foreach($statuses as $status) {
- if($request->query->has('status')
- && FALSE === \in_array($status, $statuses)) {
- continue;
- }
-
- // different query if regarding to date or 'closed'
- if (in_array($status, SingleTaskRepository::DATE_STATUSES)) {
- $params['date_status'] = $status;
- $params['is_closed'] = false;
- } else {
- $params['date_status'] = null;
- $params['is_closed'] = true;
- }
-
- $count = $taskRepository
- ->countByParameters($params, $this->getUser())
- ;
- $paginator = $paginatorFactory->create($count);
-
- $viewParams['single_task_'.$status.'_count'] = $count;
- $viewParams['single_task_'.$status.'_paginator'] = $paginator;
- $viewParams['single_task_'.$status.'_tasks'] = $taskRepository
- ->findByParameters($params, $this->getUser(),
- $singleStatus ? $paginator->getCurrentPage()->getFirstItemNumber() : 0,
- $singleStatus ? $paginator->getItemsPerPage() : 10)
- ;
-
- $tasks_count = $tasks_count + $count;
- }
-
- // total number of tasks
- $viewParams['tasks_count'] = $tasks_count;
-
- if ($viewParams['person'] !== null){
- $viewParams['layout'] = '@ChillPerson/Person/layout.html.twig';
+ if (0 < $nb) {
+ $tasks = $this->singleTaskAclAwareRepository->findByAllViewable(
+ $filterOrder->getQueryString(),
+ $flags,
+ $paginator->getCurrentPageFirstItemNumber(),
+ $paginator->getItemsPerPage(),
+ [
+ 'startDate' => 'DESC',
+ 'endDate' => 'DESC',
+ ]
+ );
} else {
- $viewParams['layout'] = '@ChillMain/layout.html.twig';
+ $tasks = [];
}
- // Form for filtering tasks
- $form = $formFactory->createNamed(null, SingleTaskListType::class, null, [
- 'person' => $viewParams['person'],
- 'method' => Request::METHOD_GET,
- 'csrf_protection' => false,
- 'add_type' => true
- ]);
+ return $this->render('@ChillTask/SingleTask/List/index.html.twig', [
+ 'tasks' => $tasks,
+ 'paginator' => $paginator,
+ 'filter_order' => $filterOrder
+ ]);
- $form->handleRequest($request);
-
- if (isset($person)) {
- $event = new PrivacyEvent($person, array(
- 'element_class' => SingleTask::class,
- 'action' => 'list'
- ));
- $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
- }
-
- return $this->render('ChillTaskBundle:SingleTask:index.html.twig',
- \array_merge($viewParams, [ 'form' => $form->createView() ]));
- }
-
-
- protected function getPersonParam(Request $request, EntityManagerInterface $em)
- {
- $person = $em->getRepository(Person::class)
- ->find($request->query->getInt('person_id'))
- ;
-
- if (NULL === $person) {
- throw $this->createNotFoundException('person not found');
- }
-
- $this->denyAccessUnlessGranted(PersonVoter::SEE, $person, "You are "
- . "not allowed to see this person");
-
- return $person;
- }
-
- protected function getUserParam(Request $request, EntityManagerInterface $em)
- {
- $user = $em->getRepository(User::class)
- ->find($request->query->getInt('user_id'))
- ;
-
- if (NULL === $user) {
- throw $this->createNotFoundException('user not found');
- }
-
- return $user;
}
/**
@@ -623,4 +581,103 @@ class SingleTaskController extends AbstractController
;
}
+ /**
+ * @Route(
+ * "/{_locale}/task/single-task/by-course/{id}",
+ * name="chill_task_singletask_by-course_list")
+ */
+ public function listCourseTasks(
+ AccompanyingPeriod $course,
+ FormFactoryInterface $formFactory,
+ Request $request
+ ): Response
+ {
+ $this->denyAccessUnlessGranted(TaskVoter::SHOW, $course);
+
+ $filterOrder = $this->buildFilterOrder();
+ $flags = \array_merge(
+ $filterOrder->getCheckboxData('status'),
+ \array_map(fn ($i) => 'state_'.$i, $filterOrder->getCheckboxData('states'))
+ );
+ $nb = $this->singleTaskAclAwareRepository->countByCourse(
+ $course,
+ $filterOrder->getQueryString(),
+ $flags
+ );
+ $paginator = $this->paginatorFactory->create($nb);
+
+ if (0 < $nb) {
+ $tasks = $this->singleTaskAclAwareRepository->findByCourse(
+ $course,
+ $filterOrder->getQueryString(),
+ $flags,
+ $paginator->getCurrentPageFirstItemNumber(),
+ $paginator->getItemsPerPage(),
+ [
+ 'startDate' => 'DESC',
+ 'endDate' => 'DESC',
+ ]
+ );
+ } else {
+ $tasks = [];
+ }
+
+ return $this->render(
+ '@ChillTask/SingleTask/AccompanyingCourse/list.html.twig',
+ [
+ 'tasks' => $tasks,
+ 'accompanyingCourse' => $course,
+ 'paginator' => $paginator,
+ 'filter_order' => $filterOrder
+ ]);
+ }
+
+ /**
+ * @Route(
+ * "/{_locale}/task/single-task/by-person/{id}",
+ * name="chill_task_singletask_by-person_list")
+ */
+ public function listPersonTasks(
+ Person $person
+ ): Response {
+
+ $this->denyAccessUnlessGranted(TaskVoter::SHOW, $person);
+
+ $filterOrder = $this->buildFilterOrder();
+ $flags = \array_merge(
+ $filterOrder->getCheckboxData('status'),
+ \array_map(fn ($i) => 'state_'.$i, $filterOrder->getCheckboxData('states'))
+ );
+ $nb = $this->singleTaskAclAwareRepository->countByPerson(
+ $person,
+ $filterOrder->getQueryString(),
+ $flags
+ );
+ $paginator = $this->paginatorFactory->create($nb);
+
+ if (0 < $nb) {
+ $tasks = $this->singleTaskAclAwareRepository->findByPerson(
+ $person,
+ $filterOrder->getQueryString(),
+ $flags,
+ $paginator->getCurrentPageFirstItemNumber(),
+ $paginator->getItemsPerPage(),
+ [
+ 'startDate' => 'DESC',
+ 'endDate' => 'DESC',
+ ]
+ );
+ } else {
+ $tasks = [];
+ }
+
+ return $this->render(
+ '@ChillTask/SingleTask/Person/list.html.twig',
+ [
+ 'tasks' => $tasks,
+ 'person' => $person,
+ 'paginator' => $paginator,
+ 'filter_order' => $filterOrder
+ ]);
+ }
}
diff --git a/src/Bundle/ChillTaskBundle/Controller/TaskController.php b/src/Bundle/ChillTaskBundle/Controller/TaskController.php
index 0c2b1f8d2..c137c166d 100644
--- a/src/Bundle/ChillTaskBundle/Controller/TaskController.php
+++ b/src/Bundle/ChillTaskBundle/Controller/TaskController.php
@@ -64,7 +64,7 @@ class TaskController extends AbstractController
'id' => $task->getId(),
'list_params' => $request->query->get('list_params', [])
]);
- $defaultTemplate = '@ChillTask/SingleTask/transition.html.twig';
+ $task->getCourse() === null ? $defaultTemplate = '@ChillTask/SingleTask/Person/transition.html.twig' : $defaultTemplate = '@ChillTask/SingleTask/AccompanyingCourse/transition.html.twig';
break;
default:
return new Response("The type '$kind' is not implemented",
diff --git a/src/Bundle/ChillTaskBundle/DataFixtures/ORM/LoadTaskACL.php b/src/Bundle/ChillTaskBundle/DataFixtures/ORM/LoadTaskACL.php
index 5af11f6a2..513d18ea8 100644
--- a/src/Bundle/ChillTaskBundle/DataFixtures/ORM/LoadTaskACL.php
+++ b/src/Bundle/ChillTaskBundle/DataFixtures/ORM/LoadTaskACL.php
@@ -40,7 +40,7 @@ class LoadTaskACL extends AbstractFixture implements OrderedFixtureInterface
return 16000;
}
-
+
public function load(ObjectManager $manager)
{
foreach (LoadPermissionsGroup::$refs as $permissionsGroupRef) {
@@ -58,33 +58,38 @@ class LoadTaskACL extends AbstractFixture implements OrderedFixtureInterface
case 'direction':
if (in_array($scope->getName()['en'], array('administrative', 'social'))) {
break 2; // we do not want any power on social or administrative
- }
+ }
break;
}
-
+
printf("Adding CHILL_TASK_TASK_UPDATE & CHILL_TASK_TASK_CREATE & Chill_TASK_TASK_DELETE permissions to %s "
- . "permission group, scope '%s' \n",
+ . "permission group, scope '%s' \n",
$permissionsGroup->getName(), $scope->getName()['en']);
$roleScopeUpdate = (new RoleScope())
->setRole(TaskVoter::UPDATE)
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeUpdate);
- $roleScopeCreate = (new RoleScope())
- ->setRole(TaskVoter::CREATE)
+ $roleScopeCreateP = (new RoleScope())
+ ->setRole(TaskVoter::CREATE_PERSON)
->setScope($scope);
- $permissionsGroup->addRoleScope($roleScopeCreate);
+ $permissionsGroup->addRoleScope($roleScopeCreateP);
+ $roleScopeCreateC = (new RoleScope())
+ ->setRole(TaskVoter::CREATE_COURSE)
+ ->setScope($scope);
+ $permissionsGroup->addRoleScope($roleScopeCreateC);
$roleScopeDelete = (new RoleScope())
->setRole(TaskVoter::DELETE)
->setScope($scope);
$permissionsGroup->addRoleScope($roleScopeDelete);
-
+
$manager->persist($roleScopeUpdate);
- $manager->persist($roleScopeCreate);
+ $manager->persist($roleScopeCreateP);
+ $manager->persist($roleScopeCreateC);
$manager->persist($roleScopeDelete);
}
-
+
}
-
+
$manager->flush();
}
diff --git a/src/Bundle/ChillTaskBundle/DependencyInjection/ChillTaskExtension.php b/src/Bundle/ChillTaskBundle/DependencyInjection/ChillTaskExtension.php
index 5ffbd38e7..ed2a36595 100644
--- a/src/Bundle/ChillTaskBundle/DependencyInjection/ChillTaskExtension.php
+++ b/src/Bundle/ChillTaskBundle/DependencyInjection/ChillTaskExtension.php
@@ -44,7 +44,7 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface
$this->prependRoute($container);
$this->prependWorkflows($container);
}
-
+
protected function prependRoute(ContainerBuilder $container)
{
//declare routes for task bundle
@@ -56,17 +56,18 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface
)
));
}
-
+
protected function prependAuthorization(ContainerBuilder $container)
{
$container->prependExtensionConfig('security', array(
'role_hierarchy' => array(
TaskVoter::UPDATE => [TaskVoter::SHOW],
- TaskVoter::CREATE => [TaskVoter::SHOW]
+ TaskVoter::CREATE_COURSE => [TaskVoter::SHOW],
+ TaskVoter::CREATE_PERSON => [TaskVoter::SHOW],
)
));
}
-
+
protected function prependWorkflows(ContainerBuilder $container)
{
$container->prependExtensionConfig('framework', [
diff --git a/src/Bundle/ChillTaskBundle/Entity/AbstractTask.php b/src/Bundle/ChillTaskBundle/Entity/AbstractTask.php
index 73e98c0dd..d34a0b05f 100644
--- a/src/Bundle/ChillTaskBundle/Entity/AbstractTask.php
+++ b/src/Bundle/ChillTaskBundle/Entity/AbstractTask.php
@@ -10,15 +10,12 @@ use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\HasCenterInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency;
+use Chill\PersonBundle\Entity\AccompanyingPeriod;
/**
* AbstractTask
*
* @ORM\MappedSuperclass()
- * @UserCircleConsistency(
- * "CHILL_TASK_TASK_SHOW",
- * getUserFunction="getAssignee"
- * )
*/
abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
{
@@ -51,7 +48,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
* @ORM\Column(name="description", type="text")
*/
private $description = '';
-
+
/**
*
* @var User
@@ -60,36 +57,42 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
* )
*/
private $assignee;
-
+
/**
*
* @var Person
* @ORM\ManyToOne(
* targetEntity="\Chill\PersonBundle\Entity\Person"
* )
- * @Assert\NotNull()
*/
private $person;
-
+
+
+ /**
+ * @var AccompanyingPeriod
+ * @ORM\ManyToOne(targetEntity="\Chill\PersonBundle\Entity\AccompanyingPeriod")
+ */
+
+ private $course;
+
/**
*
* @var Scope
* @ORM\ManyToOne(
* targetEntity="\Chill\MainBundle\Entity\Scope"
- * )
- * @Assert\NotNull()
+ * )
*/
private $circle;
-
+
/**
* @var boolean
* @ORM\Column(name="closed", type="boolean", options={ "default"=false })
*/
private $closed = false;
-
+
public function __construct()
{
-
+
}
/**
@@ -118,7 +121,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
/**
* Set currentStates
- *
+ *
* The current states are sorted in a single array, non associative.
*
* @param $currentStates
@@ -134,8 +137,8 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
/**
* Get currentStates
- *
- * The states are returned as required by marking store format.
+ *
+ * The states are returned as required by marking store format.
*
* @return array
*/
@@ -191,7 +194,7 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
{
return $this->description;
}
-
+
public function getAssignee(): ?User
{
return $this->assignee;
@@ -202,11 +205,16 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
return $this->person;
}
+ public function getCourse(): ?AccompanyingPeriod
+ {
+ return $this->course;
+ }
+
public function getCircle(): ?Scope
{
return $this->circle;
}
-
+
public function setAssignee(User $assignee = null)
{
$this->assignee = $assignee;
@@ -219,6 +227,12 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
return $this;
}
+ public function setCourse(AccompanyingPeriod $course)
+ {
+ $this->course = $course;
+ return $this;
+ }
+
public function setCircle(Scope $circle)
{
$this->circle = $circle;
@@ -229,12 +243,19 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
{
if ($this->getPerson() instanceof Person) {
return $this->getPerson()->getCenter();
+ } else {
+ return $this->getCourse()->getCenter();
}
-
+
return null;
-
+
}
-
+
+ public function getContext()
+ {
+ return $this->getPerson() ?? $this->getCourse();
+ }
+
public function getScope(): ?\Chill\MainBundle\Entity\Scope
{
return $this->getCircle();
@@ -247,9 +268,9 @@ abstract class AbstractTask implements HasScopeInterface, HasCenterInterface
{
return $this->closed;
}
-
+
/**
- *
+ *
* @param bool $closed
*/
public function setClosed(bool $closed)
diff --git a/src/Bundle/ChillTaskBundle/Entity/SingleTask.php b/src/Bundle/ChillTaskBundle/Entity/SingleTask.php
index 23bdd61df..cef59a10a 100644
--- a/src/Bundle/ChillTaskBundle/Entity/SingleTask.php
+++ b/src/Bundle/ChillTaskBundle/Entity/SingleTask.php
@@ -19,6 +19,10 @@ use Doctrine\Common\Collections\Collection;
* @ORM\Index(
* name="by_current_state",
* columns={ "current_states" }
+ * ),
+ * @ORM\Index(
+ * name="by_end_date",
+ * columns={ "end_date" }
* )
* }
* )
@@ -40,12 +44,12 @@ class SingleTask extends AbstractTask
*
* @ORM\Column(name="start_date", type="date", nullable=true)
* @Assert\Date()
- *
+ *
* @Assert\Expression(
* "value === null or this.getEndDate() === null or value < this.getEndDate()",
* message="The start date must be before the end date"
* )
- *
+ *
* @Assert\Expression(
* "value === null or this.getWarningDate() === null or this.getWarningDate() > this.getStartDate()",
* message="The start date must be before warning date"
@@ -66,16 +70,16 @@ class SingleTask extends AbstractTask
* and this.getEndDate() === null
*
* @ORM\Column(name="warning_interval", type="dateinterval", nullable=true)
- *
+ *
* @Assert\Expression(
* "!(value != null and this.getEndDate() == null)",
* message="An end date is required if a warning interval is set"
* )
- *
- *
+ *
+ *
*/
private $warningInterval;
-
+
/**
*
* @var RecurringTask
@@ -85,7 +89,7 @@ class SingleTask extends AbstractTask
* )
*/
private $recurringTask;
-
+
/**
*
* @var \Doctrine\Common\Collections\Collection
@@ -96,15 +100,15 @@ class SingleTask extends AbstractTask
* )
*/
private $taskPlaceEvents;
-
+
public function __construct()
{
$this->taskPlaceEvents = $events = new \Doctrine\Common\Collections\ArrayCollection;
-
+
parent::__construct();
}
-
+
/**
* Get id
*
@@ -186,13 +190,13 @@ class SingleTask extends AbstractTask
{
return $this->warningInterval;
}
-
+
/**
- * Get the Warning date, computed from the difference between the
+ * Get the Warning date, computed from the difference between the
* end date and the warning interval
- *
+ *
* Return null if warningDate or endDate is null
- *
+ *
* @return \DateTimeImmutable
*/
public function getWarningDate()
@@ -200,15 +204,15 @@ class SingleTask extends AbstractTask
if ($this->getWarningInterval() === null) {
return null;
}
-
+
if ($this->getEndDate() === null) {
return null;
}
-
+
return \DateTimeImmutable::createFromMutable($this->getEndDate())
->sub($this->getWarningInterval());
}
-
+
function getRecurringTask(): RecurringTask
{
return $this->recurringTask;
@@ -227,7 +231,7 @@ class SingleTask extends AbstractTask
public function setTaskPlaceEvents(Collection $taskPlaceEvents)
{
$this->taskPlaceEvents = $taskPlaceEvents;
-
+
return $this;
}
}
diff --git a/src/Bundle/ChillTaskBundle/Form/SingleTaskListType.php b/src/Bundle/ChillTaskBundle/Form/SingleTaskListType.php
index 99e8ddb42..634035f96 100644
--- a/src/Bundle/ChillTaskBundle/Form/SingleTaskListType.php
+++ b/src/Bundle/ChillTaskBundle/Form/SingleTaskListType.php
@@ -284,6 +284,7 @@ class SingleTaskListType extends AbstractType
->setDefined('person')
->setDefault('person', null)
->setAllowedTypes('person', [Person::class, 'null'])
+ ->setDefined('accompanyingCourse')
->setDefined('add_status')
->setDefault('add_status', false)
->setAllowedTypes('add_status', ['bool'])
diff --git a/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php b/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php
index 16f000aa2..a3dd1777f 100644
--- a/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php
+++ b/src/Bundle/ChillTaskBundle/Form/SingleTaskType.php
@@ -17,6 +17,10 @@
*/
namespace Chill\TaskBundle\Form;
+use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
+use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
+use Chill\TaskBundle\Security\Authorization\TaskVoter;
+use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
@@ -29,15 +33,26 @@ use Symfony\Component\Security\Core\Role\Role;
use Chill\MainBundle\Form\Type\DateIntervalType;
use Chill\MainBundle\Form\Type\ChillTextareaType;
-/**
- *
- *
- * @author Julien Fastré
- */
class SingleTaskType extends AbstractType
{
+ private ParameterBagInterface $parameterBag;
+ private CenterResolverDispatcher $centerResolverDispatcher;
+ private ScopeResolverDispatcher $scopeResolverDispatcher;
+
+ public function __construct(ParameterBagInterface $parameterBag, CenterResolverDispatcher $centerResolverDispatcher, ScopeResolverDispatcher $scopeResolverDispatcher)
+ {
+ $this->parameterBag = $parameterBag;
+ $this->centerResolverDispatcher = $centerResolverDispatcher;
+ $this->scopeResolverDispatcher = $scopeResolverDispatcher;
+ }
+
public function buildForm(FormBuilderInterface $builder, array $options)
{
+ if (NULL !== $task = $options['data']) {
+ $center = $this->centerResolverDispatcher->resolveCenter($task);
+ $isScopeConcerned = $this->scopeResolverDispatcher->isConcerned($task);
+ }
+
$builder
->add('title', TextType::class)
->add('description', ChillTextareaType::class, [
@@ -45,14 +60,10 @@ class SingleTaskType extends AbstractType
])
->add('assignee', UserPickerType::class, [
'required' => false,
- 'center' => $options['center'],
- 'role' => $options['role'],
+ 'center' => $center,
+ 'role' => TaskVoter::SHOW,
'placeholder' => 'Not assigned'
- ])
- ->add('circle', ScopePickerType::class, [
- 'center' => $options['center'],
- 'role' => $options['role']
- ])
+ ])
->add('startDate', ChillDateType::class, [
'required' => false
])
@@ -61,17 +72,24 @@ class SingleTaskType extends AbstractType
])
->add('warningInterval', DateIntervalType::class, [
'required' => false
- ])
- ;
+ ]);
+
+ if ($this->parameterBag->get('chill_main')['acl']['form_show_scopes']
+ && $isScopeConcerned) {
+ $builder
+ ->add('circle', ScopePickerType::class, [
+ 'center' => $center,
+ 'role' => $options['role'],
+ 'required' => false
+ ]);
+ }
}
-
+
public function configureOptions(OptionsResolver $resolver)
{
$resolver
- ->setRequired('center')
- ->setAllowedTypes('center', [ Center::class ])
->setRequired('role')
- ->setAllowedTypes('role', [ Role::class ])
+ ->setAllowedTypes('role', [ Role::class, 'string' ])
;
}
}
diff --git a/src/Bundle/ChillTaskBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillTaskBundle/Menu/MenuBuilder.php
similarity index 56%
rename from src/Bundle/ChillTaskBundle/Menu/PersonMenuBuilder.php
rename to src/Bundle/ChillTaskBundle/Menu/MenuBuilder.php
index ee0afa497..dfc95535b 100644
--- a/src/Bundle/ChillTaskBundle/Menu/PersonMenuBuilder.php
+++ b/src/Bundle/ChillTaskBundle/Menu/MenuBuilder.php
@@ -24,24 +24,24 @@ use Chill\TaskBundle\Security\Authorization\TaskVoter;
use Symfony\Component\Translation\TranslatorInterface;
/**
- *
+ *
*
* @author Julien Fastré
*/
-class PersonMenuBuilder implements LocalMenuBuilderInterface
+class MenuBuilder implements LocalMenuBuilderInterface
{
/**
*
* @var TranslatorInterface
*/
protected $translator;
-
+
/**
*
* @var AuthorizationCheckerInterface
*/
protected $authorizationChecker;
-
+
public function __construct(
AuthorizationCheckerInterface $authorizationChecker,
TranslatorInterface $translator)
@@ -50,31 +50,58 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
$this->authorizationChecker = $authorizationChecker;
}
-
+
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
- /* @var $person \Chill\PersonBundle\Entity\Person */
- $person = $parameters['person'] ?? null;
-
- if ($this->authorizationChecker->isGranted(TaskVoter::SHOW, $person)) {
- $menu->addChild(
- $this->translator->trans('Tasks'), [
- 'route' => 'chill_task_singletask_list',
- 'routeParameters' => $menuId === 'person' ?
- [ 'person_id' => $person->getId() ]
- :
- null,
- ])
- ->setExtra('order', 400)
- ;
- if ($menuId === 'section') {
- $menu->setExtra('icons', 'tasks');
- }
+ switch($menuId) {
+ case 'person':
+ $this->buildPersonMenu($menu, $parameters);
+ break;
+ case 'accompanyingCourse':
+ $this->buildAccompanyingCourseMenu($menu, $parameters);
+ break;
+ case 'section':
+ $menu->setExtras('icons', 'tasks');
+ break;
+ default:
+ throw new \LogicException("this menuid $menuId is not implemented");
}
}
+ public function buildPersonMenu($menu, $parameters){
+
+ //var $person \Chill\PersonBundle\Entity\Person */
+ $person = $parameters['person'] ?? null;
+
+ if ($this->authorizationChecker->isGranted(TaskVoter::SHOW, $person)) {
+ $menu->addChild(
+ $this->translator->trans('Tasks'), [
+ 'route' => 'chill_task_singletask_by-person_list',
+ 'routeParameters' =>
+ [ 'id' => $person->getId() ]
+ ])
+ ->setExtra('order', 400);
+ }
+ }
+
+ public function buildAccompanyingCourseMenu($menu, $parameters){
+
+ $course = $parameters['accompanyingCourse'];
+
+ if ($this->authorizationChecker->isGranted(TaskVoter::SHOW, $course)) {
+ $menu->addChild(
+ $this->translator->trans('Tasks'), [
+ 'route' => 'chill_task_singletask_by-course_list',
+ 'routeParameters' =>
+ [ 'id' => $course->getId() ]
+ ])
+ ->setExtra('order', 400);
+ }
+ }
+
+
public static function getMenuIds(): array
{
- return ['person'];
+ return ['person', 'accompanyingCourse'];
}
}
diff --git a/src/Bundle/ChillTaskBundle/Menu/UserMenuBuilder.php b/src/Bundle/ChillTaskBundle/Menu/UserMenuBuilder.php
index 0330699ad..419dab9b3 100644
--- a/src/Bundle/ChillTaskBundle/Menu/UserMenuBuilder.php
+++ b/src/Bundle/ChillTaskBundle/Menu/UserMenuBuilder.php
@@ -88,69 +88,49 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
if ($ended > 0) {
$this->addItemInMenu(
$menu,
- $user,
'%number% tasks over deadline',
- 'My tasks over deadline',
- SingleTaskRepository::DATE_STATUS_ENDED,
$ended,
- -15);
+ -15,
+ ['new', 'in_progress'],
+ ['alert']
+ );
}
if ($warning > 0) {
$this->addItemInMenu(
$menu,
- $user,
'%number% tasks near deadline',
- 'My tasks near deadline',
- SingleTaskRepository::DATE_STATUS_WARNING,
$warning,
- -14);
+ -14,
+ ['new', 'in_progress'],
+ ['warning']
+ );
}
$menu->addChild("My tasks", [
- 'route' => 'chill_task_single_my_tasks'
+ 'route' => 'chill_task_singletask_my_tasks'
])
->setExtras([
'order' => -10,
'icon' => 'tasks'
]);
- // $menu->addChild("My calendar list", [
- // 'route' => 'chill_calendar_calendar_list',
- // 'routeParameters' => [
- // 'user_id' => $user->getId(),
- // ]
- // ])
- // ->setExtras([
- // 'order' => -9,
- // 'icon' => 'tasks'
- // ]);
-
- /*
- $menu->addChild("My aside activities", [
- 'route' => 'chill_crud_aside_activity_index'
- ])
- ->setExtras([
- 'order' => -10,
- 'icon' => 'tasks'
- ]);
- */
}
- protected function addItemInMenu(MenuItem $menu, User $u, $message, $title, $status, $number, $order)
+ protected function addItemInMenu(MenuItem $menu, $message, $number, $order, array $states = [], array $status = [])
{
if ($number > 0) {
$menu->addChild(
$this->translator->transChoice($message, $number),
[
- 'route' => 'chill_task_singletask_list',
+ 'route' => 'chill_task_singletask_my_tasks',
'routeParameters' => [
- 'user_id' => $u->getId(),
- 'status' => [
- $status
- ],
- 'hide_form' => true,
- 'title' => $this->translator->trans($title)
+ 'f' => [
+ 'checkboxes' => [
+ 'states' => $states,
+ 'status' => $status
+ ]
+ ]
]
])
->setExtras([
diff --git a/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php b/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php
new file mode 100644
index 000000000..be8833e75
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepository.php
@@ -0,0 +1,335 @@
+centerResolverDispatcher = $centerResolverDispatcher;
+ $this->em = $em;
+ $this->security = $security;
+ $this->authorizationHelper = $authorizationHelper;
+ }
+
+ public function findByCurrentUsersTasks(
+ ?string $pattern = null,
+ ?array $flags = [],
+ ?int $start = 0,
+ ?int $limit = 50,
+ ?array $orderBy = []
+ ): array {
+ $qb = $this->buildQueryMyTasks($pattern, $flags);
+
+ return $this->getResult($qb, $start, $limit, $orderBy);
+ }
+
+ public function countByCurrentUsersTasks(
+ ?string $pattern = null,
+ ?array $flags = []
+ ): int {
+ return $this->buildQueryMyTasks($pattern, $flags)
+ ->select('COUNT(t)')
+ ->getQuery()->getSingleScalarResult();
+ }
+
+ public function findByCourse(
+ AccompanyingPeriod $course,
+ ?string $pattern = null,
+ ?array $flags = [],
+ ?int $start = 0,
+ ?int $limit = 50,
+ ?array $orderBy = []
+ ): array {
+ $qb = $this->buildQueryByCourse($course, $pattern, $flags);
+ $qb = $this->addACL($qb, $course);
+
+ return $this->getResult($qb, $start, $limit, $orderBy);
+ }
+
+ public function countByCourse(
+ AccompanyingPeriod $course,
+ ?string $pattern = null,
+ ?array $flags = []
+ ): int {
+ $qb = $this->buildQueryByCourse($course, $pattern, $flags);
+
+ return $this
+ ->addACL($qb, $course)
+ ->select('COUNT(t)')
+ ->getQuery()->getSingleScalarResult();
+ }
+
+ public function findByPerson(
+ Person $person,
+ ?string $pattern = null,
+ ?array $flags = [],
+ ?int $start = 0,
+ ?int $limit = 50,
+ ?array $orderBy = []
+ ): array {
+ $qb = $this->buildQueryByPerson($person, $pattern, $flags);
+ $qb = $this->addACL($qb, $person);
+
+ return $this->getResult($qb, $start, $limit, $orderBy);
+ }
+
+ public function countByPerson(
+ Person $person,
+ ?string $pattern = null,
+ ?array $flags = []
+ ): int {
+ $qb = $this->buildQueryByPerson($person, $pattern, $flags);
+
+ return $this
+ ->addACL($qb, $person)
+ ->select('COUNT(t)')
+ ->getQuery()->getSingleScalarResult();
+ }
+
+ public function countByAllViewable(
+ ?string $pattern = null,
+ ?array $flags = []
+ ): int {
+ $qb = $this->buildBaseQuery($pattern, $flags);
+
+ return $this
+ ->addACLGlobal($qb)
+ ->select('COUNT(t)')
+ ->getQuery()->getSingleScalarResult();
+ }
+
+ public function findByAllViewable(
+ ?string $pattern = null,
+ ?array $flags = [],
+ ?int $start = 0,
+ ?int $limit = 50,
+ ?array $orderBy = []
+ ): array {
+ $qb = $this->buildBaseQuery($pattern, $flags);
+ $qb = $this->addACLGlobal($qb);
+
+ return $this->getResult($qb, $start, $limit, $orderBy);
+ }
+
+ public function buildQueryByCourse(
+ AccompanyingPeriod $course,
+ ?string $pattern = null,
+ ?array $flags = []
+ ) : QueryBuilder {
+ $qb = $this->buildBaseQuery($pattern, $flags);
+
+ return $qb
+ ->andWhere($qb->expr()->eq('t.course', ':course'))
+ ->setParameter('course', $course)
+ ;
+ }
+
+ public function buildQueryByPerson(
+ Person $person,
+ ?string $pattern = null,
+ ?array $flags = []
+ ): QueryBuilder
+ {
+ $qb = $this->buildBaseQuery($pattern, $flags);
+
+ return $qb
+ ->andWhere($qb->expr()->eq('t.person', ':person'))
+ ->setParameter('person', $person);
+ }
+
+ public function buildQueryMyTasks(
+ ?string $pattern = null,
+ ?array $flags = []
+ ): QueryBuilder {
+ $qb = $this->buildBaseQuery($pattern, $flags);
+
+ return $qb
+ ->andWhere($qb->expr()->eq('t.assignee', ':user'))
+ ->setParameter('user', $this->security->getUser())
+ ;
+ }
+
+ public function getResult(
+ QueryBuilder $qb,
+ ?int $start = 0,
+ ?int $limit = 50,
+ ?array $orderBy = []
+ ): array {
+ $qb->select('t');
+
+ $qb
+ ->setFirstResult($start)
+ ->setMaxResults($limit)
+ ;
+
+ foreach ($orderBy as $field => $direction) {
+ $qb->addOrderBy('t.'.$field, $direction);
+ }
+
+ return $qb->getQuery()->getResult();
+ }
+
+ private function addACL(
+ QueryBuilder $qb,
+ $entity
+ ): QueryBuilder {
+ $scopes = $this->authorizationHelper->getReachableScopes($this->security->getUser(),
+ TaskVoter::SHOW, $this->centerResolverDispatcher->resolveCenter($entity));
+
+ return $qb->andWhere($qb->expr()->in('t.circle', ':scopes'))
+ ->setParameter('scopes', $scopes);
+ }
+
+ private function addACLGlobal(
+ QueryBuilder $qb
+ ): QueryBuilder {
+ $allowedCenters = $this->authorizationHelper
+ ->getReachableCenters($this->security->getUser(), TaskVoter::SHOW);
+
+ if ([] === $allowedCenters) {
+ $qb
+ ->andWhere($qb->expr()->lt('t.id', ':falseid'))
+ ->setParameter('falseid', -1);
+ }
+
+ $qb->leftJoin('t.person', 'person')
+ ->leftJoin('t.course', 'course')
+ ->leftJoin('course.participations', 'participation')
+ ->leftJoin('participation.person', 'person_p')
+ ;
+ $qb->distinct(true);
+
+ $k = 0;
+ $orX = $qb->expr()->orX();
+ foreach ($allowedCenters as $center) {
+ $allowedScopes = $this->authorizationHelper->getReachableScopes($this->security->getUser(),
+ TaskVoter::SHOW, $center);
+
+ $and = $qb->expr()->andX(
+ $qb->expr()->orX(
+ $qb->expr()->eq('person.center', ':center_'.$k),
+ $qb->expr()->eq('person_p.center', ':center_'.$k)
+ ),
+ $qb->expr()->in('t.circle', ':scopes_'.$k)
+ );
+ $qb
+ ->setParameter('center_'.$k, $center)
+ ->setParameter('scopes_'.$k, $allowedScopes);
+ $orX->add($and);
+
+ $k++;
+ }
+ $qb->andWhere($orX);
+
+ return $qb;
+ }
+
+ public function buildBaseQuery (
+ ?string $pattern = null,
+ ?array $flags = []
+ ): QueryBuilder {
+ $qb = $this->em->createQueryBuilder();
+ $qb
+ ->from(SingleTask::class, 't')
+ ;
+
+ if (!empty($pattern)) {
+ $qb->andWhere($qb->expr()->like('LOWER(UNACCENT(t.title))', 'LOWER(UNACCENT(:pattern))'))
+ ->setParameter('pattern', '%'.$pattern.'%')
+ ;
+ }
+
+ if (count($flags) > 0) {
+ $orXDate = $qb->expr()->orX();
+ $orXState = $qb->expr()->orX();
+ $now = new \DateTime();
+
+ foreach ($flags as $key => $flag) {
+ switch ($flag) {
+ case 'no-alert':
+ $orXDate
+ ->add(
+ $qb->expr()->orX(
+ $qb->expr()->isNull('t.endDate'),
+ $qb->expr()->gte('t.endDate - COALESCE(t.warningInterval, :intervalBlank)', ':now')
+ )
+ );
+ $qb
+ ->setParameter('intervalBlank', new \DateInterval('P0D'))
+ ->setParameter('now', $now);
+ break;
+ case 'warning':
+ $orXDate
+ ->add(
+ $qb->expr()->andX(
+ $qb->expr()->not($qb->expr()->isNull('t.endDate')),
+ $qb->expr()->not($qb->expr()->isNull('t.warningInterval')),
+ $qb->expr()->lte('t.endDate - t.warningInterval', ':now'),
+ $qb->expr()->gt('t.endDate', ':now')
+ )
+ );
+ $qb
+ ->setParameter('now', $now);
+ break;
+ case 'alert':
+ $orXDate
+ ->add(
+ $qb->expr()->andX(
+ $qb->expr()->not($qb->expr()->isNull('t.endDate')),
+ $qb->expr()->lte('t.endDate', ':now')
+ )
+ );
+ $qb
+ ->setParameter('now', $now);
+ break;
+ case 'state_new':
+ $orXState
+ ->add(
+ "JSONB_ARRAY_LENGTH(t.currentStates) = 0"
+ );
+ break;
+ case \substr($flag, 0, 6) === 'state_':
+ $state = \substr($flag, 6);
+ $orXState
+ ->add(
+ "JSONB_EXISTS_IN_ARRAY(t.currentStates, :state_$key) = 'TRUE'"
+ );
+ $qb->setParameter("state_$key", $state);
+ break;
+ default:
+ throw new \LogicException("this flag is not supported: $flag");
+ }
+ }
+
+ if ($orXDate->count() > 0) {
+ $qb->andWhere($orXDate);
+ }
+ if ($orXState->count() > 0) {
+ $qb->andWhere($orXState);
+ }
+ }
+
+ return $qb;
+ }
+
+}
diff --git a/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepositoryInterface.php b/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepositoryInterface.php
new file mode 100644
index 000000000..58532daa0
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Repository/SingleTaskAclAwareRepositoryInterface.php
@@ -0,0 +1,56 @@
+andWhere($qb->expr()->isNull('st.assignee'));
}
@@ -139,7 +139,7 @@ class SingleTaskRepository extends EntityRepository
$qb->andWhere($qb->expr()->eq('st.circle', ':scope'));
$qb->setParameter('scope', $params['scope']);
}
-
+
if (\array_key_exists('types', $params) && $params['types'] !== NULL) {
if (count($params['types']) > 0) {
$qb->andWhere($qb->expr()->in('st.type', ':types'));
@@ -154,7 +154,7 @@ class SingleTaskRepository extends EntityRepository
if (\array_key_exists('is_closed', $params)) {
$qb->andWhere($this->buildIsClosed($qb, !$params['is_closed']));
}
-
+
}
protected function addTypeFilter(QueryBuilder $qb, $params)
@@ -191,7 +191,7 @@ class SingleTaskRepository extends EntityRepository
->add($this->buildNowIsAfterStartDate($qb, true))
;
}
- $qb->setParameter('now', new \DateTime('today'), Type::DATE);
+ $qb->setParameter('now', new \DateTime('today'), Types::DATE_MUTABLE);
$qb->andWhere($andWhere);
}
diff --git a/src/Bundle/ChillTaskBundle/Resources/public/chill/scss/_task-list.scss b/src/Bundle/ChillTaskBundle/Resources/public/chill/scss/_task-list.scss
index 3350a4ace..e69cf0325 100644
--- a/src/Bundle/ChillTaskBundle/Resources/public/chill/scss/_task-list.scss
+++ b/src/Bundle/ChillTaskBundle/Resources/public/chill/scss/_task-list.scss
@@ -1,13 +1,21 @@
+/*
+ !!!!!!!
+ This is a legacy version for task list. The new task are now
+ layed out in page/tile_list/index.js
+ !!!!!!
+*/
+
table.chill-task-list {
+
.chill-task-list__row > div {
margin-bottom: 0.50rem;
}
-
+
.chill-task-list__row__title {
font-weight: bold;
font-size: 1.40rem;
}
-
+
.chill-task-list__row__type {
font-variant: small-caps;
display: inline;
@@ -21,31 +29,31 @@ table.chill-task-list {
border: 1px solid var(--chill-dark-gray);
color: var(--chill-dark-gray);
}
-
+
.chill-task-list__row__person-for {
display: inline;
font-weight: bold;
}
-
+
.chill-task-list__row__assignee {
display: inline;
-
+
}
-
+
.chill_task-list__row__assignee_by {
display: inline;
font-weight: bold;
}
-
+
.chill-task-list__row__dates {
& > ul {
display: inline;
list-style: none;
-
+
& > li {
display: inline;
margin-right: 0.25rem;
-
+
}
}
}
diff --git a/src/Bundle/ChillTaskBundle/Resources/public/chill/scss/_task-statuses.scss b/src/Bundle/ChillTaskBundle/Resources/public/chill/scss/_task-statuses.scss
index 5e371359e..4f598fc7b 100644
--- a/src/Bundle/ChillTaskBundle/Resources/public/chill/scss/_task-statuses.scss
+++ b/src/Bundle/ChillTaskBundle/Resources/public/chill/scss/_task-statuses.scss
@@ -1,34 +1,22 @@
+// Access to Bootstrap variables and mixins
+@import '~ChillMainAssets/module/bootstrap/shared';
+
.task-status {
- &.box {
- font-variant: small-caps;
- display: inline;
- padding: .2em .6em .3em;
- font-size: 0.88rem;
- font-weight: bold;
- line-height: 1;
- text-align: center;
- white-space: nowrap;
- vertical-align: baseline;
- border-radius: .25em;
- color: white;
+
+ // 'new', 'in_progress', 'closed', 'canceled'
+ &.place-new {
+ background-color: $chill-yellow;
}
-
- &.type-task_default {
- // 'new', 'in_progress', 'closed', 'canceled'
- &.place-new {
- background-color: var(--chill-yellow);
- }
- &.place-in_progress {
- background-color: var(--chill-green);
- }
+ &.place-in_progress {
+ background-color: $chill-green;
+ }
- &.place-closed {
- background-color: var(--chill-blue);
- }
+ &.place-closed {
+ background-color: $chill-blue;
+ }
- &.place-canceled {
- background-color: var(--chill-beige);
- }
+ &.place-canceled {
+ background-color: $chill-beige;
}
}
diff --git a/src/Bundle/ChillTaskBundle/Resources/public/page/tile_list/index.js b/src/Bundle/ChillTaskBundle/Resources/public/page/tile_list/index.js
new file mode 100644
index 000000000..a8217c7a7
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/public/page/tile_list/index.js
@@ -0,0 +1 @@
+require("./task_list.scss");
diff --git a/src/Bundle/ChillTaskBundle/Resources/public/page/tile_list/task_list.scss b/src/Bundle/ChillTaskBundle/Resources/public/page/tile_list/task_list.scss
new file mode 100644
index 000000000..4c71462d1
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/public/page/tile_list/task_list.scss
@@ -0,0 +1,24 @@
+.chill-task-list {
+ .task-type {
+ font-variant: small-caps;
+ display: inline;
+ padding: 0.05rem .15rem;
+ font-size: 0.88rem;
+ font-weight: light;
+ line-height: 1;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ border: 1px solid var(--chill-dark-gray);
+ color: var(--chill-dark-gray);
+ }
+
+ .assignee {
+ text-align: right;
+ }
+
+ .dates {
+ text-align: right;
+ }
+
+}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/confirm_delete.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/confirm_delete.html.twig
new file mode 100644
index 000000000..02a0aa31d
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/confirm_delete.html.twig
@@ -0,0 +1,19 @@
+{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_task_task_list' %}
+{% set course = task.course %}
+
+{% block title 'Remove task'|trans %}
+
+{% block content %}
+
+ {{ include('@ChillMain/Util/confirmation_template.html.twig',
+ {
+ 'title' : 'Remove task'|trans,
+ 'confirm_question' : 'Are you sure you want to remove the task "%title%" ?'|trans({ '%title%' : task.title } ),
+ 'cancel_route' : 'chill_task_singletask_by-course_list',
+ 'cancel_parameters' : {'id' : task.course.id },
+ 'form' : delete_form,
+ } ) }}
+
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/edit.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/edit.html.twig
new file mode 100644
index 000000000..ccf0bab04
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/edit.html.twig
@@ -0,0 +1,15 @@
+{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_task_single_task_edit' %}
+{% set course = task.course %}
+
+{% block title %}
+ {{ 'Edit task'|trans }}
+{% endblock %}
+
+{% block content %}
+
+ {% include '@ChillTask/SingleTask/_edit.html.twig' %}
+
+
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/list.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/list.html.twig
new file mode 100644
index 000000000..d00ee7a5e
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/list.html.twig
@@ -0,0 +1,46 @@
+{% extends '@ChillPerson/AccompanyingCourse/layout.html.twig' %}
+
+{% block title 'Tasks for this accompanying period'|trans %}
+
+{% block content %}
+
+
+
{{ block('title') }}
+
+ {{ filter_order|chill_render_filter_order_helper }}
+
+ {% if tasks|length == 0 %}
+
{{ 'Any tasks'|trans }}
+ {% else %}
+
+ {% for task in tasks %}
+ {% include 'ChillTaskBundle:SingleTask/List:index_item.html.twig' with { 'showContext' : false } %}
+ {% endfor %}
+
+ {% endif %}
+
+ {{ chill_pagination(paginator) }}
+
+ {% if is_granted('CHILL_TASK_TASK_CREATE_FOR_COURSE', accompanyingCourse) %}
+
+ {% endif %}
+
+
+{% endblock %}
+
+{% block css %}
+ {{ parent() }}
+ {{ encore_entry_link_tags('page_task_list') }}
+{% endblock %}
+{% block js %}
+ {{ parent() }}
+ {{ encore_entry_script_tags('page_task_list') }}
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/new.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/new.html.twig
new file mode 100644
index 000000000..b8f810540
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/new.html.twig
@@ -0,0 +1,13 @@
+{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_task_single_task_new' %}
+{# {% set person = task.person %} #}
+
+{% block title %}
+ {{ 'New task'|trans }}
+{% endblock %}
+
+{% block content %}
+ {% include '@ChillTask/SingleTask/_new.html.twig' %}
+
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/show.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/show.html.twig
new file mode 100644
index 000000000..06ff62a60
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/show.html.twig
@@ -0,0 +1,17 @@
+{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
+
+
+{% set activeRouteKey = 'chill_task_single_task_show' %}
+{% set accompanyingCourse = task.course %}
+
+{% block title %}
+ {{ 'Task'|trans }}
+{% endblock %}
+
+
+{% block content %}
+
+ {% include '@ChillTask/SingleTask/_show.html.twig' %}
+
+
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/transition.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/transition.html.twig
new file mode 100644
index 000000000..08bedd31c
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/AccompanyingCourse/transition.html.twig
@@ -0,0 +1,12 @@
+{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_task_task_list' %}
+{% set accompanyingCourse = task.course %}
+
+{% block title 'Remove task'|trans %}
+
+{% block content %}
+
+ {% include '@ChillTask/SingleTask/_transition.html.twig' %}
+
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index.html.twig
new file mode 100644
index 000000000..8fe45959e
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index.html.twig
@@ -0,0 +1,34 @@
+{% extends 'ChillMainBundle::layout.html.twig' %}
+
+{% block title 'Tasks'|trans %}
+
+{% block content %}
+
+
+
{{ block('title') }}
+
+ {{ filter_order|chill_render_filter_order_helper }}
+
+ {% if tasks|length == 0 %}
+
{{ 'Any tasks'|trans }}
+ {% else %}
+
+ {% for task in tasks %}
+ {% include 'ChillTaskBundle:SingleTask/List:index_item.html.twig' with { 'showContext' : true} %}
+ {% endfor %}
+
+ {% endif %}
+
+ {{ chill_pagination(paginator) }}
+
+
+{% endblock %}
+
+{% block css %}
+ {{ parent() }}
+ {{ encore_entry_link_tags('page_task_list') }}
+{% endblock %}
+{% block js %}
+ {{ parent() }}
+ {{ encore_entry_script_tags('page_task_list') }}
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_item.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_item.html.twig
new file mode 100644
index 000000000..284769448
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_item.html.twig
@@ -0,0 +1,125 @@
+
+
+
+
+ {{ task.title }}
+ {% for place in workflow_marked_places(task) %}
+
+ {{ place|trans }}
+
+ {% endfor %}
+
+ {% if task.type != 'task_default'%}
+
+ {{ task_workflow_metadata(task, 'definition.name')|trans }}
+
+ {% endif %}
+ {% if showContext %}
+
+ {% if task.person is not null %}
+
+ {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
+ targetEntity: { name: 'person', id: task.person.id },
+ action: 'show',
+ displayBadge: true,
+ buttonText: task.person|chill_entity_render_string
+ } %}
+
+ {% elseif task.course is not null %}
+
+
+
+
+ {% for part in task.course.currentParticipations %}
+ {% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
+ targetEntity: { name: 'person', id: part.person.id },
+ action: 'show',
+ displayBadge: true,
+ buttonText: part.person|chill_entity_render_string
+ } %}
+ {% endfor %}
+
+ {% endif %}
+
+ {% endif %}
+
+
+
+
+
+ {% if task.assignee is not null %}
+
+ {{ task.assignee|chill_entity_render_box }}
+
+ {% endif %}
+
+ {% if task.startDate is not null or task.warningDate is not null or task.endDate is not null %}
+
+
+ {% if task.startDate is not null %}
+
+
+ {{ task.startDate|format_date('medium') }}
+
+ {% endif %}
+ {% if task.warningDate is not null %}
+
+
+ {{ task.warningDate|format_date('medium') }}
+
+ {% endif %}
+ {% if task.endDate is not null %}
+
+
+ {{ task.endDate|format_date('medium') }}
+
+ {% endif %}
+
+
+ {% endif %}
+
+
+
+
+
+
+
+ {% if workflow_transitions(task)|length > 0 %}
+
+
+
+ {% endif %}
+
+
+
+
+
+ {% if is_granted('CHILL_TASK_TASK_UPDATE', task) %}
+
+
+
+ {% endif %}
+
+ {% if is_granted('CHILL_TASK_TASK_DELETE', task) %}
+
+
+
+ {% endif %}
+
+
+
+
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_my_tasks.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_my_tasks.html.twig
new file mode 100644
index 000000000..5d7e88542
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/List/index_my_tasks.html.twig
@@ -0,0 +1,34 @@
+{% extends 'ChillMainBundle::layout.html.twig' %}
+
+{% block title 'My tasks'|trans %}
+
+{% block content %}
+
+
+
{{ block('title') }}
+
+ {{ filter_order|chill_render_filter_order_helper }}
+
+ {% if tasks|length == 0 %}
+
{{ 'Any tasks'|trans }}
+ {% else %}
+
+ {% for task in tasks %}
+ {% include 'ChillTaskBundle:SingleTask/List:index_item.html.twig' with { 'showContext' : true} %}
+ {% endfor %}
+
+ {% endif %}
+
+ {{ chill_pagination(paginator) }}
+
+
+{% endblock %}
+
+{% block css %}
+ {{ parent() }}
+ {{ encore_entry_link_tags('page_task_list') }}
+{% endblock %}
+{% block js %}
+ {{ parent() }}
+ {{ encore_entry_script_tags('page_task_list') }}
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/confirm_delete.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/confirm_delete.html.twig
similarity index 100%
rename from src/Bundle/ChillTaskBundle/Resources/views/SingleTask/confirm_delete.html.twig
rename to src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/confirm_delete.html.twig
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/edit.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/edit.html.twig
new file mode 100644
index 000000000..6188bd859
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/edit.html.twig
@@ -0,0 +1,31 @@
+{#
+ * Copyright (C) 2014, Champs Libres Cooperative SCRLFS,
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+#}
+{% extends person is defined ? "@ChillPerson/Person/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_task_single_task_edit' %}
+{% set person = task.person %}
+
+{% block title %}
+ {{ 'Edit task'|trans }}
+{% endblock %}
+
+{% block personcontent %}
+
+ {% include '@ChillTask/SingleTask/_edit.html.twig' %}
+
+
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/list.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/list.html.twig
new file mode 100644
index 000000000..ee236b644
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/list.html.twig
@@ -0,0 +1,44 @@
+{% extends '@ChillPerson/Person/layout.html.twig' %}
+
+{% set activeRouteKey = '' %}
+
+{% block title 'Tasks for {{ name }}'|trans({ '{{ name }}' : person|chill_entity_render_string }) %}
+
+{% block personcontent %}
+
+
+
{{ block('title') }}
+
+ {{ filter_order|chill_render_filter_order_helper }}
+
+ {% if tasks|length == 0 %}
+
{{ 'Any tasks'|trans }}
+ {% else %}
+
+ {% for task in tasks %}
+ {% include 'ChillTaskBundle:SingleTask/List:index_item.html.twig' with { 'showContext' : false } %}
+ {% endfor %}
+
+ {% endif %}
+
+ {{ chill_pagination(paginator) }}
+
+ {% if is_granted('CHILL_TASK_TASK_CREATE_FOR_PERSON', person) %}
+
+ {% endif %}
+
+
+{% endblock %}
+
+{% block css %}
+ {{ encore_entry_link_tags('page_task_list') }}
+{% endblock %}
+{% block js %}
+ {{ encore_entry_script_tags('page_task_list') }}
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/new.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/new.html.twig
similarity index 51%
rename from src/Bundle/ChillTaskBundle/Resources/views/SingleTask/new.html.twig
rename to src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/new.html.twig
index 8f6ba2a4b..226956aec 100644
--- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/new.html.twig
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/new.html.twig
@@ -14,37 +14,17 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
#}
-{% extends "@ChillPerson/Person/layout.html.twig" %}
+{% extends person is defined ? "@ChillPerson/Person/layout.html.twig" : "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
{% set activeRouteKey = 'chill_task_single_task_new' %}
{% set person = task.person %}
-{% block title %}{{ 'New task'|trans }}{% endblock %}
+{% block title %}
+ {{ 'New task'|trans }}
+{% endblock %}
+
{% block personcontent %}
-
-
-
{{ 'New task'|trans }}
+ {% include '@ChillTask/SingleTask/_new.html.twig' %}
- {{ form_start(form) }}
-
- {{ form_errors(form) }}
-
- {{ form_row(form.title) }}
- {{ form_row(form.description) }}
- {{ form_row(form.assignee) }}
- {{ form_row(form.circle) }}
- {{ form_row(form.startDate) }}
- {{ form_row(form.endDate) }}
- {{ form_row(form.warningInterval) }}
-
-
-
- {{ form_end(form) }}
-
-
{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/show.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/show.html.twig
new file mode 100644
index 000000000..3ac97f0f3
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/show.html.twig
@@ -0,0 +1,33 @@
+{#
+ * Copyright (C) 2014, Champs Libres Cooperative SCRLFS,
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+#}
+{% extends person is defined ? "@ChillPerson/Person/layout.html.twig" : "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
+
+
+{% set activeRouteKey = 'chill_task_single_task_show' %}
+{% set person = task.person %}
+
+{% block title %}
+ {{ 'Task'|trans }}
+{% endblock %}
+
+
+{% block personcontent %}
+
+ {% include '@ChillTask/SingleTask/_show.html.twig' %}
+
+
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/transition.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/transition.html.twig
new file mode 100644
index 000000000..05009a2f0
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/Person/transition.html.twig
@@ -0,0 +1,12 @@
+{% extends "@ChillPerson/Person/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_task_task_list' %}
+{% set person = task.person %}
+
+{% block title 'Remove task'|trans %}
+
+{% block personcontent %}
+
+{% include '@ChillTask/SingleTask/_transition.html.twig' %}
+
+{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig
new file mode 100644
index 000000000..d0a906c9c
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_edit.html.twig
@@ -0,0 +1,27 @@
+
+
+
{{ 'Edit task'|trans }}
+
+ {{ form_start(form) }}
+
+ {{ form_row(form.title) }}
+ {{ form_row(form.description) }}
+ {{ form_row(form.assignee) }}
+ {% if form.circle is defined %}
+ {{ form_row(form.circle) }}
+ {% endif %}
+ {{ form_row(form.startDate) }}
+ {{ form_row(form.endDate) }}
+ {{ form_row(form.warningInterval) }}
+
+
+ {{ form_end(form) }}
+
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_list.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_list.html.twig
deleted file mode 100644
index a5a867dcc..000000000
--- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_list.html.twig
+++ /dev/null
@@ -1,244 +0,0 @@
-{% macro date_status(title, tasks, count, paginator, status, isSingleStatus, person, user) %}
- {% if tasks|length > 0 %}
- {{ title|trans }}
-
-
-
- {% for task in tasks %}
-
-
-
- {{ task.title }}
-
-
- {% if person is null %}
-
- {% endif %}
-
-
- {{ task_workflow_metadata(task, 'definition.name')|trans }}
-
-
-
- {% for place in workflow_marked_places(task) %}
-
{{ place|trans }}
- {% endfor %}
- {% if task.assignee is not null %}
-
{{ 'By'|trans }} : {{ task.assignee.username }}
- {% endif %}
-
-
- {% if task.startDate is not null or task.warningDate is not null or task.endDate is not null %}
-
-
- {% if task.startDate is not null %}
-
- {{ task.startDate|format_date('medium') }}
-
- {% endif %}
- {% if task.warningDate is not null %}
-
- {{ task.warningDate|format_date('medium') }}
-
- {% endif %}
- {% if task.endDate is not null %}
-
- {{ task.endDate|format_date('medium') }}
-
- {% endif %}
-
-
- {% endif %}
-
-
-
-
- {% if workflow_transitions(task)|length > 0 %}
-
-
-
- {% endif %}
-
-
-
-
-
- {% if is_granted('CHILL_TASK_TASK_UPDATE', task) %}
-
-
-
- {% endif %}
-
- {% if is_granted('CHILL_TASK_TASK_DELETE', task) %}
-
-
-
- {% endif %}
-
-
-
- {% endfor %}
-
-
-
- {% if isSingleStatus %}
- {% if tasks|length < paginator.getTotalItems %}
- {{ chill_pagination(paginator) }}
- {% endif %}
-
-
-
- {% else %}
-
- {% endif %}
-
- {% endif %}
-{% endmacro %}
-
-{% import _self as helper %}
-
- {{ app.request.query.get('title', null)|escape('html')|default('Task list'|trans) }}
-
- {% if false == app.request.query.boolean('hide_form', false) %}
- {{ 'Filter the tasks'|trans }}
- {{ form_start(form) }}
- {{ form_row(form.user_id) }}
-
- {% if form.status is defined %}
- {{ form_row(form.status) }}
- {% endif %}
-
- {% if form.types is defined %}
- {{ form_row(form.types) }}
- {% endif %}
-
- {% if form.person_id is defined %}
- {{ form_row(form.person_id) }}
- {% endif %}
-
- {% if form.center_id is defined %}
- {{ form_row(form.center_id) }}
- {% endif %}
-
-
-
- {{ 'Filter'|trans }}
-
-
-
- {{ form_end(form)}}
- {% endif %}
-
- {% if tasks_count == 0 %}
- {{ "There is no tasks."|trans }}
- {% if person is not null and is_granted('CHILL_TASK_TASK_CREATE', person) %}
-
- {% endif %}
- {% else %}
-
- {% if false == app.request.query.boolean('hide_form', false) %}
- {{ 'Tasks'|trans }}
- {% endif %}
-
- {% if person is not null and is_granted('CHILL_TASK_TASK_CREATE', person) %}
-
- {% endif %}
-
- {% if single_task_ended_tasks is defined %}
- {{ helper.date_status('Tasks with expired deadline', single_task_ended_tasks, single_task_ended_count, single_task_ended_paginator, 'ended', isSingleStatus, person) }}
- {% endif %}
-
- {% if single_task_warning_tasks is defined %}
- {{ helper.date_status('Tasks with warning deadline reached', single_task_warning_tasks, single_task_warning_count, single_task_warning_paginator, 'warning', isSingleStatus, person) }}
- {% endif %}
-
- {% if single_task_current_tasks is defined %}
- {{ helper.date_status('Current tasks', single_task_current_tasks, single_task_current_count, single_task_current_paginator, 'current', isSingleStatus, person) }}
- {% endif %}
-
- {% if single_task_not_started_tasks is defined %}
- {{ helper.date_status('Tasks not started', single_task_not_started_tasks, single_task_not_started_count, single_task_not_started_paginator, 'not_started', isSingleStatus, person) }}
- {% endif %}
-
- {% if single_task_closed_tasks is defined %}
- {{ helper.date_status('Closed tasks', single_task_closed_tasks, single_task_closed_count, single_task_closed_paginator, 'closed', isSingleStatus, person) }}
- {% endif %}
-
- {% if isSingleStatus == false %}
-
- {% endif %}
-
-{% endif %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_new.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_new.html.twig
new file mode 100644
index 000000000..9d324ea85
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_new.html.twig
@@ -0,0 +1,32 @@
+
+
+
{{ 'New task'|trans }}
+
+ {{ form_start(form) }}
+
+ {{ form_errors(form) }}
+
+ {{ form_row(form.title) }}
+ {{ form_row(form.description) }}
+ {{ form_row(form.assignee) }}
+ {% if form.circle is defined %}
+ {{ form_row(form.circle) }}
+ {% endif %}
+ {{ form_row(form.startDate) }}
+ {{ form_row(form.endDate) }}
+ {{ form_row(form.warningInterval) }}
+
+
+
+ {{ form_end(form) }}
+
+
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_show.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_show.html.twig
new file mode 100644
index 000000000..26d269191
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_show.html.twig
@@ -0,0 +1,113 @@
+
+
+
{{ 'Task'|trans }}
+
+
{{ task.title }}
+ {% for place in workflow_marked_places(task) %}
+ {{ place|trans }}
+ {% endfor %}
+
+
+
+
+ {{ 'Description'|trans }}
+
+ {% if task.description is empty %}
+ {{"No description"|trans}}
+ {% else %}
+
+ {{ task.description|chill_markdown_to_html }}
+
+ {% endif %}
+
+
+ {{ 'Assignee'|trans }}
+
+ {% if task.assignee is null %}
+ {{"No one assignee"|trans}}
+ {% else %}
+ {{ task.assignee }}
+ {% endif %}
+
+
+ {% if task.scope is not null %}
+ {{ 'Scope'|trans }}
+
+ {{ task.scope.name|localize_translatable_string }}
+
+ {% endif %}
+
+ {{"Dates"|trans}}
+ {% if task.startDate is null and task.endDate is null and task.warningDate is null %}
+
+
+ {{"No dates specified"|trans}}
+
+
+ {% else %}
+ {% if task.startDate is not null %}
+ {{ 'Start'|trans }}
+ {{ task.startDate|format_date('long') }}
+ {% endif %}
+
+ {% if task.endDate is not null %}
+ {{ 'End'|trans }}
+ {{ task.endDate|format_date('long') }}
+ {% endif %}
+
+ {% if task.warningDate is not null %}
+ {{ 'Warning'|trans }}
+ {{ task.warningDate|format_date('long') }}
+ {% endif %}
+
+ {% endif %}
+
+
+{% if timeline is not null %}
+
{{"Timeline"|trans}}
+ {{ timeline|raw }}
+{% endif %}
+
+
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_transition.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_transition.html.twig
new file mode 100644
index 000000000..8f6345352
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/_transition.html.twig
@@ -0,0 +1,23 @@
+{{ 'Apply transition on task %title% '|trans({ '%title%': task.title } )|raw }}
+
+
+{% if task_workflow_metadata(task, 'transition.sentence_confirmation', transition) is not empty %}
+ {{ task_workflow_metadata(task, 'transition.sentence_confirmation', transition)|trans }}
+{% else %}
+ {{ 'Are you sure to apply the transition %name% on this task ?'|trans({ '%name%': task_workflow_metadata(task, 'transition.name', transition)|default(transition.name)|trans }) }}
+{% endif %}
+
+{{ form_start(form) }}
+
+
+
+
+ {{ 'Back to the list'|trans }}
+
+
+
+ {{ form_widget(form.submit, { 'attr' : { 'class' : "btn btn-task-exchange green" }, 'label': task_workflow_metadata(task, 'transition.apply_transition_submit_label', transition)|default('apply')|trans } ) }}
+
+
+
+{{ form_end(form) }}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/edit.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/edit.html.twig
deleted file mode 100644
index 67604f015..000000000
--- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/edit.html.twig
+++ /dev/null
@@ -1,59 +0,0 @@
-{#
- * Copyright (C) 2014, Champs Libres Cooperative SCRLFS,
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
-#}
-{% extends "@ChillPerson/Person/layout.html.twig" %}
-
-{% set activeRouteKey = 'chill_task_single_task_edit' %}
-{% set person = task.person %}
-
-{% block title %}{{ 'Edit task'|trans }}{% endblock %}
-
-{% block personcontent %}
-
-
-
{{ 'Edit task'|trans }}
-
- {{ form_start(form) }}
-
- {{ form_row(form.title) }}
- {{ form_row(form.description) }}
- {{ form_row(form.assignee) }}
- {{ form_row(form.circle) }}
- {{ form_row(form.startDate) }}
- {{ form_row(form.endDate) }}
- {{ form_row(form.warningInterval) }}
-
-
-
- {{ form_end(form) }}
-
-
-{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/index.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/index.html.twig
index 2a33fc10c..3cd2a1971 100644
--- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/index.html.twig
+++ b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/index.html.twig
@@ -15,30 +15,32 @@
* along with this program. If not, see .
#}
-{% extends '@ChillPerson/Person/layout.html.twig' %}
+{% extends layout %}
{% set activeRouteKey = 'chill_task_single_task_new' %}
-{% block title %}{{ 'Task list'|trans }}{% endblock %}
+{% block title %}
+ {{ 'Task list'|trans }}
+{% endblock %}
-{% macro thead() %}
-{% endmacro %}
+{% macro thead() %}{% endmacro %}
-{% macro row(task) %}
-{% endmacro %}
+{% macro row(task) %}{% endmacro %}
{% block filtertasks %}
-{% if person is not null %}
- {% block personcontent %}
-
- {% include 'ChillTaskBundle:SingleTask:_list.html.twig' %}
-
- {% endblock %}
-{% else %}
- {% block content %}
-
- {% include 'ChillTaskBundle:SingleTask:_list.html.twig' %}
-
- {% endblock %}
-{% endif %}
+ {% if person is not null %}
+ {% block personcontent %}
+
+ {% include '@ChillTask/SingleTask/Person/list.html.twig' %}
+
+
+ {% endblock %}
+ {% else %}
+ {% block content %}
+
+ {% include '@ChillTask/SingleTask/AccompanyingCourse/list.html.twig' %}
+
+
+ {% endblock %}
+ {% endif %}
{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/show.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/show.html.twig
deleted file mode 100644
index 8cfa1ea33..000000000
--- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/show.html.twig
+++ /dev/null
@@ -1,139 +0,0 @@
-{#
- * Copyright (C) 2014, Champs Libres Cooperative SCRLFS,
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
-#}
-{% extends "@ChillPerson/Person/layout.html.twig" %}
-
-{% set activeRouteKey = 'chill_task_single_task_show' %}
-{% set person = task.person %}
-
-{% block title %}{{ 'Task'|trans }}{% endblock %}
-
-
-{% block personcontent %}
-
-
-
{{ 'Task'|trans }}
-
-
{{ task.title }} {% for place in workflow_marked_places(task) %}
- {{ place|trans }}
- {% endfor %}
-
-
-
- {{ 'Description'|trans }}
-
- {% if task.description is empty %}
- {{"No description"|trans}}
- {% else %}
-
- {{ task.description|chill_markdown_to_html }}
-
- {% endif %}
-
-
- {{ 'Assignee'|trans }}
-
- {% if task.assignee is null %}
- {{"No one assignee"|trans}}
- {% else %}
- {{ task.assignee }}
- {% endif %}
-
-
- {{ 'Scope'|trans }}
- {{ task.scope.name|localize_translatable_string }}
-
- {{"Dates"|trans}}
- {% if task.startDate is null and task.endDate is null and task.warningDate is null %}
-
- {{"No dates specified"|trans}}
-
- {% else %}
- {% if task.startDate is not null %}
- {{ 'Start'|trans }}
- {{ task.startDate|format_date('long') }}
- {% endif %}
-
- {% if task.endDate is not null %}
- {{ 'End'|trans }}
- {{ task.endDate|format_date('long') }}
- {% endif %}
-
- {% if task.warningDate is not null %}
- {{ 'Warning'|trans }}
- {{ task.warningDate|format_date('long') }}
- {% endif %}
-
- {% endif %}
-
-
- {% if timeline is not null %}
-
{{"Timeline"|trans}}
- {{ timeline|raw }}
- {% endif %}
-
-
-
-
-
-{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/transition.html.twig b/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/transition.html.twig
deleted file mode 100644
index 617816e4c..000000000
--- a/src/Bundle/ChillTaskBundle/Resources/views/SingleTask/transition.html.twig
+++ /dev/null
@@ -1,34 +0,0 @@
-{% extends "@ChillPerson/Person/layout.html.twig" %}
-
-{% set activeRouteKey = 'chill_task_task_list' %}
-{% set person = task.person %}
-
-{% block title 'Remove task'|trans %}
-
-{% block personcontent %}
-
-{{ 'Apply transition on task %title% '|trans({ '%title%': task.title } )|raw }}
-
-
-{% if task_workflow_metadata(task, 'transition.sentence_confirmation', transition) is not empty %}
- {{ task_workflow_metadata(task, 'transition.sentence_confirmation', transition)|trans }}
-{% else %}
- {{ 'Are you sure to apply the transition %name% on this task ?'|trans({ '%name%': task_workflow_metadata(task, 'transition.name', transition)|default(transition.name)|trans }) }}
-{% endif %}
-
-{{ form_start(form) }}
-
-
-
-
- {{ 'Back to the list'|trans }}
-
-
-
- {{ form_widget(form.submit, { 'attr' : { 'class' : "btn btn-task-exchange green" }, 'label': task_workflow_metadata(task, 'transition.apply_transition_submit_label', transition)|default('apply')|trans } ) }}
-
-
-
-{{ form_end(form) }}
-
-{% endblock %}
diff --git a/src/Bundle/ChillTaskBundle/Security/Authorization/TaskVoter.php b/src/Bundle/ChillTaskBundle/Security/Authorization/TaskVoter.php
index 2f760a84b..39d9eef32 100644
--- a/src/Bundle/ChillTaskBundle/Security/Authorization/TaskVoter.php
+++ b/src/Bundle/ChillTaskBundle/Security/Authorization/TaskVoter.php
@@ -32,23 +32,27 @@ use Psr\Log\LoggerInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Chill\MainBundle\Entity\User;
+use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
+use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Chill\TaskBundle\Security\Authorization\AuthorizationEvent;
final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{
- const CREATE = 'CHILL_TASK_TASK_CREATE';
- const UPDATE = 'CHILL_TASK_TASK_UPDATE';
- const SHOW = 'CHILL_TASK_TASK_SHOW';
+ const CREATE_COURSE = 'CHILL_TASK_TASK_CREATE_FOR_COURSE';
+ const CREATE_PERSON = 'CHILL_TASK_TASK_CREATE_FOR_PERSON';
const DELETE = 'CHILL_TASK_TASK_DELETE';
+ const SHOW = 'CHILL_TASK_TASK_SHOW';
+ const UPDATE = 'CHILL_TASK_TASK_UPDATE';
const ROLES = [
- self::CREATE,
- self::UPDATE,
+ self::CREATE_COURSE,
+ self::CREATE_PERSON,
+ self::DELETE,
self::SHOW,
- self::DELETE
+ self::UPDATE,
];
protected AuthorizationHelper $authorizationHelper;
@@ -63,7 +67,9 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
protected VoterHelperInterface $voter;
+
public function __construct(
+ VoterHelperFactoryInterface $voterHelperFactory,
AccessDecisionManagerInterface $accessDecisionManager,
AuthorizationHelper $authorizationHelper,
EventDispatcherInterface $eventDispatcher,
@@ -80,7 +86,8 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
$this->voter = $voterFactory
->generate(AbstractTask::class)
->addCheckFor(AbstractTask::class, self::ROLES)
- ->addCheckFor(Person::class, [self::SHOW])
+ ->addCheckFor(Person::class, [self::SHOW, self::CREATE_PERSON])
+ ->addCheckFor(AccompanyingPeriod::class, [self::SHOW, self::CREATE_COURSE])
->addCheckFor(null, [self::SHOW])
->build()
;
@@ -89,14 +96,6 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
public function supports($attribute, $subject)
{
return $this->voter->supports($attribute, $subject);
- /*
- return ($subject instanceof AbstractTask && in_array($attribute, self::ROLES))
- ||
- ($subject instanceof Person && \in_array($attribute, [ self::CREATE, self::SHOW ]))
- ||
- (NULL === $subject && $attribute === self::SHOW )
- ;
- */
}
/**
@@ -132,50 +131,24 @@ final class TaskVoter extends AbstractChillVoter implements ProvideRoleHierarchy
// do pre-flight check, relying on other decision manager
// those pre-flight check concern associated entities
if ($subject instanceof AbstractTask) {
+ // a user can always see his own tasks
+ if ($subject->getAssignee() === $token->getUser()) {
+ return true;
+ }
+
if (NULL !== $person = $subject->getPerson()) {
if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
return false;
}
- } elseif (false) {
- // here will come the test if the task is associated to an accompanying course
+ } elseif (NULL !== $period = $subject->getCourse()) {
+ if (!$this->accessDecisionManager->decide($token, [AccompanyingPeriodVoter::SEE], $period)) {
+ return false;
+ }
}
}
// do regular check.
return $this->voter->voteOnAttribute($attribute, $subject, $token);
-
- /*
- if ($subject instanceof AbstractTask) {
- if ($subject->getPerson() === null) {
- throw new \LogicException("You should associate a person with task "
- . "in order to check autorizations");
- }
-
- $person = $subject->getPerson();
- } elseif ($subject instanceof Person) {
- $person = $subject;
- } else {
- // subject is null. We check that at least one center is reachable
- $centers = $this->authorizationHelper->getReachableCenters($token->getUser(), new Role($attribute));
-
- return count($centers) > 0;
- }
-
- if (!$this->accessDecisionManager->decide($token, [PersonVoter::SEE], $person)) {
- return false;
- }
- $center = $this->centerResolverDispatcher->resolveCenter($subject);
-
- if (NULL === $center) {
- return false;
- }
-
- return $this->authorizationHelper->userHasAccess(
- $token->getUser(),
- $subject,
- $attribute
- );
- */
}
public function getRoles()
diff --git a/src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php b/src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php
index fe5a8f78d..20e6a5480 100644
--- a/src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php
+++ b/src/Bundle/ChillTaskBundle/Timeline/TaskLifeCycleEventTimelineProvider.php
@@ -38,18 +38,18 @@ use Chill\MainBundle\Timeline\TimelineSingleQuery;
class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
{
protected EntityManagerInterface $em;
-
+
protected Registry $registry;
-
+
protected AuthorizationHelper $authorizationHelper;
protected Security $security;
-
-
+
+
const TYPE = 'chill_task.transition';
-
+
public function __construct(
- EntityManagerInterface $em,
+ EntityManagerInterface $em,
Registry $registry,
AuthorizationHelper $authorizationHelper,
Security $security
@@ -64,7 +64,7 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
{
$metadata = $this->em
->getClassMetadata(SingleTaskPlaceEvent::class);
-
+
switch ($context) {
case 'person':
[ $where, $parameters ] = $this->getWhereClauseForPerson($args['person']);
@@ -157,7 +157,7 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
// the parameters
- $parameters = [];
+ $parameters = $circleIds = [];
// the clause that we will fill
$clause = "{person}.{person_id} = ? AND {task}.{circle} IN ({circle_ids})";
@@ -212,27 +212,27 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
'{single_task}' => sprintf('%s.%s', $singleTask->getSchemaName(), $singleTask->getTableName()),
'{single_task_event}' => sprintf('%s.%s', $taskEvent->getSchemaName(), $taskEvent->getTableName()),
'{task_pk}' => $singleTask->getColumnName('id'),
- '{event_fk_task}' => $eventFkTask,
+ '{event_fk_task}' => $eventFkTask,
'{person}' => $person->getTableName(),
'{task_person_fk}' => $taskFkPerson,
- '{person_pk}' => $personPk
+ '{person_pk}' => $personPk
]
);
- }
-
- public function getEntities(array $ids)
+ }
+
+ public function getEntities(array $ids)
{
$events = $this->em
->getRepository(SingleTaskPlaceEvent::class)
->findBy([ 'id' => $ids ])
;
-
+
return \array_combine(
\array_map(function($e) { return $e->getId(); }, $events ),
$events
);
- }
-
+ }
+
public function getEntityTemplate($entity, $context, array $args)
{
$workflow = $this->registry->get($entity->getTask(),
@@ -242,22 +242,22 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
// `Notice: Undefined property: Chill\TaskBundle\Entity\Task\SingleTaskPlaceEvent::$getData`
// * fix syntax error on $entity->getData['workflow']
// * return null if not set
-
+
$transition = $this->getTransitionByName($entity->getTransition(), $workflow);
-
+
return [
'template' => 'ChillTaskBundle:Timeline:single_task_transition.html.twig',
- 'template_data' => [
+ 'template_data' => [
'context' => $context,
'event' => $entity,
'task' => $entity->getTask(),
'transition' => $transition
]
];
- }
-
+ }
+
/**
- *
+ *
* @param string $name
* @param Workflow $workflow
* @return \Symfony\Component\Workflow\Transition
@@ -270,7 +270,7 @@ class TaskLifeCycleEventTimelineProvider implements TimelineProviderInterface
}
}
}
-
+
public function supportsType($type): bool
{
return $type === self::TYPE;
diff --git a/src/Bundle/ChillTaskBundle/chill.webpack.config.js b/src/Bundle/ChillTaskBundle/chill.webpack.config.js
index 83c08a6ce..e96855d80 100644
--- a/src/Bundle/ChillTaskBundle/chill.webpack.config.js
+++ b/src/Bundle/ChillTaskBundle/chill.webpack.config.js
@@ -1,4 +1,6 @@
module.exports = function(encore, entries)
{
entries.push(__dirname + '/Resources/public/chill/index.js');
+
+ encore.addEntry('page_task_list', __dirname + '/Resources/public/page/tile_list/index.js');
};
diff --git a/src/Bundle/ChillTaskBundle/config/services/controller.yaml b/src/Bundle/ChillTaskBundle/config/services/controller.yaml
index 533c57f47..76799c72d 100644
--- a/src/Bundle/ChillTaskBundle/config/services/controller.yaml
+++ b/src/Bundle/ChillTaskBundle/config/services/controller.yaml
@@ -1,11 +1,6 @@
services:
- Chill\TaskBundle\Controller\:
- resource: '../../Controller'
- tags: ['controller.service_arguments']
-
- Chill\TaskBundle\Controller\SingleTaskController:
- arguments:
- $eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
- $timelineBuilder: '@chill_main.timeline_builder'
- $logger: '@chill.main.logger'
- tags: ['controller.service_arguments']
+ Chill\TaskBundle\Controller\:
+ resource: "../../Controller"
+ autowire: true
+ autoconfigure: ture
+ tags: ["controller.service_arguments"]
diff --git a/src/Bundle/ChillTaskBundle/config/services/form.yaml b/src/Bundle/ChillTaskBundle/config/services/form.yaml
index f82e4a562..728b8d7da 100644
--- a/src/Bundle/ChillTaskBundle/config/services/form.yaml
+++ b/src/Bundle/ChillTaskBundle/config/services/form.yaml
@@ -1,4 +1,9 @@
services:
+ Chill\TaskBundle\Form\:
+ resource: '../../Form/'
+ autowire: true
+ autoconfigure: true
+
Chill\TaskBundle\Form\SingleTaskListType:
arguments:
$em: '@Doctrine\ORM\EntityManagerInterface'
diff --git a/src/Bundle/ChillTaskBundle/config/services/menu.yaml b/src/Bundle/ChillTaskBundle/config/services/menu.yaml
index b4ff7a421..a9e1948d6 100644
--- a/src/Bundle/ChillTaskBundle/config/services/menu.yaml
+++ b/src/Bundle/ChillTaskBundle/config/services/menu.yaml
@@ -1,23 +1,22 @@
services:
- Chill\TaskBundle\Menu\UserMenuBuilder:
- arguments:
- $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
- $counter: '@Chill\TaskBundle\Templating\UI\CountNotificationTask'
- $translator: '@Symfony\Component\Translation\TranslatorInterface'
- $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
- tags:
- - { name: 'chill.menu_builder' }
-
- Chill\TaskBundle\Menu\PersonMenuBuilder:
- arguments:
- $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
- $translator: '@Symfony\Component\Translation\TranslatorInterface'
- tags:
- - { name: 'chill.menu_builder' }
-
- Chill\TaskBundle\Menu\SectionMenuBuilder:
- arguments:
- $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
- $translator: '@Symfony\Component\Translation\TranslatorInterface'
- tags:
- - { name: 'chill.menu_builder' }
+ Chill\TaskBundle\Menu\UserMenuBuilder:
+ arguments:
+ $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
+ $counter: '@Chill\TaskBundle\Templating\UI\CountNotificationTask'
+ $translator: '@Symfony\Component\Translation\TranslatorInterface'
+ $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
+ tags:
+ - { name: "chill.menu_builder" }
+
+ Chill\TaskBundle\Menu\MenuBuilder:
+ autowire: true
+ autoconfigure: true
+ tags:
+ - { name: "chill.menu_builder" }
+
+ Chill\TaskBundle\Menu\SectionMenuBuilder:
+ arguments:
+ $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface'
+ $translator: '@Symfony\Component\Translation\TranslatorInterface'
+ tags:
+ - { name: "chill.menu_builder" }
diff --git a/src/Bundle/ChillTaskBundle/config/services/repositories.yaml b/src/Bundle/ChillTaskBundle/config/services/repositories.yaml
index 3cc867d96..7bee5abd0 100644
--- a/src/Bundle/ChillTaskBundle/config/services/repositories.yaml
+++ b/src/Bundle/ChillTaskBundle/config/services/repositories.yaml
@@ -9,3 +9,9 @@ services:
arguments:
- "@chill.main.security.authorization.helper"
Chill\TaskBundle\Repository\SingleTaskRepository: '@chill_task.single_task_repository'
+
+ Chill\TaskBundle\Repository\SingleTaskAclAwareRepository:
+ autowire: true
+ autoconfigure: true
+
+ Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface: '@Chill\TaskBundle\Repository\SingleTaskAclAwareRepository'
diff --git a/src/Bundle/ChillTaskBundle/migrations/Version20210909153533.php b/src/Bundle/ChillTaskBundle/migrations/Version20210909153533.php
new file mode 100644
index 000000000..8a81b4ab9
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/migrations/Version20210909153533.php
@@ -0,0 +1,38 @@
+addSql('ALTER TABLE chill_task.recurring_task ADD course_id INT DEFAULT NULL');
+ $this->addSql('ALTER TABLE chill_task.recurring_task ADD CONSTRAINT FK_9F663B90591CC992 FOREIGN KEY (course_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('CREATE INDEX IDX_9F663B90591CC992 ON chill_task.recurring_task (course_id)');
+ $this->addSql('ALTER TABLE chill_task.single_task ADD course_id INT DEFAULT NULL');
+ $this->addSql('ALTER TABLE chill_task.single_task ADD CONSTRAINT FK_194CB3D8591CC992 FOREIGN KEY (course_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
+ $this->addSql('CREATE INDEX IDX_194CB3D8591CC992 ON chill_task.single_task (course_id)');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE chill_task.recurring_task DROP CONSTRAINT FK_9F663B90591CC992');
+ $this->addSql('ALTER TABLE chill_task.recurring_task DROP course_id');
+ $this->addSql('ALTER TABLE chill_task.single_task DROP CONSTRAINT FK_194CB3D8591CC992');
+ $this->addSql('ALTER TABLE chill_task.single_task DROP course_id');
+ }
+}
diff --git a/src/Bundle/ChillTaskBundle/migrations/Version20211029213909.php b/src/Bundle/ChillTaskBundle/migrations/Version20211029213909.php
new file mode 100644
index 000000000..1ccf52bcb
--- /dev/null
+++ b/src/Bundle/ChillTaskBundle/migrations/Version20211029213909.php
@@ -0,0 +1,26 @@
+addSql('CREATE INDEX by_end_date ON chill_task.single_task (end_date DESC NULLS FIRST)');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('DROP INDEX chill_task.by_end_date');
+ }
+}
diff --git a/src/Bundle/ChillTaskBundle/translations/messages.fr.yml b/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
index 71bc22612..5633c0219 100644
--- a/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
@@ -1,53 +1,55 @@
-Tasks: 'Tâches'
-'New task': 'Nouvelle tâche'
-'Add a new task': 'Ajouter une nouvelle tâche'
+Tasks: "Tâches"
+"New task": "Nouvelle tâche"
+"Add a new task": "Ajouter une nouvelle tâche"
Title: Titre
Description: Description
-Assignee: 'Personne assignée'
+Assignee: "Personne assignée"
Scope: Cercle
-'Start date': 'Date de début'
-'End date': "Date d'échéance"
-'Warning date': "Date d'avertissement"
-'Warning interval': "Délai d'avertissement avant la date d'échéance"
-'Unknown dates': 'Dates non spécifiées'
-'N': ''
-'Unit': ''
+"Start date": "Date de début"
+"End date": "Date d'échéance"
+"Warning date": "Date d'avertissement"
+"Warning interval": "Délai d'avertissement avant la date d'échéance"
+"Unknown dates": "Dates non spécifiées"
+"N": ""
+"Unit": ""
Task: Tâche
Details: Détails
Person: Personne
Date: Date
Dates: Dates
User: Utilisateur
-'Task list': 'Liste des tâches'
-'Tasks with expired deadline': "Tâches avec une date d'échéance dépassée"
-'Tasks with warning deadline reached': "Tâches avec une date d'avertissement atteinte"
-'Current tasks': 'Tâches en cours'
-'Closed tasks': Tâches terminées
-'Tasks not started': 'Tâches non commencées'
-'Task start date': 'Date de début'
-'Task warning date': "Date d'avertissement"
-'Task end date': "Date d'échéance"
-'Start': 'Début'
-'Warning': 'Avertissement'
-'End': 'Échéance'
-'Task type': 'Type'
-'Task status': 'Statut'
-'Edit the task': 'Modifier la tâche'
-'Edit task': 'Modifier la tâche'
-'Save task': 'Enregistrer la tâche'
-'View the task': 'Voir la tâche'
-'Update the task': 'Mettre à jour la tâche'
-'Remove task': 'Supprimer la tâche'
-'Delete': 'Supprimer'
-'Change task status': 'Changer le statut'
+"Task list": "Liste des tâches"
+"Tasks with expired deadline": "Tâches avec une date d'échéance dépassée"
+"Tasks with warning deadline reached": "Tâches avec une date d'avertissement atteinte"
+"Current tasks": "Tâches en cours"
+"Closed tasks": Tâches terminées
+"Tasks not started": "Tâches non commencées"
+"Task start date": "Date de début"
+"Task warning date": "Date d'avertissement"
+"Task end date": "Date d'échéance"
+"Start": "Début"
+"Warning": "Avertissement"
+"End": "Échéance"
+"Task type": "Type"
+"Task status": "Statut"
+"Edit the task": "Modifier la tâche"
+"Edit task": "Modifier la tâche"
+"Save task": "Enregistrer la tâche"
+"View the task": "Voir la tâche"
+"Update the task": "Mettre à jour la tâche"
+"Remove task": "Supprimer la tâche"
+"Delete": "Supprimer"
+"Change task status": "Changer le statut"
'Are you sure you want to remove the task about "%name%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche de "%name%"?'
-'See more': 'Voir plus'
-'Associated tasks': 'Tâches associées'
-'My tasks': 'Mes tâches'
-'No description': 'Pas de description'
-'No dates specified': 'Dates non spécifiées'
-'No one assignee': 'Aucune personne assignée'
-'Task types': Types de tâches
+'Are you sure you want to remove the task "%title%" ?': 'Êtes-vous sûr·e de vouloir supprimer la tâche "%title%" ?'
+"See more": "Voir plus"
+"Associated tasks": "Tâches associées"
+"My tasks": "Mes tâches"
+"Tasks for this accompanying period": "Tâches pour ce parcours d'accompagnement"
+"No description": "Pas de description"
+"No dates specified": "Dates non spécifiées"
+"No one assignee": "Aucune personne assignée"
+"Task types": Types de tâches
Days: Jour(s)
Weeks: Semaine(s)
Months: Mois
@@ -61,38 +63,43 @@ Default task: Tâche par défaut
Not assigned: Aucun utilisateur assigné
For person: Pour
By: Par
+Any tasks: Aucune tâche
# transitions - default task definition
-'new': 'nouvelle'
-'in_progress': 'en cours'
-'closed': 'fermée'
-'canceled': 'supprimée'
+"new": "nouvelle"
+"in_progress": "en cours"
+"closed": "fermée"
+"canceled": "supprimée"
start: démarrer
close: clotûrer
cancel: annuler
Start_verb: Démarrer
Close_verb: Clotûrer
Set this task to cancel state: Marquer cette tâche comme annulée
-'%user% has closed the task': '%user% a fermé la tâche'
-'%user% has canceled the task': '%user% a annulé la tâche'
-'%user% has started the task': '%user% a commencé la tâche'
-'%user% has created the task': '%user% a introduit la tâche'
+"%user% has closed the task": "%user% a fermé la tâche"
+"%user% has canceled the task": "%user% a annulé la tâche"
+"%user% has started the task": "%user% a commencé la tâche"
+"%user% has created the task": "%user% a introduit la tâche"
Are you sure you want to close this task ?: Êtes-vous sûrs de vouloir clotûrer cette tâche ?
Are you sure you want to cancel this task ?: Êtes-vous sûrs de vouloir annuler cette tâche ?
Are you sure you want to start this task ?: Êtes-vous sûrs de vouloir démarrer cette tâche ?
#Flash messages
-'The task is created': 'La tâche a été créée'
-'There is no tasks.': Aucune tâche.
-'The task has been successfully removed.': 'La tâche a bien été supprimée'
-'This form contains errors': 'Ce formulaire contient des erreurs'
-'The task has been updated': 'La tâche a été mise à jour'
-'The transition is successfully applied': 'La transition a bien été effectuée'
-'The transition could not be applied': "La transition n'a pas pu être appliquée"
+"The task is created": "La tâche a été créée"
+"There is no tasks.": Aucune tâche.
+"The task has been successfully removed.": "La tâche a bien été supprimée"
+"This form contains errors": "Ce formulaire contient des erreurs"
+"The task has been updated": "La tâche a été mise à jour"
+"The transition is successfully applied": "La transition a bien été effectuée"
+"The transition could not be applied": "La transition n'a pas pu être appliquée"
#widget
-'%number% tasks over deadline': '{0} Aucune tâche dépassée|{1} Une tâche dépassée | ]1,Inf[ %count% tâches dépassées'
-'%number% tasks near deadline': '{0} Aucune tâche en rappel|{1} Une tâche en rappel | ]1,Inf[ %count% tâches en rappel'
+"%number% tasks over deadline": "{0} Aucune tâche dépassée|{1} Une tâche dépassée | ]1,Inf[ %count% tâches dépassées"
+"%number% tasks near deadline": "{0} Aucune tâche en rappel|{1} Une tâche en rappel | ]1,Inf[ %count% tâches en rappel"
+
+Tasks near deadline: Tâches à échéance proche
+Tasks over deadline: Tâches à échéance dépassée
+Tasks without alert: Tâches à échéance future ou sans échéance
#title
My tasks near deadline: Mes tâches à échéance proche
@@ -107,4 +114,4 @@ All centers: Tous les centres
CHILL_TASK_TASK_CREATE: Ajouter une tâche
CHILL_TASK_TASK_DELETE: Supprimer une tâche
CHILL_TASK_TASK_SHOW: Voir une tâche
-CHILL_TASK_TASK_UPDATE: Modifier une tâche
\ No newline at end of file
+CHILL_TASK_TASK_UPDATE: Modifier une tâche
diff --git a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php
index bd40c1108..2ce2c2185 100644
--- a/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php
+++ b/src/Bundle/ChillThirdPartyBundle/Entity/ThirdParty.php
@@ -173,6 +173,7 @@ class ThirdParty implements TrackCreationInterface, TrackUpdateInterface
/**
* @var bool
* @ORM\Column(name="contact_data_anonymous", type="boolean", options={"default":false})
+ * @Groups({"read"})
*/
private bool $contactDataAnonymous = false;
diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue
index 8b8fc6449..1504bdfb9 100644
--- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue
+++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue
@@ -5,7 +5,6 @@
@@ -73,6 +90,7 @@