Merge branch 'master' into social_action_exports

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

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