Merge branch 'master' into social_action_exports

This commit is contained in:
Julie Lenaerts 2022-07-28 15:45:46 +02:00
commit 8fdb2747ac
73 changed files with 1108 additions and 757 deletions

View File

@ -16,6 +16,8 @@ and this project adheres to
* [person-thirdparty]: fix quick-add of names that consist of multiple parts (eg. De Vlieger) within onthefly modal person/thirdparty
* [search]: Order of birthdate fields changed in advanced search to avoid confusion.
* [workflow]: Constraint added to workflow (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/675)
* [household]: Reposition and cut button for enfant hors menage have been deleted (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/620)
* [admin]: Add crud for composition type in admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/611)
## Test releases

View File

@ -24,6 +24,7 @@ parameters:
- "/spec/"
- "/var/"
- "/vendor/"
- "/tests/app"
# Psalm
tasks.psalm.blocking: true

File diff suppressed because it is too large Load Diff

View File

@ -25,16 +25,6 @@ parameters:
count: 1
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
-
message: "#^Only booleans are allowed in &&, mixed given on the right side\\.$#"
count: 3
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 2
path: src/Bundle/ChillActivityBundle/Form/ActivityType.php
-
message: "#^Only booleans are allowed in an if condition, mixed given\\.$#"
count: 3

View File

