Merge branch 'create_gender_entity' into 'master'

Add gender entity

See merge request Chill-Projet/chill-bundles!740
This commit is contained in:
LenaertsJ 2024-10-30 09:10:26 +00:00
commit 2b45a51f57
62 changed files with 1144 additions and 280 deletions

View File

@ -0,0 +1,5 @@
kind: Feature
body: Introduce a gender entity
time: 2024-10-30T09:57:13.110347067+01:00
custom:
Issue: ""

View File

@ -59,7 +59,8 @@
"vue-i18n": "^9.1.6", "vue-i18n": "^9.1.6",
"vue-multiselect": "3.0.0-alpha.2", "vue-multiselect": "3.0.0-alpha.2",
"vue-toast-notification": "^3.1.2", "vue-toast-notification": "^3.1.2",
"vuex": "^4.0.0" "vuex": "^4.0.0",
"bootstrap-icons": "^1.11.3"
}, },
"browserslist": [ "browserslist": [
"Firefox ESR" "Firefox ESR"

View File

@ -178,7 +178,7 @@ class CustomFieldsGroup
* *
* @return CustomFieldsGroup * @return CustomFieldsGroup
*/ */
public function setName($name) public function setName(array|string $name)
{ {
$this->name = $name; $this->name = $name;

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
class GenderApiController extends ApiController
{
protected function customizeQuery(string $action, Request $request, $query): void
{
$query
->andWhere(
$query->expr()->eq('e.active', "'TRUE'")
);
}
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
{
return $query->addOrderBy('e.order', 'ASC');
}
}

View File

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

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\MainBundle\DataFixtures\ORM;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\GenderEnum;
use Chill\MainBundle\Entity\GenderIconEnum;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
class LoadGenders extends AbstractFixture implements OrderedFixtureInterface
{
private array $genders = [
[
'label' => ['en' => 'man', 'fr' => 'homme'],
'genderTranslation' => GenderEnum::MALE,
'icon' => GenderIconEnum::MALE,
],
[
'label' => ['en' => 'woman', 'fr' => 'femme'],
'genderTranslation' => GenderEnum::FEMALE,
'icon' => GenderIconEnum::FEMALE,
],
[
'label' => ['en' => 'neutral', 'fr' => 'neutre'],
'genderTranslation' => GenderEnum::NEUTRAL,
'icon' => GenderIconEnum::NEUTRAL,
],
];
public function getOrder()
{
return 100;
}
public function load(ObjectManager $manager)
{
echo "loading genders... \n";
foreach ($this->genders as $g) {
echo $g['label']['fr'].' ';
$new_g = new Gender();
$new_g->setGenderTranslation($g['genderTranslation']);
$new_g->setLabel($g['label']);
$new_g->setIcon($g['icon']);
$this->addReference('g_'.$g['genderTranslation']->value, $new_g);
$manager->persist($new_g);
}
$manager->flush();
}
}

View File

@ -17,6 +17,8 @@ use Chill\MainBundle\Controller\CivilityApiController;
use Chill\MainBundle\Controller\CivilityController; use Chill\MainBundle\Controller\CivilityController;
use Chill\MainBundle\Controller\CountryApiController; use Chill\MainBundle\Controller\CountryApiController;
use Chill\MainBundle\Controller\CountryController; use Chill\MainBundle\Controller\CountryController;
use Chill\MainBundle\Controller\GenderApiController;
use Chill\MainBundle\Controller\GenderController;
use Chill\MainBundle\Controller\GeographicalUnitApiController; use Chill\MainBundle\Controller\GeographicalUnitApiController;
use Chill\MainBundle\Controller\LanguageController; use Chill\MainBundle\Controller\LanguageController;
use Chill\MainBundle\Controller\LocationController; use Chill\MainBundle\Controller\LocationController;
@ -52,6 +54,7 @@ use Chill\MainBundle\Doctrine\Type\PointType;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Country; use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\GeographicalUnitLayer; use Chill\MainBundle\Entity\GeographicalUnitLayer;
use Chill\MainBundle\Entity\Language; use Chill\MainBundle\Entity\Language;
use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\Location;
@ -63,6 +66,7 @@ use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Form\CenterType; use Chill\MainBundle\Form\CenterType;
use Chill\MainBundle\Form\CivilityType; use Chill\MainBundle\Form\CivilityType;
use Chill\MainBundle\Form\CountryType; use Chill\MainBundle\Form\CountryType;
use Chill\MainBundle\Form\GenderType;
use Chill\MainBundle\Form\LanguageType; use Chill\MainBundle\Form\LanguageType;
use Chill\MainBundle\Form\LocationFormType; use Chill\MainBundle\Form\LocationFormType;
use Chill\MainBundle\Form\LocationTypeType; use Chill\MainBundle\Form\LocationTypeType;
@ -485,6 +489,28 @@ class ChillMainExtension extends Extension implements
], ],
], ],
], ],
[
'class' => Gender::class,
'name' => 'main_gender',
'base_path' => '/admin/main/gender',
'base_role' => 'ROLE_ADMIN',
'form_class' => GenderType::class,
'controller' => GenderController::class,
'actions' => [
'index' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/Gender/index.html.twig',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/Gender/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/Gender/edit.html.twig',
],
],
],
[ [
'class' => Language::class, 'class' => Language::class,
'name' => 'main_language', 'name' => 'main_language',
@ -788,6 +814,21 @@ class ChillMainExtension extends Extension implements
], ],
], ],
], ],
[
'class' => Gender::class,
'name' => 'gender',
'base_path' => '/api/1.0/main/gender',
'base_role' => 'ROLE_USER',
'controller' => GenderApiController::class,
'actions' => [
'_index' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true,
],
],
],
],
[ [
'class' => GeographicalUnitLayer::class, 'class' => GeographicalUnitLayer::class,
'controller' => GeographicalUnitApiController::class, 'controller' => GeographicalUnitApiController::class,

View File

@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\MainBundle\Entity;
use Chill\MainBundle\Repository\GenderRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
#[Serializer\DiscriminatorMap(typeProperty: 'type', mapping: ['chill_main_gender' => Gender::class])]
#[ORM\Entity(repositoryClass: GenderRepository::class)]
#[ORM\Table(name: 'chill_main_gender')]
class Gender
{
#[Serializer\Groups(['read'])]
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER)]
private ?int $id = null;
#[Serializer\Groups(['read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)]
private array $label = [];
#[Serializer\Groups(['read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
private bool $active = true;
#[Assert\NotNull(message: 'You must choose a gender translation')]
#[Serializer\Groups(['read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, enumType: GenderEnum::class)]
private GenderEnum $genderTranslation;
#[Serializer\Groups(['read'])]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, enumType: GenderIconEnum::class)]
private GenderIconEnum $icon;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::FLOAT, name: 'ordering', nullable: true, options: ['default' => '0.0'])]
private float $order = 0;
public function getId(): int
{
return $this->id;
}
public function getLabel(): array
{
return $this->label;
}
public function setLabel(array $label): void
{
$this->label = $label;
}
public function isActive(): bool
{
return $this->active;
}
public function setActive(bool $active): void
{
$this->active = $active;
}
public function getGenderTranslation(): GenderEnum
{
return $this->genderTranslation;
}
public function setGenderTranslation(GenderEnum $genderTranslation): void
{
$this->genderTranslation = $genderTranslation;
}
public function getIcon(): GenderIconEnum
{
return $this->icon;
}
public function setIcon(GenderIconEnum $icon): void
{
$this->icon = $icon;
}
public function getOrder(): float
{
return $this->order;
}
public function setOrder(float $order): void
{
$this->order = $order;
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\MainBundle\Entity;
enum GenderEnum: string
{
case MALE = 'man';
case FEMALE = 'woman';
case NEUTRAL = 'neutral';
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\MainBundle\Entity;
enum GenderIconEnum: string
{
case MALE = 'bi bi-gender-male';
case FEMALE = 'bi bi-gender-female';
case NEUTRAL = 'bi bi-gender-neuter';
case AMBIGUOUS = 'bi bi-gender-ambiguous';
case TRANS = 'bi bi-gender-trans';
}

View File

@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\GenderEnum;
use Chill\MainBundle\Entity\GenderIconEnum;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EnumType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class GenderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('label', TranslatableStringFormType::class, [
'required' => true,
])
->add('icon', EnumType::class, [
'class' => GenderIconEnum::class,
'choices' => GenderIconEnum::cases(),
'expanded' => true,
'multiple' => false,
'mapped' => true,
'choice_label' => fn (GenderIconEnum $enum) => '<i class="'.strtolower($enum->value).'"></i>',
'choice_value' => fn (?GenderIconEnum $enum) => null !== $enum ? $enum->value : null,
'label' => 'gender.admin.Select Gender Icon',
'label_html' => true,
])
->add('genderTranslation', EnumType::class, [
'class' => GenderEnum::class,
'choice_label' => fn (GenderEnum $enum) => $enum->value,
'label' => 'gender.admin.Select Gender Translation',
])
->add('active', ChoiceType::class, [
'choices' => [
'Active' => true,
'Inactive' => false,
],
])
->add('order', NumberType::class);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Gender::class,
]);
}
}

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Gender;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Gender>
*/
class GenderRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Gender::class);
}
public function findByActiveOrdered(): array
{
return $this->createQueryBuilder('g')
->select('g')
->where('g.active = True')
->orderBy('g.order', 'ASC')
->getQuery()
->getResult();
}
public function findByGenderTranslation($gender): array
{
return $this->createQueryBuilder('g')
->select('g')
->where('g.genderTranslation = :gender')
->setParameter('gender', $gender)
->getQuery()
->getResult();
}
}

View File

@ -10,6 +10,7 @@ import Modal from 'bootstrap/js/dist/modal';
import Collapse from 'bootstrap/js/src/collapse'; import Collapse from 'bootstrap/js/src/collapse';
import Carousel from 'bootstrap/js/src/carousel'; import Carousel from 'bootstrap/js/src/carousel';
import Popover from 'bootstrap/js/src/popover'; import Popover from 'bootstrap/js/src/popover';
import 'bootstrap-icons/font/bootstrap-icons.css';
// //
// Carousel: ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section // Carousel: ACHeaderSlider is a small slider used in banner of AccompanyingCourse Section

View File

@ -0,0 +1,11 @@
<template>
<i :class="gender.icon"></i>
</template>
<script setup>
const props = defineProps({
gender: Object
})
</script>

View File

