mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-12 21:34:25 +00:00
Merge remote-tracking branch 'origin/master' into issue388_order_social_issues
This commit is contained in:
commit
57a88845dc
25
CHANGELOG.md
25
CHANGELOG.md
@ -11,16 +11,34 @@ and this project adheres to
|
||||
## Unreleased
|
||||
|
||||
<!-- write down unreleased development here -->
|
||||
* renommer "dossier numéro" en "parcours numéro" dans les résultats de recherche
|
||||
* renomme date de début en date d'ouverture dans le formulaire parcours
|
||||
* change order for accompanying course work list
|
||||
* [person]: style fix in parcours listing per person. (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/432)
|
||||
|
||||
|
||||
## Test releases
|
||||
|
||||
### test release 2021-01-31
|
||||
### test release 2021-02-01
|
||||
|
||||
* renommer "dossier numéro" en "parcours numéro" dans les résultats de recherche
|
||||
* renomme date de début en date d'ouverture dans le formulaire parcours
|
||||
* [homepage widget] improve content tables, improve counter pluralization with style on number
|
||||
* [notification lists] add comments counter information
|
||||
* [workflows] fix popover header with previous transition
|
||||
* [parcours]: validation + message for closing parcours adjusted.
|
||||
* [household]: household composition double edit button replaced by a delete action (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/426)
|
||||
[fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413
|
||||
[homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.
|
||||
* [person]: Comment on marital status is possible even if marital status is not defined (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/421)
|
||||
* [parcours]: In the list of person results the requestor is not displayed if defined as anonymous (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/424)
|
||||
* [bugfix]: modal closes and newly created person/thirdparty is selected when multiple persons/thirdparties are created through the modal (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/429)
|
||||
* [person_resource]: Onthefly button added to view person/thirdparty and badge differentiation for a contact-thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/428)
|
||||
* [workflow][notification] improve how notifications and workflows are 'attached' to entities: contextual list, counter, buttons and vue modal
|
||||
* [AddAddress] disable multiselect search, and rely only on most pertinent Cities and Street computed backend
|
||||
* [fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413
|
||||
* [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.
|
||||
|
||||
### test release 2021-01-31
|
||||
|
||||
* [person] accompanying course: optimisation: do not fetch some resources for the banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/409)
|
||||
* [person] accompanying course: close modal when edit participation (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420)
|
||||
* [person] accompanying course: treat validation error when editing on-the-fly entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420)
|
||||
@ -31,7 +49,6 @@ and this project adheres to
|
||||
* [user]: page with accompanying periods to which is user is referent (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/408)
|
||||
* [person] age added to renderstring + renderbox/ vue component created to display person text (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/389)
|
||||
* [household member editor] allow to push to existing household
|
||||
* [workflow][notification] improve how notifications and workflows are 'attached' to entities: contextual list, counter, buttons and vue modal
|
||||
|
||||
|
||||
### test release 2021-01-28
|
||||
|
@ -34,6 +34,7 @@ use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@ -200,12 +201,36 @@ final class ActivityController extends AbstractController
|
||||
'role' => new Role('CHILL_ACTIVITY_UPDATE'),
|
||||
'activityType' => $entity->getActivityType(),
|
||||
'accompanyingPeriod' => $accompanyingPeriod,
|
||||
])->handleRequest($request);
|
||||
]);
|
||||
|
||||
if ($form->has('documents')) {
|
||||
$form->add('gendocTemplateId', HiddenType::class, [
|
||||
'mapped' => false,
|
||||
'data' => null,
|
||||
'attr' => [
|
||||
// required for js
|
||||
'data-template-id' => 'data-template-id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->persist($entity);
|
||||
$this->entityManager->flush();
|
||||
|
||||
if ($form->has('gendocTemplateId') && null !== $form['gendocTemplateId']->getData()) {
|
||||
return $this->redirectToRoute(
|
||||
'chill_docgenerator_generate_from_template',
|
||||
[
|
||||
'template' => $form->get('gendocTemplateId')->getData(),
|
||||
'entityClassName' => Activity::class,
|
||||
'entityId' => $entity->getId(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$this->addFlash('success', $this->get('translator')->trans('Success : activity updated!'));
|
||||
|
||||
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
|
||||
@ -393,12 +418,36 @@ final class ActivityController extends AbstractController
|
||||
'role' => new Role('CHILL_ACTIVITY_CREATE'),
|
||||
'activityType' => $entity->getActivityType(),
|
||||
'accompanyingPeriod' => $accompanyingPeriod,
|
||||
])->handleRequest($request);
|
||||
]);
|
||||
|
||||
if ($form->has('documents')) {
|
||||
$form->add('gendocTemplateId', HiddenType::class, [
|
||||
'mapped' => false,
|
||||
'data' => null,
|
||||
'attr' => [
|
||||
// required for js
|
||||
'data-template-id' => 'data-template-id',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$this->entityManager->persist($entity);
|
||||
$this->entityManager->flush();
|
||||
|
||||
if ($form->has('gendocTemplateId') && null !== $form['gendocTemplateId']->getData()) {
|
||||
return $this->redirectToRoute(
|
||||
'chill_docgenerator_generate_from_template',
|
||||
[
|
||||
'template' => $form->get('gendocTemplateId')->getData(),
|
||||
'entityClassName' => Activity::class,
|
||||
'entityId' => $entity->getId(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$this->addFlash('success', $this->get('translator')->trans('Success : activity created!'));
|
||||
|
||||
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
|
||||
|
@ -2,11 +2,15 @@ import { createApp } from 'vue';
|
||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
|
||||
import { activityMessages } from './i18n'
|
||||
import store from './store'
|
||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
||||
import {fetchTemplates} from 'ChillDocGeneratorAssets/api/pickTemplate.js';
|
||||
|
||||
import App from './App.vue';
|
||||
|
||||
const i18n = _createI18n(activityMessages);
|
||||
|
||||
// app for activity
|
||||
|
||||
const hasSocialIssues = document.querySelector('#social-issues-acc') !== null;
|
||||
const hasLocation = document.querySelector('#location') !== null;
|
||||
const hasPerson = document.querySelector('#add-persons') !== null;
|
||||
@ -29,3 +33,54 @@ const app = createApp({
|
||||
.use(i18n)
|
||||
.component('app', App)
|
||||
.mount('#activity');
|
||||
|
||||
|
||||
// app for picking template
|
||||
|
||||
const i18nGendoc = _createI18n({});
|
||||
|
||||
document.querySelectorAll('div[data-docgen-template-picker]').forEach(el => {
|
||||
fetchTemplates(el.dataset.entityClass).then(templates => {
|
||||
const picker = {
|
||||
template:
|
||||
'<pick-template :templates="this.templates" :preventDefaultMoveToGenerate="true" ' +
|
||||
':entityClass="faked" @go-to-generate-document="generateDoc"></pick-template>',
|
||||
components: {
|
||||
PickTemplate,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
templates: templates,
|
||||
entityId: el.dataset.entityId,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateDoc({event, link, template}) {
|
||||
console.log('generateDoc');
|
||||
console.log('link', link);
|
||||
console.log('template', template);
|
||||
|
||||
let hiddenInput = document.querySelector("input[data-template-id]");
|
||||
|
||||
if (hiddenInput === null) {
|
||||
console.error('hidden input not found');
|
||||
return;
|
||||
}
|
||||
|
||||
hiddenInput.value = template;
|
||||
|
||||
let form = document.querySelector('form[name="chill_activitybundle_activity"');
|
||||
|
||||
if (form === null) {
|
||||
console.error('form not found');
|
||||
return;
|
||||
}
|
||||
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
};
|
||||
createApp(picker).use(i18nGendoc).mount(el);
|
||||
})
|
||||
|
||||
});
|
||||
|
@ -68,7 +68,7 @@
|
||||
<div class="wl-col title"><h3>{{ 'Referrer'|trans }}</h3></div>
|
||||
<div class="wl-col list">
|
||||
<p class="wl-item">
|
||||
{{ activity.user|chill_entity_render_string|capitalize }}
|
||||
{{ activity.user|chill_entity_render_box }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -89,9 +89,9 @@
|
||||
|
||||
{%- if edit_form.documents is defined -%}
|
||||
{{ form_row(edit_form.documents) }}
|
||||
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\ActivityBundle\Entity\Activity" data-entity-id="{{ entity.id }}"></div>
|
||||
{% endif %}
|
||||
|
||||
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\ActivityBundle\Entity\Activity" data-entity-id="{{ entity.id }}"></div>
|
||||
|
||||
{% set person_id = null %}
|
||||
{% if entity.person %}
|
||||
|
@ -24,12 +24,10 @@
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
</script>
|
||||
{{ encore_entry_script_tags('vue_activity') }}
|
||||
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
{{ encore_entry_link_tags('vue_activity') }}
|
||||
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
||||
{% endblock %}
|
||||
|
@ -39,11 +39,9 @@
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
</script>
|
||||
{{ encore_entry_script_tags('vue_activity') }}
|
||||
{{ encore_entry_script_tags('mod_docgen_picktemplate') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ encore_entry_link_tags('mod_async_upload') }}
|
||||
{{ encore_entry_link_tags('vue_activity') }}
|
||||
{{ encore_entry_link_tags('mod_docgen_picktemplate') }}
|
||||
{% endblock %}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<p class="date-label">{{ activity.date|format_date('short') }}</p>
|
||||
{%- endif -%}
|
||||
|
||||
<span class="like-h3">{{ activity.type.name | localize_translatable_string }}</span>
|
||||
<span class="like-h3">{{ activity.type.name|localize_translatable_string }}</span>
|
||||
|
||||
{% if activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6">{{ 'Emergency'|trans|upper }}</span>
|
||||
@ -41,7 +41,7 @@
|
||||
{% if activity.user and t.userVisible %}
|
||||
<li>
|
||||
<span class="item-key">{{ 'Referrer'|trans ~ ': ' }}</span>
|
||||
<b>{{ activity.user.usernameCanonical }}</b>
|
||||
<b>{{ activity.user|chill_entity_render_box}}</b>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
|
@ -87,6 +87,7 @@
|
||||
|
||||
{%- if form.documents is defined -%}
|
||||
{{ form_row(form.documents) }}
|
||||
<div data-docgen-template-picker="data-docgen-template-picker" data-entity-class="Chill\ActivityBundle\Entity\Activity" data-entity-id="{{ entity.id }}"></div>
|
||||
{% endif %}
|
||||
|
||||
{%- if form.attendee is defined -%}
|
||||
|
@ -17,10 +17,6 @@
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_async_upload') }}
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
chill.displayAlertWhenLeavingUnsubmittedForm('form[name="{{ form.vars.form.vars.name }}"]',
|
||||
'{{ "You are going to leave a page with unsubmitted data. Are you sure you want to leave ?"|trans }}');
|
||||
});
|
||||
window.activity = {{ activity_json|json_encode|raw }};
|
||||
{% if default_location is not null %}window.default_location_id = {{ default_location.id }}{% endif %};
|
||||
</script>
|
||||
|
@ -17,7 +17,6 @@ use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
|
||||
use Chill\DocGeneratorBundle\Context\Exception\UnexpectedTypeException;
|
||||
use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate;
|
||||
use Chill\DocGeneratorBundle\Service\Context\BaseContextData;
|
||||
use Chill\DocStoreBundle\Entity\Document;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
@ -210,11 +209,8 @@ class ActivityContext implements
|
||||
*/
|
||||
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
|
||||
{
|
||||
$doc = new StoredObject();
|
||||
// TODO push document to remote
|
||||
$entity->addDocument($storedObject);
|
||||
|
||||
$this->em->persist($doc);
|
||||
|
||||
$entity->addDocument($doc);
|
||||
$this->em->persist($storedObject);
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,6 @@ class AsideActivityCategory
|
||||
}
|
||||
|
||||
$this->parent = $parent;
|
||||
dump($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -97,8 +97,6 @@ class CalendarType extends AbstractType
|
||||
return $res;
|
||||
},
|
||||
static function (?string $dateAsString): DateTimeImmutable {
|
||||
dump($dateAsString);
|
||||
|
||||
return new DateTimeImmutable($dateAsString);
|
||||
}
|
||||
));
|
||||
|
@ -0,0 +1,13 @@
|
||||
|
||||
const buildLink = function(templateId, entityId, entityClass) {
|
||||
const
|
||||
entityIdEncoded = encodeURI(entityId),
|
||||
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
|
||||
entityClassEncoded = encodeURI(entityClass),
|
||||
url = `/fr/doc/gen/generate/from/${templateId}/for/${entityClassEncoded}/${entityIdEncoded}?returnPath=${returnPath}`
|
||||
;
|
||||
console.log('computed Url');
|
||||
return url;
|
||||
};
|
||||
|
||||
export {buildLink};
|
@ -20,8 +20,8 @@
|
||||
<option v-bind:value="t.id">{{ t.name.fr || 'Aucun nom défini' }}</option>
|
||||
</template>
|
||||
</select>
|
||||
<button v-if="canGenerate" class="btn btn-update btn-sm change-icon" type="button" @click="generateDocument"><i class="fa fa-fw fa-cog"></i></button>
|
||||
<button v-else class="btn btn-update btn-sm change-icon" type="button" disabled ><i class="fa fa-fw fa-cog"></i></button>
|
||||
<a v-if="canGenerate" class="btn btn-update btn-sm change-icon" :href="buildUrlGenerate" @click.prevent="clickGenerate($event, buildUrlGenerate)"><i class="fa fa-fw fa-cog"></i></a>
|
||||
<a v-else class="btn btn-update btn-sm change-icon" href="#" disabled ><i class="fa fa-fw fa-cog"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -39,24 +39,27 @@
|
||||
|
||||
<script>
|
||||
|
||||
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
|
||||
|
||||
export default {
|
||||
name: "PickTemplate",
|
||||
props: {
|
||||
entityId: [String, Number],
|
||||
entityClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
required: false,
|
||||
},
|
||||
templates: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
// beforeMove execute "something" before
|
||||
beforeMove: {
|
||||
type: Function,
|
||||
preventDefaultMoveToGenerate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
emits: ['goToGenerateDocument'],
|
||||
data() {
|
||||
return {
|
||||
template: null,
|
||||
@ -74,66 +77,37 @@ export default {
|
||||
return true;
|
||||
},
|
||||
getDescription() {
|
||||
if (null === this.template) {
|
||||
return '';
|
||||
}
|
||||
let desc = this.templates.find(t => t.id === this.template);
|
||||
if (null === desc) {
|
||||
return '';
|
||||
}
|
||||
return desc.description || '';
|
||||
},
|
||||
buildUrlGenerate() {
|
||||
if (null === this.template) {
|
||||
return '#';
|
||||
}
|
||||
|
||||
return buildLink(this.template, this.entityId, this.entityClass);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateDocument() {
|
||||
console.log('generateDocument');
|
||||
console.log('beforeMove', this.beforeMove);
|
||||
if (this.beforeMove != null) {
|
||||
console.log('execute before move');
|
||||
let r = this.beforeMove();
|
||||
if (r instanceof Promise) {
|
||||
r.then((obj) => this.goToGenerate(obj));
|
||||
} else {
|
||||
this.goToGenerate();
|
||||
}
|
||||
} else {
|
||||
this.goToGenerate();
|
||||
}
|
||||
},
|
||||
goToGenerate(obj) {
|
||||
console.log('goToGenerate');
|
||||
console.log('obj', obj);
|
||||
console.log('entityId', this.entityId);
|
||||
let
|
||||
realId = this.entityId,
|
||||
realEntityClass = this.entityClass
|
||||
;
|
||||
|
||||
if (obj !== undefined) {
|
||||
if (obj.entityId !== undefined) {
|
||||
realId = obj.entityId;
|
||||
}
|
||||
if (obj.entityClass !== undefined) {
|
||||
realEntityClass = obj.entityClass;
|
||||
}
|
||||
clickGenerate(event, link) {
|
||||
if (!this.preventDefaultMoveToGenerate) {
|
||||
window.location.assign(link);
|
||||
}
|
||||
|
||||
console.log('realId', realId);
|
||||
console.log('realEntityClass', realEntityClass);
|
||||
|
||||
const
|
||||
entityId = encodeURI(realId),
|
||||
returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash),
|
||||
fqdnEntityClass = encodeURI(realEntityClass),
|
||||
url = `/fr/doc/gen/generate/from/${this.template}/for/${fqdnEntityClass}/${entityId}?returnPath=${returnPath}`
|
||||
;
|
||||
|
||||
console.log('I will generate your doc at', url);
|
||||
window.location.assign(url);
|
||||
this.$emit('goToGenerateDocument', {event, link, template: this.template});
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
generate_document: 'Générer un document',
|
||||
select_a_template: 'Choisir un gabarit',
|
||||
select_a_template: 'Choisir un modèle',
|
||||
choose_a_template: 'Choisir',
|
||||
}
|
||||
}
|
||||
|
@ -8,14 +8,13 @@
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<h3>{{ document.title }}</h3>
|
||||
<small>{{ document.object.type }}</small>
|
||||
|
||||
|
||||
{% if document.description is not empty %}
|
||||
<blockquote class="chill-user-quote mt-2">
|
||||
{{ document.description }}
|
||||
</blockquote>
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -32,6 +31,13 @@
|
||||
|
||||
{% if display_action is defined and display_action == true %}
|
||||
<ul class="record_actions">
|
||||
{% if document.course != null and is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', document.course) %}
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_course_index', {'accompanying_period_id': document.course.id}) }}" class="btn btn-show change-icon">
|
||||
<i class="fa fa-random"></i> {{ 'Course number'|trans }} {{ document.course.id }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
{{ m.download_button(document.object, document.title) }}
|
||||
</li>
|
||||
@ -43,19 +49,25 @@
|
||||
'changeClass' string
|
||||
'noText' boolean
|
||||
#}
|
||||
{# vue component #}
|
||||
{# vue component
|
||||
<span
|
||||
data-module="wopi-link"
|
||||
data-wopi-url="{{ path('chill_wopi_file_edit', {'fileId': document.object.uuid}) }}"
|
||||
data-doc-title="{{ document.title|e('html_attr') }}"
|
||||
data-doc-type="{{ document.object.type|e('html_attr') }}"
|
||||
data-button="{{ button|json_encode }}"
|
||||
></span>
|
||||
></span> #}
|
||||
<a class="btn btn-update" href="{{ chill_path_add_return_path('chill_wopi_file_edit', {'fileId': document.object.uuid}) }}">{{ 'Edit'|trans }}</a>
|
||||
{% else %}
|
||||
<a class="btn btn-update change-icon disabled" href="#" title="{{ 'workflow.freezed document'|trans }}">
|
||||
<i class="fa fa-lock me-2"></i>{{ 'Update document'|trans }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', document) and document.course != null %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('accompanying_course_document_show', {'course': document.course.id, 'id': document.id}) }}" class="btn btn-show"></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
@ -236,6 +236,10 @@ class NotificationController extends AbstractController
|
||||
'_fragment' => 'comment-' . $commentId,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($editedCommentForm->isSubmitted() && !$editedCommentForm->isValid()) {
|
||||
$this->addFlash('error', $this->translator->trans('This form contains errors'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,6 +261,10 @@ class NotificationController extends AbstractController
|
||||
'id' => $notification->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($appendCommentForm->isSubmitted() && !$appendCommentForm->isValid()) {
|
||||
$this->addFlash('error', $this->translator->trans('This form contains errors'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ use DateTimeInterface;
|
||||
use Doctrine\ORM\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
@ -28,6 +29,7 @@ class NotificationComment implements TrackCreationInterface, TrackUpdateInterfac
|
||||
{
|
||||
/**
|
||||
* @ORM\Column(type="text")
|
||||
* @Assert\NotBlank(message="notification.Comment content might not be blank")
|
||||
*/
|
||||
private string $content = '';
|
||||
|
||||
@ -136,9 +138,9 @@ class NotificationComment implements TrackCreationInterface, TrackUpdateInterfac
|
||||
$this->recentlyPersisted = true;
|
||||
}
|
||||
|
||||
public function setContent(string $content): self
|
||||
public function setContent(?string $content): self
|
||||
{
|
||||
$this->content = $content;
|
||||
$this->content = (string) $content;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -79,11 +79,8 @@ class NotificationMailer
|
||||
continue;
|
||||
}
|
||||
|
||||
$email = new Email();
|
||||
$email
|
||||
->subject($notification->getTitle());
|
||||
|
||||
if ($notification->isSystem()) {
|
||||
$email = new Email();
|
||||
$email
|
||||
->text($notification->getMessage());
|
||||
} else {
|
||||
@ -96,7 +93,9 @@ class NotificationMailer
|
||||
]);
|
||||
}
|
||||
|
||||
$email->to($addressee->getEmail());
|
||||
$email
|
||||
->subject($notification->getTitle())
|
||||
->to($addressee->getEmail());
|
||||
|
||||
try {
|
||||
$this->mailer->send($email);
|
||||
|
@ -110,7 +110,8 @@ class EntityWorkflowRepository implements ObjectRepository
|
||||
$qb->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->isMemberOf(':user', 'step.destUser'),
|
||||
$qb->expr()->isNull('step.transitionAfter')
|
||||
$qb->expr()->isNull('step.transitionAfter'),
|
||||
$qb->expr()->eq('step.isFinal', "'FALSE'")
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Repository\Workflow;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflowStep;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
class EntityWorkflowStepRepository implements ObjectRepository
|
||||
{
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->repository = $entityManager->getRepository(EntityWorkflowStep::class);
|
||||
}
|
||||
|
||||
public function countUnreadByUser(User $user, ?array $orderBy = null, $limit = null, $offset = null): int
|
||||
{
|
||||
$qb = $this->buildQueryByUser($user)->select('count(e)');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function find($id): ?EntityWorkflowStep
|
||||
{
|
||||
return $this->repository->find($id);
|
||||
}
|
||||
|
||||
public function findAll(): array
|
||||
{
|
||||
return $this->repository->findAll();
|
||||
}
|
||||
|
||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array
|
||||
{
|
||||
return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
public function findOneBy(array $criteria): ?EntityWorkflowStep
|
||||
{
|
||||
return $this->repository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
public function getClassName()
|
||||
{
|
||||
return EntityWorkflow::class;
|
||||
}
|
||||
|
||||
private function buildQueryByUser(User $user): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('e');
|
||||
|
||||
$qb->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->isMemberOf(':user', 'e.destUser'),
|
||||
$qb->expr()->isNull('e.transitionAt'),
|
||||
$qb->expr()->eq('e.isFinal', ':bool'),
|
||||
)
|
||||
);
|
||||
|
||||
$qb->setParameter('user', $user);
|
||||
$qb->setParameter('bool', false);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import { chill } from './js/chill.js';
|
||||
global.chill = chill;
|
||||
|
||||
require('./js/date.js');
|
||||
require('./js/counter.js');
|
||||
|
||||
/// Load fonts
|
||||
require('./fonts/OpenSans/OpenSans.scss')
|
||||
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
*
|
||||
* This script search for span.counter elements like
|
||||
* <span class="counter">Il y a 4 notifications</span>
|
||||
* and return
|
||||
* <span class="counter">Il y a <span>4</span> notifications</span>
|
||||
*
|
||||
*/
|
||||
const isNum = (v) => !isNaN(v);
|
||||
|
||||
const parseCounter = () => {
|
||||
document.querySelectorAll('span.counter')
|
||||
.forEach(el => {
|
||||
let r = [];
|
||||
el.innerText
|
||||
.trim()
|
||||
.split(' ')
|
||||
.forEach(w => {
|
||||
if (isNum(w)) {
|
||||
r.push(`<span>${w}</span>`);
|
||||
} else {
|
||||
r.push(w);
|
||||
}
|
||||
})
|
||||
;
|
||||
el.innerHTML = r.join(' ');
|
||||
})
|
||||
;
|
||||
};
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
parseCounter();
|
||||
});
|
||||
|
||||
export { parseCounter };
|
@ -82,7 +82,7 @@ div#notification-fold {
|
||||
|
||||
// Counter
|
||||
div.notification-counter {
|
||||
span {
|
||||
span.counter {
|
||||
&:not(:first-child) {
|
||||
&::before {
|
||||
content: '/ ';
|
||||
@ -90,3 +90,11 @@ div.notification-counter {
|
||||
}
|
||||
}
|
||||
}
|
||||
span.counter {
|
||||
& > span {
|
||||
font-weight: bold;
|
||||
background-color: $chill-ll-gray;
|
||||
padding: 0 0.4rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
:deselect-label="$t('create_address')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
@search-change="listenInputSearch"
|
||||
:internal-search="false"
|
||||
ref="addressSelector"
|
||||
@select="selectAddress"
|
||||
@remove="remove"
|
||||
|
@ -18,6 +18,7 @@
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
:taggable="true"
|
||||
:multiple="false"
|
||||
:internal-search="false"
|
||||
@tag="addPostcode"
|
||||
:tagPlaceholder="$t('create_postal_code')"
|
||||
:loading="isLoading"
|
||||
|
@ -3,16 +3,38 @@
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">id</th>
|
||||
<th scope="col">Ouvert le</th>
|
||||
<th scope="col">Usagers concernés</th>
|
||||
<th scope="col">{{ $t('opening_date') }}</th>
|
||||
<th scope="col">{{ $t('social_issues') }}</th>
|
||||
<th scope="col">{{ $t('concerned_persons') }}</th>
|
||||
<th scope="col"></th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(c, i) in accompanyingCourses.results" :key="`course-${i}`">
|
||||
<td>{{ c.id}}</td>
|
||||
<td>{{ $d(c.openingDate.datetime, 'long') }}</td>
|
||||
<td>{{ c.participations.length }}</td>
|
||||
<td>{{ $d(c.openingDate.datetime, 'short') }}</td>
|
||||
<td>
|
||||
<span v-for="i in c.socialIssues"
|
||||
class="chill-entity entity-social-issue">
|
||||
<span class="badge bg-chill-l-gray text-dark">
|
||||
{{ i.title.fr }}
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-for="p in c.participations" class="me-1" :key="p.person.id">
|
||||
<on-the-fly
|
||||
:type="p.person.type"
|
||||
:id="p.person.id"
|
||||
:buttonText="p.person.textAge"
|
||||
:displayBadge="'true' === 'true'"
|
||||
action="show">
|
||||
</on-the-fly>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="c.emergency" class="badge rounded-pill bg-danger">{{ $t('emergency') }}</span>
|
||||
<span v-if="c.confidential" class="badge rounded-pill bg-danger">{{ $t('confidential') }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(c)">
|
||||
{{ $t('show_entity', { entity: $t('the_course') }) }}
|
||||
@ -26,11 +48,13 @@
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
|
||||
|
||||
export default {
|
||||
name: "MyAccompanyingCourses",
|
||||
components: {
|
||||
TabTable
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
|
@ -6,22 +6,34 @@
|
||||
<div class="custom1">
|
||||
<ul class="list-unstyled">
|
||||
<li v-if="counter.notifications > 0">
|
||||
<b>{{ counter.notifications }}</b> {{ $t('counter.unread_notifications') }}
|
||||
<i18n-t keypath="counter.unread_notifications" tag="span" :class="counterClass" :plural="counter.notifications">
|
||||
<template v-slot:n><span>{{ counter.notifications }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.accompanyingCourses > 0">
|
||||
<b>{{ counter.accompanyingCourses }}</b> {{ $t('counter.assignated_courses') }}
|
||||
<i18n-t keypath="counter.assignated_courses" tag="span" :class="counterClass" :plural="counter.accompanyingCourses">
|
||||
<template v-slot:n><span>{{ counter.accompanyingCourses }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.works > 0">
|
||||
<b>{{ counter.works }}</b> {{ $t('counter.assignated_actions') }}
|
||||
<i18n-t keypath="counter.assignated_actions" tag="span" :class="counterClass" :plural="counter.works">
|
||||
<template v-slot:n><span>{{ counter.works }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.evaluations > 0">
|
||||
<b>{{ counter.evaluations }}</b> {{ $t('counter.assignated_evaluations') }}
|
||||
<i18n-t keypath="counter.assignated_evaluations" tag="span" :class="counterClass" :plural="counter.evaluations">
|
||||
<template v-slot:n><span>{{ counter.evaluations }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.tasksAlert > 0">
|
||||
<b>{{ counter.tasksAlert }}</b> {{ $t('counter.alert_tasks') }}
|
||||
<i18n-t keypath="counter.alert_tasks" tag="span" :class="counterClass" :plural="counter.tasksAlert">
|
||||
<template v-slot:n><span>{{ counter.tasksAlert }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
<li v-if="counter.tasksWarning > 0">
|
||||
<b>{{ counter.tasksWarning }}</b> {{ $t('counter.warning_tasks') }}
|
||||
<i18n-t keypath="counter.warning_tasks" tag="span" :class="counterClass" :plural="counter.tasksWarning">
|
||||
<template v-slot:n><span>{{ counter.tasksWarning }}</span></template>
|
||||
</i18n-t>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -54,6 +66,13 @@ import Masonry from 'masonry-layout/masonry';
|
||||
|
||||
export default {
|
||||
name: "MyCustoms",
|
||||
data() {
|
||||
return {
|
||||
counterClass: {
|
||||
counter: true //hack to pass class 'counter' in i18n-t
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['counter']),
|
||||
noResults() {
|
||||
@ -67,11 +86,16 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
div.custom4,
|
||||
div.custom3,
|
||||
div.custom2 {
|
||||
font-style: italic;
|
||||
color: var(--bs-chill-gray);
|
||||
}
|
||||
span.counter {
|
||||
& > span {
|
||||
background-color: unset;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,32 +1,71 @@
|
||||
<template>
|
||||
<div class="accompanying_course_work">
|
||||
<div class="alert alert-light">{{ $t('my_evaluations.description') }}</div>
|
||||
<span v-if="noResults" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">id</th>
|
||||
<th scope="col">{{ $t('max_date') }}</th>
|
||||
<th scope="col">{{ $t('evaluation') }}</th>
|
||||
<th scope="col">{{ $t('SocialAction') }}</th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(e, i) in evaluations.results" :key="`evaluation-${i}`">
|
||||
<td>{{ e.id}}</td>
|
||||
<td>{{ $d(e.maxDate.datetime, 'short') }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(e)">
|
||||
{{ $t('show_entity', { entity: $t('the_evaluation') }) }}
|
||||
</a>
|
||||
{{ e.evaluation.title.fr }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="chill-entity entity-social-issue">
|
||||
<span class="badge bg-chill-l-gray text-dark">
|
||||
{{ e.accompanyingPeriodWork.socialAction.issue.text }}
|
||||
</span>
|
||||
</span>
|
||||
<h4 class="badge-title">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ e.accompanyingPeriodWork.socialAction.text }}
|
||||
</span>
|
||||
</h4>
|
||||
<span v-for="person in e.accompanyingPeriodWork.persons" class="me-1" :key="person.id">
|
||||
<on-the-fly
|
||||
:type="person.type"
|
||||
:id="person.id"
|
||||
:buttonText="person.textAge"
|
||||
:displayBadge="'true' === 'true'"
|
||||
action="show">
|
||||
</on-the-fly>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical" role="group" aria-label="Actions">
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(e)">
|
||||
{{ $t('show_entity', { entity: $t('the_evaluation') }) }}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-update" :href="getUrl(e.accompanyingPeriodWork)">
|
||||
{{ $t('show_entity', { entity: $t('the_action') }) }}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(e.accompanyingPeriodWork.accompanyingPeriod)">
|
||||
{{ $t('show_entity', { entity: $t('the_course') }) }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tab-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
|
||||
|
||||
export default {
|
||||
name: "MyEvaluations",
|
||||
components: {
|
||||
TabTable
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
@ -45,8 +84,17 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
getUrl(e) {
|
||||
let anchor = '#evaluations';
|
||||
return `/fr/person/accompanying-period/work/${e.id}/edit${anchor}`
|
||||
switch (e.type) {
|
||||
case 'accompanying_period_work_evaluation':
|
||||
let anchor = '#evaluations';
|
||||
return `/fr/person/accompanying-period/work/${e.accompanyingPeriodWork.id}/edit${anchor}`;
|
||||
case 'accompanying_period_work':
|
||||
return `/fr/person/accompanying-period/work/${e.id}/edit`
|
||||
case 'accompanying_period':
|
||||
return `/fr/parcours/${e.id}`
|
||||
default:
|
||||
throw 'entity type unknown';
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -1,15 +1,21 @@
|
||||
<template>
|
||||
|
||||
<div class="alert alert-light">{{ $t('my_tasks.description_alert') }}</div>
|
||||
<span v-if="noResultsWarning" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<div class="alert alert-light">{{ $t('my_tasks.description_warning') }}</div>
|
||||
<span v-if="noResultsAlert" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">id</th>
|
||||
<th scope="col">{{ $t('warning_date') }}</th>
|
||||
<th scope="col">{{ $t('max_date') }}</th>
|
||||
<th scope="col">{{ $t('task') }}</th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(t, i) in tasks.warning" :key="`task-warning-${i}`">
|
||||
<td>{{ t.id}}</td>
|
||||
<tr v-for="(t, i) in tasks.alert.results" :key="`task-alert-${i}`">
|
||||
<td>{{ $d(t.warningDate.datetime, 'short') }}</td>
|
||||
<td>
|
||||
<span class="outdated">{{ $d(t.endDate.datetime, 'short') }}</span>
|
||||
</td>
|
||||
<td>{{ t.title }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(t)">
|
||||
{{ $t('show_entity', { entity: $t('the_task') }) }}
|
||||
@ -19,16 +25,22 @@
|
||||
</template>
|
||||
</tab-table>
|
||||
|
||||
<div class="alert alert-light">{{ $t('my_tasks.description_warning') }}</div>
|
||||
<span v-if="noResultsAlert" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<div class="alert alert-light">{{ $t('my_tasks.description_alert') }}</div>
|
||||
<span v-if="noResultsWarning" class="chill-no-data-statement">{{ $t('no_data') }}</span>
|
||||
<tab-table v-else>
|
||||
<template v-slot:thead>
|
||||
<th scope="col">id</th>
|
||||
<th scope="col">{{ $t('warning_date') }}</th>
|
||||
<th scope="col">{{ $t('max_date') }}</th>
|
||||
<th scope="col">{{ $t('task') }}</th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(t, i) in tasks.alert" :key="`task-alert-${i}`">
|
||||
<td>{{ t.id}}</td>
|
||||
<tr v-for="(t, i) in tasks.warning.results" :key="`task-warning-${i}`">
|
||||
<td>
|
||||
<span class="outdated">{{ $d(t.warningDate.datetime, 'short') }}</span>
|
||||
</td>
|
||||
<td>{{ $d(t.endDate.datetime, 'short') }}</td>
|
||||
<td>{{ t.title }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-show" :href="getUrl(t)">
|
||||
{{ $t('show_entity', { entity: $t('the_task') }) }}
|
||||
@ -81,5 +93,8 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
span.outdated {
|
||||
font-weight: bold;
|
||||
color: var(--bs-warning);
|
||||
}
|
||||
</style>
|
@ -6,12 +6,18 @@
|
||||
<template v-slot:thead>
|
||||
<th scope="col">{{ $t('StartDate') }}</th>
|
||||
<th scope="col">{{ $t('SocialAction') }}</th>
|
||||
<th scope="col">{{ $t('concerned_persons') }}</th>
|
||||
<th scope="col"></th>
|
||||
</template>
|
||||
<template v-slot:tbody>
|
||||
<tr v-for="(w, i) in works.results" :key="`works-${i}`">
|
||||
<td>{{ $d(w.startDate.datetime, 'short') }}</td>
|
||||
<td>
|
||||
<span class="chill-entity entity-social-issue">
|
||||
<span class="badge bg-chill-l-gray text-dark">
|
||||
{{ w.socialAction.issue.text }}
|
||||
</span>
|
||||
</span>
|
||||
<h4 class="badge-title">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
@ -20,7 +26,18 @@
|
||||
</h4>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group" aria-label="Actions">
|
||||
<span v-for="person in w.persons" class="me-1" :key="person.id">
|
||||
<on-the-fly
|
||||
:type="person.type"
|
||||
:id="person.id"
|
||||
:buttonText="person.textAge"
|
||||
:displayBadge="'true' === 'true'"
|
||||
action="show">
|
||||
</on-the-fly>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical" role="group" aria-label="Actions">
|
||||
<a class="btn btn-sm btn-update" :href="getUrl(w)">
|
||||
{{ $t('show_entity', { entity: $t('the_action') }) }}
|
||||
</a>
|
||||
@ -38,11 +55,13 @@
|
||||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import TabTable from "./TabTable";
|
||||
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly';
|
||||
|
||||
export default {
|
||||
name: "MyWorks",
|
||||
components: {
|
||||
TabTable
|
||||
TabTable,
|
||||
OnTheFly,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<span v-if="isCounterAvailable"
|
||||
class="badge rounded-pill bg-danger counter">
|
||||
class="badge rounded-pill bg-danger">
|
||||
{{ count }}
|
||||
</span>
|
||||
</template>
|
||||
|
@ -22,8 +22,15 @@ const appMessages = {
|
||||
tab: "Mes notifications",
|
||||
description: "Liste des notifications reçues et non lues.",
|
||||
},
|
||||
opening_date: "Date d'ouverture",
|
||||
social_issues: "Problématiques sociales",
|
||||
concerned_persons: "Usagers concernés",
|
||||
max_date: "Date d'échéance",
|
||||
warning_date: "Date de rappel",
|
||||
evaluation: "Évaluation",
|
||||
task: "Tâche",
|
||||
Date: "Date",
|
||||
From: "De",
|
||||
From: "Expéditeur",
|
||||
Subject: "Objet",
|
||||
Entity: "Associé à",
|
||||
show_entity: "Voir {entity}",
|
||||
@ -37,12 +44,12 @@ const appMessages = {
|
||||
no_data: "Aucun résultats",
|
||||
no_dashboard: "Pas de tableaux de bord",
|
||||
counter: {
|
||||
unread_notifications: "notifications non lues",
|
||||
assignated_courses: "parcours récents assignés",
|
||||
assignated_actions: "actions assignées",
|
||||
assignated_evaluations: "évaluations assignées",
|
||||
alert_tasks: "tâches en rappel",
|
||||
warning_tasks: "tâches à échéances",
|
||||
unread_notifications: "{n} notification non lue | {n} notifications non lues",
|
||||
assignated_courses: "{n} parcours récent assigné | {n} parcours récents assignés",
|
||||
assignated_actions: "{n} action assignée | {n} actions assignées",
|
||||
assignated_evaluations: "{n} évaluation assignée | {n} évaluations assignées",
|
||||
alert_tasks: "{n} tâche en rappel | {n} tâches en rappel",
|
||||
warning_tasks: "{n} tâche à échéance | {n} tâches à échéance",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -62,27 +62,27 @@ const store = createStore({
|
||||
},
|
||||
mutations: {
|
||||
addWorks(state, works) {
|
||||
console.log('addWorks', works);
|
||||
//console.log('addWorks', works);
|
||||
state.works = works;
|
||||
},
|
||||
addEvaluations(state, evaluations) {
|
||||
console.log('addEvaluations', evaluations);
|
||||
//console.log('addEvaluations', evaluations);
|
||||
state.evaluations = evaluations;
|
||||
},
|
||||
addTasksWarning(state, tasks) {
|
||||
console.log('addTasksWarning', tasks);
|
||||
//console.log('addTasksWarning', tasks);
|
||||
state.tasks.warning = tasks;
|
||||
},
|
||||
addTasksAlert(state, tasks) {
|
||||
console.log('addTasksAlert', tasks);
|
||||
//console.log('addTasksAlert', tasks);
|
||||
state.tasks.alert = tasks;
|
||||
},
|
||||
addCourses(state, courses) {
|
||||
console.log('addCourses', courses);
|
||||
//console.log('addCourses', courses);
|
||||
state.accompanyingCourses = courses;
|
||||
},
|
||||
addNotifications(state, notifications) {
|
||||
console.log('addNotifications', notifications);
|
||||
//console.log('addNotifications', notifications);
|
||||
state.notifications = notifications;
|
||||
},
|
||||
setLoading(state, bool) {
|
||||
|
@ -93,8 +93,9 @@ export default {
|
||||
},
|
||||
getPopTitle(step) {
|
||||
if (step.transitionPrevious != null) {
|
||||
//console.log(step.transitionPrevious.text);
|
||||
let freezed = step.isFreezed ? `<i class="fa fa-snowflake-o fa-sm me-1"></i>` : ``;
|
||||
return `${freezed}${step.currentStep.text}`;
|
||||
return `${freezed}${step.transitionPrevious.text}`;
|
||||
}
|
||||
},
|
||||
getPopContent(step) {
|
||||
|
@ -47,7 +47,8 @@ export default {
|
||||
console.log('goToGenerateWorkflow', event, workflowName);
|
||||
|
||||
if (!this.$props.preventDefaultMoveToGenerate) {
|
||||
event.target.click();
|
||||
console.log('to go generate');
|
||||
window.location.assign(this.makeLink(workflowName));
|
||||
}
|
||||
|
||||
this.$emit('goToGenerateWorkflow', {event, workflowName, link: this.makeLink(workflowName)});
|
||||
|
@ -91,11 +91,11 @@ export const multiSelectMessages = {
|
||||
multiselect: {
|
||||
placeholder: 'Choisir',
|
||||
tag_placeholder: 'Créer un nouvel élément',
|
||||
select_label: 'Appuyer sur "Entrée" pour sélectionner',
|
||||
deselect_label: 'Appuyer sur "Entrée" pour désélectionner',
|
||||
select_label: '"Entrée" ou cliquez pour sélectionner',
|
||||
deselect_label: '"Entrée" ou cliquez pour désélectionner',
|
||||
select_group_label: 'Appuyer sur "Entrée" pour sélectionner ce groupe',
|
||||
deselect_group_label: 'Appuyer sur "Entrée" pour désélectionner ce groupe',
|
||||
selected_label: 'Sélectionné'
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -15,11 +15,11 @@
|
||||
|
||||
<div class="notification-comment-list my-5">
|
||||
<h2 class="chill-blue">{{ 'notification.comments_list'|trans }}</h2>
|
||||
|
||||
|
||||
{% if notification.comments|length > 0 %}
|
||||
<div class="flex-table">
|
||||
{% for comment in notification.comments %}
|
||||
|
||||
|
||||
{% if editedCommentForm is null or editedCommentId != comment.id %}
|
||||
{{ m.show_comment(comment, {
|
||||
'recordAction': _self.recordAction(comment)
|
||||
@ -28,10 +28,11 @@
|
||||
<div class="item-bloc">
|
||||
<div class="item-row row">
|
||||
<a id="comment-{{ comment.id }}"></a>
|
||||
|
||||
|
||||
{{ form_start(editedCommentForm) }}
|
||||
{{ form_errors(editedCommentForm) }}
|
||||
{{ form_widget(editedCommentForm.content) }}
|
||||
{{ form_errors(editedCommentForm.content) }}
|
||||
<input type="hidden" name="form" value="edit" />
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
@ -46,24 +47,25 @@
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(editedCommentForm) }}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="chill-no-data-statement">{{ 'No comments'|trans }}</span>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if appendCommentForm is not null %}
|
||||
<div class="new-comment my-5">
|
||||
<h2 class="chill-blue mb-4">{{ 'Write a new comment'|trans }}</h2>
|
||||
|
||||
|
||||
{{ form_start(appendCommentForm) }}
|
||||
{{ form_errors(appendCommentForm) }}
|
||||
{{ form_widget(appendCommentForm.content) }}
|
||||
{{ form_errors(appendCommentForm.content) }}
|
||||
<input type="hidden" name="form" value="append" />
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
@ -71,7 +73,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
{{ form_end(appendCommentForm) }}
|
||||
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -72,10 +72,13 @@
|
||||
<div class="item-row separator">
|
||||
<div class="item-col item-meta">
|
||||
|
||||
{# TODO twig extension to count comments #}
|
||||
<div class="comment-counter visually-hidden">
|
||||
<span>x commentaires</span>
|
||||
</div>
|
||||
{% if c.notification.comments|length > 0 %}
|
||||
<div class="comment-counter">
|
||||
<span class="counter">
|
||||
{{ 'notification.counter comments'|trans({'nb': c.notification.comments|length }) }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="item-col">
|
||||
|
@ -1,11 +1,11 @@
|
||||
<div class="notification-counter">
|
||||
{% if counter.total > 0 %}
|
||||
<span>
|
||||
<span class="counter">
|
||||
{{ 'notification.counter total notifications'|trans({'total': counter.total }) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if counter.unread > 0 %}
|
||||
<span>
|
||||
<span class="counter">
|
||||
{{ 'notification.counter unread notifications'|trans({'unread': counter.unread }) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<h2>
|
||||
{{ 'workflow_'|trans }}
|
||||
</h2>
|
||||
{% include handler.templateTitle(l.entity_workflow) with handler.templateTitleData(entity_workflow)|merge({
|
||||
{% include handler.templateTitle(entity_workflow) with handler.templateTitleData(entity_workflow)|merge({
|
||||
'description': true,
|
||||
'breadcrumb': true,
|
||||
'add_classes': 'ms-3 h3'
|
||||
|
@ -14,6 +14,7 @@ namespace Chill\MainBundle\Routing\MenuBuilder;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Notification\Counter\NotificationByUserCounter;
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Chill\MainBundle\Workflow\Counter\WorkflowByUserCounter;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
@ -25,12 +26,16 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private WorkflowByUserCounter $workflowByUserCounter;
|
||||
|
||||
public function __construct(
|
||||
NotificationByUserCounter $notificationByUserCounter,
|
||||
WorkflowByUserCounter $workflowByUserCounter,
|
||||
Security $security,
|
||||
TranslatorInterface $translator
|
||||
) {
|
||||
$this->notificationByUserCounter = $notificationByUserCounter;
|
||||
$this->workflowByUserCounter = $workflowByUserCounter;
|
||||
$this->security = $security;
|
||||
$this->translator = $translator;
|
||||
}
|
||||
@ -69,9 +74,11 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
||||
'counter' => $nbNotifications,
|
||||
]);
|
||||
|
||||
$workflowCount = $this->workflowByUserCounter->getCountUnreadByUser($user);
|
||||
|
||||
$menu
|
||||
->addChild(
|
||||
$this->translator->trans('workflow.My workflows'),
|
||||
$this->translator->trans('workflow.My workflows with counter', ['wc' => $workflowCount]),
|
||||
['route' => 'chill_main_workflow_list_dest']
|
||||
)
|
||||
->setExtras([
|
||||
|
@ -44,7 +44,6 @@ class NotificationNormalizer implements NormalizerAwareInterface, NormalizerInte
|
||||
*/
|
||||
public function normalize($object, ?string $format = null, array $context = [])
|
||||
{
|
||||
dump($object);
|
||||
$entity = $this->entityManager
|
||||
->getRepository($object->getRelatedEntityClass())
|
||||
->find($object->getRelatedEntityId());
|
||||
|
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Workflow\Counter;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
|
||||
use Chill\MainBundle\Repository\Workflow\EntityWorkflowStepRepository;
|
||||
use Chill\MainBundle\Templating\UI\NotificationCounterInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Workflow\Event\Event;
|
||||
|
||||
final class WorkflowByUserCounter implements NotificationCounterInterface, EventSubscriberInterface
|
||||
{
|
||||
private CacheItemPoolInterface $cacheItemPool;
|
||||
|
||||
private EntityWorkflowStepRepository $workflowStepRepository;
|
||||
|
||||
public function __construct(EntityWorkflowStepRepository $workflowStepRepository, CacheItemPoolInterface $cacheItemPool)
|
||||
{
|
||||
$this->workflowStepRepository = $workflowStepRepository;
|
||||
$this->cacheItemPool = $cacheItemPool;
|
||||
}
|
||||
|
||||
public function addNotification(UserInterface $user): int
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$key = self::generateCacheKeyWorkflowByUser($user);
|
||||
$item = $this->cacheItemPool->getItem($key);
|
||||
|
||||
if ($item->isHit()) {
|
||||
return $item->get();
|
||||
}
|
||||
|
||||
$nb = $this->getCountUnreadByUser($user);
|
||||
|
||||
$item->set($nb)
|
||||
->expiresAfter(60 * 15);
|
||||
$this->cacheItemPool->save($item);
|
||||
|
||||
return $nb;
|
||||
}
|
||||
|
||||
public static function generateCacheKeyWorkflowByUser(User $user): string
|
||||
{
|
||||
return 'chill_main_workflow_by_u_' . $user->getId();
|
||||
}
|
||||
|
||||
public function getCountUnreadByUser(User $user): int
|
||||
{
|
||||
return $this->workflowStepRepository->countUnreadByUser($user);
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
{
|
||||
return [
|
||||
'workflow.leave' => 'resetWorkflowCache',
|
||||
];
|
||||
}
|
||||
|
||||
public function resetWorkflowCache(Event $event): void
|
||||
{
|
||||
if (!$event->getSubject() instanceof EntityWorkflow) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var EntityWorkflow $entityWorkflow */
|
||||
$entityWorkflow = $event->getSubject();
|
||||
$step = $entityWorkflow->getCurrentStep();
|
||||
|
||||
if (null === $step) {
|
||||
return;
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
|
||||
foreach ($step->getDestUser() as $user) {
|
||||
$keys[] = self::generateCacheKeyWorkflowByUser($user);
|
||||
}
|
||||
|
||||
if ([] !== $keys) {
|
||||
$this->cacheItemPool->deleteItems($keys);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,12 @@ class WorkflowNotificationHandler implements NotificationHandlerInterface
|
||||
|
||||
private EntityWorkflowRepository $entityWorkflowRepository;
|
||||
|
||||
public function __construct(EntityWorkflowRepository $entityWorkflowRepository, EntityWorkflowManager $entityWorkflowManager)
|
||||
{
|
||||
$this->entityWorkflowRepository = $entityWorkflowRepository;
|
||||
$this->entityWorkflowManager = $entityWorkflowManager;
|
||||
}
|
||||
|
||||
public function getTemplate(Notification $notification, array $options = []): string
|
||||
{
|
||||
return '@ChillMain/Workflow/_notification_include.html.twig';
|
||||
|
@ -29,3 +29,19 @@ notification:
|
||||
few {# non-lues}
|
||||
other {# non-lues}
|
||||
}
|
||||
counter comments: >-
|
||||
{nb, plural,
|
||||
=0 {Aucun commentaire}
|
||||
one {# commentaire}
|
||||
few {# commentaires}
|
||||
other {# commentaires}
|
||||
}
|
||||
|
||||
workflow:
|
||||
My workflows with counter: >-
|
||||
{wc, plural,
|
||||
=0 {Mes workflows}
|
||||
one {Une workflow}
|
||||
few {# workflows}
|
||||
other {# workflows}
|
||||
}
|
||||
|
@ -27,4 +27,4 @@ address:
|
||||
notification:
|
||||
At least one addressee: Indiquez au moins un destinataire
|
||||
Title must be defined: Un titre doit être indiqué
|
||||
|
||||
Comment content might not be blank: Le commentaire ne peut pas être vide
|
||||
|
@ -15,6 +15,7 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
@ -23,7 +24,7 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Translation\TranslatorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class AccompanyingCourseWorkController extends AbstractController
|
||||
{
|
||||
@ -60,7 +61,7 @@ class AccompanyingCourseWorkController extends AbstractController
|
||||
*/
|
||||
public function createWork(AccompanyingPeriod $period): Response
|
||||
{
|
||||
// TODO ACL
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::CREATE, $period);
|
||||
|
||||
if ($period->getSocialIssues()->count() === 0) {
|
||||
$this->addFlash(
|
||||
@ -93,7 +94,8 @@ class AccompanyingCourseWorkController extends AbstractController
|
||||
*/
|
||||
public function deleteWork(AccompanyingPeriodWork $work, Request $request): Response
|
||||
{
|
||||
// TODO ACL
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::UPDATE, $work);
|
||||
|
||||
$em = $this->getDoctrine()->getManager();
|
||||
|
||||
$form = $this->createDeleteForm($work->getId());
|
||||
@ -138,7 +140,8 @@ class AccompanyingCourseWorkController extends AbstractController
|
||||
*/
|
||||
public function editWork(AccompanyingPeriodWork $work): Response
|
||||
{
|
||||
// TODO ACL
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::UPDATE, $work);
|
||||
|
||||
$json = $this->serializer->normalize($work, 'json', ['groups' => ['read']]);
|
||||
|
||||
return $this->render('@ChillPerson/AccompanyingCourseWork/edit.html.twig', [
|
||||
@ -157,13 +160,13 @@ class AccompanyingCourseWorkController extends AbstractController
|
||||
*/
|
||||
public function listWorkByAccompanyingPeriod(AccompanyingPeriod $period): Response
|
||||
{
|
||||
// TODO ACL
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::SEE, $period);
|
||||
|
||||
$totalItems = $this->workRepository->countByAccompanyingPeriod($period);
|
||||
$paginator = $this->paginator->create($totalItems);
|
||||
|
||||
$works = $this->workRepository->findByAccompanyingPeriod(
|
||||
$works = $this->workRepository->findByAccompanyingPeriodOpenFirst(
|
||||
$period,
|
||||
['startDate' => 'DESC', 'endDate' => 'DESC'],
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
@ -99,7 +99,7 @@ class AccompanyingPeriodWorkEvaluationApiController
|
||||
|
||||
if ($request->query->getBoolean('countOnly', false)) {
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize(new Counter($total), 'json', ['groups' => 'read']),
|
||||
$this->serializer->serialize(new Counter($total), 'json', ['groups' => ['read']]),
|
||||
JsonResponse::HTTP_OK,
|
||||
[],
|
||||
true
|
||||
@ -117,7 +117,7 @@ class AccompanyingPeriodWorkEvaluationApiController
|
||||
$collection = new Collection($works, $paginator);
|
||||
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize($collection, 'json', ['groups' => 'read']),
|
||||
$this->serializer->serialize($collection, 'json', ['groups' => ['read', 'read:evaluation:include-work']]),
|
||||
JsonResponse::HTTP_OK,
|
||||
[],
|
||||
true
|
||||
|
@ -16,9 +16,12 @@ use Chill\PersonBundle\Entity\Household\Household;
|
||||
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
|
||||
use Chill\PersonBundle\Form\HouseholdCompositionType;
|
||||
use Chill\PersonBundle\Repository\Household\HouseholdCompositionRepository;
|
||||
use Chill\PersonBundle\Repository\Household\HouseholdRepository;
|
||||
use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\FormFactoryInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@ -31,7 +34,7 @@ use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Templating\EngineInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class HouseholdCompositionController
|
||||
class HouseholdCompositionController extends AbstractController
|
||||
{
|
||||
private EngineInterface $engine;
|
||||
|
||||
@ -41,6 +44,8 @@ class HouseholdCompositionController
|
||||
|
||||
private HouseholdCompositionRepository $householdCompositionRepository;
|
||||
|
||||
private HouseholdRepository $householdRepository;
|
||||
|
||||
private PaginatorFactory $paginatorFactory;
|
||||
|
||||
private Security $security;
|
||||
@ -52,6 +57,7 @@ class HouseholdCompositionController
|
||||
public function __construct(
|
||||
Security $security,
|
||||
HouseholdCompositionRepository $householdCompositionRepository,
|
||||
HouseholdRepository $householdRepository,
|
||||
PaginatorFactory $paginatorFactory,
|
||||
FormFactoryInterface $formFactory,
|
||||
EntityManagerInterface $entityManager,
|
||||
@ -67,6 +73,59 @@ class HouseholdCompositionController
|
||||
$this->translator = $translator;
|
||||
$this->engine = $engine;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->householdRepository = $householdRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{_locale}/person/household/{household_id}/composition/{composition_id}/delete", name="chill_person_household_composition_delete")
|
||||
*
|
||||
* @param mixed $household_id
|
||||
* @param mixed $composition_id
|
||||
*/
|
||||
public function deleteAction(Request $request, $household_id, $composition_id): Response
|
||||
{
|
||||
$composition = $this->householdCompositionRepository->find($composition_id);
|
||||
$household = $this->householdRepository->find($household_id);
|
||||
|
||||
$this->denyAccessUnlessGranted(HouseholdVoter::EDIT, $household);
|
||||
|
||||
if (null === $composition) {
|
||||
throw $this->createNotFoundException('Unable to find composition entity.');
|
||||
}
|
||||
|
||||
$form = $this->createFormBuilder()
|
||||
->setAction($this->generateUrl('chill_person_household_composition_delete', [
|
||||
'composition_id' => $composition_id,
|
||||
'household_id' => $household_id,
|
||||
]))
|
||||
->setMethod('DELETE')
|
||||
->add('submit', SubmitType::class, ['label' => 'Delete'])
|
||||
->getForm();
|
||||
|
||||
if ($request->getMethod() === Request::METHOD_DELETE) {
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isValid()) {
|
||||
$this->entityManager->remove($composition);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', $this->translator
|
||||
->trans('The composition has been successfully removed.'));
|
||||
|
||||
return $this->redirectToRoute('chill_person_household_composition_index', [
|
||||
'id' => $household_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
'ChillPersonBundle:HouseholdComposition:delete.html.twig',
|
||||
[
|
||||
'household' => $household,
|
||||
'composition' => $composition,
|
||||
'form' => $form->createView(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,7 +133,11 @@ class AccompanyingPeriod implements
|
||||
* @ORM\Column(type="date", nullable=true)
|
||||
* @Groups({"read", "write", "docgen:read"})
|
||||
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED})
|
||||
* @Assert\GreaterThan(propertyPath="openingDate", groups={AccompanyingPeriod::STEP_CLOSED})
|
||||
* @Assert\GreaterThanOrEqual(
|
||||
* propertyPath="openingDate",
|
||||
* groups={AccompanyingPeriod::STEP_CLOSED},
|
||||
* message="The closing date must be later than the date of creation"
|
||||
* )
|
||||
*/
|
||||
private ?DateTime $closingDate = null;
|
||||
|
||||
@ -213,8 +217,8 @@ class AccompanyingPeriod implements
|
||||
*
|
||||
* @ORM\Column(type="date")
|
||||
* @Groups({"read", "write", "docgen:read"})
|
||||
* @Assert\LessThan(value="today", groups={AccompanyingPeriod::STEP_CONFIRMED})
|
||||
* @Assert\LessThan(propertyPath="closingDate", groups={AccompanyingPeriod::STEP_CONFIRMED})
|
||||
* @Assert\LessThan(value="tomorrow", groups={AccompanyingPeriod::STEP_CONFIRMED})
|
||||
* @Assert\LessThanOrEqual(propertyPath="closingDate", groups={AccompanyingPeriod::STEP_CONFIRMED})
|
||||
*/
|
||||
private ?DateTime $openingDate = null;
|
||||
|
||||
|
@ -44,7 +44,8 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
|
||||
{
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class)
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Serializer\Groups({"read", "read:accompanyingPeriodWork:light"})
|
||||
* @Serializer\Context(normalizationContext={"groups": {"read"}}, groups={"read:accompanyingPeriodWork:light"})
|
||||
*/
|
||||
private ?AccompanyingPeriod $accompanyingPeriod = null;
|
||||
|
||||
@ -63,26 +64,26 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="datetime_immutable")
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
|
||||
*/
|
||||
private ?DateTimeImmutable $createdAt = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
|
||||
*/
|
||||
private bool $createdAutomatically = false;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="text")
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
|
||||
*/
|
||||
private string $createdAutomaticallyReason = '';
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=User::class)
|
||||
* @ORM\JoinColumn(nullable=false)
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
|
||||
*/
|
||||
private ?User $createdBy = null;
|
||||
|
||||
@ -90,7 +91,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
|
||||
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
|
||||
* @Serializer\Groups({"accompanying_period_work:create"})
|
||||
* @Serializer\Groups({"accompanying_period_work:edit"})
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
|
||||
* @Assert\GreaterThan(propertyPath="startDate",
|
||||
* message="accompanying_course_work.The endDate should be greater than the start date"
|
||||
* )
|
||||
@ -122,7 +123,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue
|
||||
* @ORM\Column(type="integer")
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light", "read:evaluation:include-work"})
|
||||
*/
|
||||
private ?int $id = null;
|
||||
|
||||
@ -135,7 +136,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=Person::class)
|
||||
* @ORM\JoinTable(name="chill_person_accompanying_period_work_person")
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
|
||||
* @Serializer\Groups({"accompanying_period_work:edit"})
|
||||
* @Serializer\Groups({"accompanying_period_work:create"})
|
||||
*/
|
||||
@ -151,8 +152,9 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity=SocialAction::class)
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
|
||||
* @Serializer\Groups({"accompanying_period_work:create"})
|
||||
* @Serializer\Context(normalizationContext={"groups": {"read"}}, groups={"read:accompanyingPeriodWork:light"})
|
||||
*/
|
||||
private ?SocialAction $socialAction = null;
|
||||
|
||||
@ -160,7 +162,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
|
||||
* @ORM\Column(type="date_immutable")
|
||||
* @Serializer\Groups({"accompanying_period_work:create"})
|
||||
* @Serializer\Groups({"accompanying_period_work:edit"})
|
||||
* @Serializer\Groups({"read", "docgen:read"})
|
||||
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
|
||||
*/
|
||||
private ?DateTimeImmutable $startDate = null;
|
||||
|
||||
|
@ -39,6 +39,8 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
|
||||
* targetEntity=AccompanyingPeriodWork::class,
|
||||
* inversedBy="accompanyingPeriodWorkEvaluations"
|
||||
* )
|
||||
* @Serializer\Groups({"read:evaluation:include-work"})
|
||||
* @Serializer\Context(normalizationContext={"groups": {"read:accompanyingPeriodWork:light"}}, groups={"read:evaluation:include-work"})
|
||||
*/
|
||||
private ?AccompanyingPeriodWork $accompanyingPeriodWork = null;
|
||||
|
||||
|
@ -13,7 +13,10 @@ namespace Chill\PersonBundle\Menu;
|
||||
|
||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
|
||||
use Knp\Menu\MenuItem;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Workflow\Registry;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
@ -24,15 +27,18 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
|
||||
{
|
||||
protected Registry $registry;
|
||||
|
||||
protected Security $security;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
protected $translator;
|
||||
|
||||
public function __construct(TranslatorInterface $translator, Registry $registry)
|
||||
public function __construct(TranslatorInterface $translator, Registry $registry, Security $security)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
$this->registry = $registry;
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
|
||||
@ -47,38 +53,44 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
|
||||
], ])
|
||||
->setExtras(['order' => 10]);
|
||||
|
||||
$menu->addChild($this->translator->trans('Edit Accompanying Course'), [
|
||||
'route' => 'chill_person_accompanying_course_edit',
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
], ])
|
||||
->setExtras(['order' => 20]);
|
||||
if ($this->security->isGranted(AccompanyingPeriodVoter::EDIT, $period)) {
|
||||
$menu->addChild($this->translator->trans('Edit Accompanying Course'), [
|
||||
'route' => 'chill_person_accompanying_course_edit',
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
], ])
|
||||
->setExtras(['order' => 20]);
|
||||
}
|
||||
|
||||
if (AccompanyingPeriod::STEP_DRAFT === $period->getStep()) {
|
||||
// no more menu items if the period is draft
|
||||
return;
|
||||
}
|
||||
|
||||
$menu->addChild($this->translator->trans('Accompanying Course History'), [
|
||||
'route' => 'chill_person_accompanying_course_history',
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
], ])
|
||||
->setExtras(['order' => 30]);
|
||||
if ($this->security->isGranted(AccompanyingPeriodVoter::SEE_DETAILS, $period)) {
|
||||
$menu->addChild($this->translator->trans('Accompanying Course History'), [
|
||||
'route' => 'chill_person_accompanying_course_history',
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
], ])
|
||||
->setExtras(['order' => 30]);
|
||||
|
||||
$menu->addChild($this->translator->trans('Accompanying Course Action'), [
|
||||
'route' => 'chill_person_accompanying_period_work_list',
|
||||
'routeParameters' => [
|
||||
'id' => $period->getId(),
|
||||
], ])
|
||||
->setExtras(['order' => 40]);
|
||||
$menu->addChild($this->translator->trans('Accompanying Course Comment'), [
|
||||
'route' => 'chill_person_accompanying_period_comment_list',
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
], ])
|
||||
->setExtras(['order' => 50]);
|
||||
}
|
||||
|
||||
$menu->addChild($this->translator->trans('Accompanying Course Comment'), [
|
||||
'route' => 'chill_person_accompanying_period_comment_list',
|
||||
'routeParameters' => [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
], ])
|
||||
->setExtras(['order' => 50]);
|
||||
if ($this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $period)) {
|
||||
$menu->addChild($this->translator->trans('Accompanying Course Action'), [
|
||||
'route' => 'chill_person_accompanying_period_work_list',
|
||||
'routeParameters' => [
|
||||
'id' => $period->getId(),
|
||||
], ])
|
||||
->setExtras(['order' => 40]);
|
||||
}
|
||||
|
||||
$workflow = $this->registry->get($period, 'accompanying_period_lifecycle');
|
||||
|
||||
|
@ -16,17 +16,22 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
final class AccompanyingPeriodWorkRepository implements ObjectRepository
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
$this->repository = $entityManager->getRepository(AccompanyingPeriodWork::class);
|
||||
}
|
||||
|
||||
@ -78,6 +83,36 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository
|
||||
return $this->repository->findByAccompanyingPeriod($period, $orderBy, $limit, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of accompanying period with a defined order:.
|
||||
*
|
||||
* * first, opened works
|
||||
* * then, closed works
|
||||
*
|
||||
* @return AccompanyingPeriodWork[]
|
||||
*/
|
||||
public function findByAccompanyingPeriodOpenFirst(AccompanyingPeriod $period, int $limit = 10, int $offset = 0): array
|
||||
{
|
||||
$rsm = new ResultSetMappingBuilder($this->em);
|
||||
$rsm->addRootEntityFromClassMetadata(AccompanyingPeriodWork::class, 'w');
|
||||
|
||||
$sql = "SELECT {$rsm} FROM chill_person_accompanying_period_work w
|
||||
WHERE accompanyingPeriod_id = :periodId
|
||||
ORDER BY
|
||||
CASE WHEN enddate IS NULL THEN '-infinity'::timestamp ELSE 'infinity'::timestamp END ASC,
|
||||
startdate DESC,
|
||||
enddate DESC,
|
||||
id DESC
|
||||
LIMIT :limit OFFSET :offset";
|
||||
|
||||
$nq = $this->em->createNativeQuery($sql, $rsm)
|
||||
->setParameter('periodId', $period->getId(), Types::INTEGER)
|
||||
->setParameter('limit', $limit, Types::INTEGER)
|
||||
->setParameter('offset', $offset, Types::INTEGER);
|
||||
|
||||
return $nq->getResult();
|
||||
}
|
||||
|
||||
public function findNearEndDateByUser(User $user, DateTimeImmutable $since, DateTimeImmutable $until, int $limit = 20, int $offset = 0): array
|
||||
{
|
||||
return $this->buildQueryNearEndDateByUser($user, $since, $until)
|
||||
|
@ -60,14 +60,14 @@
|
||||
<template v-slot:body>
|
||||
<p>{{ $t('confirm.sure_description') }}</p>
|
||||
<div v-if="accompanyingCourse.user === null">
|
||||
<div v-if="filteredReferrersSuggested.length === 0">
|
||||
<div v-if="usersSuggestedFilteredByJob.length === 0">
|
||||
<p class="alert alert-warning">{{ $t('confirm.no_suggested_referrer') }}</p>
|
||||
</div>
|
||||
<div v-if="filteredReferrersSuggested.length === 1" class="alert alert-info">
|
||||
<div v-if="usersSuggestedFilteredByJob.length === 1" class="alert alert-info">
|
||||
<p>{{ $t('confirm.one_suggested_referrer') }}:</p>
|
||||
<ul class="list-suggest add-items inline">
|
||||
<li>
|
||||
<user-render-box-badge :user="filteredReferrersSuggested[0]"></user-render-box-badge>
|
||||
<user-render-box-badge :user="usersSuggestedFilteredByJob[0]"></user-render-box-badge>
|
||||
</li>
|
||||
</ul>
|
||||
<p>{{ $t('confirm.choose_suggested_referrer') }}</p>
|
||||
@ -150,7 +150,6 @@ export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
accompanyingCourse: state => state.accompanyingCourse,
|
||||
filteredReferrersSuggested: state => state.filteredReferrersSuggested
|
||||
}),
|
||||
...mapGetters([
|
||||
'isParticipationValid',
|
||||
@ -160,15 +159,16 @@ export default {
|
||||
'isLocationValid',
|
||||
'isJobValid',
|
||||
'validationKeys',
|
||||
'isValidToBeConfirmed'
|
||||
'isValidToBeConfirmed',
|
||||
'usersSuggestedFilteredByJob',
|
||||
]),
|
||||
deleteLink() {
|
||||
return `/fr/parcours/${this.accompanyingCourse.id}/delete`; //TODO locale
|
||||
},
|
||||
disableConfirm() {
|
||||
return this.clickedDoNotChooseReferrer
|
||||
? (this.accompanyingCourse.user === null && this.filteredReferrersSuggested.length === 0)
|
||||
: (this.accompanyingCourse.user === null && this.filteredReferrersSuggested.length === 0) || (this.filteredReferrersSuggested.length === 1);
|
||||
return (this.accompanyingCourse.user === null
|
||||
&& this.usersSuggestedFilteredByJob.length === 1
|
||||
&& this.clickedDoNotChooseReferrer === false);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@ -183,7 +183,7 @@ export default {
|
||||
});
|
||||
},
|
||||
chooseSuggestedReferrer() {
|
||||
this.$store.dispatch('updateReferrer', this.filteredReferrersSuggested[0])
|
||||
this.$store.dispatch('updateReferrer', this.usersSuggestedFilteredByJob[0])
|
||||
.catch(({name, violations}) => {
|
||||
if (name === 'ValidationException' || name === 'AccessException') {
|
||||
violations.forEach((violation) => this.$toast.open({message: violation}));
|
||||
|
@ -1,164 +1,178 @@
|
||||
<template>
|
||||
<div class="vue-component">
|
||||
<h2><a id="section-80"></a>{{ $t('referrer.title') }}</h2>
|
||||
<div class="vue-component">
|
||||
<h2><a id="section-80"></a>{{ $t('referrer.title') }}</h2>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
|
||||
<label class="col-form-label" for="selectJob">
|
||||
{{ $t('job.label') }}
|
||||
{{ $t('job.label') }}
|
||||
</label>
|
||||
|
||||
<VueMultiselect
|
||||
name="selectJob"
|
||||
label="text"
|
||||
:custom-label="customJobLabel"
|
||||
track-by="id"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('job.placeholder')"
|
||||
v-model="valueJob"
|
||||
:options="jobs"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
@select="updateJob">
|
||||
</VueMultiselect>
|
||||
name="selectJob"
|
||||
label="text"
|
||||
:custom-label="customJobLabel"
|
||||
track-by="id"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('job.placeholder')"
|
||||
v-model="valueJob"
|
||||
:options="jobs"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
></VueMultiselect>
|
||||
|
||||
<label class="col-form-label" for="selectReferrer">
|
||||
{{ $t('referrer.label') }}
|
||||
{{ $t('referrer.label') }}
|
||||
</label>
|
||||
|
||||
<VueMultiselect
|
||||
name="selectReferrer"
|
||||
label="text"
|
||||
track-by="id"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('referrer.placeholder')"
|
||||
v-model="value"
|
||||
:options="users"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
@select="updateReferrer">
|
||||
</VueMultiselect>
|
||||
name="selectReferrer"
|
||||
label="text"
|
||||
track-by="id"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
:placeholder="$t('referrer.placeholder')"
|
||||
v-model="value"
|
||||
:options="users"
|
||||
:select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')"
|
||||
:selected-label="$t('multiselect.selected_label')"
|
||||
></VueMultiselect>
|
||||
|
||||
<template v-if="filteredReferrersSuggested.length > 0">
|
||||
<ul class="list-suggest add-items inline">
|
||||
<li v-for="(u, i) in filteredReferrersSuggested" @click="updateReferrer(u)" :key="`referrer-${i}`">
|
||||
<template v-if="usersSuggestedFilteredByJob.length > 0">
|
||||
<ul class="list-suggest add-items inline">
|
||||
<li v-for="(u, i) in usersSuggestedFilteredByJob" @click="updateReferrer(u)" :key="`referrer-${i}`">
|
||||
<span>
|
||||
<user-render-box-badge :user="u"></user-render-box-badge>
|
||||
</span>
|
||||
</li>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
<button
|
||||
class="btn btn-create"
|
||||
type="button"
|
||||
name="button"
|
||||
@click="assignMe">
|
||||
{{ $t('referrer.assign_me') }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class="btn btn-create"
|
||||
type="button"
|
||||
name="button"
|
||||
@click="assignMe">
|
||||
{{ $t('referrer.assign_me') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isJobValid" class="alert alert-warning to-confirm">
|
||||
<div v-if="!isJobValid" class="alert alert-warning to-confirm">
|
||||
{{ $t('job.not_valid') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueMultiselect from 'vue-multiselect';
|
||||
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import {makeFetch} from 'ChillMainAssets/lib/api/apiMethods';
|
||||
import {mapState, mapGetters} from 'vuex';
|
||||
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge";
|
||||
|
||||
export default {
|
||||
name: "Referrer",
|
||||
components: {
|
||||
UserRenderBoxBadge,
|
||||
VueMultiselect,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
jobs: []
|
||||
name: "Referrer",
|
||||
components: {
|
||||
UserRenderBoxBadge,
|
||||
VueMultiselect,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
jobs: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
valueJob: state => state.accompanyingCourse.job,
|
||||
}),
|
||||
...mapGetters(['isJobValid', 'usersSuggestedFilteredByJob']),
|
||||
users: function () {
|
||||
let users = this.$store.getters.usersFilteredByJob;
|
||||
|
||||
console.log('users filtered by job', users);
|
||||
|
||||
// ensure that the selected user is in the list. add it if necessary
|
||||
if (this.$store.state.accompanyingCourse.user !== null && users.find(u => this.$store.state.accompanyingCourse.user.id === u.id) === undefined) {
|
||||
console.log('add user to users');
|
||||
users.push(this.$store.state.accompanyingCourse.user);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
value: state => state.accompanyingCourse.user,
|
||||
valueJob: state => state.accompanyingCourse.job,
|
||||
users: state => state.users.filter(u => {
|
||||
if (u.user_job && state.accompanyingCourse.job) {
|
||||
return u.user_job.id === state.accompanyingCourse.job.id;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
filteredReferrersSuggested: state => state.filteredReferrersSuggested,
|
||||
}),
|
||||
...mapGetters([
|
||||
'isJobValid'
|
||||
])
|
||||
},
|
||||
mounted() {
|
||||
this.getJobs();
|
||||
},
|
||||
methods: {
|
||||
updateReferrer(value) {
|
||||
this.$store.dispatch('updateReferrer', value)
|
||||
|
||||
console.log('users to return', users);
|
||||
return users;
|
||||
},
|
||||
valueJob: {
|
||||
get() {
|
||||
return this.$store.state.accompanyingCourse.job;
|
||||
},
|
||||
set(value) {
|
||||
this.$store.dispatch('updateJob', value)
|
||||
.catch(({name, violations}) => {
|
||||
if (name === 'ValidationException' || name === 'AccessException') {
|
||||
violations.forEach((violation) => this.$toast.open({message: violation}));
|
||||
} else {
|
||||
this.$toast.open({message: 'An error occurred'})
|
||||
}
|
||||
if (name === 'ValidationException' || name === 'AccessException') {
|
||||
violations.forEach((violation) => this.$toast.open({message: violation}));
|
||||
} else {
|
||||
this.$toast.open({message: 'An error occurred'})
|
||||
}
|
||||
});
|
||||
},
|
||||
getJobs() {
|
||||
const url = '/api/1.0/main/user-job.json';
|
||||
makeFetch('GET', url)
|
||||
.then(response => {
|
||||
this.jobs = response.results;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toast.open({message: error.txt})
|
||||
})
|
||||
},
|
||||
updateJob(value) {
|
||||
this.$store.dispatch('updateJob', value)
|
||||
.catch(({name, violations}) => {
|
||||
if (name === 'ValidationException' || name === 'AccessException') {
|
||||
violations.forEach((violation) => this.$toast.open({message: violation}));
|
||||
} else {
|
||||
this.$toast.open({message: 'An error occurred'})
|
||||
}
|
||||
});
|
||||
},
|
||||
customJobLabel(value) {
|
||||
return value.label.fr;
|
||||
},
|
||||
assignMe() {
|
||||
const url = `/api/1.0/main/whoami.json`;
|
||||
makeFetch('GET', url)
|
||||
.then(response => {
|
||||
this.$store.dispatch('updateReferrer', response);
|
||||
return response;
|
||||
})
|
||||
.catch((error) => {
|
||||
commit('catchError', error);
|
||||
this.$toast.open({message: error.body})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
value: {
|
||||
get() {
|
||||
return this.$store.state.accompanyingCourse.user;
|
||||
},
|
||||
set(value) {
|
||||
console.log('set referrer', value);
|
||||
this.$store.dispatch('updateReferrer', value)
|
||||
.catch(({name, violations}) => {
|
||||
if (name === 'ValidationException' || name === 'AccessException') {
|
||||
violations.forEach((violation) => this.$toast.open({message: violation}));
|
||||
} else {
|
||||
this.$toast.open({message: 'An error occurred'})
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getJobs();
|
||||
},
|
||||
methods: {
|
||||
updateReferrer(value) {
|
||||
this.value = value;
|
||||
},
|
||||
getJobs() {
|
||||
const url = '/api/1.0/main/user-job.json';
|
||||
makeFetch('GET', url)
|
||||
.then(response => {
|
||||
this.jobs = response.results;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.$toast.open({message: error.txt})
|
||||
})
|
||||
},
|
||||
customJobLabel(value) {
|
||||
return value.label.fr;
|
||||
},
|
||||
assignMe() {
|
||||
const url = `/api/1.0/main/whoami.json`;
|
||||
makeFetch('GET', url)
|
||||
.then(user => {
|
||||
this.value = user
|
||||
})
|
||||
/*.catch((error) => {
|
||||
commit('catchError', error);
|
||||
this.$toast.open({message: error.body})
|
||||
})*/
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -140,7 +140,7 @@ const appMessages = {
|
||||
sure_description: "Une fois le changement confirmé, il ne sera plus possible de le remettre à l'état de brouillon !",
|
||||
ok: "Confirmer le parcours",
|
||||
delete: "Supprimer le parcours",
|
||||
no_suggested_referrer: "Il n'y a aucun référent qui puisse être désigné pour ce parcours. Vérifiez la localisation du parcours, les métiers et service indiqués. Si le problème persiste, contactez l'administrateur du logiciel.",
|
||||
no_suggested_referrer: "Il n'y a aucun référent qui puisse être suggéré pour ce parcours. Vérifiez la localisation du parcours, les métiers et service indiqués. Si les données sont correctes, vous pouvez confirmer ce parcours.",
|
||||
one_suggested_referrer: "Un unique référent peut être suggéré pour ce parcours",
|
||||
choose_suggested_referrer: "Voulez-vous le désigner directement ?",
|
||||
choose_button: "Désigner",
|
||||
|
@ -40,7 +40,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
|
||||
scopesAtBackend: accompanyingCourse.scopes.map(scope => scope),
|
||||
// the users which are available for referrer
|
||||
referrersSuggested: [],
|
||||
filteredReferrersSuggested: [],
|
||||
// all the users available
|
||||
users: [],
|
||||
permissions: {}
|
||||
@ -93,6 +92,30 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
|
||||
|
||||
return false;
|
||||
},
|
||||
usersFilteredByJob(state) {
|
||||
return state.users.filter(u => {
|
||||
if (null !== state.accompanyingCourse.job) {
|
||||
return (u.user_job === null ? null : u.user_job.id) === state.accompanyingCourse.job.id;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
},
|
||||
usersSuggestedFilteredByJob(state) {
|
||||
return state.referrersSuggested.filter(u => {
|
||||
if (null !== state.accompanyingCourse.job) {
|
||||
return (u.user_job === null ? null : u.user_job.id) === state.accompanyingCourse.job.id;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}).filter(u => {
|
||||
if (null !== state.accompanyingCourse.user) {
|
||||
return u.id !== state.accompanyingCourse.user.id;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
catchError(state, error) {
|
||||
@ -216,22 +239,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
|
||||
return u;
|
||||
});
|
||||
},
|
||||
setFilteredReferrersSuggested(state) {
|
||||
state.filteredReferrersSuggested = state.referrersSuggested.filter(u => {
|
||||
if (u.user_job && state.accompanyingCourse.job && state.accompanyingCourse.user) {
|
||||
return u.user_job.id === state.accompanyingCourse.job.id && state.accompanyingCourse.user.id !== u.id
|
||||
} else {
|
||||
if (null === state.accompanyingCourse.user) {
|
||||
if (u.user_job && state.accompanyingCourse.job) {
|
||||
return u.user_job.id === state.accompanyingCourse.job.id
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return state.accompanyingCourse.user.id !== u.id;
|
||||
}
|
||||
})
|
||||
},
|
||||
confirmAccompanyingCourse(state, response) {
|
||||
//console.log('### mutation: confirmAccompanyingCourse: response', response);
|
||||
state.accompanyingCourse.step = response.step;
|
||||
@ -689,8 +696,14 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
|
||||
})
|
||||
},
|
||||
updateReferrer({ commit }, payload) {
|
||||
console.log('update referrer', payload);
|
||||
console.log('payload !== null', payload !== null);
|
||||
const url = `/api/1.0/person/accompanying-course/${id}.json`;
|
||||
const body = { type: "accompanying_period", user: { id: payload.id, type: payload.type }};
|
||||
let body = { type: "accompanying_period", user: null };
|
||||
|
||||
if (payload !== null) {
|
||||
body = { type: "accompanying_period", user: { id: payload.id, type: payload.type } };
|
||||
}
|
||||
|
||||
return makeFetch('PATCH', url, body)
|
||||
.then((response) => {
|
||||
@ -704,12 +717,15 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
|
||||
},
|
||||
updateJob({ commit }, payload) {
|
||||
const url = `/api/1.0/person/accompanying-course/${id}.json`;
|
||||
const body = { type: "accompanying_period", job: { id: payload.id, type: payload.type }};
|
||||
let body = { type: "accompanying_period", job: null };
|
||||
|
||||
if (payload !== null) {
|
||||
body = { type: "accompanying_period", job: { id: payload.id, type: payload.type } };
|
||||
}
|
||||
|
||||
return makeFetch('PATCH', url, body)
|
||||
.then((response) => {
|
||||
commit('updateJob', response.job);
|
||||
commit('setFilteredReferrersSuggested');
|
||||
})
|
||||
.catch((error) => {
|
||||
commit('catchError', error);
|
||||
@ -734,7 +750,6 @@ let initPromise = (root) => Promise.all([getScopesPromise(root), accompanyingCou
|
||||
async fetchReferrersSuggested({ state, commit}) {
|
||||
let users = await getReferrersSuggested(state.accompanyingCourse);
|
||||
commit('setReferrersSuggested', users);
|
||||
commit('setFilteredReferrersSuggested');
|
||||
if (
|
||||
null === state.accompanyingCourse.user
|
||||
&& !state.accompanyingCourse.confidential
|
||||
|
@ -92,7 +92,8 @@
|
||||
entityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation"
|
||||
:id="evaluation.id"
|
||||
:templates="getTemplatesAvailables"
|
||||
:beforeMove="submitBeforeGenerate"
|
||||
:preventDefaultMoveToGenerate="true"
|
||||
@go-to-generate-document="submitBeforeGenerate"
|
||||
>
|
||||
<template v-slot:title>
|
||||
<label class="col-sm-4 col-form-label">{{ $t('evaluation_generate_a_document') }}</label>
|
||||
@ -109,6 +110,7 @@ import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js';
|
||||
import { mapGetters, mapState } from 'vuex';
|
||||
import PickTemplate from 'ChillDocGeneratorAssets/vuejs/_components/PickTemplate.vue';
|
||||
import {buildLink} from 'ChillDocGeneratorAssets/lib/document-generator';
|
||||
|
||||
const i18n = {
|
||||
messages: {
|
||||
@ -123,7 +125,7 @@ const i18n = {
|
||||
evaluation_public_comment: "Note publique",
|
||||
evaluation_comment_placeholder: "Commencez à écrire ...",
|
||||
evaluation_generate_a_document: "Générer un document",
|
||||
evaluation_choose_a_template: "Choisir un gabarit",
|
||||
evaluation_choose_a_template: "Choisir un modèle",
|
||||
evaluation_add_a_document: "Ajouter un document",
|
||||
evaluation_add: "Ajouter une évaluation",
|
||||
Documents: "Documents",
|
||||
@ -207,10 +209,11 @@ export default {
|
||||
return `/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent(
|
||||
window.location.pathname + window.location.search + window.location.hash);
|
||||
},
|
||||
submitBeforeGenerate() {
|
||||
submitBeforeGenerate({template}) {
|
||||
const callback = (data) => {
|
||||
let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id;
|
||||
return Promise.resolve({entityId: evaluationId});
|
||||
|
||||
window.location.assign(buildLink(template, evaluationId, 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation'));
|
||||
};
|
||||
|
||||
return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; });
|
||||
|
@ -195,18 +195,18 @@ export default {
|
||||
|
||||
setTimeout(function() {
|
||||
if (query === "") {
|
||||
this.loadSuggestions([]);
|
||||
return;
|
||||
this.loadSuggestions([]);
|
||||
return;
|
||||
}
|
||||
if (query === this.search.query) {
|
||||
if (this.currentSearchQueryController !== undefined) {
|
||||
this.currentSearchQueryController.abort()
|
||||
}
|
||||
this.currentSearchQueryController = new AbortController();
|
||||
searchEntities({ query, options: this.options }, this.currentSearchQueryController)
|
||||
if (this.currentSearchQueryController !== undefined) {
|
||||
this.currentSearchQueryController.abort()
|
||||
}
|
||||
this.currentSearchQueryController = new AbortController();
|
||||
searchEntities({ query, options: this.options }, this.currentSearchQueryController)
|
||||
.then(suggested => new Promise((resolve, reject) => {
|
||||
this.loadSuggestions(suggested.results);
|
||||
resolve();
|
||||
this.loadSuggestions(suggested.results);
|
||||
resolve();
|
||||
}));
|
||||
}
|
||||
}.bind(this), query.length > 3 ? 300 : 700);
|
||||
@ -241,13 +241,12 @@ export default {
|
||||
return item.result.type + item.result.id;
|
||||
},
|
||||
addPriorSuggestion() {
|
||||
//console.log('addPriorSuggestion', this.hasPriorSuggestion);
|
||||
// console.log('prior suggestion', this.priorSuggestion);
|
||||
if (this.hasPriorSuggestion) {
|
||||
console.log('addPriorSuggestion',);
|
||||
// console.log('addPriorSuggestion',);
|
||||
this.suggested.unshift(this.priorSuggestion);
|
||||
this.selected.unshift(this.priorSuggestion);
|
||||
|
||||
console.log('reset priorSuggestion');
|
||||
this.newPriorSuggestion(null);
|
||||
}
|
||||
},
|
||||
@ -260,13 +259,14 @@ export default {
|
||||
result: entity
|
||||
}
|
||||
this.search.priorSuggestion = suggestion;
|
||||
console.log('search priorSuggestion', this.search.priorSuggestion);
|
||||
// console.log('search priorSuggestion', this.search.priorSuggestion);
|
||||
this.addPriorSuggestion(suggestion);
|
||||
} else {
|
||||
this.search.priorSuggestion = {};
|
||||
}
|
||||
},
|
||||
saveFormOnTheFly({ type, data }) {
|
||||
console.log('saveFormOnTheFly from addPersons, type', type, ', data', data);
|
||||
// console.log('saveFormOnTheFly from addPersons, type', type, ', data', data);
|
||||
if (type === 'person') {
|
||||
makeFetch('POST', '/api/1.0/person/person.json', data)
|
||||
.then(response => {
|
||||
@ -276,10 +276,10 @@ export default {
|
||||
.catch((error) => {
|
||||
if (error.name === 'ValidationException') {
|
||||
for (let v of error.violations) {
|
||||
this.$toast.open({message: v });
|
||||
this.$toast.open({message: v });
|
||||
}
|
||||
} else {
|
||||
this.$toast.open({message: 'An error occurred'});
|
||||
this.$toast.open({message: 'An error occurred'});
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -292,13 +292,14 @@ export default {
|
||||
.catch((error) => {
|
||||
if (error.name === 'ValidationException') {
|
||||
for (let v of error.violations) {
|
||||
this.$toast.open({message: v });
|
||||
this.$toast.open({message: v });
|
||||
}
|
||||
} else {
|
||||
this.$toast.open({message: 'An error occurred'});
|
||||
this.$toast.open({message: 'An error occurred'});
|
||||
}
|
||||
})
|
||||
}
|
||||
this.canCloseOnTheFlyModal = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -23,6 +23,8 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_new', { 'id': accompanyingCourse.id }) }}"
|
||||
|
@ -41,7 +41,7 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="wh-col">
|
||||
<div class="wh-col" style="align-items: center;">
|
||||
{% if chill_accompanying_periods.fields.user == 'visible' %}
|
||||
{# the tags `data-referrer-text` is used by module `@ChillPerson/mod/AccompanyingPeriod/setReferrer.js` #}
|
||||
{% if period.user %}
|
||||
|
@ -0,0 +1,38 @@
|
||||
{% extends '@ChillPerson/Household/layout.html.twig' %}
|
||||
|
||||
{% set activeRouteKey = 'chill_person_household_composition_index' %}
|
||||
|
||||
{% block title 'Remove household composition'|trans %}
|
||||
|
||||
{% block display_content %}
|
||||
<p>{{ 'Concerns household n°%id%'|trans({ '%id%' : household.id } ) }}</p>
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
<h3>{{ 'Composition'|trans }}:</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{% for m in household.members %}
|
||||
<span class="wl-item">
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
action: 'show', displayBadge: true,
|
||||
targetEntity: { name: 'person', id: m.person.id },
|
||||
buttonText: m.person|chill_entity_render_string,
|
||||
isDead: m.person.deathdate is not null
|
||||
} %}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ include('@ChillMain/Util/confirmation_template.html.twig',
|
||||
{
|
||||
'title' : 'Remove household composition'|trans,
|
||||
'confirm_question' : 'Are you sure you want to remove this composition?'|trans,
|
||||
'display_content' : block('display_content'),
|
||||
'cancel_route' : 'chill_person_household_composition_index',
|
||||
'cancel_parameters' : { 'composition_id' : composition.id, 'id' : household.id },
|
||||
'form' : form
|
||||
} ) }}
|
||||
{% endblock %}
|
@ -22,7 +22,7 @@
|
||||
<h3>{{ c.householdCompositionType.label|localize_translatable_string }}</h3>
|
||||
<p>{{ 'household_composition.numberOfChildren'|trans }}: {{ c.numberOfChildren }}</p>
|
||||
</div>
|
||||
<div class="item-col">{{ 'household_composition.Since'|trans({'startDate': c.startDate}) }}</div>
|
||||
<div class="item-col" style="justify-content: flex-end">{{ 'household_composition.Since'|trans({'startDate': c.startDate}) }}</div>
|
||||
</div>
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
@ -45,7 +45,10 @@
|
||||
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id, 'edit': c.id}) }}" class="btn btn-edit"></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id, 'edit': c.id}) }}" class="btn btn-edit"></a>
|
||||
<a href="{{ chill_path_add_return_path('chill_person_household_composition_delete', {'composition_id': c.id,
|
||||
'household_id': c.household.id}) }}"
|
||||
class="btn btn-delete"
|
||||
title="{{ 'Delete'|trans }}"></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -57,8 +60,8 @@
|
||||
{{ form_widget(form) }}
|
||||
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id}) }}">{{ 'Cancel'|trans }}</a>
|
||||
<li class="cancel" style="margin-right: auto;">
|
||||
<a class="btn btn-cancel" href="{{ path('chill_person_household_composition_index', {'id': c.household.id}) }}">{{ 'Cancel'|trans }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<button type="submit" class="btn btn-create">{{ 'Save'|trans }}</button>
|
||||
|
@ -85,9 +85,11 @@
|
||||
</div>
|
||||
<div id="maritalStatusDate">
|
||||
{{ form_row(form.maritalStatusDate, { 'label' : 'Date of last marital status change'} ) }}
|
||||
{{ form_row(form.maritalStatusComment, { 'label' : 'Comment on the marital status'} ) }}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
<div id="maritalStatusComment">
|
||||
{{ form_row(form.maritalStatusComment, { 'label' : 'Comment on the marital status'} ) }}
|
||||
</div>
|
||||
</fieldset>
|
||||
{%- endif -%}
|
||||
|
||||
|
@ -99,11 +99,12 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
{% if acp.requestorPerson == person %}
|
||||
{% if acp.requestoranonymous == false and acp.requestorPerson == person %}
|
||||
<span class="as-requestor badge bg-info" title="{{ 'Requestor'|trans|e('html_attr') }}">
|
||||
{{ 'Requestor'|trans({'gender': person.gender}) }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if acp.emergency %}
|
||||
<span class="badge rounded-pill bg-danger">{{- 'Emergency'|trans|upper -}}</span>
|
||||
{% endif %}
|
||||
@ -178,47 +179,49 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if acp.requestoranonymous == false %}
|
||||
{% if (acp.requestorPerson is not null and acp.requestorPerson.id != person.id) or acp.requestorThirdParty is not null %}
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
<h3>
|
||||
{% if acp.requestorPerson is not null %}
|
||||
{{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }}
|
||||
{% else %}
|
||||
{{ 'Requestor'|trans({'gender': 'other'})}}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{% if acp.requestorThirdParty is not null %}
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
targetEntity: { name: 'thirdparty', id: acp.requestorThirdParty.id },
|
||||
action: 'show',
|
||||
displayBadge: true,
|
||||
buttonText: acp.requestorThirdParty|chill_entity_render_string
|
||||
} %}
|
||||
{% else %}
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
targetEntity: { name: 'person', id: acp.requestorPerson.id },
|
||||
action: 'show',
|
||||
displayBadge: true,
|
||||
buttonText: acp.requestorPerson|chill_entity_render_string,
|
||||
isDead: acp.requestorPerson.deathdate is not null
|
||||
} %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions record_actions_column">
|
||||
<li>
|
||||
<a href="{{ path('chill_person_accompanying_course_index', { 'accompanying_period_id': acp.id }) }}"
|
||||
class="btn btn-sm btn-outline-primary" title="{{ 'See accompanying period'|trans }}">
|
||||
class="btn btn-sm btn-outline-primary" title="{{ 'See accompanying period'|trans }}">
|
||||
<i class="fa fa-random fa-fw"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% if (acp.requestorPerson is not null and acp.requestorPerson.id != person.id) or acp.requestorThirdParty is not null %}
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title">
|
||||
<h3>
|
||||
{% if acp.requestorPerson is not null %}
|
||||
{{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }}
|
||||
{% else %}
|
||||
{{ 'Requestor'|trans({'gender': 'other'})}}
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
{% if acp.requestorThirdParty is not null %}
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
targetEntity: { name: 'thirdparty', id: acp.requestorThirdParty.id },
|
||||
action: 'show',
|
||||
displayBadge: true,
|
||||
buttonText: acp.requestorThirdParty|chill_entity_render_string
|
||||
} %}
|
||||
{% else %}
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
targetEntity: { name: 'person', id: acp.requestorPerson.id },
|
||||
action: 'show',
|
||||
displayBadge: true,
|
||||
buttonText: acp.requestorPerson|chill_entity_render_string,
|
||||
isDead: acp.requestorPerson.deathdate is not null
|
||||
} %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -23,18 +23,27 @@
|
||||
{% for resource in personResources %}
|
||||
<div class="item-bloc">
|
||||
<div class="item-row">
|
||||
<div class="item-col">
|
||||
<div class="item-col" style="width: 50%">
|
||||
{% if resource.person is not null %}
|
||||
<div class="denomination h3">
|
||||
<span class="name">{{ resource.person }}</span>
|
||||
<span class="badge rounded-pill bg-person">{{ 'person'|trans|capitalize }}</span>
|
||||
<span>
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
action: 'show', displayBadge: true,
|
||||
targetEntity: { name: 'person', id: resource.person.id },
|
||||
buttonText: resource.person|chill_entity_render_string,
|
||||
isDead: resource.person.deathdate is not null
|
||||
} %}
|
||||
</span>
|
||||
</div>
|
||||
{% elseif resource.thirdparty is not null %}
|
||||
<div class="denomination h3">
|
||||
<span class="name">{{ resource.thirdparty }}</span>
|
||||
<span class="badge rounded-pill bg-thirdparty">
|
||||
{{ 'thirdparty'|trans|capitalize }}
|
||||
<i class="fa fa-fw fa-user-md"></i>
|
||||
<span>
|
||||
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
|
||||
action: 'show', displayBadge: true,
|
||||
targetEntity: { name: 'thirdparty', id: resource.thirdparty.id },
|
||||
buttonText: resource.thirdParty|chill_entity_render_string,
|
||||
parent: resource.thirdparty.parent
|
||||
} %}
|
||||
</span>
|
||||
</div>
|
||||
{% else %}
|
||||
@ -43,7 +52,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<div class="item-col" style="justify-content: flex-end; ">
|
||||
{% if resource.kind %}
|
||||
<span>{{ resource.kind.title.fr|capitalize }}</span>
|
||||
{% endif %}
|
||||
@ -56,7 +65,7 @@
|
||||
</section>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_PERSON_UPDATE', resource.person) %}
|
||||
{% if is_granted('CHILL_PERSON_UPDATE', resource.personOwner) %}
|
||||
<div class="item-row separator">
|
||||
<div class="item-col">
|
||||
<ul class="record_actions">
|
||||
|
@ -11,17 +11,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\PersonBundle\Security\Authorization;
|
||||
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use UnexpectedValueException;
|
||||
use function get_class;
|
||||
use function in_array;
|
||||
|
||||
class AccompanyingPeriodWorkVoter extends Voter
|
||||
{
|
||||
public const CREATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_CREATE';
|
||||
|
||||
public const SEE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE';
|
||||
|
||||
public const UPDATE = 'CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE';
|
||||
|
||||
private Security $security;
|
||||
|
||||
public function __construct(Security $security)
|
||||
@ -31,8 +37,14 @@ class AccompanyingPeriodWorkVoter extends Voter
|
||||
|
||||
protected function supports($attribute, $subject): bool
|
||||
{
|
||||
return $subject instanceof AccompanyingPeriodWork
|
||||
&& in_array($attribute, $this->getRoles(), true);
|
||||
return
|
||||
(
|
||||
$subject instanceof AccompanyingPeriodWork
|
||||
&& in_array($attribute, $this->getRoles(), true)
|
||||
) || (
|
||||
$subject instanceof AccompanyingPeriod
|
||||
&& in_array($attribute, [self::SEE, self::CREATE], true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,13 +53,28 @@ class AccompanyingPeriodWorkVoter extends Voter
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
|
||||
{
|
||||
switch ($attribute) {
|
||||
case self::SEE:
|
||||
return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod());
|
||||
if ($subject instanceof AccompanyingPeriodWork) {
|
||||
switch ($attribute) {
|
||||
case self::SEE:
|
||||
return $this->security->isGranted(AccompanyingPeriodVoter::SEE_DETAILS, $subject->getAccompanyingPeriod());
|
||||
|
||||
default:
|
||||
throw new UnexpectedValueException("attribute {$attribute} is not supported");
|
||||
default:
|
||||
throw new UnexpectedValueException("attribute {$attribute} is not supported");
|
||||
}
|
||||
} elseif ($subject instanceof AccompanyingPeriod) {
|
||||
switch ($attribute) {
|
||||
case self::SEE:
|
||||
return $this->security->isGranted(AccompanyingPeriodVoter::SEE_DETAILS, $subject);
|
||||
|
||||
default:
|
||||
throw new UnexpectedValueException(sprintf(
|
||||
"attribute {$attribute} is not supported on instance %s",
|
||||
AccompanyingPeriod::class
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnexpectedValueException(sprintf("attribute {$attribute} on instance %s is not supported", get_class($subject)));
|
||||
}
|
||||
|
||||
private function getRoles(): array
|
||||
|
@ -109,7 +109,6 @@ final class AccompanyingPeriodConfidentialTest extends WebTestCase
|
||||
$user = new stdClass();
|
||||
$user->id = 0;
|
||||
$user->type = 'user';
|
||||
dump($user);
|
||||
|
||||
$this->client->request(
|
||||
Request::METHOD_PATCH,
|
||||
|
@ -36,7 +36,7 @@ class HouseholdMembershipSequentialValidator extends ConstraintValidator
|
||||
public function validate($person, Constraint $constraint)
|
||||
{
|
||||
if (!$person instanceof Person) {
|
||||
throw new UnexpectedTypeException($constraint, Person::class);
|
||||
throw new UnexpectedTypeException($person, Person::class);
|
||||
}
|
||||
|
||||
$participations = $person->getHouseholdParticipationsShareHousehold();
|
||||
|
@ -148,7 +148,7 @@ and %number% other: '{0} et aucun autre| {1} et une autre |]1, Inf] et %number%
|
||||
'Last opening since %last_opening%': 'Dernière ouverture le %last_opening%.'
|
||||
'Person accompanying period - %name%': 'Historique du dossier - %name%'
|
||||
'Opening date': 'Date d''ouverture'
|
||||
'Closing date': 'Date de fermeture'
|
||||
'Closing date': 'Date de clôture'
|
||||
'Period opened': 'Période ouverte'
|
||||
'Close accompanying period': 'Clôre la période'
|
||||
'Open accompanying period': 'Ouvrir la période'
|
||||
@ -211,7 +211,7 @@ No requestor: Pas de demandeur
|
||||
No resources: "Pas d'interlocuteurs privilégiés"
|
||||
Persons associated: Usagers concernés
|
||||
Referrer: Référent
|
||||
Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur appartenance à un ménage dès que possible.
|
||||
Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur ménage dès que possible.
|
||||
Add to household now: Ajouter à un ménage
|
||||
Any resource for this accompanying course: Aucun interlocuteur privilégié pour ce parcours
|
||||
course.draft: Brouillon
|
||||
@ -237,6 +237,7 @@ no comment found: "Aucun commentaire"
|
||||
Select a type: "Choisissez un type"
|
||||
Select a person: "Choisissez un usager"
|
||||
Select a thirdparty: "Choisissez un tiers"
|
||||
Contact person: "Personne de contact"
|
||||
|
||||
|
||||
# pickAPersonType
|
||||
@ -486,6 +487,10 @@ Household summary: Résumé du ménage
|
||||
Edit household address: Modifier l'adresse du ménage
|
||||
Show household: Voir le ménage
|
||||
Back to household: Revenir au ménage
|
||||
Remove household composition: Supprimer composition familiale
|
||||
Are you sure you want to remove this composition?: Etes-vous sûr de vouloir supprimer cette composition familiale ?
|
||||
Concerns household n°%id%: Concerne le ménage n°%id%
|
||||
Composition: Composition
|
||||
|
||||
# accompanying course work
|
||||
Accompanying Course Actions: Actions d'accompagnements
|
||||
@ -552,7 +557,7 @@ household_composition:
|
||||
numberOfChildren: Nombre d'enfants mineurs au sein du ménage
|
||||
Household composition: Composition du ménage
|
||||
Composition added: Information sur la composition familiale ajoutée
|
||||
Currently no composition: Aucune composition famiale renseignée.
|
||||
Currently no composition: Aucune composition familiale renseignée.
|
||||
Add a composition: Ajouter une composition familiale
|
||||
Update composition: Modifier la composition familiale
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
'Closing date is not valid': 'La date de fermeture n''est pas valide'
|
||||
'Closing date can not be null': 'La date de fermeture ne peut être nulle'
|
||||
The date of closing is before the date of opening: La période de fermeture est avant la période d'ouverture
|
||||
The closing date must be later than the date of creation: La date de clôture doit être postérieure à la date de création du parcours
|
||||
The birthdate must be before %date%: La date de naissance doit être avant le %date%
|
||||
'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33123456789': 'Numéro de téléphone invalide: il doit commencer par le préfixe international précédé de "+", ne comporter que des chiffres et faire moins de 20 caractères. Ex: +31623456789'
|
||||
'Invalid phone number: it should begin with the international prefix starting with "+", hold only digits and be smaller than 20 characters. Ex: +33623456789': 'Numéro de téléphone invalide: il doit commencer par le préfixe international précédé de "+", ne comporter que des chiffres et faire moins de 20 caractères. Ex: +33623456789'
|
||||
|
@ -17,6 +17,7 @@ use DateTimeImmutable;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Serializer\Annotation as Serializer;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
@ -48,6 +49,7 @@ class SingleTask extends AbstractTask
|
||||
*
|
||||
* @ORM\Column(name="end_date", type="date", nullable=true)
|
||||
* @Assert\Date
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private $endDate;
|
||||
|
||||
@ -57,6 +59,7 @@ class SingleTask extends AbstractTask
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
private $id;
|
||||
|
||||
@ -73,6 +76,7 @@ class SingleTask extends AbstractTask
|
||||
* @var DateTime
|
||||
*
|
||||
* @ORM\Column(name="start_date", type="date", nullable=true)
|
||||
* @Serializer\Groups({"read"})
|
||||
* @Assert\Date
|
||||
*
|
||||
* @Assert\Expression(
|
||||
@ -102,6 +106,7 @@ class SingleTask extends AbstractTask
|
||||
* and this.getEndDate() === null
|
||||
*
|
||||
* @ORM\Column(name="warning_interval", type="dateinterval", nullable=true)
|
||||
* @Serializer\Groups({"read"})
|
||||
*
|
||||
* @Assert\Expression(
|
||||
* "!(value != null and this.getEndDate() == null)",
|
||||
@ -162,6 +167,7 @@ class SingleTask extends AbstractTask
|
||||
* Return null if warningDate or endDate is null
|
||||
*
|
||||
* @return DateTimeImmutable
|
||||
* @Serializer\Groups({"read"})
|
||||
*/
|
||||
public function getWarningDate()
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user