@ -21,6 +21,7 @@ use Chill\ActivityBundle\Repository\ActivityTypeRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent;
@ -56,6 +57,8 @@ final class ActivityController extends AbstractController
private ActivityTypeRepository $activityTypeRepository;
private CenterResolverManagerInterface $centerResolver;
private EntityManagerInterface $entityManager;
private EventDispatcherInterface $eventDispatcher;
@ -82,7 +85,8 @@ final class ActivityController extends AbstractController
EntityManagerInterface $entityManager,
EventDispatcherInterface $eventDispatcher,
LoggerInterface $logger,
SerializerInterface $serializer
SerializerInterface $serializer,
CenterResolverManagerInterface $centerResolver
) {
$this->activityACLAwareRepository = $activityACLAwareRepository;
$this->activityTypeRepository = $activityTypeRepository;
@ -96,6 +100,7 @@ final class ActivityController extends AbstractController
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
$this->serializer = $serializer;
$this->centerResolver = $centerResolver;
}
/**
@ -198,7 +203,7 @@ final class ActivityController extends AbstractController
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_UPDATE', $entity);
$form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(),
'center' => $this->centerResolver->resolveCenters($entity)[0] ?? null,
'role' => new Role('CHILL_ACTIVITY_UPDATE'),
'activityType' => $entity->getActivityType(),
'accompanyingPeriod' => $accompanyingPeriod,
@ -416,7 +421,7 @@ final class ActivityController extends AbstractController
$this->denyAccessUnlessGranted(ActivityVoter::CREATE, $entity);
$form = $this->createForm(ActivityType::class, $entity, [
'center' => $entity->getCenter(),
'center' => $this->centerResolver->resolveCenters($entity)[0] ?? null,
'role' => new Role('CHILL_ACTIVITY_CREATE'),
'activityType' => $entity->getActivityType(),
'accompanyingPeriod' => $accompanyingPeriod,

View File

@ -61,8 +61,6 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
ActivityVoter::DELETE => [ActivityVoter::SEE_DETAILS],
ActivityVoter::SEE_DETAILS => [ActivityVoter::SEE],
ActivityVoter::FULL => [
ActivityVoter::CREATE_PERSON,
ActivityVoter::CREATE_ACCOMPANYING_COURSE,
ActivityVoter::DELETE,
ActivityVoter::UPDATE,
],

View File

@ -16,8 +16,8 @@ use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\HasCentersInterface;
use Chill\MainBundle\Entity\HasScopesInterface;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
@ -55,7 +55,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* getUserFunction="getUser",
* path="scope")
*/
class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCenterInterface, HasScopeInterface
class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterface, HasCentersInterface, HasScopesInterface
{
public const SENTRECEIVED_RECEIVED = 'received';
@ -306,13 +306,17 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
* get the center
* center is extracted from person.
*/
public function getCenter(): ?Center
public function getCenters(): iterable
{
if ($this->person instanceof Person) {
return $this->person->getCenter();
return [$this->person->getCenter()];
}
return null;
if ($this->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
return $this->getAccompanyingPeriod()->getCenters() ?? [];
}
return [];
}
public function getComment(): CommentEmbeddable
@ -422,6 +426,19 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this->scope;
}
public function getScopes(): iterable
{
if (null !== $this->getAccompanyingPeriod()) {
return $this->getAccompanyingPeriod()->getScopes();
}
if (null !== $this->getPerson()) {
return [$this->scope];
}
return [];
}
public function getSentReceived(): string
{
return $this->sentReceived;

View File

@ -14,17 +14,20 @@ namespace Chill\ActivityBundle\Form;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Form\Type\PrivateCommentType;
use Chill\MainBundle\Form\Type\ScopePickerType;
use Chill\MainBundle\Form\Type\UserPickerType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
@ -50,6 +53,7 @@ use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use function in_array;
class ActivityType extends AbstractType
@ -109,19 +113,18 @@ class ActivityType extends AbstractType
$activityType = $options['activityType'];
// TODO revoir la gestion des center au niveau du form des activité.
if ($options['center']) {
if ($options['center'] instanceof Center && null !== $options['data']->getPerson()) {
$builder->add('scope', ScopePickerType::class, [
'center' => $options['center'],
'role' => $options['role'],
// TODO make required again once scope and rights are fixed
'required' => false,
'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'],
'required' => true,
]);
}
/** @var ? \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingPeriod */
$accompanyingPeriod = null;
if ($options['accompanyingPeriod']) {
if ($options['accompanyingPeriod'] instanceof AccompanyingPeriod) {
$accompanyingPeriod = $options['accompanyingPeriod'];
}
@ -218,12 +221,10 @@ class ActivityType extends AbstractType
]);
}
if ($activityType->isVisible('user') && $options['center']) {
$builder->add('user', UserPickerType::class, [
if ($activityType->isVisible('user') && $options['center'] instanceof Center) {
$builder->add('user', PickUserDynamicType::class, [
'label' => $activityType->getLabel('user'),
'required' => $activityType->isRequired('user'),
'center' => $options['center'],
'role' => $options['role'],
]);
}
@ -442,8 +443,8 @@ class ActivityType extends AbstractType
$resolver
->setRequired(['center', 'role', 'activityType', 'accompanyingPeriod'])
->setAllowedTypes('center', ['null', 'Chill\MainBundle\Entity\Center'])
->setAllowedTypes('role', 'Symfony\Component\Security\Core\Role\Role')
->setAllowedTypes('center', ['null', Center::class])
->setAllowedTypes('role', [Role::class, 'string'])
->setAllowedTypes('activityType', \Chill\ActivityBundle\Entity\ActivityType::class)
->setAllowedTypes('accompanyingPeriod', [\Chill\PersonBundle\Entity\AccompanyingPeriod::class, 'null']);
}

View File

@ -120,3 +120,11 @@
{{ form_end(edit_form) }}
{# {{ form(delete_form) }} #}
{% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}

View File

@ -46,7 +46,7 @@
{% include 'ChillActivityBundle:Activity:list.html.twig' with {'context': 'person'} %}
{% if is_granted('CHILL_ACTIVITY_CREATE_PERSON', person) %}
{% if is_granted('CHILL_ACTIVITY_CREATE', 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

@ -119,3 +119,11 @@
</li>
</ul>
{{ form_end(form) }}
{% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}

View File

@ -133,7 +133,7 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
// change attribute CREATE
if (self::CREATE === $attribute) {
$attribute = self::CREATE_PERSON;
return $this->voterHelper->voteOnAttribute(self::CREATE_PERSON, $subject->getPerson(), $token);
}
} elseif ($subject->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod())) {
@ -144,7 +144,8 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
if (AccompanyingPeriod::STEP_CLOSED === $subject->getAccompanyingPeriod()->getStep()) {
return false;
}
$attribute = self::CREATE_ACCOMPANYING_COURSE;
return $this->voterHelper->voteOnAttribute(self::CREATE_ACCOMPANYING_COURSE, $subject->getAccompanyingPeriod(), $token);
}
} else {
throw new RuntimeException('Could not determine context of activity.');
@ -158,12 +159,12 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
// transform the attribute
if (self::CREATE === $attribute) {
$attribute = self::CREATE_ACCOMPANYING_COURSE;
return $this->voterHelper->voteOnAttribute(self::CREATE_ACCOMPANYING_COURSE, $subject, $token);
}
} elseif ($subject instanceof Person) {
// transform the attribute
if (self::CREATE === $attribute) {
$attribute = self::CREATE_PERSON;
return $this->voterHelper->voteOnAttribute(self::CREATE_PERSON, $subject, $token);
}
}

View File

@ -130,8 +130,10 @@ class ActivityContext implements
return $this->personRender->renderString($p, []);
},
'multiple' => false,
'required' => false,
'expanded' => true,
'label' => $options[$key . 'Label'],
'placeholder' => $this->translator->trans('Any person selected'),
]);
}
}

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Entity;
use Chill\MainBundle\Entity\HasScopesInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\Mapping as ORM;
@ -18,7 +19,7 @@ use Doctrine\ORM\Mapping as ORM;
* @ORM\Entity
* @ORM\Table("chill_doc.accompanyingcourse_document")
*/
class AccompanyingCourseDocument extends Document
class AccompanyingCourseDocument extends Document implements HasScopesInterface
{
/**
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class)
@ -31,6 +32,15 @@ class AccompanyingCourseDocument extends Document
return $this->course;
}
public function getScopes(): iterable
{
if (null !== $this->course) {
return [];
}
return $this->course->getScopes();
}
public function setCourse(?AccompanyingPeriod $course): self
{
$this->course = $course;

View File

@ -16,8 +16,6 @@ use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackCreationTrait;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait;
use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
@ -26,7 +24,7 @@ use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\MappedSuperclass
*/
class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdateInterface
class Document implements TrackCreationInterface, TrackUpdateInterface
{
use TrackCreationTrait;
@ -70,13 +68,6 @@ class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdate
*/
private $object;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope")
*
* @var \Chill\MainBundle\Entity\Scope The document's center
*/
private $scope;
/**
* @ORM\ManyToOne(targetEntity="Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate")
*/
@ -122,16 +113,6 @@ class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdate
return $this->object;
}
/**
* Get scope.
*
* @return \Chill\MainBundle\Entity\Scope
*/
public function getScope(): ?Scope
{
return $this->scope;
}
public function getTemplate(): ?DocGeneratorTemplate
{
return $this->template;
@ -175,13 +156,6 @@ class Document implements HasScopeInterface, TrackCreationInterface, TrackUpdate
return $this;
}
public function setScope($scope): self
{
$this->scope = $scope;
return $this;
}
public function setTemplate(?DocGeneratorTemplate $template): self
{
$this->template = $template;

View File

@ -13,6 +13,7 @@ namespace Chill\DocStoreBundle\Entity;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\HasScopeInterface;
use Chill\MainBundle\Entity\Scope;
use Chill\PersonBundle\Entity\Person;
use Doctrine\ORM\Mapping as ORM;
@ -27,6 +28,13 @@ class PersonDocument extends Document implements HasCenterInterface, HasScopeInt
*/
private Person $person;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope")
*
* @var \Chill\MainBundle\Entity\Scope The document's center
*/
private $scope;
public function getCenter()
{
return $this->getPerson()->getCenter();
@ -37,10 +45,22 @@ class PersonDocument extends Document implements HasCenterInterface, HasScopeInt
return $this->person;
}
public function getScope(): ?Scope
{
return $this->scope;
}
public function setPerson($person): self
{
$this->person = $person;
return $this;
}
public function setScope($scope): self
{
$this->scope = $scope;
return $this;
}
}

View File

@ -88,10 +88,5 @@ class AccompanyingCourseDocumentType extends AbstractType
$resolver->setDefaults([
'data_class' => Document::class,
]);
// $resolver->setRequired(['role', 'center'])
// ->setAllowedTypes('role', [ \Symfony\Component\Security\Core\Role\Role::class ])
// ->setAllowedTypes('center', [ \Chill\MainBundle\Entity\Center::class ])
// ;
}
}