@ -38,7 +38,8 @@ const messages = {
person: "Usager", person: "Usager",
birthday: { birthday: {
man: "Né le", man: "Né le",
woman: "Née le" woman: "Née le",
neutral: "Né·e le"
}, },
deathdate: "Date de décès", deathdate: "Date de décès",
household_without_address: "Le ménage de l'usager est sans adresse", household_without_address: "Le ménage de l'usager est sans adresse",

View File

@ -0,0 +1,75 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
{% endblock %}
{% form_theme form _self %}
{% block _gender_icon_widget %}
{% for child in form %}
<div class="form-check">
<input
type="radio"
id="{{ child.vars.id }}"
name="{{ child.vars.full_name }}"
value="{{ child.vars.value }}"
{% if child.vars.checked %}checked="checked"{% endif %}
/>
<label for="{{ child.vars.id }}">{{ child.vars.label|raw }}</label>
</div>
{% endfor %}
{% endblock %}
{% block admin_content %}
{% set formId = crudMainFormId|default('crud_main_form') %}
{% block crud_content_header %}
<h1 class="mb-5">{{ ('crud.'~crud_name~'.title_edit')|trans }}</h1>
{% endblock crud_content_header %}
{% block crud_content_form %}
{{ form_start(form, { 'attr' : { 'id': formId } }) }}
{{ form_row(form.label) }}
{{ form_row(form.genderTranslation) }}
{{ form_row(form.icon) }}
{{ form_row(form.active) }}
{{ form_row(form.order) }}
{{ form_end(form) }}
{% block crud_content_after_form %}{% endblock %}
{% block crud_content_form_actions %}
<ul class="record_actions sticky-form-buttons">
{% block content_form_actions_back %}
<li class="cancel">
{# <a class="btn btn-cancel" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_index') }}">#}
{# {{ 'Cancel'|trans }}#}
{# </a>#}
</li>
{% endblock %}
{% block content_form_actions_before %}{% endblock %}
{% block content_form_actions_delete %}
{% if chill_crud_action_exists(crud_name, 'delete') %}
{% if is_granted(chill_crud_config('role', crud_name, 'delete'), entity) %}
<li class="">
<a class="btn btn-small btn-delete" href="{{ chill_path_add_return_path('chill_crud_'~crud_name~'_delete', { 'id': entity.id }) }}"></a>
</li>
{% endif %}
{% endif %}
{% endblock content_form_actions_delete %}
{% block content_form_actions_save_and_close %}
<li class="">
<button type="submit" name="submit" value="save-and-close" class="btn btn-update" form="{{ formId }}">
{{ 'crud.edit.save_and_close'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_after %}{% endblock %}
</ul>
{% endblock %}
{% endblock %}
{% endblock admin_content %}

View File

@ -0,0 +1,46 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_index.html.twig' %}
{% block table_entities_thead_tr %}
<th>id</th>
<th>{{ 'label'|trans }}</th>
<th>{{ 'icon'|trans }}</th>
<th>{{ 'gender.genderTranslation'|trans }}</th>
<th>{{ 'active'|trans }}</th>
<th>{{ 'ordering'|trans }}</th>
<th></th>
{% endblock %}
{% block table_entities_tbody %}
{% for entity in entities %}
<tr>
<td>{{ entity.id }}</td>
<td>{{ entity.label|localize_translatable_string }}</td>
<td>{{ entity.icon|chill_entity_render_box }}</td>
<td>{{ entity.genderTranslation.value }}</td>
<td style="text-align:center;">
{%- if entity.active -%}
<i class="fa fa-check-square-o"></i>
{%- else -%}
<i class="fa fa-square-o"></i>
{%- endif -%}
</td>
<td>{{ entity.order }}</td>
<td>
<ul class="record_actions">
<li>
<a href="{{ chill_path_add_return_path('chill_crud_main_gender_edit', { 'id': entity.id}) }}" class="btn btn-sm btn-edit btn-mini"></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,79 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
{% endblock %}
{% form_theme form _self %}
{% block _gender_icon_widget %}
{% for child in form %}
<div class="form-check">
<input
type="radio"
id="{{ child.vars.id }}"
name="{{ child.vars.full_name }}"
value="{{ child.vars.value }}"
{% if child.vars.checked %}checked="checked"{% endif %}
/>
<label for="{{ child.vars.id }}">{{ child.vars.label|raw }}</label>
</div>
{% endfor %}
{% endblock %}
{% block admin_content %}
{% set formId = crudMainFormId|default('crud_main_form') %}
{% block crud_content_header %}
<h1>{{ ('crud.' ~ crud_name ~ '.title_new')|trans({'%crud_name%' : crud_name }) }}</h1>
{% endblock crud_content_header %}
{% block crud_content_form %}
{{ form_start(form, { 'attr' : { 'id': formId } }) }}
{{ form_row(form.label) }}
{{ form_row(form.genderTranslation) }}
{{ form_row(form.icon) }}
{{ form_row(form.active) }}
{{ form_row(form.order) }}
{{ form_end(form) }}
{% block crud_content_after_form %}{% endblock %}
{% block crud_content_form_actions %}
<ul class="record_actions sticky-form-buttons">
{% block content_form_actions_back %}
<li class="cancel">
<a class="btn btn-cancel" href="{{ chill_return_path_or('chill_crud_'~crud_name~'_index') }}">
{{ 'Cancel'|trans }}
</a>
</li>
{% endblock %}
{% block content_form_actions_save_and_close %}
<li class="">
<button type="submit" name="submit" value="save-and-close" class="btn btn-create" form="{{ formId }}">
{{ 'crud.new.save_and_close'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_show %}
<li class="">
<button type="submit" name="submit" value="save-and-show" class="btn btn-create" form="{{ formId }}">
{{ 'crud.new.save_and_show'|trans }}
</button>
</li>
{% endblock %}
{% block content_form_actions_save_and_new %}
<li class="">
<button type="submit" name="submit" value="save-and-new" class="btn btn-create" form="{{ formId }}">
{{ 'crud.new.save_and_new'|trans }}
</button>
</li>
{% endblock %}
</ul>
{% endblock %}
{{ form_end(form) }}
{% endblock %}
{% endblock admin_content %}

View File

@ -62,12 +62,12 @@ abstract class AbstractSearch implements SearchInterface
$recomposed .= ' '.$term.':'; $recomposed .= ' '.$term.':';
$containsSpace = str_contains((string) $terms[$term], ' '); $containsSpace = str_contains((string) $terms[$term], ' ');
if ($containsSpace) { if ($containsSpace || is_numeric($terms[$term])) {
$recomposed .= '"'; $recomposed .= '"';
} }
$recomposed .= (false === mb_stristr(' ', (string) $terms[$term])) ? $terms[$term] : '('.$terms[$term].')'; $recomposed .= (false === mb_stristr(' ', (string) $terms[$term])) ? $terms[$term] : '('.$terms[$term].')';
if ($containsSpace) { if ($containsSpace || is_numeric($terms[$term])) {
$recomposed .= '"'; $recomposed .= '"';
} }
} }

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\MainBundle\Templating\Entity;
use Chill\MainBundle\Entity\GenderIconEnum;
/**
* @implements ChillEntityRenderInterface<GenderIconEnum>
*/
final readonly class ChillGenderIconRender implements ChillEntityRenderInterface
{
public function renderBox($icon, array $options): string
{
return '<i class="'.htmlspecialchars($icon->value, ENT_QUOTES, 'UTF-8').'"></i>';
}
public function renderString($icon, array $options): string
{
return $icon->value;
}
public function supports($icon, array $options): bool
{
return $icon instanceof GenderIconEnum;
}
}

View File

@ -49,6 +49,8 @@ services:
Chill\MainBundle\Templating\Entity\NewsItemRender: ~ Chill\MainBundle\Templating\Entity\NewsItemRender: ~
Chill\MainBundle\Templating\Entity\ChillGenderIconRender: ~
Chill\MainBundle\Templating\Entity\UserRender: ~ Chill\MainBundle\Templating\Entity\UserRender: ~
Chill\MainBundle\Templating\Listing\: Chill\MainBundle\Templating\Listing\:

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20240926093955 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create gender table and default entities';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE SEQUENCE chill_main_gender_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_main_gender (id INT NOT NULL, label JSON NOT NULL, active BOOLEAN NOT NULL, genderTranslation VARCHAR(255) NOT NULL, icon VARCHAR(255) NOT NULL, ordering DOUBLE PRECISION DEFAULT \'0.0\', PRIMARY KEY(id))');
// Insert the four gender records into the chill_main_gender table
$this->addSql("
INSERT INTO chill_main_gender (id, label, active, genderTranslation, icon, ordering)
VALUES
(nextval('chill_main_gender_id_seq'),
'{\"fr\": \"homme\", \"nl\": \"man\"}',
true,
'man',
'',
1.0
),
(nextval('chill_main_gender_id_seq'),
'{\"fr\": \"femme\", \"nl\": \"vrouw\"}',
true,
'woman',
'',
1.1
),
(nextval('chill_main_gender_id_seq'),
'{\"fr\": \"neutre\", \"nl\": \"neutraal\"}',
true,
'neutral',
'',
1.1
),
(nextval('chill_main_gender_id_seq'),
'{\"fr\": \"inconnu\", \"nl\": \"ongekend\"}',
true,
'unknown',
'',
1.2
)
");
}
public function down(Schema $schema): void
{
$this->addSql('DROP SEQUENCE chill_main_gender_id_seq CASCADE');
$this->addSql('DROP TABLE chill_main_gender');
}
}

View File

@ -43,6 +43,7 @@ lifecycleUpdate: Evenements de création et mise à jour
address_fields: Données liées à l'adresse address_fields: Données liées à l'adresse
Datas: Données Datas: Données
No title: Aucun titre No title: Aucun titre
icon: icône
user: user:
profile: profile:
@ -447,6 +448,12 @@ crud:
title_delete: Supprimer une actualité title_delete: Supprimer une actualité
button_delete: Supprimer button_delete: Supprimer
confirm_message_delete: Êtes-vous sûr de vouloir supprimer l'actualité, "%as_string%" ? confirm_message_delete: Êtes-vous sûr de vouloir supprimer l'actualité, "%as_string%" ?
main_gender:
index:
title: Liste des genres
add_new: Ajouter un genre
title_new: Nouveau genre
title_edit: Modifier un genre
No entities: Aucun élément No entities: Aucun élément
@ -704,4 +711,8 @@ news:
read_more: Lire la suite read_more: Lire la suite
show_details: Voir l'actualité show_details: Voir l'actualité
gender:
genderTranslation: traduction grammaticale
not defined: Non défini

View File

@ -15,11 +15,14 @@ use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Country; use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\GenderEnum;
use Chill\MainBundle\Entity\PostalCode; use Chill\MainBundle\Entity\PostalCode;
use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\CenterRepository; use Chill\MainBundle\Repository\CenterRepository;
use Chill\MainBundle\Repository\CountryRepository; use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\GenderRepository;
use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Repository\UserRepository;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
@ -77,12 +80,15 @@ class LoadPeople extends AbstractFixture implements ContainerAwareInterface, Ord
*/ */
protected array $cacheUsers = []; protected array $cacheUsers = [];
/**
* @var array|Gender[]
*/
protected array $cacheGenders = [];
protected Generator $faker; protected Generator $faker;
protected NativeLoader $loader; protected NativeLoader $loader;
private array $genders = [Person::MALE_GENDER, Person::FEMALE_GENDER, Person::BOTH_GENDER];
private array $peoples = [ private array $peoples = [
[ [
'lastName' => 'Depardieu', 'lastName' => 'Depardieu',
@ -90,7 +96,7 @@ class LoadPeople extends AbstractFixture implements ContainerAwareInterface, Ord
'birthdate' => '1948-12-27', 'birthdate' => '1948-12-27',
'placeOfBirth' => 'Châteauroux', 'placeOfBirth' => 'Châteauroux',
'nationality' => 'RU', 'nationality' => 'RU',
'gender' => Person::MALE_GENDER, 'gender' => GenderEnum::MALE,
'center' => 'Center A', 'center' => 'Center A',
'accompanyingPeriods' => [ 'accompanyingPeriods' => [
[ [
@ -119,7 +125,7 @@ class LoadPeople extends AbstractFixture implements ContainerAwareInterface, Ord
'maritalStatus' => 'ms_divorce', 'maritalStatus' => 'ms_divorce',
], ],
[ [
// to have a person with same birthdate of Gérard Depardieu // to have a person with same birthdate as Gérard Depardieu
'lastName' => 'Van Snick', 'lastName' => 'Van Snick',
'firstName' => 'Bart', 'firstName' => 'Bart',
'birthdate' => '1948-12-27', 'birthdate' => '1948-12-27',
@ -131,7 +137,7 @@ class LoadPeople extends AbstractFixture implements ContainerAwareInterface, Ord
'lastName' => 'Depardieu', 'lastName' => 'Depardieu',
'firstName' => 'Charline', 'firstName' => 'Charline',
'birthdate' => '1970-10-15', 'birthdate' => '1970-10-15',
'gender' => Person::FEMALE_GENDER, 'gender' => GenderEnum::FEMALE,
'center' => 'Center A', 'center' => 'Center A',
'maritalStatus' => 'ms_legalco', 'maritalStatus' => 'ms_legalco',
], ],
@ -228,6 +234,7 @@ class LoadPeople extends AbstractFixture implements ContainerAwareInterface, Ord
protected MaritalStatusRepository $maritalStatusRepository, protected MaritalStatusRepository $maritalStatusRepository,
protected ScopeRepository $scopeRepository, protected ScopeRepository $scopeRepository,
protected UserRepository $userRepository, protected UserRepository $userRepository,
protected GenderRepository $genderRepository,
) { ) {
$this->faker = Factory::create('fr_FR'); $this->faker = Factory::create('fr_FR');
$this->faker->addProvider($this); $this->faker->addProvider($this);
@ -272,9 +279,17 @@ class LoadPeople extends AbstractFixture implements ContainerAwareInterface, Ord
/** /**
* @internal This method is public and called by faker as a custom generator * @internal This method is public and called by faker as a custom generator
*/ */
public function getRandomGender(): string public function getRandomGender(int $nullPercentage = 50): ?Gender
{ {
return $this->genders[array_rand($this->genders)]; if (0 === \count($this->cacheGenders)) {
$this->cacheGenders = $this->genderRepository->findByActiveOrdered();
}
if (\random_int(0, 100) > $nullPercentage) {
return null;
}
return $this->cacheGenders[array_rand($this->cacheGenders)];
} }
/** /**

View File

@ -21,6 +21,7 @@ use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Country; use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\Language; use Chill\MainBundle\Entity\Language;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
@ -59,19 +60,11 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[HouseholdMembershipSequential(groups: ['household_memberships'])] #[HouseholdMembershipSequential(groups: ['household_memberships'])]
class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateInterface, \Stringable class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateInterface, \Stringable
{ {
final public const BOTH_GENDER = 'both';
// have days in commun // have days in commun
final public const ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD = 2; // where there exist final public const ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD = 2; // where there exist
final public const ERROR_PERIODS_ARE_COLLAPSING = 1; // when two different periods final public const ERROR_PERIODS_ARE_COLLAPSING = 1; // when two different periods
final public const FEMALE_GENDER = 'woman';
final public const MALE_GENDER = 'man';
final public const NO_INFORMATION = 'unknown';
/** /**
* Accept receiving email. * Accept receiving email.
*/ */
@ -242,11 +235,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
private ?string $fullnameCanonical = ''; private ?string $fullnameCanonical = '';
/** /**
* The person's gender. * NEW column : The person's gender.
*/ */
#[Assert\NotNull(message: 'The gender must be set')] #[Assert\NotNull(message: 'The gender must be set')]
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 9, nullable: true)] #[ORM\ManyToOne(targetEntity: Gender::class)]
private ?string $gender = null; private ?Gender $gender = null;
/** /**
* Comment on gender. * Comment on gender.
@ -1003,7 +996,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->fullnameCanonical; return $this->fullnameCanonical;
} }
public function getGender(): ?string public function getGender(): ?Gender
{ {
return $this->gender; return $this->gender;
} }
@ -1013,24 +1006,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->genderComment; return $this->genderComment;
} }
/**
* return gender as a Numeric form.
* This is used for translations.
*
* @return int
*
* @deprecated Keep for legacy. Used in Chill 1.5 for feminize before icu translations
*/
public function getGenderNumeric()
{
return match ($this->getGender()) {
self::FEMALE_GENDER => 1,
self::MALE_GENDER => 0,
self::BOTH_GENDER => 2,
default => -1,
};
}
public function getHouseholdAddresses(): Collection public function getHouseholdAddresses(): Collection
{ {
return $this->householdAddresses; return $this->householdAddresses;
@ -1532,7 +1507,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this; return $this;
} }
public function setGender(?string $gender): self public function setGender(?Gender $gender): self
{ {
$this->gender = $gender; $this->gender = $gender;

View File

@ -12,7 +12,8 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\PersonAggregators; namespace Chill\PersonBundle\Export\Aggregator\PersonAggregators;
use Chill\MainBundle\Export\AggregatorInterface; use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Entity\Person; use Chill\MainBundle\Repository\GenderRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -20,7 +21,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class GenderAggregator implements AggregatorInterface final readonly class GenderAggregator implements AggregatorInterface
{ {
public function __construct(private TranslatorInterface $translator) {} public function __construct(private TranslatorInterface $translator, private TranslatableStringHelperInterface $translatableStringHelper, private GenderRepository $repository) {}
public function addRole(): ?string public function addRole(): ?string
{ {
@ -29,7 +30,8 @@ final readonly class GenderAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data) public function alterQuery(QueryBuilder $qb, $data)
{ {
$qb->addSelect('person.gender as gender'); $qb->leftJoin('person.gender', 'g');
$qb->addSelect('g.id as gender');
$qb->addGroupBy('gender'); $qb->addGroupBy('gender');
} }
@ -48,30 +50,20 @@ final readonly class GenderAggregator implements AggregatorInterface
public function getLabels($key, array $values, $data) public function getLabels($key, array $values, $data)
{ {
return function ($value) { return function (int|string|null $value) {
switch ($value) { if (null === $value || '' === $value) {
case Person::FEMALE_GENDER: return '';
return $this->translator->trans('woman');
case Person::MALE_GENDER:
return $this->translator->trans('man');
case Person::BOTH_GENDER:
return $this->translator->trans('both');
case Person::NO_INFORMATION:
return $this->translator->trans('unknown');
case null:
case '':
return $this->translator->trans('Not given');
case '_header':
return $this->translator->trans('Gender');
default:
throw new \LogicException(sprintf('The value %s is not valid', $value));
} }
if ('_header' === $value) {
return $this->translator->trans('Gender');
}
if (null === $gender = $this->repository->find((int) $value)) {
return '';
}
return (string) $this->translatableStringHelper->localize($gender->getLabel());
}; };
} }

View File

@ -11,11 +11,13 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\PersonFilters; namespace Chill\PersonBundle\Export\Filter\PersonFilters;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Export\DataTransformerInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface; use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Entity\Person; use Chill\MainBundle\Repository\GenderRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
@ -24,17 +26,15 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class GenderFilter implements class GenderFilter implements
ExportElementValidatedInterface, ExportElementValidatedInterface,
FilterInterface FilterInterface,
DataTransformerInterface
{ {
/** // inject gender repository and find the active genders so that you can pass them to the ChoiceType (ordered by ordering)
* @var TranslatorInterface public function __construct(
*/ private readonly TranslatorInterface $translator,
protected $translator; private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly GenderRepository $genderRepository,
public function __construct(TranslatorInterface $translator) ) {}
{
$this->translator = $translator;
}
public function addRole(): ?string public function addRole(): ?string
{ {
@ -46,23 +46,17 @@ class GenderFilter implements
$where = $qb->getDQLPart('where'); $where = $qb->getDQLPart('where');
$isIn = $qb->expr()->in('person.gender', ':person_gender'); $isIn = $qb->expr()->in('person.gender', ':person_gender');
if (!\in_array('null', $data['accepted_genders'], true)) { $acceptedGenders = $data['accepted_genders_entity'];
$nullIncluded = in_array(null, $acceptedGenders ?? [], true);
if (!$nullIncluded) {
$clause = $isIn; $clause = $isIn;
} else { } else {
$clause = $qb->expr()->orX($isIn, $qb->expr()->isNull('person.gender')); $clause = $qb->expr()->orX($isIn, $qb->expr()->isNull('person.gender'));
} }
if ($where instanceof Expr\Andx) { $qb->andWhere($clause);
$where->add($clause); $qb->setParameter('person_gender', array_filter($acceptedGenders ?? [], fn ($gender) => null !== $gender));
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('person_gender', \array_filter(
$data['accepted_genders'],
static fn ($el) => 'null' !== $el
));
} }
public function applyOn() public function applyOn()
@ -72,19 +66,42 @@ class GenderFilter implements
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
$builder->add('accepted_genders', ChoiceType::class, [ $genderChoices = $this->genderRepository->findByActiveOrdered();
'choices' => [ $choices = ['None' => null];
'Woman' => Person::FEMALE_GENDER,
'Man' => Person::MALE_GENDER, foreach ($genderChoices as $gender) {
'Both' => Person::BOTH_GENDER, $choices[$this->translatableStringHelper->localize($gender->getLabel())] = $gender->getId();
'Unknown' => Person::NO_INFORMATION, }
'Not given' => 'null',
], $builder->add('accepted_genders_entity', ChoiceType::class, [
'choices' => $choices,
'multiple' => true, 'multiple' => true,
'expanded' => true, 'expanded' => true,
'placeholder' => 'Select gender',
]); ]);
} }
public function transformData(?array $before): array
{
$transformedData = [];
$transformedData['accepted_genders_entity'] = $before['accepted_genders_entity'] ?? [];
if (array_key_exists('accepted_genders', $before)) {
foreach ($before['accepted_genders'] as $genderBefore) {
foreach ($this->genderRepository->findByGenderTranslation(
match ($genderBefore) {
'both' => 'neutral',
default => $genderBefore,
}
) as $gender) {
$transformedData['accepted_genders_entity'][] = $gender;
}
}
}
return $transformedData;
}
public function getFormDefaultData(): array public function getFormDefaultData(): array
{ {
return []; return [];
@ -94,11 +111,11 @@ class GenderFilter implements
{ {
$genders = []; $genders = [];
foreach ($data['accepted_genders'] as $g) { foreach ($data['accepted_genders_entity'] as $g) {
if ('null' === $g) { if (null === $g) {
$genders[] = $this->translator->trans('Not given'); $genders[] = $this->translator->trans('export.filter.person.gender.no_gender');
} else { } else {
$genders[] = $this->translator->trans($g); $genders[] = $this->translatableStringHelper->localize($this->genderRepository->find($g)->getLabel());
} }
} }
@ -120,7 +137,7 @@ class GenderFilter implements
public function validateForm($data, ExecutionContextInterface $context) public function validateForm($data, ExecutionContextInterface $context)
{ {
if (!\is_array($data['accepted_genders']) || 0 === \count($data['accepted_genders'])) { if (!\is_iterable($data['accepted_genders_entity']) || 0 === \count($data['accepted_genders_entity'])) {
$context->buildViolation('You should select an option') $context->buildViolation('You should select an option')
->addViolation(); ->addViolation();
} }

View File

@ -16,6 +16,7 @@ use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Repository\CivilityRepositoryInterface; use Chill\MainBundle\Repository\CivilityRepositoryInterface;
use Chill\MainBundle\Repository\CountryRepository; use Chill\MainBundle\Repository\CountryRepository;
use Chill\MainBundle\Repository\GenderRepository;
use Chill\MainBundle\Repository\LanguageRepositoryInterface; use Chill\MainBundle\Repository\LanguageRepositoryInterface;
use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
@ -80,6 +81,7 @@ final readonly class ListPersonHelper
private TranslatableStringHelper $translatableStringHelper, private TranslatableStringHelper $translatableStringHelper,
private TranslatorInterface $translator, private TranslatorInterface $translator,
private UserRepositoryInterface $userRepository, private UserRepositoryInterface $userRepository,
private GenderRepository $genderRepository,
/** /**
* @var iterable<CustomizeListPersonHelperInterface> * @var iterable<CustomizeListPersonHelperInterface>
*/ */
@ -173,6 +175,11 @@ final readonly class ListPersonHelper
break; break;
case 'gender':
$qb->addSelect('IDENTITY(person.gender) AS gender');
break;
case 'maritalStatus': case 'maritalStatus':
$qb->addSelect('IDENTITY(person.maritalStatus) AS maritalStatus'); $qb->addSelect('IDENTITY(person.maritalStatus) AS maritalStatus');
@ -325,7 +332,13 @@ final readonly class ListPersonHelper
return $this->translator->trans($key); return $this->translator->trans($key);
} }
return $this->translator->trans($value); if (null === $value) {
return '';
}
$gender = $this->genderRepository->find($value);
return $this->translatableStringHelper->localize($gender->getLabel());
}; };
case 'maritalStatus': case 'maritalStatus':

View File

@ -20,8 +20,8 @@ use Chill\MainBundle\Form\Type\PickCenterType;
use Chill\MainBundle\Form\Type\PickCivilityType; use Chill\MainBundle\Form\Type\PickCivilityType;
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\Type\GenderType;
use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Form\Type\PersonAltNameType;
use Chill\PersonBundle\Form\Type\PickGenderType;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use libphonenumber\PhoneNumberType; use libphonenumber\PhoneNumberType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
@ -60,7 +60,7 @@ final class CreationPersonType extends AbstractType
'label' => 'Civility', 'label' => 'Civility',
'placeholder' => 'choose civility', 'placeholder' => 'choose civility',
]) ])
->add('gender', GenderType::class, [ ->add('gender', PickGenderType::class, [
'required' => true, 'placeholder' => null, 'required' => true, 'placeholder' => null,
]) ])
->add('birthdate', ChillDateType::class, [ ->add('birthdate', ChillDateType::class, [

View File

@ -24,9 +24,9 @@ use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper; use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Entity\PersonPhone;
use Chill\PersonBundle\Form\Type\GenderType;
use Chill\PersonBundle\Form\Type\PersonAltNameType; use Chill\PersonBundle\Form\Type\PersonAltNameType;
use Chill\PersonBundle\Form\Type\PersonPhoneType; use Chill\PersonBundle\Form\Type\PersonPhoneType;
use Chill\PersonBundle\Form\Type\PickGenderType;
use Chill\PersonBundle\Form\Type\Select2MaritalStatusType; use Chill\PersonBundle\Form\Type\Select2MaritalStatusType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\CallbackTransformer;
@ -80,7 +80,7 @@ class PersonType extends AbstractType
'input' => 'datetime_immutable', 'input' => 'datetime_immutable',
'widget' => 'single_text', 'widget' => 'single_text',
]) ])
->add('gender', GenderType::class, [ ->add('gender', PickGenderType::class, [
'required' => true, 'required' => true,
]) ])
->add('genderComment', CommentType::class, [ ->add('genderComment', CommentType::class, [

View File

@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\PersonBundle\Form\Type;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* A type to select the civil union state.
*/
class GenderType extends AbstractType
{
public function configureOptions(OptionsResolver $resolver)
{
$a = [
Person::MALE_GENDER => Person::MALE_GENDER,
Person::FEMALE_GENDER => Person::FEMALE_GENDER,
Person::BOTH_GENDER => Person::BOTH_GENDER,
];
$resolver->setDefaults([
'choices' => $a,
'expanded' => true,
'multiple' => false,
'placeholder' => null,
]);
}
public function getParent()
{
return ChoiceType::class;
}
}

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\PersonBundle\Form\Type;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* A type to select the civil union state.
*/
class PickGenderType extends AbstractType
{
public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {}
public function configureOptions(OptionsResolver $resolver)
{
$resolver
->setDefault('label', 'Gender')
->setDefault(
'choice_label',
fn (Gender $gender): string => $this->translatableStringHelper->localize($gender->getLabel())
)
->setDefault(
'query_builder',
static fn (EntityRepository $er): QueryBuilder => $er->createQueryBuilder('g')
->where('g.active = true')
->orderBy('g.order'),
)
->setDefault('placeholder', 'choose gender')
->setDefault('class', Gender::class);
}
public function getParent()
{
return EntityType::class;
}
}

View File

@ -45,13 +45,17 @@ class AdminPersonMenuBuilder implements LocalMenuBuilderInterface
'route' => 'chill_crud_main_civility_index', 'route' => 'chill_crud_main_civility_index',
])->setExtras(['order' => 2010]); ])->setExtras(['order' => 2010]);
$menu->addChild('Gender', [
'route' => 'chill_crud_main_gender_index',
])->setExtras(['order' => 2020]);
$menu->addChild('Marital status', [ $menu->addChild('Marital status', [
'route' => 'chill_crud_person_marital-status_index', 'route' => 'chill_crud_person_marital-status_index',
])->setExtras(['order' => 2020]); ])->setExtras(['order' => 2030]);
$menu->addChild('person_admin.person_resource_kind', [ $menu->addChild('person_admin.person_resource_kind', [
'route' => 'chill_crud_person_resource-kind_index', 'route' => 'chill_crud_person_resource-kind_index',
])->setExtras(['order' => 2030]); ])->setExtras(['order' => 2040]);
} }
public static function getMenuIds(): array public static function getMenuIds(): array

View File

@ -34,7 +34,7 @@ final readonly class PersonACLAwareRepository implements PersonACLAwareRepositor
?\DateTimeInterface $birthdate = null, ?\DateTimeInterface $birthdate = null,
?\DateTimeInterface $birthdateBefore = null, ?\DateTimeInterface $birthdateBefore = null,
?\DateTimeInterface $birthdateAfter = null, ?\DateTimeInterface $birthdateAfter = null,
?string $gender = null, ?int $gender = null,
?string $countryCode = null, ?string $countryCode = null,
?string $phonenumber = null, ?string $phonenumber = null,
?string $city = null, ?string $city = null,
@ -62,7 +62,7 @@ final readonly class PersonACLAwareRepository implements PersonACLAwareRepositor
?\DateTimeInterface $birthdate = null, ?\DateTimeInterface $birthdate = null,
?\DateTimeInterface $birthdateBefore = null, ?\DateTimeInterface $birthdateBefore = null,
?\DateTimeInterface $birthdateAfter = null, ?\DateTimeInterface $birthdateAfter = null,
?string $gender = null, ?int $gender = null,
?string $countryCode = null, ?string $countryCode = null,
?string $phonenumber = null, ?string $phonenumber = null,
?string $city = null, ?string $city = null,
@ -96,7 +96,7 @@ final readonly class PersonACLAwareRepository implements PersonACLAwareRepositor
?\DateTimeInterface $birthdate = null, ?\DateTimeInterface $birthdate = null,
?\DateTimeInterface $birthdateBefore = null, ?\DateTimeInterface $birthdateBefore = null,
?\DateTimeInterface $birthdateAfter = null, ?\DateTimeInterface $birthdateAfter = null,
?string $gender = null, ?int $gender = null,
?string $countryCode = null, ?string $countryCode = null,
?string $phonenumber = null, ?string $phonenumber = null,
?string $city = null, ?string $city = null,
@ -202,7 +202,7 @@ final readonly class PersonACLAwareRepository implements PersonACLAwareRepositor
} }
if (null !== $gender) { if (null !== $gender) {
$query->andWhereClause('person.gender = ?', [$gender]); $query->andWhereClause('person.gender_id = ?', [$gender]);
} }
return $query; return $query;
@ -253,7 +253,7 @@ final readonly class PersonACLAwareRepository implements PersonACLAwareRepositor
?\DateTimeInterface $birthdate = null, ?\DateTimeInterface $birthdate = null,
?\DateTimeInterface $birthdateBefore = null, ?\DateTimeInterface $birthdateBefore = null,
?\DateTimeInterface $birthdateAfter = null, ?\DateTimeInterface $birthdateAfter = null,
?string $gender = null, ?int $gender = null,
?string $countryCode = null, ?string $countryCode = null,
?string $phonenumber = null, ?string $phonenumber = null,
?string $city = null, ?string $city = null,

View File

@ -23,7 +23,7 @@ interface PersonACLAwareRepositoryInterface
?\DateTimeInterface $birthdate = null, ?\DateTimeInterface $birthdate = null,
?\DateTimeInterface $birthdateBefore = null, ?\DateTimeInterface $birthdateBefore = null,
?\DateTimeInterface $birthdateAfter = null, ?\DateTimeInterface $birthdateAfter = null,
?string $gender = null, ?int $gender = null,
?string $countryCode = null, ?string $countryCode = null,
?string $phonenumber = null, ?string $phonenumber = null,
?string $city = null, ?string $city = null,
@ -36,7 +36,7 @@ interface PersonACLAwareRepositoryInterface
?\DateTimeInterface $birthdate = null, ?\DateTimeInterface $birthdate = null,
?\DateTimeInterface $birthdateBefore = null, ?\DateTimeInterface $birthdateBefore = null,
?\DateTimeInterface $birthdateAfter = null, ?\DateTimeInterface $birthdateAfter = null,
?string $gender = null, ?int $gender = null,
?string $countryCode = null, ?string $countryCode = null,
?string $phonenumber = null, ?string $phonenumber = null,
?string $city = null, ?string $city = null,
@ -55,7 +55,7 @@ interface PersonACLAwareRepositoryInterface
?\DateTimeInterface $birthdate = null, ?\DateTimeInterface $birthdate = null,
?\DateTimeInterface $birthdateBefore = null, ?\DateTimeInterface $birthdateBefore = null,
?\DateTimeInterface $birthdateAfter = null, ?\DateTimeInterface $birthdateAfter = null,
?string $gender = null, ?int $gender = null,
?string $countryCode = null, ?string $countryCode = null,
?string $phonenumber = null, ?string $phonenumber = null,
?string $city = null, ?string $city = null,

View File

@ -128,7 +128,7 @@ export default {
body.mobilenumber = payload.data.mobilenumber; body.mobilenumber = payload.data.mobilenumber;
body.email = payload.data.email; body.email = payload.data.email;
body.altNames = payload.data.altNames; body.altNames = payload.data.altNames;
body.gender = payload.data.gender; body.gender = {id: payload.data.gender.id, type: payload.data.gender.type };
if (payload.data.civility !== null) { body.civility = {id: payload.data.civility.id, type: payload.data.civility.type }; } if (payload.data.civility !== null) { body.civility = {id: payload.data.civility.id, type: payload.data.civility.type }; }
makeFetch('PATCH', `/api/1.0/person/person/${payload.data.id}.json`, body) makeFetch('PATCH', `/api/1.0/person/person/${payload.data.id}.json`, body)

View File

@ -153,7 +153,7 @@ export default {
body.mobilenumber = payload.data.mobilenumber; body.mobilenumber = payload.data.mobilenumber;
body.email = payload.data.email; body.email = payload.data.email;
body.altNames = payload.data.altNames; body.altNames = payload.data.altNames;
body.gender = payload.data.gender; body.gender = {id: payload.data.gender.id, type: payload.data.gender.type };
if (payload.data.civility !== null) { body.civility = {id: payload.data.civility.id, type: payload.data.civility.type}; } if (payload.data.civility !== null) { body.civility = {id: payload.data.civility.id, type: payload.data.civility.type}; }
makeFetch('PATCH', `/api/1.0/person/person/${payload.data.id}.json`, body) makeFetch('PATCH', `/api/1.0/person/person/${payload.data.id}.json`, body)

View File

@ -8,7 +8,7 @@
{{ $t('household_members_editor.holder') }} {{ $t('household_members_editor.holder') }}
</span> </span>
</div> </div>
<div v-if="conc.person.birthdate !== null">{{ $t('person.born', {'gender': conc.person.gender} ) }}</div> <div v-if="conc.person.birthdate !== null">{{ $t('person.born', {'gender': conc.person.gender.genderTranslation} ) }}</div>
</div> </div>
<div class="item-col"> <div class="item-col">
<ul class="list-content fa-ul"> <ul class="list-content fa-ul">

View File

@ -12,10 +12,6 @@ const visMessages = {
Holder: 'Titulaire', Holder: 'Titulaire',
Legend: 'Calques', Legend: 'Calques',
concerned: 'concerné', concerned: 'concerné',
// both: 'neutre, non binaire',
woman: 'féminin',
man: 'masculin',
undefined: "genre non précisé",
years: 'ans', years: 'ans',
click_to_expand: 'cliquez pour étendre', click_to_expand: 'cliquez pour étendre',
add_relationship_link: "Créer un lien de filiation", add_relationship_link: "Créer un lien de filiation",
@ -64,7 +60,7 @@ const visMessages = {
placeholder: "Choisissez le genre de l'usager", placeholder: "Choisissez le genre de l'usager",
woman: "Féminin", woman: "Féminin",
man: "Masculin", man: "Masculin",
both: "Neutre, non binaire", neutral: "Neutre, non binaire",
undefined: "Non renseigné", undefined: "Non renseigné",
unknown: "Non renseigné" unknown: "Non renseigné"
} }

View File

@ -1,6 +1,6 @@
import { createStore } from 'vuex' import { createStore } from 'vuex'
import { getHouseholdByPerson, getCoursesByPerson, getRelationshipsByPerson } from './api' import { getHouseholdByPerson, getCoursesByPerson, getRelationshipsByPerson } from './api'
import { getHouseholdLabel, getHouseholdWidth, getRelationshipLabel, getRelationshipTitle, getRelationshipDirection, splitId, getGender, getAge } from './vis-network' import { getHouseholdLabel, getHouseholdWidth, getRelationshipLabel, getRelationshipTitle, getRelationshipDirection, splitId, getAge } from './vis-network'
import {visMessages} from "./i18n"; import {visMessages} from "./i18n";
import { darkBlue, darkBrown, darkGreen, lightBlue, lightBrown, lightGreen } from './colors'; import { darkBlue, darkBrown, darkGreen, lightBlue, lightBrown, lightGreen } from './colors';
@ -148,7 +148,7 @@ const store = createStore({
person.group = person.type person.group = person.type
person._id = person.id person._id = person.id
person.id = `person_${person.id}` person.id = `person_${person.id}`
person.label = `*${person.text}${person.deathdate ? ' (‡)' : ''}*\n_${getGender(person.gender)}${age}_${debug}` person.label = `*${person.text}${person.deathdate ? ' (‡)' : ''}*\n_${person.gender.label.fr}${age}_${debug}`
person.folded = false person.folded = false
// folded is used for missing persons // folded is used for missing persons
if (options.folded) { if (options.folded) {

View File

@ -141,25 +141,6 @@ window.options = {
} }
} }
/**
* @param gender
* @returns {string}
*/
const getGender = (gender) => {
switch (gender) {
case 'both':
return visMessages.fr.visgraph.both
case 'woman':
return visMessages.fr.visgraph.woman
case 'man':
return visMessages.fr.visgraph.man
case 'unknown':
return visMessages.fr.visgraph.unknown
default:
return visMessages.fr.visgraph.undefined
}
}
/** /**
* TODO only one abstract function (-> getAge() is repeated in PersonRenderBox.vue) * TODO only one abstract function (-> getAge() is repeated in PersonRenderBox.vue)
* @param person * @param person
@ -251,7 +232,6 @@ const splitId = (id, position) => {
} }
export { export {
getGender,
getAge, getAge,
getHouseholdLabel, getHouseholdLabel,
getHouseholdWidth, getHouseholdWidth,

View File

@ -24,6 +24,13 @@ const getCivilities = () =>
throw Error('Error with request resource response'); throw Error('Error with request resource response');
}); });
const getGenders = () => makeFetch("GET", '/api/1.0/main/gender.json')
// .then(response => {
// console.log(response)
// if (response.ok) { return response.json(); }
// throw Error('Error with request resource response');
// });
const getCentersForPersonCreation = () => makeFetch('GET', '/api/1.0/person/creation/authorized-centers', null); const getCentersForPersonCreation = () => makeFetch('GET', '/api/1.0/person/creation/authorized-centers', null);
/* /*
@ -63,10 +70,11 @@ const patchPerson = (id, body) => {
}; };
export { export {
getCentersForPersonCreation, getCentersForPersonCreation,
getPerson, getPerson,
getPersonAltNames, getPersonAltNames,
getCivilities, getCivilities,
getGenders,
postPerson, postPerson,
patchPerson patchPerson
}; };

View File

@ -37,10 +37,9 @@
</div> </div>
<p v-if="options.addInfo === true" class="moreinfo"> <p v-if="options.addInfo === true" class="moreinfo">
<i :class="'fa fa-fw ' + getGenderIcon" :title="$t(getGender)"></i> <gender-icon-render-box v-if="person.gender" :gender="person.gender"></gender-icon-render-box>
<time v-if="person.birthdate && !person.deathdate" :datetime="person.birthdate" :title="birthdate"> <time v-if="person.birthdate && !person.deathdate" :datetime="person.birthdate" :title="birthdate">
{{ $t(getGenderTranslation) + ' ' + $d(birthdate, 'text') }} {{ $t(person.gender ? `renderbox.birthday.${person.gender.genderTranslation}` : 'renderbox.birthday.neutral') + ' ' + $d(birthdate, 'text') }}
</time> </time>
<time v-else-if="person.birthdate && person.deathdate" :datetime="person.deathdate" <time v-else-if="person.birthdate && person.deathdate" :datetime="person.deathdate"
@ -180,6 +179,7 @@
<script> <script>
import {dateToISO, ISOToDate} from 'ChillMainAssets/chill/js/date'; import {dateToISO, ISOToDate} from 'ChillMainAssets/chill/js/date';
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue'; import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
import GenderIconRenderBox from 'ChillMainAssets/vuejs/_components/Entity/GenderIconRenderBox.vue'
import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue'; import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue';
import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue'; import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue';
import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue'; import PersonText from 'ChillPersonAssets/vuejs/_components/Entity/PersonText.vue';
@ -190,6 +190,7 @@ export default {
name: "PersonRenderBox", name: "PersonRenderBox",
components: { components: {
AddressRenderBox, AddressRenderBox,
GenderIconRenderBox,
Confidential, Confidential,
BadgeEntity, BadgeEntity,
PersonText, PersonText,
@ -222,15 +223,6 @@ export default {
return false return false
} }
}, },
getGenderIcon: function () {
return this.person.gender === 'woman' ? 'fa-venus' : this.person.gender === 'man' ? 'fa-mars' : this.person.gender === 'both' ? 'fa-neuter' : 'fa-genderless';
},
getGenderTranslation: function () {
return this.person.gender === 'woman' ? 'renderbox.birthday.woman' : 'renderbox.birthday.man';
},
getGender() {
return this.person.gender === 'woman' ? 'person.gender.woman' : this.person.gender === 'man' ? 'person.gender.man' : this.person.gender === 'both' ? 'person.gender.both' : 'person.gender.undefined';
},
birthdate: function () { birthdate: function () {
if (this.person.birthdate !== null || this.person.birthdate === "undefined") { if (this.person.birthdate !== null || this.person.birthdate === "undefined") {
return ISOToDate(this.person.birthdate.datetime); return ISOToDate(this.person.birthdate.datetime);

View File

@ -73,17 +73,16 @@
<!-- TODO fix placeholder if undefined <!-- TODO fix placeholder if undefined
--> -->
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<select <select
class="form-select form-select-lg" class="form-select form-select-lg"
id="gender" id="gender"
v-model="gender" v-model="gender"
@change="checkErrors" >
> <option selected disabled >{{ $t('person.gender.placeholder') }}</option>
<option selected disabled >{{ $t('person.gender.placeholder') }}</option> <option v-for="g in config.genders" :value="g.id" :key="g.id">
<option value="woman">{{ $t('person.gender.woman') }}</option> {{ g.label.fr }}
<option value="man">{{ $t('person.gender.man') }}</option> </option>
<option value="both">{{ $t('person.gender.both') }}</option> </select>
</select>
<label>{{ $t('person.gender.title') }}</label> <label>{{ $t('person.gender.title') }}</label>
</div> </div>
@ -178,7 +177,7 @@
</template> </template>
<script> <script>
import { getCentersForPersonCreation, getCivilities, getPerson, getPersonAltNames } from '../../_api/OnTheFly'; import { getCentersForPersonCreation, getCivilities, getGenders, getPerson, getPersonAltNames } from '../../_api/OnTheFly';
import PersonRenderBox from '../Entity/PersonRenderBox.vue'; import PersonRenderBox from '../Entity/PersonRenderBox.vue';
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"; import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
@ -204,6 +203,7 @@ export default {
altNames: [], altNames: [],
civilities: [], civilities: [],
centers: [], centers: [],
genders: []
}, },
showCenters: false, // NOTE: must remains false if the form is not in create mode showCenters: false, // NOTE: must remains false if the form is not in create mode
showAddressFormValue: false, showAddressFormValue: false,
@ -237,8 +237,8 @@ export default {
get() { return this.person.lastName; } get() { return this.person.lastName; }
}, },
gender: { gender: {
set(value) { this.person.gender = value; }, set(value) { this.person.gender = {id: value, type: 'chill_main_gender'}; },
get() { return this.person.gender; } get() { return this.person.gender ? this.person.gender.id : null; }
}, },
civility: { civility: {
set(value) { this.person.civility = {id: value, type: 'chill_main_civility'}; }, set(value) { this.person.civility = {id: value, type: 'chill_main_civility'}; },
@ -300,13 +300,13 @@ export default {
} }
}, },
genderTranslation() { genderTranslation() {
switch (this.person.gender) { switch (this.person.gender.genderTranslation) {
case 'woman': case 'woman':
return 'person.gender.woman'; return 'person.gender.woman';
case 'man': case 'man':
return 'person.gender.man'; return 'person.gender.man';
case 'both': case 'neutral':
return 'person.gender.both'; return 'person.gender.neutral';
case 'unknown': case 'unknown':
return 'person.gender.unknown'; return 'person.gender.unknown';
default: default:
@ -334,6 +334,12 @@ export default {
this.config.civilities = civilities.results; this.config.civilities = civilities.results;
} }
}); });
getGenders()
.then(genders => {
if ('results' in genders) {
this.config.genders = genders.results;
}
});
if (this.action !== 'create') { if (this.action !== 'create') {
this.loadData(); this.loadData();
} else { } else {

View File

@ -15,7 +15,7 @@ const personMessages = {
person: { person: {
firstname: "Prénom", firstname: "Prénom",
lastname: "Nom", lastname: "Nom",
born: (ctx: {gender: "man"|"woman"|"unknown"}) => { born: (ctx: {gender: "man"|"woman"|"neutral"}) => {
if (ctx.gender === 'man') { if (ctx.gender === 'man') {
return 'Né le'; return 'Né le';
} else if (ctx.gender === 'woman') { } else if (ctx.gender === 'woman') {
@ -36,7 +36,7 @@ const personMessages = {
placeholder: "Choisissez le genre de l'usager", placeholder: "Choisissez le genre de l'usager",
woman: "Féminin", woman: "Féminin",
man: "Masculin", man: "Masculin",
both: "Neutre, non binaire", neutral: "Neutre, non binaire",
unknown: "Non renseigné", unknown: "Non renseigné",
undefined: "Non renseigné" undefined: "Non renseigné"
}, },

View File

@ -85,13 +85,8 @@
{%- endif -%} {%- endif -%}
</div> </div>
{%- if options['addInfo'] -%} {%- if options['addInfo'] -%}
{% set gender = (person.gender == 'woman') ? 'fa-venus' :
(person.gender == 'man') ? 'fa-mars' : (person.gender == 'both') ? 'fa-neuter' : 'fa-genderless' %}
{% set genderTitle = (person.gender == 'woman') ? 'woman' :
(person.gender == 'man') ? 'man' : (person.gender == 'both') ? 'both' : 'Not given'|trans %}
<p class="moreinfo"> <p class="moreinfo">
<i class="fa fa-fw {{ gender }}" title="{{ genderTitle|trans }}"></i> {% if person.gender is not null %}{{ person.gender.icon|chill_entity_render_box }}{% endif %}
{%- if person.deathdate is not null -%} {%- if person.deathdate is not null -%}
{%- if person.birthdate is not null -%} {%- if person.birthdate is not null -%}
{# must be on one line to avoid spaces with dash #} {# must be on one line to avoid spaces with dash #}
@ -106,7 +101,7 @@
{%- endif -%} {%- endif -%}
{%- elseif person.birthdate is not null -%} {%- elseif person.birthdate is not null -%}
<time datetime="{{ person.birthdate|date('Y-m-d') }}" title="{{ 'Birthdate'|trans }}"> <time datetime="{{ person.birthdate|date('Y-m-d') }}" title="{{ 'Birthdate'|trans }}">
{{ 'Born the date'|trans({'gender': person.gender, {{ 'Born the date'|trans({'gender': person.gender ? person.gender.genderTranslation.value : 'neutral',
'birthdate': person.birthdate|format_date("medium") }) }} 'birthdate': person.birthdate|format_date("medium") }) }}
</time> </time>
{%- if options['addAge'] -%} {%- if options['addAge'] -%}

View File

@ -45,7 +45,7 @@
<div class="ms-auto"> <div class="ms-auto">
{% if acp.requestoranonymous == false and acp.requestorPerson is same as(person) %} {% if acp.requestoranonymous == false and acp.requestorPerson is same as(person) %}
<span class="as-requestor badge bg-info" title="{{ 'Requestor'|trans|e('html_attr') }}"> <span class="as-requestor badge bg-info" title="{{ 'Requestor'|trans|e('html_attr') }}">
{{ 'Requestor'|trans({'gender': person.gender}) }} {{ 'Requestor'|trans({'gender': person.gender ? person.gender.genderTranslation.value : 'neutral'}) }}
</span> </span>
{% endif %} {% endif %}
@ -119,7 +119,7 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if participating %} {% if participating %}
{{ 'person.and_himself'|trans({'gender': person.gender}) }} {{ 'person.And himself'|trans({'gender': person.gender ? person.gender.genderTranslation.value : 'neutral'}) }}
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -131,7 +131,7 @@
<div class="wl-col title"> <div class="wl-col title">
<h3> <h3>
{% if acp.requestorPerson is not null %} {% if acp.requestorPerson is not null %}
{{ 'Requestor'|trans({'gender': acp.requestorPerson.gender}) }} {{ 'Requestor'|trans({'gender': acp.requestorPerson.gender ? person.gender.genderTranslation.value : 'neutral'}) }}
{% else %} {% else %}
{{ 'Requestor'|trans({'gender': 'other'})}} {{ 'Requestor'|trans({'gender': 'other'})}}
{% endif %} {% endif %}

View File

@ -73,8 +73,11 @@ This view should receive those arguments:
{% endfor %} {% endfor %}
<dt>{{ 'Gender'|trans }}&nbsp;:</dt> <dt>{{ 'Gender'|trans }}&nbsp;:</dt>
<dd>{{ ( person.gender|default('Not given'))|trans }}</dd> {% if person.gender %}
<dd>{{ ( person.gender.label|localize_translatable_string ) }}</dd>
{% else %}
<dd>{{ 'gender.not defined'|trans }}</dd>
{% endif %}
</dl> </dl>
</figure> </figure>
</div> </div>
@ -253,7 +256,6 @@ This view should receive those arguments:
<dd>{% if el.description is not empty %}{{ el.description }}&nbsp;:&nbsp;{% endif %}<a href="tel:{{ el.phonenumber|phone_number_format('E164') }}">{{ el.phonenumber|chill_format_phonenumber }}</a></dd> <dd>{% if el.description is not empty %}{{ el.description }}&nbsp;:&nbsp;{% endif %}<a href="tel:{{ el.phonenumber|phone_number_format('E164') }}">{{ el.phonenumber|chill_format_phonenumber }}</a></dd>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul>
</dl> </dl>
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@ -2,7 +2,7 @@
<ul> <ul>
<li><b>{{ 'gender'|trans }}</b>: <li><b>{{ 'gender'|trans }}</b>:
{{ person.gender|trans }}</li> {{ person.gender.label|localize_translatable_string }}</li>
<li><b>{{ 'maritalStatus'|trans }}</b>: <li><b>{{ 'maritalStatus'|trans }}</b>:
{% if person.maritalStatus %}{{ person.maritalStatus.name|localize_translatable_string }}{% endif %}</li> {% if person.maritalStatus %}{{ person.maritalStatus.name|localize_translatable_string }}{% endif %}</li>
<li><b>{{ 'birthdate'|trans }}</b>: <li><b>{{ 'birthdate'|trans }}</b>:

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Search;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\ChillPhoneNumberType; use Chill\MainBundle\Form\Type\ChillPhoneNumberType;
use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\GenderRepository;
use Chill\MainBundle\Search\AbstractSearch; use Chill\MainBundle\Search\AbstractSearch;
use Chill\MainBundle\Search\HasAdvancedSearchFormInterface; use Chill\MainBundle\Search\HasAdvancedSearchFormInterface;
use Chill\MainBundle\Search\ParsingException; use Chill\MainBundle\Search\ParsingException;
@ -21,7 +22,7 @@ use Chill\MainBundle\Search\SearchInterface;
use Chill\MainBundle\Search\Utils\ExtractDateFromPattern; use Chill\MainBundle\Search\Utils\ExtractDateFromPattern;
use Chill\MainBundle\Search\Utils\ExtractPhonenumberFromPattern; use Chill\MainBundle\Search\Utils\ExtractPhonenumberFromPattern;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Form\Type\GenderType; use Chill\PersonBundle\Form\Type\PickGenderType;
use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface; use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface;
use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumber;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
@ -36,7 +37,14 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
'birthdate-after', 'gender', 'nationality', 'phonenumber', 'city', 'birthdate-after', 'gender', 'nationality', 'phonenumber', 'city',
]; ];
public function __construct(private readonly \Twig\Environment $templating, private readonly ExtractDateFromPattern $extractDateFromPattern, private readonly ExtractPhonenumberFromPattern $extractPhonenumberFromPattern, private readonly PaginatorFactory $paginatorFactory, private readonly PersonACLAwareRepositoryInterface $personACLAwareRepository) {} public function __construct(
private readonly \Twig\Environment $templating,
private readonly ExtractDateFromPattern $extractDateFromPattern,
private readonly ExtractPhonenumberFromPattern $extractPhonenumberFromPattern,
private readonly PaginatorFactory $paginatorFactory,
private readonly PersonACLAwareRepositoryInterface $personACLAwareRepository,
private readonly GenderRepository $genderRepository,
) {}
public function buildForm(FormBuilderInterface $builder) public function buildForm(FormBuilderInterface $builder)
{ {
@ -69,7 +77,7 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
'required' => false, 'required' => false,
'label' => 'Part of the phonenumber', 'label' => 'Part of the phonenumber',
]) ])
->add('gender', GenderType::class, [ ->add('gender', PickGenderType::class, [
'label' => 'Gender', 'label' => 'Gender',
'required' => false, 'required' => false,
'expanded' => false, 'expanded' => false,
@ -87,7 +95,7 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
$string .= !isset($data['_default']) ? '' : $data['_default'].' '; $string .= !isset($data['_default']) ? '' : $data['_default'].' ';
foreach (['firstname', 'lastname', 'gender', 'city'] as $key) { foreach (['firstname', 'lastname', 'city'] as $key) {
$string .= !isset($data[$key]) ? '' : $key.':'. $string .= !isset($data[$key]) ? '' : $key.':'.
// add quote if contains spaces // add quote if contains spaces
(str_contains((string) $data[$key], ' ') ? '"'.$data[$key].'"' : $data[$key]) (str_contains((string) $data[$key], ' ') ? '"'.$data[$key].'"' : $data[$key])
@ -103,6 +111,8 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
$string .= !isset($data['phonenumber']) ? '' : 'phonenumber:'.$data['phonenumber']->getNationalNumber(); $string .= !isset($data['phonenumber']) ? '' : 'phonenumber:'.$data['phonenumber']->getNationalNumber();
$string .= !isset($data['gender']) ? '' : 'gender:"'.$data['gender']->getId().'"';
return $string; return $string;
} }
@ -110,7 +120,7 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
{ {
$data = []; $data = [];
foreach (['firstname', 'lastname', 'gender', '_default', 'phonenumber', 'city'] as $key) { foreach (['firstname', 'lastname', '_default', 'phonenumber', 'city'] as $key) {
$data[$key] = $terms[$key] ?? null; $data[$key] = $terms[$key] ?? null;
} }
@ -137,6 +147,10 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
$data['phonenumber'] = $phonenumber; $data['phonenumber'] = $phonenumber;
} }
if (array_key_exists('gender', $terms)) {
$data['gender'] = $this->genderRepository->find((int) $terms['gender']);
}
return $data; return $data;
} }
@ -256,7 +270,7 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
$birthdate, $birthdate,
$birthdateBefore, $birthdateBefore,
$birthdateAfter, $birthdateAfter,
$gender, null !== $gender ? (int) $gender : null,
$countryCode, $countryCode,
$phonenumber, $phonenumber,
$city $city
@ -316,7 +330,7 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
$birthdate, $birthdate,
$birthdateBefore, $birthdateBefore,
$birthdateAfter, $birthdateAfter,
$gender, null !== $gender ? (int) $gender : null,
$countryCode, $countryCode,
$phonenumber, $phonenumber,
$city $city

View File

@ -16,6 +16,7 @@ use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
@ -30,7 +31,6 @@ use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface; use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Contracts\Translation\TranslatorInterface;
class PersonDocGenNormalizer implements class PersonDocGenNormalizer implements
ContextAwareNormalizerInterface, ContextAwareNormalizerInterface,
@ -40,7 +40,7 @@ class PersonDocGenNormalizer implements
private const CIRCULAR_KEY = 'person:circular'; private const CIRCULAR_KEY = 'person:circular';
public function __construct(private readonly PersonRenderInterface $personRender, private readonly RelationshipRepository $relationshipRepository, private readonly TranslatorInterface $translator, private readonly TranslatableStringHelper $translatableStringHelper, private readonly SummaryBudgetInterface $summaryBudget) {} public function __construct(private readonly PersonRenderInterface $personRender, private readonly RelationshipRepository $relationshipRepository, private readonly TranslatableStringHelper $translatableStringHelper, private readonly SummaryBudgetInterface $summaryBudget) {}
public function normalize($person, $format = null, array $context = []) public function normalize($person, $format = null, array $context = [])
{ {
@ -67,6 +67,7 @@ class PersonDocGenNormalizer implements
// when a person reference the same person... take care of circular references // when a person reference the same person... take care of circular references
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => fn ($object, $format, $context) => $this->normalizer->normalize(null, $format, $context), AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => fn ($object, $format, $context) => $this->normalizer->normalize(null, $format, $context),
]); ]);
$genderContext = array_merge($context, ['docgen:expects' => Gender::class]);
if (null === $person) { if (null === $person) {
return $this->normalizeNullValue($format, $context); return $this->normalizeNullValue($format, $context);
@ -94,7 +95,7 @@ class PersonDocGenNormalizer implements
'age' => (int) $person->getAge(), 'age' => (int) $person->getAge(),
'birthdate' => $this->normalizer->normalize($person->getBirthdate(), $format, $dateContext), 'birthdate' => $this->normalizer->normalize($person->getBirthdate(), $format, $dateContext),
'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $dateContext), 'deathdate' => $this->normalizer->normalize($person->getDeathdate(), $format, $dateContext),
'gender' => $this->translator->trans($person->getGender()), 'gender' => $this->normalizer->normalize($person->getGender(), $format, $genderContext),
'maritalStatus' => null !== ($ms = $person->getMaritalStatus()) ? $this->translatableStringHelper->localize($ms->getName()) : '', 'maritalStatus' => null !== ($ms = $person->getMaritalStatus()) ? $this->translatableStringHelper->localize($ms->getName()) : '',
'maritalStatusDate' => $this->normalizer->normalize($person->getMaritalStatusDate(), $format, $dateContext), 'maritalStatusDate' => $this->normalizer->normalize($person->getMaritalStatusDate(), $format, $dateContext),
'maritalStatusComment' => $this->normalizer->normalize($person->getMaritalStatusComment(), $format, $dateContext), 'maritalStatusComment' => $this->normalizer->normalize($person->getMaritalStatusComment(), $format, $dateContext),

View File

@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility; use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface; use Chill\MainBundle\Phonenumber\PhoneNumberHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface; use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension; use Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension;
@ -112,7 +113,9 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
break; break;
case 'gender': case 'gender':
$person->setGender($data[$item]); $gender = $this->denormalizer->denormalize($data[$item], Gender::class, $format, []);
$person->setGender($gender);
break; break;
@ -199,7 +202,7 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
'phonenumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $context), 'phonenumber' => $this->normalizer->normalize($person->getPhonenumber(), $format, $context),
'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context), 'mobilenumber' => $this->normalizer->normalize($person->getMobilenumber(), $format, $context),
'email' => $person->getEmail(), 'email' => $person->getEmail(),
'gender' => $person->getGender(), 'gender' => $this->normalizer->normalize($person->getGender(), $format, $context),
'civility' => $this->normalizer->normalize($person->getCivility(), $format, $context), 'civility' => $this->normalizer->normalize($person->getCivility(), $format, $context),
]; ];

View File

@ -11,6 +11,9 @@ declare(strict_types=1);
namespace Tests\Controller\AccompanyingCoursWorkApiController; namespace Tests\Controller\AccompanyingCoursWorkApiController;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\GenderEnum;
use Chill\MainBundle\Entity\GenderIconEnum;
use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Test\PrepareClientTrait; use Chill\MainBundle\Test\PrepareClientTrait;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
@ -129,8 +132,15 @@ class ConflictTest extends WebTestCase
$period = new AccompanyingPeriod(); $period = new AccompanyingPeriod();
$em->persist($period); $em->persist($period);
$gender = new Gender();
$gender->setGenderTranslation(GenderEnum::MALE);
$gender->setLabel(['fr' => 'homme']);
$gender->setIcon(GenderIconEnum::MALE);
$em->persist($gender);
$period->addPerson(($p = new Person())->setFirstName('test')->setLastName('test') $period->addPerson(($p = new Person())->setFirstName('test')->setLastName('test')
->setBirthdate(new \DateTime('1980-01-01'))->setGender(Person::BOTH_GENDER)); ->setBirthdate(new \DateTime('1980-01-01'))->setGender($gender));
$em->persist($p); $em->persist($p);
$issue = (new SocialIssue())->setTitle(['fr' => 'test']); $issue = (new SocialIssue())->setTitle(['fr' => 'test']);
$em->persist($issue); $em->persist($issue);

View File

@ -14,6 +14,9 @@ namespace Chill\PersonBundle\Tests\Controller;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\AddressReference; use Chill\MainBundle\Entity\AddressReference;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\GenderEnum;
use Chill\MainBundle\Entity\GenderIconEnum;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Test\PrepareClientTrait; use Chill\MainBundle\Test\PrepareClientTrait;
use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Household;
@ -51,9 +54,15 @@ final class HouseholdApiControllerTest extends WebTestCase
->setMaxResults(1) ->setMaxResults(1)
->getQuery()->getSingleResult(); ->getQuery()->getSingleResult();
$gender = new Gender();
$gender->setGenderTranslation(GenderEnum::MALE);
$gender->setLabel(['fr' => 'homme']);
$gender->setIcon(GenderIconEnum::MALE);
$em->persist($gender);
$p = new Person(); $p = new Person();
$p->setFirstname('test')->setLastName('test lastname') $p->setFirstname('test')->setLastName('test lastname')
->setGender(Person::BOTH_GENDER) ->setGender($gender)
->setCenter($centerA); ->setCenter($centerA);
$em->persist($p); $em->persist($p);
$h = new Household(); $h = new Household();

View File

@ -112,24 +112,24 @@ final class PersonControllerCreateTest extends WebTestCase
$genderType = $form->get(self::GENDER_INPUT); $genderType = $form->get(self::GENDER_INPUT);
$this->assertEquals( $this->assertEquals(
'radio', 'select',
$genderType->getType(), $genderType->getType(),
'The gender input has radio buttons' 'The gender input is a select form to select a gender entity'
); );
$this->assertEquals( /* $this->assertEquals(
3, 3,
\count($genderType->availableOptionValues()), \count($genderType->availableOptionValues()),
'The gender input has three options: man, women and undefined' 'The gender input has three options: man, women and undefined'
); );
$this->assertTrue( $this->assertTrue(
\in_array('man', $genderType->availableOptionValues(), true), \in_array('man', $genderType->availableOptionValues(), true),
'gender has "homme" option' 'gender has "homme" option'
); );
$this->assertTrue( $this->assertTrue(
\in_array('woman', $genderType->availableOptionValues(), true), \in_array('woman', $genderType->availableOptionValues(), true),
'gender has "femme" option' 'gender has "femme" option'
); );
$this->assertFalse($genderType->hasValue(), 'The gender input is not checked'); $this->assertFalse($genderType->hasValue(), 'The gender input is not checked');*/
return $form; return $form;
} }
@ -226,7 +226,8 @@ final class PersonControllerCreateTest extends WebTestCase
) { ) {
$creationForm->get(self::FIRSTNAME_INPUT)->setValue($firstname.'_'.uniqid()); $creationForm->get(self::FIRSTNAME_INPUT)->setValue($firstname.'_'.uniqid());
$creationForm->get(self::LASTNAME_INPUT)->setValue($lastname.'_'.uniqid()); $creationForm->get(self::LASTNAME_INPUT)->setValue($lastname.'_'.uniqid());
$creationForm->get(self::GENDER_INPUT)->select('man'); // Todo change hardcoded id
$creationForm->get(self::GENDER_INPUT)->select(5);
$date = $birthdate ?? new \DateTime('1947-02-01'); $date = $birthdate ?? new \DateTime('1947-02-01');
$creationForm->get(self::BIRTHDATE_INPUT)->setValue($date->format('Y-m-d')); $creationForm->get(self::BIRTHDATE_INPUT)->setValue($date->format('Y-m-d'));

View File

@ -12,6 +12,9 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Tests\Controller; namespace Chill\PersonBundle\Tests\Controller;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\GenderEnum;
use Chill\MainBundle\Entity\GenderIconEnum;
use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Test\PrepareClientTrait; use Chill\MainBundle\Test\PrepareClientTrait;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
@ -160,12 +163,18 @@ final class PersonControllerUpdateTest extends WebTestCase
$em = self::getContainer()->get(EntityManagerInterface::class); $em = self::getContainer()->get(EntityManagerInterface::class);
$center = $centerRepository->findOneBy(['name' => 'Center A']); $center = $centerRepository->findOneBy(['name' => 'Center A']);
$gender = new Gender();
$gender->setGenderTranslation(GenderEnum::MALE);
$gender->setLabel(['fr' => 'homme']);
$gender->setIcon(GenderIconEnum::MALE);
$em->persist($gender);
$person = new Person(); $person = new Person();
$person $person
->setFirstName('Foo') ->setFirstName('Foo')
->setLastName('Bar') ->setLastName('Bar')
->setBirthdate(new \DateTime('2017-09-30')) ->setBirthdate(new \DateTime('2017-09-30'))
->setGender(Person::MALE_GENDER) ->setGender($gender)
->setCenter($center); ->setCenter($center);
$em->persist($person); $em->persist($person);

View File

@ -11,6 +11,9 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Tests\Controller; namespace Chill\PersonBundle\Tests\Controller;
use Chill\MainBundle\Entity\Gender;
use Chill\MainBundle\Entity\GenderEnum;
use Chill\MainBundle\Entity\GenderIconEnum;
use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Test\PrepareClientTrait; use Chill\MainBundle\Test\PrepareClientTrait;
use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\Person;
@ -99,12 +102,18 @@ final class PersonControllerViewTest extends WebTestCase
$em = self::getContainer()->get(EntityManagerInterface::class); $em = self::getContainer()->get(EntityManagerInterface::class);
$center = $centerRepository->findOneBy(['name' => 'Center A']); $center = $centerRepository->findOneBy(['name' => 'Center A']);
$gender = new Gender();
$gender->setGenderTranslation(GenderEnum::MALE);
$gender->setLabel(['fr' => 'homme']);
$gender->setIcon(GenderIconEnum::MALE);
$em->persist($gender);
$person = new Person(); $person = new Person();
$person $person
->setFirstName('Foo') ->setFirstName('Foo')
->setLastName('Bar') ->setLastName('Bar')
->setBirthdate(new \DateTime('2017-09-30')) ->setBirthdate(new \DateTime('2017-09-30'))
->setGender(Person::MALE_GENDER) ->setGender($gender)
->setCenter($center); ->setCenter($center);
$em->persist($person); $em->persist($person);

View File

@ -41,13 +41,13 @@ final class GenderFilterTest extends AbstractFilterTest
{ {
return [ return [
[ [
'accepted_genders' => [Person::FEMALE_GENDER], 'accepted_genders' => ['man'],
], ],
[ [
'accepted_genders' => [Person::MALE_GENDER], 'accepted_genders' => ['woman'],
], ],
[ [
'accepted_genders' => [Person::MALE_GENDER, Person::BOTH_GENDER], 'accepted_genders' => ['man', 'both'],
], ],
]; ];
} }

View File

@ -257,7 +257,7 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
$personDocGenNormalizer = new PersonDocGenNormalizer( $personDocGenNormalizer = new PersonDocGenNormalizer(
$personRender ?? self::getContainer()->get(PersonRender::class), $personRender ?? self::getContainer()->get(PersonRender::class),
$relationshipRepository ?? self::getContainer()->get(RelationshipRepository::class), $relationshipRepository ?? self::getContainer()->get(RelationshipRepository::class),
$translator ?? self::getContainer()->get(TranslatorInterface::class), // $translator ?? self::getContainer()->get(TranslatorInterface::class),
$translatableStringHelper ?? self::getContainer()->get(TranslatableStringHelperInterface::class), $translatableStringHelper ?? self::getContainer()->get(TranslatableStringHelperInterface::class),
$summaryBudget->reveal(), $summaryBudget->reveal(),
); );
@ -299,7 +299,7 @@ final class PersonDocGenNormalizerTest extends KernelTestCase
$normalizer = new PersonDocGenNormalizer( $normalizer = new PersonDocGenNormalizer(
$personRender ?? self::getContainer()->get(PersonRender::class), $personRender ?? self::getContainer()->get(PersonRender::class),
$relationshipRepository, $relationshipRepository,
$translator ?? self::getContainer()->get(TranslatorInterface::class), // $translator ?? self::getContainer()->get(TranslatorInterface::class),
$translatableStringHelper ?? self::getContainer()->get(TranslatableStringHelperInterface::class), $translatableStringHelper ?? self::getContainer()->get(TranslatableStringHelperInterface::class),
$summaryBudget->reveal() $summaryBudget->reveal()
); );

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/*
* 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.
*/
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20240926100337 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add foreign key gender property to person and transfer values';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_person ADD gender_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_person_person ADD CONSTRAINT FK_BF210A14708A0E0 FOREIGN KEY (gender_id) REFERENCES chill_main_gender (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_BF210A14708A0E0 ON chill_person_person (gender_id)');
// transfer gender values to point to corresponding gender entity within new column
$this->addSql("
UPDATE chill_person_person AS p
SET gender_id = g.id
FROM chill_main_gender AS g
WHERE g.genderTranslation = p.gender AND p.gender IN ('man', 'woman', 'unknown')
OR (g.genderTranslation = 'neutral' AND p.gender = 'both')
");
// delete old gender column
$this->addSql('ALTER TABLE chill_person_person DROP gender');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_person ADD gender VARCHAR(9) DEFAULT NULL');
$this->addSql('ALTER TABLE chill_person_person DROP gender_id');
}
}

View File

@ -2,6 +2,7 @@ Born the date: >-
{gender, select, {gender, select,
man {Né le {birthdate}} man {Né le {birthdate}}
woman {Née le {birthdate}} woman {Née le {birthdate}}
neutral {Né·e le {birthdate}}
other {Né·e le {birthdate}} other {Né·e le {birthdate}}
} }
@ -9,17 +10,18 @@ Requestor: >-
{gender, select, {gender, select,
man {Demandeur} man {Demandeur}
woman {Demandeuse} woman {Demandeuse}
other {Demandeur·euse} neutral {Demandeur·euse}
} }
person: person:
and_himself: >- from_the: depuis le
And himself: >-
{gender, select, {gender, select,
man {et lui-même} man {et lui-même}
woman {et elle-même} woman {et elle-même}
neutral {et lui·elle-même}
other {et lui·elle-même} other {et lui·elle-même}
} }
from_the: depuis le
household: household:
Household: Ménage Household: Ménage

View File

@ -64,6 +64,7 @@ Female: Femme
#Both: Neutre #Both: Neutre
man: Homme man: Homme
woman: Femme woman: Femme
neutral: Neutre
#both: Neutre #both: Neutre
Man: Homme Man: Homme
Woman: Femme Woman: Femme
@ -1188,6 +1189,8 @@ export:
date_after: Après le date_after: Après le
date_before: Avant le date_before: Avant le
title: Filtrer les usagers n'ayant été associés à aucun parcours title: Filtrer les usagers n'ayant été associés à aucun parcours
gender:
no_gender: genre non specifié
course: course:
not_having_address_reference: not_having_address_reference: