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

This commit is contained in:
Julien Fastré 2022-03-21 11:53:21 +01:00
commit 2fda5b751b
47 changed files with 582 additions and 236 deletions

View File

@ -12,8 +12,10 @@ and this project adheres to
<!-- write down unreleased development here -->
* [person] AccompanyingPeriodWork: add referrers to work, add doctrine event listener to add logged user to referrers collection and display a referrers list in work list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/502)
* [person] AccompanyingPeriodWorkEvaluation: fix circular reference when serialising (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/495)
* [person] order accompanying period by opening date in search persons, person and household period lists (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/493)
* [parcours] autosave of the pinned comment for draft accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/477)
* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472)
* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472)
* [person] Add url in accompanying period work evaluations entity and form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/476)
* [person] Add document generation in admin and in person/{id}/document (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/464)
@ -33,13 +35,20 @@ and this project adheres to
* [confidential] Fix position of toggle button so it does not cover text nor fall outside of box (no issue)
* [parcours] Fix edit of both thirdparty and contact name (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/474)
* [template] do not list inactive templates (for doc generator)
* [household] bugfix if position of member is null, renderbox no longer throws an error (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/480)
* [parcours] location cannot be removed if linked to a user (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/478)
* [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490)
* [person] Add link to current household in person banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/484)
* [address] person badge in address history changed to open OnTheFly with all person info (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/489)
* [person] Change 'personne' with 'usager' and '&' with 'ET' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/499)
* [thirdparty] Add parameter condition to display centers or not (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/500)
* [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496)
* [person_resource] separate create page created to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/504)
* [contact] add contact button color changed plus the pipe at the side removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/506)
* [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505)
* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486)
* [thirdparty] Thirdparty view page, english text translated (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/534)
* [social_action] Translation changed in evaluation section (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/512)
## Test releases

View File

@ -46,7 +46,7 @@
{% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'person'} %}
{% if is_granted('CHILL_ACTIVITY_CREATE', person) %}
{% if is_granted('CHILL_ACTIVITY_CREATE_PERSON', person) %}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'accompanying_period_id': accompanying_course_id}) }}"

View File

@ -26,6 +26,7 @@ use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use function count;
use function strlen;
class LoadPostalCodesCommand extends Command
{
@ -118,6 +119,13 @@ class LoadPostalCodesCommand extends Command
private function addPostalCode($row, OutputInterface $output)
{
if ('FR' === $row[2] && strlen($row[0]) === 4) {
// CP in FRANCE are on 5 digit
// For CP starting with a zero, the starting zero can be remove if stored as number in a csv
// add a zero if CP from FR and on 4 digit
$row[0] = '0' . $row[0];
}
if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
$output->writeln('handling row: ' . $row[0] . ' | ' . $row[1] . ' | ' . $row[2]);
}

View File

@ -40,6 +40,7 @@ class UserJob
* @var array|string[]A
* @ORM\Column(name="label", type="json")
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
*/
protected array $label = [];

View File

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

View File

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

View File

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

View File

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

View File

@ -42,9 +42,6 @@
<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>
@ -102,4 +99,4 @@ export default {
<style scoped>
</style>
</style>

View File

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

View File

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

View File

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

View File

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

View File

@ -16,10 +16,10 @@ use libphonenumber\PhoneNumber;
use libphonenumber\PhoneNumberUtil;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
class PhonenumberNormalizer implements NormalizerInterface, DenormalizerInterface
class PhonenumberNormalizer implements ContextAwareNormalizerInterface, DenormalizerInterface
{
private string $defaultCarrierCode;
@ -53,6 +53,10 @@ class PhonenumberNormalizer implements NormalizerInterface, DenormalizerInterfac
public function normalize($object, ?string $format = null, array $context = []): string
{
if ('docgen' === $format && null === $object) {
return '';
}
return $this->phoneNumberUtil->formatOutOfCountryCallingNumber($object, $this->defaultCarrierCode);
}
@ -61,8 +65,18 @@ class PhonenumberNormalizer implements NormalizerInterface, DenormalizerInterfac
return 'libphonenumber\PhoneNumber' === $type;
}
public function supportsNormalization($data, ?string $format = null)
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{
return $data instanceof PhoneNumber;
if ($data instanceof PhoneNumber && 'json' === $format) {
return true;
}
if ('docgen' === $format && (
$data instanceof PhoneNumber || PhoneNumber::class === ($context['docgen:expects'] ?? null)
)) {
return true;
}
return false;
}
}

View File

@ -222,6 +222,10 @@ class AccompanyingPeriodController extends AbstractController
$accompanyingPeriodsRaw = $this->accompanyingPeriodACLAwareRepository
->findByPerson($person, AccompanyingPeriodVoter::SEE);
usort($accompanyingPeriodsRaw, static function ($a, $b) {
return $b->getOpeningDate() > $a->getOpeningDate();
});
// filter visible or not visible
$accompanyingPeriods = array_filter($accompanyingPeriodsRaw, function (AccompanyingPeriod $ap) {
return $this->isGranted(AccompanyingPeriodVoter::SEE, $ap);

View File

@ -134,7 +134,7 @@ class HouseholdCompositionController extends AbstractController
public function index(Household $household, Request $request): Response
{
if (!$this->security->isGranted(HouseholdVoter::SEE, $household)) {
throw new AccessDeniedException('not allowed to edit an household');
throw new AccessDeniedException('not allowed to edit a household');
}
$count = $this->householdCompositionRepository->countByHousehold($household);
@ -146,6 +146,20 @@ class HouseholdCompositionController extends AbstractController
$paginator->getCurrentPageFirstItemNumber()
);
return new Response($this->engine->render(
'@ChillPerson/HouseholdComposition/index.html.twig',
[
'household' => $household,
'compositions' => $compositions,
]
));
}
/**
* @Route("/{_locale}/person/household/{id}/composition/new", name="chill_person_household_composition_new")
*/
public function newAction(Household $household, Request $request): Response
{
if ($this->security->isGranted(HouseholdVoter::EDIT, $household)) {
$isEdit = $request->query->has('edit');
@ -195,10 +209,9 @@ class HouseholdCompositionController extends AbstractController
}
return new Response($this->engine->render(
'@ChillPerson/HouseholdComposition/index.html.twig',
'@ChillPerson/HouseholdComposition/create.html.twig',
[
'household' => $household,
'compositions' => $compositions,
'form' => isset($form) ? $form->createView() : null,
'isPosted' => isset($form) ? $form->isSubmitted() : false,
'editId' => $request->query->getInt('edit', -1),

View File

@ -78,6 +78,10 @@ class HouseholdController extends AbstractController
}
}
usort($accompanyingPeriods, static function ($a, $b) {
return $b->getOpeningDate() > $a->getOpeningDate();
});
$oldMembers = $household->getNonCurrentMembers();
$accompanyingPeriodsOld = [];

View File

@ -133,6 +133,19 @@ final class PersonResourceController extends AbstractController
$personResources = [];
$personResources = $this->personResourceRepository->findBy(['personOwner' => $personOwner->getId()]);
return $this->render(
'ChillPersonBundle:PersonResource:list.html.twig',
[
'person' => $personOwner,
'personResources' => $personResources,
]
);
}
public function newAction(Request $request, $person_id)
{
$personOwner = $this->personRepository->find($person_id);
$form = $this->createForm(PersonResourceType::class);
$form->handleRequest($request);
@ -165,11 +178,10 @@ final class PersonResourceController extends AbstractController
}
return $this->render(
'ChillPersonBundle:PersonResource:list.html.twig',
'ChillPersonBundle:PersonResource:create.html.twig',
[
'person' => $personOwner,
'personResources' => $personResources,
'form' => $form->createView(),
'person' => $personOwner,
]
);
}

View File

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

View File

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

View File

@ -124,6 +124,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Serializer\Groups({"read", "docgen:read"})
* @Serializer\Groups({"write"})
* @Serializer\Groups({"accompanying_period_work_evaluation:create"})
*/
private ?DateTimeImmutable $maxDate = null;

View File

@ -109,6 +109,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class,
* mappedBy="person",
* cascade={"persist", "remove", "merge", "detach"})
* @ORM\OrderBy({"startDate": "DESC"})
*/
private $accompanyingPeriodParticipations;

View File

@ -68,12 +68,14 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
}
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 History'), [
'route' => 'chill_person_accompanying_course_history',
'routeParameters' => [
'accompanying_period_id' => $period->getId(),
], ])
->setExtras(['order' => 30]);
*/
$menu->addChild($this->translator->trans('Accompanying Course Comment'), [
'route' => 'chill_person_accompanying_period_comment_list',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -352,7 +352,7 @@ const i18n = {
add_thirdparties: "Ajouter des tiers",
choose_thirdparties: "Choisir des tiers",
fix_these_errors: "Veuillez corriger les erreurs suivantes :",
available_evaluations_text: "Évaluations disponibles pour ajout :",
available_evaluations_text: "Documents disponibles pour ajout :",
no_evaluations_available: "Aucune évaluation disponible",
no_goals_available: "Aucun objectif disponible",
referrers: "Agents traitants",

View File

@ -70,7 +70,7 @@
<div class="input-group input-group-lg mb-3">
<div>
<input
class="form-control form-control-lg"
class="form-control"
style="font-weight: bold;"
type="text"
:value="d.title"

View File

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

View File

@ -0,0 +1,26 @@
{% extends '@ChillPerson/Household/layout.html.twig' %}
{% block title 'household_composition.create'|trans %}
{% block block_post_menu %}
<div class="post-menu"></div>
{% endblock %}
{% block content %}
<h1>{{ 'household_composition.Create'|trans }}</h1>
<div>
{{ form_start(form) }}
{{ form_widget(form) }}
<ul class="record_actions">
<li>
<button type="submit" class="btn btn-create">{{ 'Save'|trans }}</button>
</li>
</ul>
{{ form_end(form) }}
</div>
{% endblock %}

View File

@ -15,7 +15,7 @@
{% else %}
<div class="flex-table">
{% for c in compositions %}
{% if c.id != editId %}
{# {% if c.id != editId %} #}
<div class="item-bloc">
<div class="item-row">
<div class="item-col">
@ -42,7 +42,7 @@
<div class="item-row">
<ul class="record_actions">
<li>
<a href="{{ path('chill_person_household_composition_index', {'id': c.household.id, 'edit': c.id}) }}" class="btn btn-edit"></a>
<a href="{{ path('chill_person_household_composition_new', {'id': c.household.id, 'edit': c.id}) }}" class="btn btn-edit"></a>
</li>
<li>
<a href="{{ chill_path_add_return_path('chill_person_household_composition_delete', {'composition_id': c.id,
@ -54,45 +54,17 @@
</div>
{% endif %}
</div>
{% else %}
{{ form_start(form) }}
{{ form_widget(form) }}
<ul class="record_actions">
<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>
</li>
</ul>
{{ form_end(form) }}
{% endif %}
{% endfor %}
</div>
{% endif %}
<div id="collapseForm" class="{% if not isPosted %}collapse{% endif %}">
{{ form_start(form) }}
{{ form_widget(form) }}
<ul class="record_actions">
<li>
<button type="submit" class="btn btn-create">{{ 'Save'|trans }}</button>
</li>
</ul>
{{ form_end(form) }}
</div>
{% if editId == -1 %}
{% if is_granted('CHILL_PERSON_HOUSEHOLD_EDIT', household) %}
<ul class="record_actions sticky-form-buttons">
<li>
<button class="btn btn-primary btn-create change-icon" type="button" data-bs-toggle="collapse" data-bs-target="#collapseForm" aria-expanded="false" aria-controls="collapseForm">
{{ 'Create'|trans }}
</button>
</li>
<li>
<a href="{{ chill_path_add_return_path('chill_person_household_composition_new', {'id': household.id,}) }}"
class="btn btn-new"
title="{{ 'Create'|trans }}">{{ 'Create'|trans }}</a>
</li>
</ul>
{% endif %}
</div>

View File

@ -1,15 +1,25 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set activeRouteKey = 'chill_person_resource_list' %}
{% block title %}{{ 'Person resources'|trans|capitalize ~ ' ' ~ person|chill_entity_render_string }}{% endblock %}
{% block js %}
{{ encore_entry_script_tags('page_person_resource_showhide_input') }}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('page_person_resource_showhide_input') }}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}
{% block personcontent %}
<h1 style="margin-bottom: 2rem;">{{ 'Add a person resource'|trans }}</h1>
<div class="col-md col-xxl">
<div id="collapseForm" class="{% if not form.vars.submitted %}collapse{% endif %}">
<h3 style="margin-bottom: 2rem;">{{ 'Add a person resource'|trans }}</h3>
{% include "@ChillPerson/PersonResource/form.html.twig" %}
</div>
<ul class="record_actions sticky-form-buttons">
<li>
<button class="btn btn-primary btn-create change-icon" type="button" data-bs-toggle="collapse" data-bs-target="#collapseForm" aria-expanded="false" aria-controls="collapseForm">
{{ 'Add a person resource'|trans }}
</button>
</li>
</ul>
</div>
</div>
{% endblock %}

View File

@ -85,8 +85,14 @@
<p class="chill-no-data-statement">{{ 'There are no available resources'|trans }}</p>
{% endif %}
<h1 style="margin-bottom: 2rem;">{{ 'Add a person resource'|trans }}</h1>
{% include "@ChillPerson/PersonResource/create.html.twig" %}
{% if is_granted('CHILL_PERSON_UPDATE', person) %}
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ chill_path_add_return_path('chill_person_resource_new', {'person_id': person.id,}) }}"
class="btn btn-new"
title="{{ 'Create'|trans }}">{{ 'Create'|trans }}</a>
</li>
</ul>
{% endif %}
{% endblock %}

View File

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

View File

@ -1,5 +1,10 @@
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
<div class="flex-table accompanying_course_work-list">
<div class="item-bloc evaluation-item bg-chill-llight-gray">
<div class="item-row mb-2">
<h1>{{ doc.title }}</h1>
</div>
<div class="item-row mb-2">
<h2 class="badge-title">
<span class="title_label"></span>
@ -20,6 +25,24 @@
</span>
</h2>
</div>
<div class="item-row mb-2">
<div class="item-col" style="width: 17%;">
<h4 class="title_label">
{{ 'Participants'|trans }}
</h4>
</div>
<div class="item-col list">
{% for p in evaluation.accompanyingPeriodWork.persons %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: p.id },
action: 'show',
displayBadge: true,
buttonText: p|chill_entity_render_string,
isDead: p.deathdate is not null
} %}
{% endfor %}
</div>
</div>
<div class="item-row column">
<table class="obj-res-eval my-3" style="font-size: 110% !important;">
<thead>
@ -92,12 +115,19 @@
</div>
{% if display_action is defined and display_action == true %}
{# TODO add acl #}
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork) %}
<ul class="record_actions">
<li>{{ m.download_button(doc.storedObject, doc.title) }}</li>
{% if chill_document_is_editable(doc.storedObject) %}
<li>
{{ doc.storedObject|chill_document_edit_button }}
</li>
{% endif %}
<li>
<a class="btn btn-show" href="{{ path('chill_person_accompanying_period_work_edit', {'id': evaluation.accompanyingPeriodWork.id}) }}">
{{ 'Show'|trans }}
</a>
</li>
</ul>
{% endif %}
{% endif %}

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
@ -46,7 +47,10 @@ class AccompanyingPeriodWorkEvaluationNormalizer implements ContextAwareNormaliz
{
$initial = $this->normalizer->normalize($object, $format, array_merge(
$context,
[self::IGNORE_EVALUATION => spl_object_hash($object)]
[self::IGNORE_EVALUATION => spl_object_hash($object)],
[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => static function ($object, $format, $context) {
return $object->getId();
}]
));
// due to bug: https://api-platform.com/docs/core/serialization/#collection-relation

View File

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

View File

@ -61,8 +61,8 @@ class AccompanyingPeriodWorkEvaluationDocumentWorkflowHandler implements EntityW
return $this->translator->trans(
'workflow.Doc for evaluation (n°%eval%)',
['%eval%' => $entityWorkflow->getRelatedEntityId()]
) . ' - ' . $this->translatableStringHelper->localize($doc->getAccompanyingPeriodWorkEvaluation()
->getEvaluation()->getTitle());
) . ' (' . $this->translatableStringHelper->localize($doc->getAccompanyingPeriodWorkEvaluation()
->getEvaluation()->getTitle()) . ') ' . $doc->getTitle();
}
public function getRelatedEntity(EntityWorkflow $entityWorkflow): ?AccompanyingPeriodWorkEvaluationDocument

View File

@ -59,6 +59,10 @@ chill_person_resource_list:
path: /{_locale}/person/{person_id}/resources/list
controller: Chill\PersonBundle\Controller\PersonResourceController::listAction
chill_person_resource_new:
path: /{_locale}/person/{person_id}/resources/new
controller: Chill\PersonBundle\Controller\PersonResourceController::newAction
chill_person_resource_edit:
path: /{_locale}/person/{person_id}/resources/{resource_id}/edit
controller: Chill\PersonBundle\Controller\PersonResourceController::editAction
@ -91,11 +95,6 @@ chill_person_address_update:
chill_person_timeline:
path: /{_locale}/person/{person_id}/timeline
controller: Chill\PersonBundle\Controller\TimelinePersonController::personAction
options:
menus:
person:
order: 60
label: Timeline
chill_person_admin:
path: "/{_locale}/admin/person"

View File

@ -0,0 +1,38 @@
<?php
/**
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Change constraint on pinnedComment in Accompanying period.
*/
final class Version20220310124318 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period DROP CONSTRAINT fk_e260a868b0804e90');
$this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT fk_e260a868b0804e90 FOREIGN KEY (pinnedcomment_id) REFERENCES chill_person_accompanying_period_comment (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function getDescription(): string
{
return 'Change constraint on pinnedComment in Accompanying period';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_accompanying_period DROP CONSTRAINT FK_E260A868B0804E90');
$this->addSql('ALTER TABLE chill_person_accompanying_period ADD CONSTRAINT FK_E260A868B0804E90 FOREIGN KEY (pinnedComment_id) REFERENCES chill_person_accompanying_period_comment (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE');
}
}

View File

@ -567,6 +567,7 @@ household_composition:
Currently no composition: Aucune composition familiale renseignée.
Add a composition: Ajouter une composition familiale
Update composition: Modifier la composition familiale
Create: Créér une nouvelle composition familiale
# docgen
Linked evaluations: Évaluations associées

View File

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

View File

@ -60,7 +60,7 @@
{% if types|length > 0 %}
{{ types|join(', ') }}
{% else %}
<p class="chill-no-data-statement">{{ 'thirdParty.Any categories' }}</p>
<p class="chill-no-data-statement">{{ 'thirdparty.no_categories' }}</p>
{% endif %}
</dd>
@ -110,7 +110,7 @@
<dt>{{ 'Contacts'|trans }}</dt>
<dd>
{% if thirdParty.activeChildren|length == 0 %}
<p class="chill-no-data-statement">{{ 'Any contacts associated'|trans }}</p>
<p class="chill-no-data-statement">{{ 'No contacts associated'|trans }}</p>
{% else %}
<div class="flex-table">
{% for tp in thirdParty.activeChildren %}

View File

@ -63,13 +63,13 @@ Show thirdparty: Voir le tiers
Add a contact: Ajouter un contact
Remove a contact: Supprimer
Contacts: Contacts
Any contact: Aucun contact
No contacts associated: Aucun contact
No nameCompany given: Aucune raison sociale renseignée
No acronym given: Aucun sigle renseigné
No phone given: Aucun téléphone renseigné
No email given: Aucune adresse courriel renseignée
thirdparty.Any categories: Aucune catégorie
thirdparty.no_categories: Aucune catégorie
The party is visible in those centers: Le tiers est visible dans ces centres
The party is not visible in any center: Le tiers n'est associé à aucun centre

View File

@ -14,7 +14,7 @@ window.addEventListener('DOMContentLoaded', function(e) {
office_frame.setAttribute('allowfullscreen', 'true');
// The sandbox attribute is needed to allow automatic redirection to the O365 sign-in page in the business user flow
office_frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation allow-popups-to-escape-sandbox');
office_frame.setAttribute('sandbox', 'allow-downloads allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation allow-popups-to-escape-sandbox');
frameholder.appendChild(office_frame);
document.getElementById('office_form').submit();