View File

@ -65,7 +65,7 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito
$this->addACL($qb, $person);
foreach ($orderBy as $field => $order) {
$qb->addOrderBy($field, $order);
$qb->addOrderBy('d.' . $field, $order);
}
$qb->setFirstResult($offset)->setMaxResults($limit);

View File

@ -106,6 +106,10 @@ class AccompanyingCourseDocumentVoter extends AbstractChillVoter implements Prov
) {
return false;
}
if (self::CREATE === $attribute && null !== $subject->getCourse()) {
return $this->voterHelper->voteOnAttribute($attribute, $subject->getCourse(), $token);
}
}
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);

View File

@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\EventBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class AdminController
@ -20,18 +21,12 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class AdminController extends AbstractController
{
/**
* @return \Symfony\Component\HttpFoundation\Response
* Event admin.
*
* @Route("/{_locale}/admin/event", name="chill_event_admin_index")
*/
public function indexAction()
public function indexAdminAction()
{
return $this->render('ChillEventBundle:Admin:layout.html.twig');
}
/**
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function redirectToAdminIndexAction()
{
return $this->redirectToRoute('chill_main_admin_central');
return $this->render('ChillEventBundle:Admin:index.html.twig');
}
}

View File

@ -273,7 +273,7 @@ class EventController extends AbstractController
/**
* @var Center $centers
*/
$centers = $this->authorizationHelper->getReachableCenters($this->getUser(), $role);
$centers = $this->authorizationHelper->getReachableCenters($this->getUser(), (string) $role);
if (count($centers) === 1) {
return $this->redirectToRoute('chill_event__event_new', [

View File

@ -151,7 +151,7 @@ class PickEventType extends AbstractType
} else {
$centers = $this->authorizationHelper->getReachableCenters(
$this->user,
$options['role']
(string) $options['role']
);
}

View File

@ -0,0 +1,61 @@
<?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\EventBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class AdminMenuBuilder implements LocalMenuBuilderInterface
{
/**
* @var AuthorizationCheckerInterface
*/
protected $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
if (!$this->authorizationChecker->isGranted('ROLE_ADMIN')) {
return;
}
$menu->addChild('Events', [
'route' => 'chill_event_admin_index',
])
->setAttribute('class', 'list-group-item-header')
->setExtras([
'order' => 6500,
]);
$menu->addChild('Event type', [
'route' => 'chill_eventtype_admin',
])->setExtras(['order' => 6510]);
$menu->addChild('Event status', [
'route' => 'chill_event_admin_status',
])->setExtras(['order' => 6520]);
$menu->addChild('Role', [
'route' => 'chill_event_admin_role',
])->setExtras(['order' => 6530]);
}
public static function getMenuIds(): array
{
return ['admin_section', 'admin_event'];
}
}

View File

@ -0,0 +1,13 @@
{% extends "@ChillMain/Admin/layoutWithVerticalMenu.html.twig" %}
{% block vertical_menu_content %}
{{ chill_menu('admin_event', {
'layout': '@ChillMain/Admin/menu_admin_section.html.twig',
}) }}
{% endblock %}
{% block layout_wvm_content %}
{% block admin_content %}<!-- block content empty -->
<h1>{{ 'Events configuration' |trans }}</h1>
{% endblock %}
{% endblock %}

View File

@ -1,31 +0,0 @@
{#
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
<info@champs-libres.coop> / <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% extends "@ChillMain/Admin/layoutWithVerticalMenu.html.twig" %}
{% block vertical_menu_content %}
{{ chill_menu('admin_events', {
'layout': '@ChillEvent/Admin/menu.html.twig',
}) }}
{% endblock %}
{% block layout_wvm_content %}
{% block admin_content %}<!-- block content empty -->
<h1>{{ 'Events configuration' |trans }}</h1>
{% endblock %}
{% endblock %}

View File

@ -1,21 +1,3 @@
{#
* Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS,
<info@champs-libres.coop> / <http://www.champs-libres.coop>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% extends "@ChillMain/Menu/verticalMenu.html.twig" %}
{% block v_menu_title %}{{ 'Events configuration menu'|trans }}{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}
<h1>{{ 'Role edit'|trans }}</h1>

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -1,4 +1,4 @@
{% extends "ChillEventBundle:Admin:layout.html.twig" %}
{% extends "ChillEventBundle:Admin:index.html.twig" %}
{% block admin_content -%}

View File

@ -9,24 +9,24 @@ chill_event_participation:
## ADMIN
chill_event_admin:
chill_event_admin_index:
path: /{_locale}/admin/event
controller: Chill\EventBundle\Controller\AdminController::indexAction
options:
menus:
admin_section:
order: 2100
label: "Events"
icons: ['calendar']
controller: Chill\EventBundle\Controller\AdminController::indexAdminAction
# options:
# menus:
# admin_section:
# order: 2100
# label: "Events"
# icons: ['calendar']
chill_event_admin_redirect_to_admin_index:
path: /{_locale}/admin/event_redirect_to_main
controller: Chill\EventBundle\Controller\AdminController::redirectToAdminIndexAction
options:
menus:
admin_events:
order: 0
label: Main admin menu
# chill_event_admin_redirect_to_admin_index:
# path: /{_locale}/admin/event_redirect_to_main
# controller: Chill\EventBundle\Controller\AdminController::redirectToAdminIndexAction
# options:
# menus:
# admin_events:
# order: 0
# label: Main admin menu
chill_event_admin_status:
resource: "@ChillEventBundle/config/routes/status.yaml"
@ -39,4 +39,4 @@ chill_event_admin_role:
chill_event_admin_event_type:
resource: "@ChillEventBundle/config/routes/eventtype.yaml"
prefix: /{_locale}/admin/event/event_type

View File

@ -1,2 +1,11 @@
services:
Chill\EventBundle\Controller\:
autowire: true
resource: '../Controller'
tags: ['controller.service_arguments']
Chill\EventBundle\Menu\:
autowire: true
autoconfigure: true
resource: '../Menu/'
tags: ['chill.menu_builder']

View File

@ -126,7 +126,7 @@ class Version20160318111334 extends AbstractMigration
$this->addSql('ALTER TABLE chill_event_participation '
. 'ADD CONSTRAINT FK_4E7768AC217BBB47 '
. 'FOREIGN KEY (person_id) '
. 'REFERENCES Person (id) '
. 'REFERENCES chill_person_person(id) '
. 'NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_event_participation '
. 'ADD CONSTRAINT FK_4E7768ACD60322AC '

View File

@ -92,5 +92,8 @@ final class PostalCodeAPIController extends ApiController
$qb->where('e.country = :country')
->setParameter('country', $request->query->get('country'));
}
$qb->andWhere('e.origin = :zero')
->setParameter('zero', 0);
}
}

View File

@ -12,7 +12,9 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class UserApiController extends ApiController
@ -58,4 +60,14 @@ class UserApiController extends ApiController
['groups' => ['read']]
);
}
/**
* @param QueryBuilder $query
*/
protected function customizeQuery(string $action, Request $request, $query): void
{
if ('_index' === $action) {
$query->andWhere($query->expr()->eq('e.enabled', "'TRUE'"));
}
}
}

View File

@ -0,0 +1,25 @@
<?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\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Symfony\Component\HttpFoundation\Request;
class UserJobApiController extends ApiController
{
protected function customizeQuery(string $action, Request $request, $query): void
{
if ('_index' === $action) {
$query->andWhere($query->expr()->eq('e.active', "'TRUE'"));
}
}
}

View File

@ -19,6 +19,7 @@ use Chill\MainBundle\Controller\LanguageController;
use Chill\MainBundle\Controller\LocationController;
use Chill\MainBundle\Controller\LocationTypeController;
use Chill\MainBundle\Controller\UserController;
use Chill\MainBundle\Controller\UserJobApiController;
use Chill\MainBundle\Controller\UserJobController;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
@ -501,6 +502,7 @@ class ChillMainExtension extends Extension implements
'name' => 'user_job',
'base_path' => '/api/1.0/main/user-job',
'base_role' => 'ROLE_USER',
'controller' => UserJobApiController::class,
'actions' => [
'_index' => [
'methods' => [

View File

@ -75,8 +75,6 @@ class PickCenterType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$export = $this->exportManager->getExport($options['export_alias']);
$centers = $this->authorizationHelper->getReachableCenters(
$this->user,

View File

@ -119,8 +119,8 @@ final class PostalCodeRepository implements ObjectRepository
$pertinenceClause = ['STRICT_WORD_SIMILARITY(canonical, UNACCENT(?))'];
$pertinenceArgs = [$pattern];
$orWhere = ['canonical %>> UNACCENT(?)'];
$orWhereArgs = [$pattern];
$andWhere = ['canonical %>> UNACCENT(?)'];
$andWhereArgs = [$pattern];
foreach (explode(' ', $pattern) as $part) {
$part = trim($part);
@ -129,8 +129,8 @@ final class PostalCodeRepository implements ObjectRepository
continue;
}
$orWhere[] = "canonical LIKE '%' || UNACCENT(LOWER(?)) || '%'";
$orWhereArgs[] = $part;
$andWhere[] = "canonical LIKE '%' || UNACCENT(LOWER(?)) || '%'";
$andWhereArgs[] = $part;
$pertinenceClause[] =
"(EXISTS (SELECT 1 FROM unnest(string_to_array(canonical, ' ')) AS t WHERE starts_with(t, UNACCENT(LOWER(?)))))::int";
$pertinenceClause[] =
@ -139,7 +139,7 @@ final class PostalCodeRepository implements ObjectRepository
}
$query
->setSelectPertinence(implode(' + ', $pertinenceClause), $pertinenceArgs)
->andWhereClause(implode(' OR ', $orWhere), $orWhereArgs);
->andWhereClause(implode(' AND ', $andWhere), $andWhereArgs);
return $query;
}

View File

@ -47,12 +47,12 @@ final class Version20210505153727 extends AbstractMigration
');
$this->addSql('
WITH hydrated_addresses AS (
SELECT *, rank() OVER (PARTITION BY pa_a.person_id ORDER BY validfrom)
SELECT *, rank() OVER (PARTITION BY pa_a.person_id ORDER BY validfrom, id)
FROM chill_main_address AS aa JOIN chill_person_persons_to_addresses AS pa_a ON aa.id = pa_a.address_id
)
UPDATE chill_main_address AS b
SET validto = (
SELECT validfrom - INTERVAL \'1 DAY\'
SELECT validfrom
FROM hydrated_addresses
WHERE hydrated_addresses.id = (
SELECT a1.id

View File

@ -0,0 +1,39 @@
<?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\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20220711150006 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_workflow_entity_step
DROP CONSTRAINT chill_custom_only_one_step_opened');
}
public function getDescription(): string
{
return 'Add a constraint to ensure that only one step is available at a time';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_main_workflow_entity_step
ADD CONSTRAINT chill_custom_only_one_step_opened
EXCLUDE (
entityworkflow_id WITH =
) WHERE (transitionafter IS NULL)
DEFERRABLE INITIALLY DEFERRED');
}
}

View File

@ -177,7 +177,7 @@ class AccompanyingCourseController extends Controller
*/
public function editAction(AccompanyingPeriod $accompanyingCourse): Response
{
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::EDIT, $accompanyingCourse);
return $this->render('@ChillPerson/AccompanyingCourse/edit.html.twig', [
'accompanyingCourse' => $accompanyingCourse,
@ -215,7 +215,7 @@ class AccompanyingCourseController extends Controller
// get persons without household
$withoutHousehold = [];
foreach ($accompanyingCourse->getParticipations() as $p) {
foreach ($accompanyingCourse->getCurrentParticipations() as $p) {
if (false === $p->getPerson()->isSharingHousehold()) {
$withoutHousehold[] = $p->getPerson();
}

View File

@ -0,0 +1,26 @@
<?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\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
class HouseholdCompositionTypeController extends CRUDController
{
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
$query->addOrderBy('e.id', 'ASC');
return parent::orderQuery($action, $query, $request, $paginator);
}
}

View File

@ -228,6 +228,27 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
],
],
],
[
'class' => \Chill\PersonBundle\Entity\Household\HouseholdCompositionType::class,
'name' => 'person_household_composition_type',
'base_path' => '/admin/person/household/composition-type',
'form_class' => \Chill\PersonBundle\Form\HouseholdCompositionTypeType::class,
'controller' => \Chill\PersonBundle\Controller\HouseholdCompositionTypeController::class,
'actions' => [
'index' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillPerson/HouseholdCompositionType/index.html.twig',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillPerson/HouseholdCompositionType/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillPerson/HouseholdCompositionType/edit.html.twig',
],
],
],
[
'class' => \Chill\PersonBundle\Entity\Relationships\Relation::class,
'name' => 'person_relation',

View File

@ -36,49 +36,49 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface
* inversedBy="comments")
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
private $accompanyingPeriod;
private ?AccompanyingPeriod $accompanyingPeriod;
/**
* @ORM\Column(type="text")
* @Groups({"read", "write"})
* @Groups({"read", "write", "docgen:read"})
* @Assert\NotBlank
* @Assert\NotNull
*/
private $content;
private ?string $content;
/**
* @ORM\Column(type="datetime")
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $createdAt;
private ?DateTimeInterface $createdAt;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $creator;
private ?User $creator;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private $id;
private ?int $id;
/**
* @ORM\Column(type="datetime")
* @Groups({"read"})
*/
private $updatedAt;
private ?DateTimeInterface $updatedAt;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
* @Groups({"read"})
*/
private $updatedBy;
private ?User $updatedBy;
public function getAccompanyingPeriod(): ?AccompanyingPeriod
{

View File

@ -47,7 +47,7 @@ class Resource
/**
* @ORM\Column(type="text", nullable=true)
* @Groups({"read"})
* @Groups({"read", "docgen:read"})
*/
private ?string $comment = '';

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Entity\Household;
use ArrayIterator;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Validator\Constraints\Household\MaxHolder;
use DateTime;
use DateTimeImmutable;
@ -354,7 +355,12 @@ class Household
return $this->members;
}
public function getMembersDuringMembership(HouseholdMember $membership)
/**
* get all the members during a given membership.
*
* @return Collection|HouseholdMember[]
*/
public function getMembersDuringMembership(HouseholdMember $membership): Collection
{
return $this->getMembersOnRange(
$membership->getStartDate(),
@ -441,6 +447,28 @@ class Household
return $this->getNonCurrentMembers($now)->matching($criteria);
}
/**
* get all the unique persons during a given membership.
*
* same as @see(self::getMembersDuringMembership}, except that the collection is filtered to
* return unique members.
*
* @return Collection|Person[]
*/
public function getPersonsDuringMembership(HouseholdMember $member): Collection
{
// make list unique
$membersByHash = [];
foreach ($this->getMembersDuringMembership($member) as $m) {
if (null !== $m && null !== $m->getPerson()) {
$membersByHash[spl_object_hash($m->getPerson())] = $m->getPerson();
}
}
return new ArrayCollection(array_values($membersByHash));
}
public function getPreviousAddressOf(Address $address): ?Address
{
$iterator = new ArrayIterator($this->getAddressesOrdered());

View File

@ -1184,7 +1184,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
}
/**
* @deprecated Use `getCurrentPersonAddress` instead
* @deprecated Use @see{Person::getCurrentPersonAddress} or @see{Person::getCurrentHouseholdAddress} instead
*
* @throws Exception
*
@ -1192,7 +1192,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*/
public function getLastAddress(?DateTime $from = null)
{
return $this->getCurrentPersonAddress();
return $this->getCurrentHouseholdAddress(
null !== $from ? DateTimeImmutable::createFromMutable($from) : null
);
}
public function getLastName(): string

View File

@ -0,0 +1,39 @@
<?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\PersonBundle\Form;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Chill\PersonBundle\Entity\Household\HouseholdCompositionType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class HouseholdCompositionTypeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('label', TranslatableStringFormType::class)
->add('active', CheckboxType::class, [
'label' => 'Actif?',
'required' => false,
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefault('class', HouseholdCompositionType::class);
}
}

View File

@ -46,9 +46,13 @@ class AdminHouseholdMenuBuilder implements LocalMenuBuilderInterface
'route' => 'chill_crud_person_household_position_index',
])->setExtras(['order' => 2110]);
$menu->addChild('Composition', [
'route' => 'chill_crud_person_household_composition_type_index',
])->setExtras(['order' => 2120]);
$menu->addChild('person_admin.relation', [
'route' => 'chill_crud_person_relation_index',
])->setExtras(['order' => 2120]);
])->setExtras(['order' => 2130]);
}
public static function getMenuIds(): array

View File

@ -1,7 +1,7 @@
<div class="alert alert-warning alert-with-actions">
<div class="float-button bottom">
<div class="box">
{% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE', accompanyingCourse) %}
<div class="action">
<ul class="record_actions">
<li>
@ -13,6 +13,8 @@
</li>
</ul>
</div>
{% endif %}
<p>{{ 'Some peoples does not belong to any household currently. Add them to an household soon'|trans }}</p>
</div>
</div>

View File

@ -3,6 +3,7 @@
<div class="alert alert-danger {% if hasPersonLocation %}alert-with-actions{% endif %}">
<div class="float-button bottom">
<div class="box">
{% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE', accompanyingCourse) %}
<div class="action">
<ul class="record_actions">
<li>
@ -14,6 +15,7 @@
</li>
</ul>
</div>
{% endif %}
<p>
{{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }}</p>
{% if not hasPersonLocation %}

View File

@ -14,7 +14,7 @@
{% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_UPDATE', period) %}
<div class="item-col item-meta">
{% set job_id = null %}
{% if period.job is defined %}
{% if period.job is not null %}
{% set job_id = period.job.id %}
{% endif %}
<span

View File

@ -48,13 +48,66 @@
{% endmacro %}
{% block content %}
<div class="flex-table accompanyingcourse-list">
{% for period in accompanying_periods %}
{# WARNING: this list is rendered in either person context or houshold context #}
{%- set acps = [] %}
{%- set acpsClosed = [] %}
{% for acp in accompanying_periods %}
{% if acp.step == 'CLOSED' or (person is defined and acp.requestorPerson is not same as(person) and acp.openParticipationContainsPerson(person) is null ) %}
{%- set acpsClosed = acpsClosed|merge([acp]) %}
{% else %}
{%- set acps = acps|merge([acp]) %}
{% endif %}
{% endfor %}
<div class="flex-table accompanyingcourse-list">
{% for period in acps %}
{% include 'ChillPersonBundle:AccompanyingPeriod:_list_item.html.twig' with {
'recordAction': _self.recordAction(period, contextEntity)
} %}
{% else %}
<p class="chill-no-data-statement">{{ 'Any accompanying periods are open'|trans }}</p>
{% endfor %}
</div>
{% if acpsClosed|length > 0 %}
{% if person is defined %}
{% set identifier = person.id %}
{% else %}
{% set identifier = household.id %}
{% endif %}
<div class="accordion" id="member_{{ identifier }}">
<div class="accordion-item">
<h2 class="accordion-header" id="heading_{{ identifier }}">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse_{{ identifier }}"
aria-expanded="false"
aria-controls="collapse_{{ identifier }}">
<span class="folded">{{ 'periods.show closed periods'|trans({ 'nb_items': acpsClosed|length }) }}</span>
<span class="unfolded text-secondary">{{ 'periods.hide closed periods'|trans({ 'nb_items': acpsClosed|length }) }}</span>
</button>
</h2>
<div id="collapse_{{ identifier }}"
class="accordion-collapse collapse"
aria-labelledby="heading_{{ identifier }}"
data-bs-parent="#nonCurrent">
<div class="flex-table accompanyingcourse-list">
{% for period in acpsClosed %}
{% include 'ChillPersonBundle:AccompanyingPeriod:_list_item.html.twig' with {
'recordAction': _self.recordAction(period, contextEntity)
} %}
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}
{% endblock content %}

View File

@ -180,10 +180,16 @@
{% if members|length > 0 %}
<div class="flex-table list-household-members">
{% for m in members %}
{% if m.position.shareHousehold %}
{% include '@ChillPerson/Household/_render_member.html.twig' with {
'member': m,
'customButtons': { 'before': _self.customButtons(m, household) }
} %}
{% else %}
{% include '@ChillPerson/Household/_render_member.html.twig' with {
'member': m,
} %}
{% endif %}
{% endfor %}
</div>
{% else %}

View File

@ -0,0 +1,11 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
{% endblock %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock admin_content %}

View File

@ -0,0 +1,41 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_index.html.twig' %}
{% block table_entities_thead_tr %}
<th>{{ 'Id'|trans }}</th>
<th>{{ 'Label'|trans }}</th>
<th>{{ 'Active'|trans }}</th>
<th>&nbsp;</th>
{% endblock %}
{% block table_entities_tbody %}
{% for entity in entities %}
<tr>
<td>{{ entity.id }}</td>
<td>{{ entity.label|localize_translatable_string }}</td>
<td style="text-align:center;">
{%- if entity.isActive -%}
<i class="fa fa-check-square-o"></i>
{%- else -%}
<i class="fa fa-square-o"></i>
{%- endif -%}
</td>
<td>
<ul class="record_actions">
<li>
<a href="{{ chill_path_add_return_path('chill_crud_person_household_position_edit', { 'id': entity.id }) }}" class="btn btn-edit"></a>
</li>
</ul>
</td>
</tr>
{% endfor %}
{% endblock %}
{% block actions_before %}
<li class='cancel'>
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
</li>
{% endblock %}
{% endembed %}
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
{% endblock %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock admin_content %}

View File

@ -65,18 +65,18 @@
<h3>{{ 'household.Members at same time'|trans }}</h3>
</div>
<div class="wl-col list">
{% set simultaneous = p.household.getMembersDuringMembership(p) %}
{% set simultaneous = p.household.getPersonsDuringMembership(p) %}
{% if simultaneous|length == 0 %}
<p class="chill-no-data-statement">
{{ 'household.Any simultaneous members'|trans }}
</p>
{% else %}
{% for m in simultaneous -%}
{% for person in simultaneous -%}
{% 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
targetEntity: { name: 'person', id: person.id },
buttonText: person|chill_entity_render_string,
isDead: person.deathdate is not null
} %}
{%- endfor -%}
{% endif %}

View File

@ -1,13 +1,15 @@
{% macro button_person_after(person) %}
{% set household = person.getCurrentHousehold %}
{% if household is not null %}
{% if household is not null and is_granted('CHILL_PERSON_HOUSEHOLD_SEE', household) %}
<li>
<a href="{{ path('chill_person_household_summary', { 'household_id': household.id }) }}" class="btn btn-sm btn-chill-beige"><i class="fa fa-home"></i></a>
</li>
{% endif %}
<li>
<a href="{{ path('chill_person_accompanying_course_new', { 'person_id': [ person.id ]}) }}" class="btn btn-sm btn-create change-icon" title="{{ 'Create an accompanying period'|trans }}"><i class="fa fa-random"></i></a>
</li>
{% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_CREATE', person) %}
<li>
<a href="{{ path('chill_person_accompanying_course_new', { 'person_id': [ person.id ]}) }}" class="btn btn-sm btn-create change-icon" title="{{ 'Create an accompanying period'|trans }}"><i class="fa fa-random"></i></a>
</li>
{% endif %}
{% endmacro %}
{% macro accompanying_period(acp, person) %}
@ -233,7 +235,8 @@
{%- set acpsClosed = [] %}
{%- for acp in person.accompanyingPeriodInvolved %}
{%- if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', acp) %}
{% if acp.step == 'CLOSED' %}
{# filter for "current" periods: either the person is a requestor, or is member of the period and not closed #}
{% if acp.step == 'CLOSED' or (acp.requestorPerson is not same as(person) and acp.openParticipationContainsPerson(person) is null ) %}
{%- set acpsClosed = acpsClosed|merge([acp]) %}
{% else %}
{%- set acps = acps|merge([acp]) %}

View File

@ -118,7 +118,7 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleH
->generate(self::class)
->addCheckFor(null, [self::CREATE, self::REASSIGN_BULK])
->addCheckFor(AccompanyingPeriod::class, [self::TOGGLE_CONFIDENTIAL, ...self::ALL])
->addCheckFor(Person::class, [self::SEE])
->addCheckFor(Person::class, [self::SEE, self::CREATE])
->build();
}

View File

@ -71,6 +71,7 @@ class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterf
'location' => Address::class,
'locationPerson' => Person::class,
'administrativeLocation' => Location::class,
'pinnedComment' => AccompanyingPeriod\Comment::class,
];
private ClosingMotiveRender $closingMotiveRender;
@ -164,6 +165,8 @@ class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterf
'location' => $this->normalizer->normalize($period->getLocation(), $format, $addressContext),
'administrativeLocation' => $this->normalizer->normalize($period->getAdministrativeLocation(), $format, $administrativeLocationContext),
'works' => $this->normalizer->normalize($period->getWorks(), $format, $workContext),
'comments' => $this->normalizer->normalize($period->getComments(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\Comment::class])),
'pinnedComment' => $this->normalizer->normalize($period->getPinnedComment(), $format, array_merge($context, ['docgen:expects' => AccompanyingPeriod\Comment::class])),
];
}
@ -183,6 +186,7 @@ class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterf
'hasLocationPerson' => false,
'hasAdministrativeLocation' => false,
'works' => [],
'comments' => [],
]
);
}

View File

@ -135,4 +135,51 @@ final class HouseholdTest extends TestCase
$this->assertEquals(new DateTimeImmutable('2021-12-31'), $second->getStartDate());
$this->assertEquals(new DateTimeImmutable('2021-12-31'), $inside->getEndDate());
}
public function testHouseholdGetPersonsDuringMembership()
{
$household = new Household();
$person1 = new Person();
$person2 = new Person();
$personOut = new Person();
$household->addMember(
$member1 = (new HouseholdMember())
->setStartDate(new DateTimeImmutable('2021-01-01'))
->setEndDate(new DateTimeImmutable('2021-12-01'))
->setPerson($person1)
);
$household->addMember(
$member2a = (new HouseholdMember())
->setStartDate(new DateTimeImmutable('2021-01-01'))
->setEndDate(new DateTimeImmutable('2021-05-01'))
->setPerson($person2)
);
$household->addMember(
$member2b = (new HouseholdMember())
->setStartDate(new DateTimeImmutable('2021-11-01'))
->setEndDate(new DateTimeImmutable('2022-06-01'))
->setPerson($person2)
);
$household->addMember(
$memberOut = (new HouseholdMember())
->setStartDate(new DateTimeImmutable('2019-01-01'))
->setEndDate(new DateTimeImmutable('2019-12-01'))
->setPerson($personOut)
);
$this->assertCount(0, $household->getPersonsDuringMembership($memberOut));
$this->assertCount(1, $household->getPersonsDuringMembership($member1));
$this->assertContains($person2, $household->getPersonsDuringMembership($member1));
$this->assertCount(1, $household->getPersonsDuringMembership($member2a));
$this->assertContains($person1, $household->getPersonsDuringMembership($member2a));
$this->assertCount(1, $household->getPersonsDuringMembership($member2b));
$this->assertContains($person1, $household->getPersonsDuringMembership($member2b));
}
}

View File

@ -68,6 +68,8 @@ final class AccompanyingPeriodDocGenNormalizerTest extends KernelTestCase
'closingMotive' => '@ignored',
'confidential' => true,
'confidentialText' => 'confidentiel',
'comments' => '@ignored',
'pinnedComment' => '@ignored',
'createdAt' => '@ignored',
'createdBy' => '@ignored',
'emergency' => true,

View File

@ -468,6 +468,12 @@ crud:
add_new: Ajouter un nouveau
title_new: Nouvelle position
title_edit: Modifier la position
person_household_composition_type:
index:
title: Composition
add_new: Ajouter un nouveau
title_new: Nouvelle composition
title_edit: Modifier la composition
person_relation:
index:
title: Relations de filiations