Merge branch 'master' into 'bugfix/acl-add-roles-misc-places' [ci-skip]

# Conflicts:
#   CHANGELOG.md
This commit is contained in:
Julien Fastré 2021-11-08 12:20:26 +00:00
commit 5c037ca18f
99 changed files with 1018 additions and 645 deletions

View File

@ -12,6 +12,10 @@ and this project adheres to
<!-- write down unreleased development here --> <!-- write down unreleased development here -->
* [person]: Add civility to the person
* [person]: Various improvements on the edit person form
* [person]: Set available_languages and available_countries as parameters for use in the edit person form
* [activity] Bugfix: documents can now be added to an activity.
* [tasks] improve tasks with filter order * [tasks] improve tasks with filter order
* [tasks] refactor singleControllerTasks: limit the number of conditions from the context * [tasks] refactor singleControllerTasks: limit the number of conditions from the context
* [validations] validation of accompanying period added: no duplicate participations or resources (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/60). * [validations] validation of accompanying period added: no duplicate participations or resources (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/60).
@ -22,9 +26,21 @@ and this project adheres to
* [activity]: perform client-side validation & show/hide fields in the "new location" modal * [activity]: perform client-side validation & show/hide fields in the "new location" modal
* [docstore] voter for PersonDocument and AccompanyingCourseDocument on the 2.0 way (using VoterHelperFactory) * [docstore] voter for PersonDocument and AccompanyingCourseDocument on the 2.0 way (using VoterHelperFactory)
* [docstore] add authorization check inside controller and menu * [docstore] add authorization check inside controller and menu
* [activity]: fix inheritance for role `ACTIVITY FULL` and add missing acl in menu
* [person] show current address in search results
* [person] show alt names in search results
* [admin]: links to activity admin section added again.
* [household]: endDate field deleted from household edit form.
* [household]: View accompanying periods of current and old household members.
* [tasks]: different layout for task list / my tasks, and fix link to tasks in alert or in warning
* [admin]: links to activity admin section added again.
* [household]: household addresses ordered by ValidFrom date and by id to show the last created address on top.
* [socialWorkAction]: display of social issue and parent issues + banner context added.
* [DBAL dependencies] Upgrade to DBAL 3.1
## Test releases ## Test releases
### Test release 2021-10-27 ### Test release 2021-10-27
* [person]: delete double actions buttons on search person page * [person]: delete double actions buttons on search person page

View File

@ -87,7 +87,9 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
ActivityVoter::UPDATE => array(ActivityVoter::SEE_DETAILS), ActivityVoter::UPDATE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::CREATE => array(ActivityVoter::SEE_DETAILS), ActivityVoter::CREATE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::DELETE => array(ActivityVoter::SEE_DETAILS), ActivityVoter::DELETE => array(ActivityVoter::SEE_DETAILS),
ActivityVoter::SEE_DETAILS => array(ActivityVoter::SEE) ActivityVoter::SEE_DETAILS => array(ActivityVoter::SEE),
ActivityVoter::FULL => [ActivityVoter::CREATE, ActivityVoter::DELETE,
ActivityVoter::UPDATE],
) )
)); ));
} }

View File

@ -158,7 +158,7 @@ class Activity implements HasCenterInterface, HasScopeInterface, AccompanyingPer
private ?Collection $thirdParties = null; private ?Collection $thirdParties = null;
/** /**
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject") * @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"})
*/ */
private Collection $documents; private Collection $documents;

View File

@ -44,7 +44,7 @@ class ActivityReason
/** /**
* @var array * @var array
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;

View File

@ -43,7 +43,7 @@ class ActivityReasonCategory
/** /**
* @var string * @var string
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;

View File

@ -44,7 +44,7 @@ class ActivityType
private ?int $id; private ?int $id;
/** /**
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private array $name = []; private array $name = [];

View File

@ -40,7 +40,7 @@ class ActivityTypeCategory
private ?int $id; private ?int $id;
/** /**
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private array $name = []; private array $name = [];

View File

@ -71,7 +71,7 @@ class TranslatableActivityType extends AbstractType
if ($options['active_only'] === true) { if ($options['active_only'] === true) {
$qb->where($qb->expr()->eq('at.active', ':active')); $qb->where($qb->expr()->eq('at.active', ':active'));
$qb->setParameter('active', true, \Doctrine\DBAL\Types\Type::BOOLEAN); $qb->setParameter('active', true, \Doctrine\DBAL\Types\Types::BOOLEAN);
} }
} }

View File

@ -2,29 +2,27 @@
namespace Chill\ActivityBundle\Menu; namespace Chill\ActivityBundle\Menu;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Knp\Menu\MenuItem; use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{ {
protected TokenStorageInterface $tokenStorage;
protected AuthorizationHelper $authorizationHelper;
protected TranslatorInterface $translator; protected TranslatorInterface $translator;
protected Security $security;
public function __construct( public function __construct(
TokenStorageInterface $tokenStorage, Security $security,
AuthorizationHelper $authorizationHelper,
TranslatorInterface $translator TranslatorInterface $translator
) { ) {
$this->security = $security;
$this->translator = $translator; $this->translator = $translator;
$this->authorizationHelper = $authorizationHelper;
$this->tokenStorage = $tokenStorage;
} }
public static function getMenuIds(): array public static function getMenuIds(): array
{ {
@ -35,7 +33,8 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{ {
$period = $parameters['accompanyingCourse']; $period = $parameters['accompanyingCourse'];
if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()) { if (AccompanyingPeriod::STEP_DRAFT !== $period->getStep()
&& $this->security->isGranted(ActivityVoter::SEE, $period)) {
$menu->addChild($this->translator->trans('Activity'), [ $menu->addChild($this->translator->trans('Activity'), [
'route' => 'chill_activity_activity_list', 'route' => 'chill_activity_activity_list',
'routeParameters' => [ 'routeParameters' => [

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Chill\ActivityBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
final class AdminMenuBuilder implements LocalMenuBuilderInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public static function getMenuIds(): array
{
return ['admin_index', 'admin_section', 'admin_activity'];
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
return;
}
if (in_array($menuId, ['admin_index', 'admin_section'])) {
$menu->addChild('Activities', [
'route' => 'chill_admin_activity_index'
])
->setExtras([
'order' => 2000,
'explain' => "Activity configuration"
]);
} else {
$menu
->addChild('Activities', [
'route' => 'chill_admin_activity_index'
])
->setExtras([
'order' => '60'
]);
}
}
}

View File

@ -4,7 +4,7 @@ namespace Chill\Migrations\Activity;
use Doctrine\Migrations\AbstractMigration; use Doctrine\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema; use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types;
/** /**
* Add an "active" column on activitytype table * Add an "active" column on activitytype table

View File

@ -22,7 +22,6 @@ final class Version20210401090853 extends AbstractMigration
// this up() migration is auto-generated, please modify it to your needs // this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SEQUENCE activitytypecategory_id_seq INCREMENT BY 1 MINVALUE 1 START 1000'); $this->addSql('CREATE SEQUENCE activitytypecategory_id_seq INCREMENT BY 1 MINVALUE 1 START 1000');
$this->addSql('CREATE TABLE activitytypecategory (id INT NOT NULL, name JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE activitytypecategory (id INT NOT NULL, name JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN activitytypecategory.name IS \'(DC2Type:json_array)\'');
$this->addSql('INSERT INTO activitytypecategory VALUES(1, \'{"fr": "Défaut", "en": "Default"}\', true)'); $this->addSql('INSERT INTO activitytypecategory VALUES(1, \'{"fr": "Défaut", "en": "Default"}\', true)');
} }

View File

@ -51,7 +51,6 @@ final class Version20210408122329 extends AbstractMigration
$this->addSql('ALTER TABLE activitytype ADD socialDataLabel VARCHAR(255) DEFAULT \'\' NOT NULL'); $this->addSql('ALTER TABLE activitytype ADD socialDataLabel VARCHAR(255) DEFAULT \'\' NOT NULL');
$this->addSql('ALTER TABLE activitytype ALTER name SET NOT NULL'); $this->addSql('ALTER TABLE activitytype ALTER name SET NOT NULL');
$this->addSql('ALTER TABLE activitytype ALTER active DROP DEFAULT'); $this->addSql('ALTER TABLE activitytype ALTER active DROP DEFAULT');
$this->addSql('COMMENT ON COLUMN activitytype.name IS \'(DC2Type:json_array)\'');
} }
public function down(Schema $schema) : void public function down(Schema $schema) : void

View File

@ -99,10 +99,13 @@ CHILL_ACTIVITY_LIST: Liste des activités
Activities: Activités Activities: Activités
Activity configuration: Configuration des activités Activity configuration: Configuration des activités
Activity configuration menu: Configuration des activités Activity configuration menu: Configuration des activités
Activity Types: Types d'activité Activity types: Types d'activité
Activity type configuration: Configuration des categories d'activités
Activity Reasons: Sujets d'une activité Activity Reasons: Sujets d'une activité
Activity Reasons Category: Catégories de sujet d'activités Activity Reasons Category: Catégories de sujet d'activités
Activity Types Categories: Catégories des types d'activité Activity Types Categories: Catégories des types d'activité
Activity Presences: Presences des activités
# Crud # Crud
crud: crud:

View File

@ -32,7 +32,7 @@ final class AdminMenuBuilder implements \Chill\MainBundle\Routing\LocalMenuBuild
]) ])
->setExtras([ ->setExtras([
'order' => 900, 'order' => 900,
'explain' => "Configure aside activities categories" 'explain' => "Aside activity type configuration"
]); ]);
} else { } else {
$menu $menu

View File

@ -1,7 +1,7 @@
{% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %} {% extends "@ChillAsideActivity/Admin/layout_asideactivity.html.twig" %}
{% block admin_content %} {% block admin_content %}
<h1>{{ 'Aside Activity Type list'|trans }}</h1> <h1>{{ 'Aside Activity Type List'|trans }}</h1>
<table class="records_list table table-bordered border-dark"> <table class="records_list table table-bordered border-dark">
<thead> <thead>

View File

@ -3,6 +3,7 @@ Show the aside activity: Voir l'activité annexe
Edit the aside activity: Modifier l'activité annexe Edit the aside activity: Modifier l'activité annexe
Remove aside activity: Supprimer l'activité annexe Remove aside activity: Supprimer l'activité annexe
Aside activity: Activité annexe Aside activity: Activité annexe
Aside Activity Type List: Liste des catégories d'activités annexes
Duration time: Durée Duration time: Durée
durationTime: durée durationTime: durée
user_username: nom de l'utilisateur user_username: nom de l'utilisateur
@ -156,6 +157,11 @@ The activity has been successfully removed.: L'activité a été supprimée.
#Menu #Menu
Create an aside activity: "Créer une activité annexe" Create an aside activity: "Créer une activité annexe"
Aside activity categories: Catégories des activités annexes
Aside activity configuration menu: "Menu de configuration des activités annexes" Aside activity configuration menu: "Menu de configuration des activités annexes"
Aside activity configuration: "Configuration des activités annexes"
Phonecall: "Appel téléphonique" Phonecall: "Appel téléphonique"
# admin
Aside activities: Activités annexes
Aside activity types: Types d'activités annexes
Aside activity type configuration: Configuration des categories d'activités annexes

View File

@ -34,7 +34,7 @@ class CancelReason
private $canceledBy; private $canceledBy;
/** /**
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name = []; private $name = [];

View File

@ -25,7 +25,7 @@ class Invite
private User $user; private User $user;
/** /**
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private array $status = []; private array $status = [];

View File

@ -73,19 +73,14 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
{ {
$user = $this->tokenStorage->getToken()->getUser(); $user = $this->tokenStorage->getToken()->getUser();
if ($this->authorizationChecker->isGranted('ROLE_USER') if ($this->authorizationChecker->isGranted('ROLE_USER')){
&& $user instanceof User $menu->addChild("My calendar list", [
) { 'route' => 'chill_calendar_calendar_list'
$menu->addChild("My calendar list", [ ])
'route' => 'chill_calendar_calendar_list', ->setExtras([
'routeParameters' => [ 'order' => 9,
'user_id' => $user->getId(), 'icon' => 'tasks'
] ]);
])
->setExtras([
'order' => 9,
'icon' => 'tasks'
]);
} }
} }

View File

@ -50,10 +50,7 @@ final class Version20210715141731 extends AbstractMigration
$this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.startDate IS \'(DC2Type:date_immutable)\''); $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.startDate IS \'(DC2Type:date_immutable)\'');
$this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.endDate IS \'(DC2Type:date_immutable)\''); $this->addSql('COMMENT ON COLUMN chill_calendar.calendar_range.endDate IS \'(DC2Type:date_immutable)\'');
$this->addSql('CREATE TABLE chill_calendar.cancel_reason (id INT NOT NULL, active BOOLEAN NOT NULL, canceledBy JSON NOT NULL, name JSON NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_calendar.cancel_reason (id INT NOT NULL, active BOOLEAN NOT NULL, canceledBy JSON NOT NULL, name JSON NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN chill_calendar.cancel_reason.name IS \'(DC2Type:json_array)\'');
$this->addSql('COMMENT ON COLUMN chill_calendar.cancel_reason.canceledBy IS \'(DC2Type:json_array)\'');
$this->addSql('CREATE TABLE chill_calendar.invite (id INT NOT NULL, user_id INT DEFAULT NULL, status JSON NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_calendar.invite (id INT NOT NULL, user_id INT DEFAULT NULL, status JSON NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN chill_calendar.invite.status IS \'(DC2Type:json_array)\'');
$this->addSql('CREATE INDEX IDX_F517FFA7A76ED395 ON chill_calendar.invite (user_id)'); $this->addSql('CREATE INDEX IDX_F517FFA7A76ED395 ON chill_calendar.invite (user_id)');
$this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACD7FA8EF0 FOREIGN KEY (accompanyingPeriod_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_calendar.calendar ADD CONSTRAINT FK_712315ACD7FA8EF0 FOREIGN KEY (accompanyingPeriod_id) REFERENCES chill_person_accompanying_period (id) NOT DEFERRABLE INITIALLY IMMEDIATE');

View File

@ -34,6 +34,5 @@ final class Version20210723074557 extends AbstractMigration
$this->addSql('ALTER TABLE chill_calendar.calendar_to_non_professionals ADD CONSTRAINT fk_fadf2c77217bbb47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_calendar.calendar_to_non_professionals ADD CONSTRAINT fk_fadf2c77217bbb47 FOREIGN KEY (person_id) REFERENCES chill_person_person (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy TYPE JSON'); $this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy TYPE JSON');
$this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy DROP DEFAULT'); $this->addSql('ALTER TABLE chill_calendar.cancel_reason ALTER canceledBy DROP DEFAULT');
$this->addSql('COMMENT ON COLUMN chill_calendar.cancel_reason.canceledby IS \'(DC2Type:json_array)\'');
} }
} }

View File

@ -65,14 +65,14 @@ class CustomField
/** /**
* @var array * @var array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $options = array(); private $options = array();
/** /**
* @var array * @var array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;

View File

@ -50,7 +50,7 @@ class Option
* A json representation of text (multilingual) * A json representation of text (multilingual)
* *
* @var array * @var array
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $text; private $text;

View File

@ -45,7 +45,7 @@ class CustomFieldsGroup
/** /**
* @var array * @var array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;
@ -80,7 +80,7 @@ class CustomFieldsGroup
/** /**
* @var array * @var array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $options = array(); private $options = array();

View File

@ -21,7 +21,7 @@ class DocGeneratorTemplate
private int $id; private int $id;
/** /**
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
*/ */
private array $name = []; private array $name = [];

View File

@ -22,7 +22,6 @@ final class Version20210812214310 extends AbstractMigration
$this->addSql('ALTER TABLE chill_docgen_template ADD entities TEXT'); $this->addSql('ALTER TABLE chill_docgen_template ADD entities TEXT');
$this->addSql('ALTER TABLE chill_docgen_template ADD context VARCHAR(255)'); $this->addSql('ALTER TABLE chill_docgen_template ADD context VARCHAR(255)');
$this->addSql('COMMENT ON COLUMN chill_docgen_template.entities IS \'(DC2Type:simple_array)\''); $this->addSql('COMMENT ON COLUMN chill_docgen_template.entities IS \'(DC2Type:simple_array)\'');
$this->addSql('COMMENT ON COLUMN chill_docgen_template.name IS \'(DC2Type:json_array)\'');
} }
public function down(Schema $schema): void public function down(Schema $schema): void

View File

@ -34,7 +34,7 @@ class DocumentCategory
/** /**
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;

View File

@ -39,14 +39,14 @@ class StoredObject implements AsyncFileInterface, Document
private $filename; private $filename;
/** /**
* @ORM\Column(type="json_array", name="key") * @ORM\Column(type="json", name="key")
*/ */
private array $keyInfos = []; private array $keyInfos = [];
/** /**
* *
* @var int[] * @var int[]
* @ORM\Column(type="json_array", name="iv") * @ORM\Column(type="json", name="iv")
*/ */
private array $iv = []; private array $iv = [];
@ -69,7 +69,7 @@ class StoredObject implements AsyncFileInterface, Document
private string $type = ''; private string $type = '';
/** /**
* @ORM\Column(type="json_array", name="datas") * @ORM\Column(type="json", name="datas")
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
*/ */
private array $datas = []; private array $datas = [];

View File

@ -17,7 +17,6 @@ final class Version20180605102533 extends AbstractMigration
$this->addSql('CREATE SCHEMA chill_doc'); $this->addSql('CREATE SCHEMA chill_doc');
$this->addSql('CREATE SEQUENCE chill_doc.person_document_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_doc.person_document_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_doc.document_category (bundle_id VARCHAR(255) NOT NULL, id_inside_bundle INT NOT NULL, document_class VARCHAR(255) NOT NULL, name JSON NOT NULL, PRIMARY KEY(bundle_id, id_inside_bundle))'); $this->addSql('CREATE TABLE chill_doc.document_category (bundle_id VARCHAR(255) NOT NULL, id_inside_bundle INT NOT NULL, document_class VARCHAR(255) NOT NULL, name JSON NOT NULL, PRIMARY KEY(bundle_id, id_inside_bundle))');
$this->addSql('COMMENT ON COLUMN chill_doc.document_category.name IS \'(DC2Type:json_array)\'');
$this->addSql('CREATE TABLE chill_doc.person_document (id INT NOT NULL, category_bundle_id VARCHAR(255) DEFAULT NULL, category_id_inside_bundle INT DEFAULT NULL, scope_id INT DEFAULT NULL, user_id INT DEFAULT NULL, person_id INT DEFAULT NULL, title TEXT NOT NULL, description TEXT NOT NULL, content TEXT NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_doc.person_document (id INT NOT NULL, category_bundle_id VARCHAR(255) DEFAULT NULL, category_id_inside_bundle INT DEFAULT NULL, scope_id INT DEFAULT NULL, user_id INT DEFAULT NULL, person_id INT DEFAULT NULL, title TEXT NOT NULL, description TEXT NOT NULL, content TEXT NOT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_41DA53C369A0BE36EF62EFC ON chill_doc.person_document (category_bundle_id, category_id_inside_bundle)'); $this->addSql('CREATE INDEX IDX_41DA53C369A0BE36EF62EFC ON chill_doc.person_document (category_bundle_id, category_id_inside_bundle)');
$this->addSql('CREATE INDEX IDX_41DA53C682B5931 ON chill_doc.person_document (scope_id)'); $this->addSql('CREATE INDEX IDX_41DA53C682B5931 ON chill_doc.person_document (scope_id)');

View File

@ -16,9 +16,6 @@ final class Version20180606133338 extends AbstractMigration
$this->addSql('CREATE SEQUENCE chill_doc.stored_object_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_doc.stored_object_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_doc.stored_object (id INT NOT NULL, filename TEXT NOT NULL, key JSON NOT NULL, iv JSON NOT NULL, creation_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, type TEXT NOT NULL, datas JSON NOT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_doc.stored_object (id INT NOT NULL, filename TEXT NOT NULL, key JSON NOT NULL, iv JSON NOT NULL, creation_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, type TEXT NOT NULL, datas JSON NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.key IS \'(DC2Type:json_array)\'');
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.iv IS \'(DC2Type:json_array)\'');
$this->addSql('COMMENT ON COLUMN chill_doc.stored_object.datas IS \'(DC2Type:json_array)\'');
$this->addSql('ALTER TABLE chill_doc.person_document ADD object_id INT DEFAULT NULL'); $this->addSql('ALTER TABLE chill_doc.person_document ADD object_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_doc.person_document DROP content'); $this->addSql('ALTER TABLE chill_doc.person_document DROP content');
$this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C232D562B FOREIGN KEY (object_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_doc.person_document ADD CONSTRAINT FK_41DA53C232D562B FOREIGN KEY (object_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE');

View File

@ -46,7 +46,7 @@ class EventType
/** /**
* @var array * @var array
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;

View File

@ -44,7 +44,7 @@ class Role
/** /**
* @var array * @var array
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;

View File

@ -44,7 +44,7 @@ class Status
/** /**
* @var array * @var array
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;

View File

@ -107,6 +107,9 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
$container->setParameter('chill_main.available_languages', $container->setParameter('chill_main.available_languages',
$config['available_languages']); $config['available_languages']);
$container->setParameter('chill_main.available_countries',
$config['available_countries']);
$container->setParameter('chill_main.routing.resources', $container->setParameter('chill_main.routing.resources',
$config['routing']['resources']); $config['routing']['resources']);

View File

@ -51,6 +51,10 @@ class Configuration implements ConfigurationInterface
->defaultValue(array('fr')) ->defaultValue(array('fr'))
->prototype('scalar')->end() ->prototype('scalar')->end()
->end() // end of array 'available_languages' ->end() // end of array 'available_languages'
->arrayNode('available_countries')
->defaultValue(array('FR'))
->prototype('scalar')->end()
->end() // end of array 'available_countries'
->arrayNode('routing') ->arrayNode('routing')
->children() ->children()
->arrayNode('resources') ->arrayNode('resources')

View File

@ -18,7 +18,7 @@ class NativeDateIntervalType extends DateIntervalType
public function getName(): string public function getName(): string
{ {
return \Doctrine\DBAL\Types\Type::DATEINTERVAL; return \Doctrine\DBAL\Types\Types::DATEINTERVAL;
} }
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string

View File

@ -28,7 +28,7 @@ class Country
/** /**
* @var string * @var string
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
* @groups({"read"}) * @groups({"read"})
* *
*/ */

View File

@ -43,7 +43,7 @@ class Language
/** /**
* @var string array * @var string array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;

View File

@ -52,7 +52,7 @@ class Scope
* *
* @var array * @var array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
* @Groups({"read"}) * @Groups({"read"})
*/ */
private $name = []; private $name = [];

View File

@ -116,7 +116,7 @@ class User implements AdvancedUserInterface {
* Array where SAML attributes's data are stored * Array where SAML attributes's data are stored
* @var array * @var array
* *
* @ORM\Column(type="json_array", nullable=true) * @ORM\Column(type="json", nullable=true)
*/ */
private $attributes; private $attributes;

View File

@ -54,7 +54,7 @@ class CommentType extends AbstractType
$data = $event->getForm()->getData(); $data = $event->getForm()->getData();
$comment = $event->getData() ?? ['comment' => '']; $comment = $event->getData() ?? ['comment' => ''];
if ($data->getComment() !== $comment['comment']) { if (null !== $data && $data->getComment() !== $comment['comment']) {
$data->setDate(new \DateTime()); $data->setDate(new \DateTime());
$data->setUserId($this->user->getId()); $data->setUserId($this->user->getId());
$event->getForm()->setData($data); $event->getForm()->setData($data);

View File

@ -28,6 +28,7 @@ use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
use Chill\MainBundle\Form\Type\Select2ChoiceType; use Chill\MainBundle\Form\Type\Select2ChoiceType;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/** /**
* Extends choice to allow adding select2 library on widget * Extends choice to allow adding select2 library on widget
@ -37,31 +38,25 @@ use Chill\MainBundle\Templating\TranslatableStringHelper;
*/ */
class Select2CountryType extends AbstractType class Select2CountryType extends AbstractType
{ {
/** private RequestStack $requestStack;
* @var RequestStack
*/
private $requestStack;
/** private ObjectManager $em;
*
* @var TranslatableStringHelper
*/
protected $translatableStringHelper;
/** protected TranslatableStringHelper $translatableStringHelper;
* @var ObjectManager
*/ protected ParameterBagInterface $parameterBag;
private $em;
public function __construct( public function __construct(
RequestStack $requestStack, RequestStack $requestStack,
ObjectManager $em, ObjectManager $em,
TranslatableStringHelper $translatableStringHelper TranslatableStringHelper $translatableStringHelper,
ParameterBagInterface $parameterBag
) )
{ {
$this->requestStack = $requestStack; $this->requestStack = $requestStack;
$this->em = $em; $this->em = $em;
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
$this->parameterBag = $parameterBag;
} }
public function getBlockPrefix() public function getBlockPrefix()
@ -82,19 +77,29 @@ class Select2CountryType extends AbstractType
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$locale = $this->requestStack->getCurrentRequest()->getLocale();
$countries = $this->em->getRepository('Chill\MainBundle\Entity\Country')->findAll(); $countries = $this->em->getRepository('Chill\MainBundle\Entity\Country')->findAll();
$choices = array(); $choices = [];
$preferredCountries = $this->parameterBag->get('chill_main.available_countries');
$preferredChoices = [];
foreach ($countries as $c) { foreach ($countries as $c) {
$choices[$c->getId()] = $this->translatableStringHelper->localize($c->getName()); $choices[$c->getId()] = $this->translatableStringHelper->localize($c->getName());
} }
foreach ($preferredCountries as $pc) {
foreach ($countries as $c) {
if ($c->getCountryCode() == $pc) {
$preferredChoices[$c->getId()] = $this->translatableStringHelper->localize($c->getName());
}
}
}
asort($choices, SORT_STRING | SORT_FLAG_CASE); asort($choices, SORT_STRING | SORT_FLAG_CASE);
$resolver->setDefaults(array( $resolver->setDefaults(array(
'class' => 'Chill\MainBundle\Entity\Country', 'class' => 'Chill\MainBundle\Entity\Country',
'choices' => array_combine(array_values($choices),array_keys($choices)) 'choices' => array_combine(array_values($choices),array_keys($choices)),
'preferred_choices' => array_combine(array_values($preferredChoices), array_keys($preferredChoices))
)); ));
} }
} }

View File

@ -28,37 +28,32 @@ use Chill\MainBundle\Form\Type\DataTransformer\MultipleObjectsToIdTransformer;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
use Chill\MainBundle\Form\Type\Select2ChoiceType; use Chill\MainBundle\Form\Type\Select2ChoiceType;
use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\MainBundle\Templating\TranslatableStringHelper;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/** /**
* Extends choice to allow adding select2 library on widget for languages (multiple) * Extends choice to allow adding select2 library on widget for languages (multiple)
*/ */
class Select2LanguageType extends AbstractType class Select2LanguageType extends AbstractType
{ {
/** private RequestStack $requestStack;
* @var RequestStack
*/
private $requestStack;
/** private ObjectManager $em;
* @var ObjectManager
*/
private $em;
/** protected TranslatableStringHelper $translatableStringHelper;
*
* @var TranslatableStringHelper protected ParameterBagInterface $parameterBag;
*/
protected $translatableStringHelper;
public function __construct( public function __construct(
RequestStack $requestStack, RequestStack $requestStack,
ObjectManager $em, ObjectManager $em,
TranslatableStringHelper $translatableStringHelper TranslatableStringHelper $translatableStringHelper,
ParameterBagInterface $parameterBag
) )
{ {
$this->requestStack = $requestStack; $this->requestStack = $requestStack;
$this->em = $em; $this->em = $em;
$this->translatableStringHelper = $translatableStringHelper; $this->translatableStringHelper = $translatableStringHelper;
$this->parameterBag = $parameterBag;
} }
public function getBlockPrefix() public function getBlockPrefix()
@ -79,19 +74,24 @@ class Select2LanguageType extends AbstractType
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)
{ {
$locale = $this->requestStack->getCurrentRequest()->getLocale();
$languages = $this->em->getRepository('Chill\MainBundle\Entity\Language')->findAll(); $languages = $this->em->getRepository('Chill\MainBundle\Entity\Language')->findAll();
$choices = array(); $preferredLanguages = $this->parameterBag->get('chill_main.available_languages');
$choices = [];
$preferredChoices = [];
foreach ($languages as $l) { foreach ($languages as $l) {
$choices[$l->getId()] = $this->translatableStringHelper->localize($l->getName()); $choices[$l->getId()] = $this->translatableStringHelper->localize($l->getName());
} }
foreach ($preferredLanguages as $l) {
$preferredChoices[$l] = $choices[$l];
}
asort($choices, SORT_STRING | SORT_FLAG_CASE); asort($choices, SORT_STRING | SORT_FLAG_CASE);
$resolver->setDefaults(array( $resolver->setDefaults(array(
'class' => 'Chill\MainBundle\Entity\Language', 'class' => 'Chill\MainBundle\Entity\Language',
'choices' => array_combine(array_values($choices),array_keys($choices)) 'choices' => array_combine(array_values($choices), array_keys($choices)),
'preferred_choices' => array_combine(array_values($preferredChoices), array_keys($preferredChoices))
)); ));
} }
} }

View File

@ -1 +1,5 @@
require("./show_hide.js"); //require("./show_hide.js");
import { ShowHide } from './show_hide.js'
export { ShowHide }

View File

@ -134,4 +134,4 @@ var ShowHide = function(options) {
}; };
}; };
export {ShowHide}; export { ShowHide };

View File

@ -5,82 +5,89 @@ import App from './App.vue';
const i18n = _createI18n(addressMessages); const i18n = _createI18n(addressMessages);
let inputs = document.querySelectorAll('input[type="hidden"][data-input-address]'); const addAddressInput = (inputs) => {
const isNumeric = function(v) { return !isNaN(v); }; inputs.forEach(el => {
let
addressId = el.value,
uniqid = el.dataset.inputAddress,
container = document.querySelector('div[data-input-address-container="' + uniqid + '"]'),
isEdit = addressId !== '',
addressIdInt = addressId !== '' ? parseInt(addressId) : null
;
inputs.forEach(el => { if (container === null) {
let throw Error("no container");
addressId = el.value, }
uniqid = el.dataset.inputAddress, console.log('useValidFrom', el.dataset.useValidFrom === '1');
container = document.querySelector('div[data-input-address-container="' + uniqid + '"]'),
isEdit = addressId !== '',
addressIdInt = addressId !== '' ? parseInt(addressId) : null
;
if (container === null) { const app = createApp({
throw Error("no container"); template: `<app v-bind:addAddress="this.addAddress" @address-created="associateToInput"></app>`,
} data() {
console.log('useValidFrom', el.dataset.useValidFrom === '1'); return {
addAddress: {
const app = createApp({ context: {
template: `<app v-bind:addAddress="this.addAddress" @address-created="associateToInput"></app>`, // for legacy ? can be remove ?
data() { target: {
return { name: 'input-address',
addAddress: { id: addressIdInt,
context: {
// for legacy ? can be remove ?
target: {
name: 'input-address',
id: addressIdInt,
},
edit: isEdit,
addressId: addressIdInt,
},
options: {
/// Options override default.
/// null value take default component value defined in AddAddress data()
button: {
text: {
create: el.dataset.buttonTextCreate || null,
edit: el.dataset.buttonTextUpdate || null,
}, },
size: null, edit: isEdit,
displayText: true addressId: addressIdInt,
}, },
options: {
/// Options override default.
/// null value take default component value defined in AddAddress data()
button: {
text: {
create: el.dataset.buttonTextCreate || null,
edit: el.dataset.buttonTextUpdate || null,
},
size: null,
displayText: true
},
/// Modal title text if create or edit address (trans chain, see i18n) /// Modal title text if create or edit address (trans chain, see i18n)
title: { title: {
create: null, create: null,
edit: null, edit: null,
}, },
/// Display panes in Modal for step123 /// Display panes in Modal for step123
openPanesInModal: true, openPanesInModal: true,
/// Display actions buttons of panes in a sticky-form-button navbar /// Display actions buttons of panes in a sticky-form-button navbar
stickyActions: false, stickyActions: false,
showMessageWhenNoAddress: true, showMessageWhenNoAddress: true,
/// Use Date fields /// Use Date fields
useDate: { useDate: {
validFrom: el.dataset.useValidFrom === '1' || false, //boolean, default: false validFrom: el.dataset.useValidFrom === '1' || false, //boolean, default: false
validTo: el.dataset.useValidTo === '1' || false, //boolean, default: false validTo: el.dataset.useValidTo === '1' || false, //boolean, default: false
}, },
/// Don't display show renderbox Address: showPane display only a button /// Don't display show renderbox Address: showPane display only a button
onlyButton: false, onlyButton: false,
}
} }
} }
},
methods: {
associateToInput(payload) {
el.value = payload.addressId;
}
} }
}, })
methods: { .use(i18n)
associateToInput(payload) { .component('app', App)
el.value = payload.addressId; .mount(container);
} });
} };
})
.use(i18n) document.addEventListener('DOMContentLoaded', (_e) =>
.component('app', App) addAddressInput(document.querySelectorAll('input[type="hidden"][data-input-address]'))
.mount(container); );
});
window.addEventListener('collection-add-entry', (e) =>
addAddressInput(e.detail.entry.querySelectorAll('input[type="hidden"][data-input-address]'))
);

View File

@ -39,3 +39,4 @@ assetic:
chill_main: chill_main:
available_languages: [fr, en] available_languages: [fr, en]
available_countries: [FR]

View File

@ -83,6 +83,10 @@
{% endmacro %} {% endmacro %}
{#
this enclose the rendering inside a "li", which ease the placement operation when the address
must be shown in such list
#}
{%- if render == 'list' -%} {%- if render == 'list' -%}
<li class="chill-entity entity-address"> <li class="chill-entity entity-address">
{% if options['with_picto'] %} {% if options['with_picto'] %}

View File

@ -33,12 +33,14 @@ class ParentRoleHelper
* Return all the role which give access to the given role. Only the role * Return all the role which give access to the given role. Only the role
* which are registered into Chill are taken into account. * which are registered into Chill are taken into account.
* *
* The array contains always the current $role (which give access to himself)
*
* @param string $role * @param string $role
* @return string[] the role which give access to the given $role * @return string[] the role which give access to the given $role
*/ */
public function getParentRoles(string $role): array public function getParentRoles(string $role): array
{ {
$parentRoles = []; $parentRoles = [$role];
// transform the roles from role hierarchy from string to Role // transform the roles from role hierarchy from string to Role
$roles = \array_keys($this->hierarchy); $roles = \array_keys($this->hierarchy);

View File

@ -14,7 +14,7 @@ class FilterOrderHelper
private ?array $searchBoxFields = null; private ?array $searchBoxFields = null;
private array $checkboxes = []; private array $checkboxes = [];
private ?array $submitted = null; private ?array $submitted = null;
private ?string $formName = 'filter'; private ?string $formName = 'f';
private string $formType = FilterOrderType::class; private string $formType = FilterOrderType::class;
private array $formOptions = []; private array $formOptions = [];

View File

@ -23,9 +23,10 @@ class ParentRoleHelperTest extends KernelTestCase
$parentRoles = $this->parentRoleHelper->getParentRoles(PersonVoter::SEE); $parentRoles = $this->parentRoleHelper->getParentRoles(PersonVoter::SEE);
$this->assertCount(2, $parentRoles); $this->assertCount(3, $parentRoles);
$this->assertContains(PersonVoter::CREATE, $parentRoles); $this->assertContains(PersonVoter::CREATE, $parentRoles);
$this->assertContains(PersonVoter::UPDATE, $parentRoles); $this->assertContains(PersonVoter::UPDATE, $parentRoles);
$this->assertContains(PersonVoter::SEE, $parentRoles);
} }
public function testIsRoleReached() public function testIsRoleReached()

View File

@ -20,7 +20,7 @@
namespace Chill\MainBundle\Timeline; namespace Chill\MainBundle\Timeline;
use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Doctrine\ORM\Query; use Doctrine\ORM\Query;
@ -101,7 +101,7 @@ class TimelineBuilder implements ContainerAwareInterface
public function countItems($context, array $args) public function countItems($context, array $args)
{ {
$rsm = (new ResultSetMapping()) $rsm = (new ResultSetMapping())
->addScalarResult('total', 'total', Type::INTEGER); ->addScalarResult('total', 'total', Types::INTEGER);
list($select, $parameters) = $this->buildUnionQuery($context, $args); list($select, $parameters) = $this->buildUnionQuery($context, $args);

View File

@ -25,6 +25,7 @@ services:
- "@request_stack" - "@request_stack"
- "@doctrine.orm.entity_manager" - "@doctrine.orm.entity_manager"
- "@chill.main.helper.translatable_string" - "@chill.main.helper.translatable_string"
- '@Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface'
tags: tags:
- { name: form.type, alias: select2_chill_country } - { name: form.type, alias: select2_chill_country }
@ -34,6 +35,7 @@ services:
- "@request_stack" - "@request_stack"
- "@doctrine.orm.entity_manager" - "@doctrine.orm.entity_manager"
- "@chill.main.helper.translatable_string" - "@chill.main.helper.translatable_string"
- '@Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface'
tags: tags:
- { name: form.type, alias: select2_chill_language } - { name: form.type, alias: select2_chill_language }

View File

@ -20,7 +20,6 @@ final class Version20210304085819 extends AbstractMigration
public function up(Schema $schema) : void public function up(Schema $schema) : void
{ {
$this->addSql('ALTER TABLE users ADD attributes JSONB DEFAULT NULL'); $this->addSql('ALTER TABLE users ADD attributes JSONB DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN users.attributes IS \'(DC2Type:json_array)\'');
} }
public function down(Schema $schema) : void public function down(Schema $schema) : void

View File

@ -14,6 +14,8 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\Position; use Chill\PersonBundle\Entity\Household\Position;
use Chill\PersonBundle\Repository\Household\PositionRepository; use Chill\PersonBundle\Repository\Household\PositionRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Symfony\Component\Security\Core\Security;
/** /**
* @Route("/{_locale}/person/household") * @Route("/{_locale}/person/household")
@ -24,11 +26,14 @@ class HouseholdController extends AbstractController
private PositionRepository $positionRepository; private PositionRepository $positionRepository;
public function __construct(TranslatorInterface $translator, PositionRepository $positionRepository) private Security $security;
public function __construct(TranslatorInterface $translator, PositionRepository $positionRepository, Security $security)
{ {
$this->translator = $translator; $this->translator = $translator;
$this->positionRepository = $positionRepository; $this->positionRepository = $positionRepository;
$this->security = $security;
} }
/** /**
@ -77,17 +82,46 @@ class HouseholdController extends AbstractController
*/ */
public function accompanyingPeriod(Request $request, Household $household) public function accompanyingPeriod(Request $request, Household $household)
{ {
// TODO ACL
$members = $household->getMembers(); $currentMembers = $household->getCurrentPersons();
foreach($members as $m) { $accompanyingPeriods = [];
$accompanyingPeriods = $m->getPerson()->getAccompanyingPeriods();
foreach ($currentMembers as $p) {
$accompanyingPeriodsMember = $p->getCurrentAccompanyingPeriods();
foreach ($accompanyingPeriodsMember as $accompanyingPeriod) {
if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $accompanyingPeriod)) {
continue;
} else {
$accompanyingPeriods[$accompanyingPeriod->getId()] = $accompanyingPeriod;
}
}
}
$oldMembers = $household->getNonCurrentMembers();
$accompanyingPeriodsOld = [];
foreach ($oldMembers as $m) {
$accompanyingPeriodsOldMember = $m->getPerson()->getAccompanyingPeriods();
foreach ($accompanyingPeriodsOldMember as $accompanyingPeriod) {
if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $accompanyingPeriod)) {
continue;
} else {
$id = $accompanyingPeriod->getId();
if (!array_key_exists($id, $accompanyingPeriodsOld) && !array_key_exists($id, $accompanyingPeriods)) {
$accompanyingPeriodsOld[$id] = $accompanyingPeriod;
}
}
}
} }
return $this->render('@ChillPerson/Household/accompanying_period.html.twig', return $this->render('@ChillPerson/Household/accompanying_period.html.twig',
[ [
'household' => $household, 'household' => $household,
'accompanying_periods' => $accompanyingPeriods 'accompanying_periods' => $accompanyingPeriods,
'accompanying_periods_old' => $accompanyingPeriodsOld
] ]
); );
} }

View File

@ -77,11 +77,13 @@ class Configuration implements ConfigurationInterface
->append($this->addFieldNode('nationality')) ->append($this->addFieldNode('nationality'))
->append($this->addFieldNode('country_of_birth')) ->append($this->addFieldNode('country_of_birth'))
->append($this->addFieldNode('marital_status')) ->append($this->addFieldNode('marital_status'))
->append($this->addFieldNode('civility'))
->append($this->addFieldNode('spoken_languages')) ->append($this->addFieldNode('spoken_languages'))
->append($this->addFieldNode('address')) ->append($this->addFieldNode('address'))
->append($this->addFieldNode('accompanying_period')) ->append($this->addFieldNode('accompanying_period'))
->append($this->addFieldNode('memo')) ->append($this->addFieldNode('memo'))
->append($this->addFieldNode('number_of_children')) ->append($this->addFieldNode('number_of_children'))
->append($this->addFieldNode('acceptEmail'))
->arrayNode('alt_names') ->arrayNode('alt_names')
->defaultValue([]) ->defaultValue([])
->arrayPrototype() ->arrayPrototype()

View File

@ -41,7 +41,7 @@ class Household
* targetEntity="Chill\MainBundle\Entity\Address", * targetEntity="Chill\MainBundle\Entity\Address",
* cascade={"persist", "remove", "merge", "detach"}) * cascade={"persist", "remove", "merge", "detach"})
* @ORM\JoinTable(name="chill_person_household_to_addresses") * @ORM\JoinTable(name="chill_person_household_to_addresses")
* @ORM\OrderBy({"validFrom" = "DESC"}) * @ORM\OrderBy({"validFrom" = "DESC", "id" = "DESC"})
* @Serializer\Groups({"write"}) * @Serializer\Groups({"write"})
*/ */
private Collection $addresses; private Collection $addresses;
@ -245,6 +245,16 @@ class Household
$members->getIterator() $members->getIterator()
->uasort( ->uasort(
function (HouseholdMember $a, HouseholdMember $b) { function (HouseholdMember $a, HouseholdMember $b) {
if ($a->getPosition() === NULL) {
if ($b->getPosition() === NULL) {
return 0;
} else {
return -1;
}
} elseif ($b->getPosition() === NULL) {
return 1;
}
if ($a->getPosition()->getOrdering() < $b->getPosition()->getOrdering()) { if ($a->getPosition()->getOrdering() < $b->getPosition()->getOrdering()) {
return -1; return -1;
} }
@ -334,6 +344,26 @@ class Household
return $this->getNonCurrentMembers($now)->matching($criteria); return $this->getNonCurrentMembers($now)->matching($criteria);
} }
public function getCurrentMembersWithoutPosition(\DateTimeInterface $now = null)
{
$criteria = new Criteria();
$expr = Criteria::expr();
$criteria->where($expr->isNull('position'));
return $this->getCurrentMembers($now)->matching($criteria);
}
public function getNonCurrentMembersWithoutPosition(\DateTimeInterface $now = null)
{
$criteria = new Criteria();
$expr = Criteria::expr();
$criteria->where($expr->isNull('position'));
return $this->getNonCurrentMembers($now)->matching($criteria);
}
public function addMember(HouseholdMember $member): self public function addMember(HouseholdMember $member): self
{ {
if (!$this->members->contains($member)) { if (!$this->members->contains($member)) {

View File

@ -29,7 +29,7 @@ class HouseholdMember
/** /**
* @ORM\ManyToOne(targetEntity=Position::class) * @ORM\ManyToOne(targetEntity=Position::class)
* @Serializer\Groups({"read"}) * @Serializer\Groups({"read"})
* @Assert\NotNull(groups={"household_memberships"}) * @Assert\NotNull(groups={"household_memberships_created"})
*/ */
private ?Position $position = null; private ?Position $position = null;
@ -159,6 +159,12 @@ class HouseholdMember
return $this->shareHousehold; return $this->shareHousehold;
} }
public function setShareHousehold(bool $shareHousehold): self
{
$this->shareHousehold = $shareHousehold;
return $this;
}
public function getPerson(): ?Person public function getPerson(): ?Person
{ {

View File

@ -41,7 +41,7 @@ class MaritalStatus
/** /**
* @var string array * @var string array
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $name; private $name;

View File

@ -34,6 +34,7 @@ use Chill\PersonBundle\Entity\MaritalStatus;
use Chill\PersonBundle\Entity\Household\HouseholdMember; use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\MainBundle\Entity\HasCenterInterface; use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable; use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\PersonBundle\Entity\Person\PersonCurrentAddress; use Chill\PersonBundle\Entity\Person\PersonCurrentAddress;
use DateTime; use DateTime;
@ -212,6 +213,15 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
*/ */
private $maritalStatus; private $maritalStatus;
/**
* The marital status of the person
* @var Civility
*
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Civility")
* @ORM\JoinColumn(nullable=true)
*/
private $civility;
/** /**
* The date of the last marital status change of the person * The date of the last marital status change of the person
* @var \DateTime * @var \DateTime
@ -406,7 +416,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* Array where customfield's data are stored * Array where customfield's data are stored
* @var array * @var array
* *
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $cFData; private $cFData;
@ -613,6 +623,26 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $accompanyingPeriods; return $accompanyingPeriods;
} }
/**
* Get current accompanyingPeriods array
*/
public function getCurrentAccompanyingPeriods(): array
{
$currentAccompanyingPeriods = [];
$currentDate = new DateTime();
foreach ($this->accompanyingPeriodParticipations as $participation)
{
$endDate = $participation->getEndDate();
if ($endDate === null || $endDate > $currentDate){
$currentAccompanyingPeriods[] = $participation->getAccompanyingPeriod();
}
}
return $currentAccompanyingPeriods;
}
/** /**
* Get AccompanyingPeriodParticipations Collection * Get AccompanyingPeriodParticipations Collection
* *
@ -962,6 +992,28 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->maritalStatus; return $this->maritalStatus;
} }
/**
* Set civility
*
* @param Civility $civility
* @return Person
*/
public function setCivility(Civility $civility = null)
{
$this->civility = $civility;
return $this;
}
/**
* Get civility
*
* @return Civility
*/
public function getCivility()
{
return $this->civility;
}
/** /**
* Set contactInfo * Set contactInfo
* *
@ -1283,11 +1335,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/** /**
* get the address associated with the person at the given date * get the address associated with the person at the given date
* *
* If the `$at` parameter is now, use the method `getCurrentPersonAddress`, which is optimized
* on database side.
*
* @param DateTime|null $at * @param DateTime|null $at
* @return Address|null * @return Address|null
* @throws \Exception * @throws \Exception
*/ */
public function getCurrentPersonAddress(?\DateTime $at = null): ?Address public function getAddressAt(?\DateTime $at = null): ?Address
{ {
$at ??= new DateTime('now'); $at ??= new DateTime('now');
@ -1305,6 +1360,20 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
current($addresses); current($addresses);
} }
/**
* Get the current person address
*
* @return Address|null
*/
public function getCurrentPersonAddress(): ?Address
{
if (null === $this->currentPersonAddress) {
return null;
}
return $this->currentPersonAddress->getAddress();
}
/** /**
* Validation callback that checks if the accompanying periods are valid * Validation callback that checks if the accompanying periods are valid
* *

View File

@ -21,7 +21,7 @@ use Chill\MainBundle\Export\FilterInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types;
use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement; use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
/** /**
@ -44,8 +44,8 @@ class AccompanyingPeriodClosingFilter extends AbstractAccompanyingPeriodExportEl
$qb->expr()->gte('accompanying_period.closingDate', ':date_from')); $qb->expr()->gte('accompanying_period.closingDate', ':date_from'));
$qb->andWhere($clause); $qb->andWhere($clause);
$qb->setParameter('date_from', $data['date_from'], Type::DATE); $qb->setParameter('date_from', $data['date_from'], Types::DATE_MUTABLE);
$qb->setParameter('date_to', $data['date_to'], Type::DATE); $qb->setParameter('date_to', $data['date_to'], Types::DATE_MUTABLE);
} }
public function applyOn(): string public function applyOn(): string

View File

@ -21,7 +21,7 @@ use Chill\MainBundle\Export\FilterInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types;
use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement; use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
/** /**
@ -52,8 +52,8 @@ class AccompanyingPeriodFilter extends AbstractAccompanyingPeriodExportElement i
); );
$qb->andWhere($clause); $qb->andWhere($clause);
$qb->setParameter('date_from', $data['date_from'], Type::DATE); $qb->setParameter('date_from', $data['date_from'], Types::DATE_MUTABLE);
$qb->setParameter('date_to', $data['date_to'], Type::DATE); $qb->setParameter('date_to', $data['date_to'], Types::DATE_MUTABLE);
} }
public function applyOn(): string public function applyOn(): string

View File

@ -21,7 +21,7 @@ use Chill\MainBundle\Export\FilterInterface;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types;
use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement; use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
/** /**
@ -44,8 +44,8 @@ class AccompanyingPeriodOpeningFilter extends AbstractAccompanyingPeriodExportEl
$qb->expr()->gte('accompanying_period.openingDate', ':date_from')); $qb->expr()->gte('accompanying_period.openingDate', ':date_from'));
$qb->andWhere($clause); $qb->andWhere($clause);
$qb->setParameter('date_from', $data['date_from'], Type::DATE); $qb->setParameter('date_from', $data['date_from'], Types::DATE_MUTABLE);
$qb->setParameter('date_to', $data['date_to'], Type::DATE); $qb->setParameter('date_to', $data['date_to'], Types::DATE_MUTABLE);
} }
public function applyOn(): string public function applyOn(): string

View File

@ -16,13 +16,7 @@ class HouseholdMemberType extends AbstractType
->add('startDate', ChillDateType::class, [ ->add('startDate', ChillDateType::class, [
'label' => 'household.Start date', 'label' => 'household.Start date',
'input' => 'datetime_immutable', 'input' => 'datetime_immutable',
]) ]);
->add('endDate', ChillDateType::class, [
'label' => 'household.End date',
'input' => 'datetime_immutable',
'required' => false
])
;
if ($options['data']->getPosition()->isAllowHolder()) { if ($options['data']->getPosition()->isAllowHolder()) {
$builder $builder
->add('holder', ChoiceType::class, [ ->add('holder', ChoiceType::class, [

View File

@ -22,6 +22,7 @@
namespace Chill\PersonBundle\Form; namespace Chill\PersonBundle\Form;
use Chill\CustomFieldsBundle\Form\Type\CustomFieldType; use Chill\CustomFieldsBundle\Form\Type\CustomFieldType;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Form\Type\ChillCollectionType; use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillTextareaType; use Chill\MainBundle\Form\Type\ChillTextareaType;
use Chill\MainBundle\Form\Type\Select2CountryType; use Chill\MainBundle\Form\Type\Select2CountryType;
@ -35,6 +36,11 @@ use Chill\PersonBundle\Form\Type\Select2MaritalStatusType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Chill\MainBundle\Form\Type\ChillDateType; use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\CommentType; use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TelType; use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
@ -62,16 +68,20 @@ class PersonType extends AbstractType
*/ */
protected $configAltNamesHelper; protected $configAltNamesHelper;
protected TranslatableStringHelper $translatableStringHelper;
/** /**
* *
* @param string[] $personFieldsConfiguration configuration of visibility of some fields * @param string[] $personFieldsConfiguration configuration of visibility of some fields
*/ */
public function __construct( public function __construct(
array $personFieldsConfiguration, array $personFieldsConfiguration,
ConfigPersonAltNamesHelper $configAltNamesHelper ConfigPersonAltNamesHelper $configAltNamesHelper,
TranslatableStringHelper $translatableStringHelper
) { ) {
$this->config = $personFieldsConfiguration; $this->config = $personFieldsConfiguration;
$this->configAltNamesHelper = $configAltNamesHelper; $this->configAltNamesHelper = $configAltNamesHelper;
$this->translatableStringHelper = $translatableStringHelper;
} }
/** /**
@ -114,7 +124,19 @@ class PersonType extends AbstractType
} }
if ($this->config['place_of_birth'] === 'visible') { if ($this->config['place_of_birth'] === 'visible') {
$builder->add('placeOfBirth', TextType::class, array('required' => false)); $builder->add('placeOfBirth', TextType::class, array(
'required' => false,
'attr' => ['style' => 'text-transform: uppercase;'],
));
$builder->get('placeOfBirth')->addModelTransformer(new CallbackTransformer(
function ($string) {
return strtoupper($string);
},
function ($string) {
return strtoupper($string);
}
));
} }
if ($this->config['contact_info'] === 'visible') { if ($this->config['contact_info'] === 'visible') {
@ -150,7 +172,11 @@ class PersonType extends AbstractType
if ($this->config['email'] === 'visible') { if ($this->config['email'] === 'visible') {
$builder $builder
->add('email', EmailType::class, array('required' => false)) ->add('email', EmailType::class, array('required' => false));
}
if ($this->config['acceptEmail'] === 'visible') {
$builder
->add('acceptEmail', CheckboxType::class, array('required' => false)); ->add('acceptEmail', CheckboxType::class, array('required' => false));
} }
@ -173,6 +199,23 @@ class PersonType extends AbstractType
)); ));
} }
if ($this->config['civility'] === 'visible'){
$builder
->add('civility', EntityType::class, [
'label' => 'Civility',
'class' => Civility::class,
'choice_label' => function (Civility $civility): string {
return $this->translatableStringHelper->localize($civility->getName());
},
'query_builder' => function (EntityRepository $er): QueryBuilder {
return $er->createQueryBuilder('c')
->where('c.active = true');
},
'placeholder' => 'choose civility',
'required' => false
]);
}
if ($this->config['marital_status'] === 'visible'){ if ($this->config['marital_status'] === 'visible'){
$builder $builder
->add('maritalStatus', Select2MaritalStatusType::class, array( ->add('maritalStatus', Select2MaritalStatusType::class, array(
@ -192,6 +235,7 @@ class PersonType extends AbstractType
array('attr' => array('class' => 'cf-fields'), 'group' => $options['cFGroup'])) array('attr' => array('class' => 'cf-fields'), 'group' => $options['cFGroup']))
; ;
} }
} }
/** /**

View File

@ -19,8 +19,10 @@ class MembersEditor
private array $persistables = []; private array $persistables = [];
private array $membershipsAffected = []; private array $membershipsAffected = [];
private array $oldMembershipsHashes = [];
public const VALIDATION_GROUP = 'household_memberships'; public const VALIDATION_GROUP_CREATED = 'household_memberships_created';
public const VALIDATION_GROUP_AFFECTED = 'household_memberships';
public function __construct(ValidatorInterface $validator, ?Household $household) public function __construct(ValidatorInterface $validator, ?Household $household)
{ {
@ -56,6 +58,7 @@ class MembersEditor
if ($participation->getEndDate() === NULL || $participation->getEndDate() > $date) { if ($participation->getEndDate() === NULL || $participation->getEndDate() > $date) {
$participation->setEndDate($date); $participation->setEndDate($date);
$this->membershipsAffected[] = $participation; $this->membershipsAffected[] = $participation;
$this->oldMembershipsHashes[] = \spl_object_hash($participation);
} }
} }
} }
@ -97,13 +100,18 @@ class MembersEditor
{ {
if ($this->hasHousehold()) { if ($this->hasHousehold()) {
$list = $this->validator $list = $this->validator
->validate($this->getHousehold(), null, [ self::VALIDATION_GROUP ]); ->validate($this->getHousehold(), null, [ self::VALIDATION_GROUP_AFFECTED ]);
} else { } else {
$list = new ConstraintViolationList(); $list = new ConstraintViolationList();
} }
foreach ($this->membershipsAffected as $m) { foreach ($this->membershipsAffected as $m) {
$list->addAll($this->validator->validate($m, null, [ self::VALIDATION_GROUP ])); if (\in_array(\spl_object_hash($m), $this->oldMembershipsHashes)) {
$list->addAll($this->validator->validate($m, null, [ self::VALIDATION_GROUP_AFFECTED ]));
} else {
$list->addAll($this->validator->validate($m, null, [ self::VALIDATION_GROUP_CREATED,
self::VALIDATION_GROUP_AFFECTED ]));
}
} }
return $list; return $list;

View File

@ -7,7 +7,6 @@ const personAcceptEmail = document.getElementById("personAcceptEmail");
const personPhoneNumber = document.getElementById("personPhoneNumber"); const personPhoneNumber = document.getElementById("personPhoneNumber");
const personAcceptSMS = document.getElementById("personAcceptSMS"); const personAcceptSMS = document.getElementById("personAcceptSMS");
new ShowHide({ new ShowHide({
froms: [maritalStatus], froms: [maritalStatus],
container: [maritalStatusDate], container: [maritalStatusDate],
@ -24,21 +23,24 @@ new ShowHide({
event_name: 'change' event_name: 'change'
}); });
new ShowHide({ if (personAcceptEmail) {
froms: [personEmail], new ShowHide({
container: [personAcceptEmail], froms: [personEmail],
test: function(froms) { container: [personAcceptEmail],
for (let f of froms.values()) { test: function(froms) {
for (let input of f.querySelectorAll('input').values()) { for (let f of froms.values()) {
if (input.value) { for (let input of f.querySelectorAll('input').values()) {
return true if (input.value) {
return true
}
} }
} }
} return false;
return false; },
}, event_name: 'input'
event_name: 'input' });
}); }
new ShowHide({ new ShowHide({
froms: [personPhoneNumber], froms: [personPhoneNumber],

View File

@ -9,7 +9,7 @@
<p>{{ $t('pick_social_issue_linked_with_action') }}</p> <p>{{ $t('pick_social_issue_linked_with_action') }}</p>
<div v-for="si in socialIssues"> <div v-for="si in socialIssues">
<input type="radio" v-bind:value="si.id" name="socialIssue" v-model="socialIssuePicked"> {{ si.title.fr }} <input type="radio" v-bind:value="si.id" name="socialIssue" v-model="socialIssuePicked"><span class="badge bg-chill-l-gray text-dark">{{ si.text }}</span>
</div> </div>
<div v-if="hasSocialIssuePicked"> <div v-if="hasSocialIssuePicked">
@ -222,3 +222,16 @@ export default {
} }
</script> </script>
<style lang="scss" scoped>
@import 'ChillMainAssets/module/bootstrap/shared';
@import 'ChillPersonAssets/chill/scss/mixins';
@import 'ChillMainAssets/chill/scss/chill_variables';
span.badge {
@include badge_social($social-issue-color);
font-size: 95%;
margin-bottom: 5px;
margin-right: 1em;
margin-left: 1em;
}
</style>

View File

@ -16,3 +16,4 @@ chill_person:
country_of_birth: hidden country_of_birth: hidden
marital_status: hidden marital_status: hidden
spoken_languages: hidden spoken_languages: hidden
civility: hidden

View File

@ -18,6 +18,7 @@
window.accompanyingCourse = {{ json|json_encode|raw }}; window.accompanyingCourse = {{ json|json_encode|raw }};
</script> </script>
{{ parent() }}
{{ encore_entry_script_tags('vue_accourse_work_create') }} {{ encore_entry_script_tags('vue_accourse_work_create') }}
{% endblock %} {% endblock %}

View File

@ -77,7 +77,7 @@
<div class="wl-row"> <div class="wl-row">
<div class="wl-col title"><h3>{{ 'Participants'|trans }}</h3></div> <div class="wl-col title"><h3>{{ 'Participants'|trans }}</h3></div>
<div class="wl-col list"> <div class="wl-col list">
{% for p in accompanying_period.participations %} {% for p in accompanying_period.getCurrentParticipations %}
<span class="wl-item badge-person"> <span class="wl-item badge-person">
<a href="{{ path('chill_person_accompanying_period_list', { person_id: p.person.id }) }}"> <a href="{{ path('chill_person_accompanying_period_list', { person_id: p.person.id }) }}">
{{ p.person|chill_entity_render_string }} {{ p.person|chill_entity_render_string }}

View File

@ -123,13 +123,15 @@
</div> </div>
<div class="item-col"> <div class="item-col">
<ul class="list-content fa-ul"> <ul class="list-content fa-ul">
{% set multiline = (options['address_multiline']) ? true : false %} {% if person.getCurrentPersonAddress is not null %}
{{ person.getLastAddress|chill_entity_render_box({ {% set multiline = (options['address_multiline']) ? true : false %}
'render': 'list', {{ person.getCurrentPersonAddress|chill_entity_render_box({
'with_picto': true, 'render': 'list',
'multiline': multiline, 'with_picto': true,
'with_valid_from': false 'multiline': multiline,
}) }} 'with_valid_from': false
}) }}
{% endif %}
<li> <li>
{% if person.mobilenumber %} {% if person.mobilenumber %}
<i class="fa fa-li fa-mobile"></i><a href="{{ 'tel:' ~ person.mobilenumber }}"> <i class="fa fa-li fa-mobile"></i><a href="{{ 'tel:' ~ person.mobilenumber }}">

View File

@ -9,6 +9,37 @@
{% include 'ChillPersonBundle:AccompanyingPeriod:_list.html.twig' %} {% include 'ChillPersonBundle:AccompanyingPeriod:_list.html.twig' %}
{% if accompanying_periods_old|length > 0 %}
<style>
button[aria-expanded="true"] > span.folded,
button[aria-expanded="false"] > span.unfolded { display: none; }
button[aria-expanded="false"] > span.folded,
button[aria-expanded="true"] > span.unfolded { display: inline; }
</style>
<div class="accordion" id="nonCurrent">
<div class="accordion-item">
<h2 class="accordion-header" id="heading_{{ household.id }}">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse_{{ household.id }}"
aria-expanded="false"
aria-controls="collapse_{{ household.id }}">
<span class="folded">{{ 'household.Show accompanying periods of past or future memberships'|trans({'length': accompanying_periods_old|length}) }}</span>
<span class="unfolded text-secondary">{{ 'household.Hide memberships'|trans }}</span>
</button>
</h2>
<div id="collapse_{{ household.id }}"
class="accordion-collapse collapse"
aria-labelledby="heading_{{ household.id }}"
data-bs-parent="#nonCurrent">
{% include 'ChillPersonBundle:AccompanyingPeriod:_list.html.twig' with {'accompanying_periods' : accompanying_periods_old} %}
</div>
</div>
</div>
{% endif %}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li class="cancel"> <li class="cancel">
<a href="{{ path ('chill_person_household_summary', {'household_id' : household.id } ) }}" class="btn btn-cancel"> <a href="{{ path ('chill_person_household_summary', {'household_id' : household.id } ) }}" class="btn btn-cancel">

View File

@ -22,8 +22,11 @@
{%- for m in members -%} {%- for m in members -%}
<span <span
class="badge-member{%- if m.holder %} holder{% endif -%}{%- if m.position.ordering >= 2 %} child{% endif -%}" class="badge-member{%- if m.holder %} holder{% endif -%}"
title="{{ m.position.label.fr }}"> {% if m.position is not null %}
title="{{ m.position.label.fr }}"
{% endif %}
>
<a href="{{ path('chill_person_view', { person_id: m.person.id}) }}"> <a href="{{ path('chill_person_view', { person_id: m.person.id}) }}">
{%- if m.holder %} {%- if m.holder %}
<span class="fa-stack fa-holder" title="{{ 'household.holder'|trans }}"> <span class="fa-stack fa-holder" title="{{ 'household.holder'|trans }}">

View File

@ -106,81 +106,95 @@
<h2 class="my-5">{{ 'household.Household members'|trans }}</h2> <h2 class="my-5">{{ 'household.Household members'|trans }}</h2>
{% for p in positions %} {% for p in positions|merge([ '_none' ]) %}
<div class="mb-5">
<h3>{{ p.label|localize_translatable_string }}
{% if false == p.shareHousehold %}
<i class="chill-help-tooltip" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true"
title="{{ 'household.Those members does not share address'|trans }}"></i>
{% endif %}
</h3>
{%- set members = household.currentMembersByPosition(p) %} {% if p == '_none' %}
{% set members = household.currentMembersWithoutPosition %}
{% set old_members = household.nonCurrentMembersWithoutPosition %}
{% else %}
{%- set members = household.currentMembersByPosition(p) %}
{% set old_members = household.nonCurrentMembersByPosition(p) %}
{% endif %}
{% macro customButtons(member, household) %} {% if not (p == '_none' and members|length == 0 and old_members|length == 0) %}
<li> <div class="mb-5">
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'persons': [ member.person.id ], 'allow_leave_without_household': true } ) }}" {% if p != '_none' %}
class="btn btn-sm btn-unlink" title="{{ 'household.person.leave'|trans }}"></a> <h3>{{ p.label|localize_translatable_string }}
</li> {% if false == p.shareHousehold %}
<li> <i class="chill-help-tooltip" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true"
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'persons': [ member.person.id ], 'household': household.id} ) }}" title="{{ 'household.Those members does not share address'|trans }}"></i>
class="btn btn-sm btn-misc" title="{{ 'household.Change position'|trans }}"><i class="fa fa-arrows-h"></i></a> {% endif %}
</li> </h3>
{% endmacro %} {% else %}
<h3 class="chill-entity">{{ 'household.Members without position'|trans }}</h3>
{% endif %}
{% if members|length > 0 %}
<div class="flex-table list-household-members">
{% for m in members %}
{% include '@ChillPerson/Household/_render_member.html.twig' with {
'member': m,
'customButtons': { 'after': _self.customButtons(m, household) }
} %}
{% endfor %}
</div>
{% else %}
<p class="chill-no-data-statement">{{ 'household.Any persons into this position'|trans }}</p>
{% endif %}
{% set members = household.nonCurrentMembersByPosition(p) %} {% macro customButtons(member, household) %}
{% if members|length > 0 %} <li>
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'persons': [ member.person.id ], 'allow_leave_without_household': true } ) }}"
class="btn btn-sm btn-unlink" title="{{ 'household.person.leave'|trans }}"></a>
</li>
<li>
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'persons': [ member.person.id ], 'household': household.id} ) }}"
class="btn btn-sm btn-misc" title="{{ 'household.Change position'|trans }}"><i class="fa fa-arrows-h"></i></a>
</li>
{% endmacro %}
<style> {% if members|length > 0 %}
button[aria-expanded="true"] > span.folded, <div class="flex-table list-household-members">
button[aria-expanded="false"] > span.unfolded { display: none; } {% for m in members %}
button[aria-expanded="false"] > span.folded, {% include '@ChillPerson/Household/_render_member.html.twig' with {
button[aria-expanded="true"] > span.unfolded { display: inline; } 'member': m,
</style> 'customButtons': { 'after': _self.customButtons(m, household) }
<div class="accordion" id="nonCurrent"> } %}
<div class="accordion-item"> {% endfor %}
<h2 class="accordion-header" id="heading_{{ p.id }}"> </div>
<button {% else %}
class="accordion-button collapsed" <p class="chill-no-data-statement">{{ 'household.Any persons into this position'|trans }}</p>
type="button" {% endif %}
data-bs-toggle="collapse"
data-bs-target="#collapse_{{ p.id }}" {% if old_members|length > 0 %}
aria-expanded="false" <style>
aria-controls="collapse_{{ p.id }}"> button[aria-expanded="true"] > span.folded,
<span class="folded">{{ 'household.Show future or past memberships'|trans({'length': members|length}) }}</span> button[aria-expanded="false"] > span.unfolded { display: none; }
<span class="unfolded text-secondary">{{ 'household.Hide memberships'|trans }}</span> button[aria-expanded="false"] > span.folded,
</button> button[aria-expanded="true"] > span.unfolded { display: inline; }
</h2> </style>
<div id="collapse_{{ p.id }}" <div class="accordion" id="nonCurrent">
class="accordion-collapse collapse" <div class="accordion-item">
aria-labelledby="heading_{{ p.id }}" <h2 class="accordion-header" id="heading_{{ p == '_none' ? '_none' : p.id }}">
data-bs-parent="#nonCurrent"> <button
<div class="flex-table my-0 list-household-members"> class="accordion-button collapsed"
{% for m in members %} type="button"
{% include '@ChillPerson/Household/_render_member.html.twig' with { 'member': m } %} data-bs-toggle="collapse"
{% endfor %} data-bs-target="#collapse_{{ p == '_none' ? '_none' : p.id }}"
aria-expanded="false"
aria-controls="collapse_{{ p == '_none' ? '_none' : p.id }}">
<span class="folded">{{ 'household.Show future or past memberships'|trans({'length': old_members|length}) }}</span>
<span class="unfolded text-secondary">{{ 'household.Hide memberships'|trans }}</span>
</button>
</h2>
<div id="collapse_{{ p == '_none' ? '_none' : p.id }}"
class="accordion-collapse collapse"
aria-labelledby="heading_{{ p == '_none' ? '_none' : p.id }}"
data-bs-parent="#nonCurrent">
<div class="flex-table my-0 list-household-members">
{% for m in old_members %}
{% include '@ChillPerson/Household/_render_member.html.twig' with { 'member': m } %}
{% endfor %}
</div>
</div>
</div> </div>
</div> </div>
</div> {% endif %}
</div>
{% endif %}
</div> </div>
{% endif %}
{% endfor %} {% endfor %}
<ul class="record_actions"> <ul class="record_actions">
<li> <li>
<a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'household': household.id }) }}" <a href="{{ chill_path_add_return_path('chill_person_household_members_editor', {'household': household.id }) }}"

View File

@ -37,6 +37,9 @@
<fieldset> <fieldset>
<legend><h2>{{ 'General information'|trans }}</h2></legend> <legend><h2>{{ 'General information'|trans }}</h2></legend>
{%- if form.civility is defined -%}
{{ form_row(form.civility, {'label' : 'Civility'}) }}
{% endif %}
{{ form_row(form.firstName, {'label' : 'First name'}) }} {{ form_row(form.firstName, {'label' : 'First name'}) }}
{{ form_row(form.lastName, {'label' : 'Last name'}) }} {{ form_row(form.lastName, {'label' : 'Last name'}) }}
{% if form.altNames is defined %} {% if form.altNames is defined %}
@ -95,6 +98,8 @@
<div id="personEmail"> <div id="personEmail">
{{ form_row(form.email, {'label': 'Email'}) }} {{ form_row(form.email, {'label': 'Email'}) }}
</div> </div>
{% endif %}
{%- if form.acceptEmail is defined -%}
<div id="personAcceptEmail"> <div id="personAcceptEmail">
{{ form_row(form.acceptEmail, {'label' : 'Accept emails ?'}) }} {{ form_row(form.acceptEmail, {'label' : 'Accept emails ?'}) }}
</div> </div>
@ -135,12 +140,11 @@
</button> </button>
</li> </li>
</ul> </ul>
{{ form_end(form) }} {{ form_end(form) }}
</div> </div>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ encore_entry_link_tags('page_person') }} {{ encore_entry_script_tags('page_person') }}
{% endblock %} {% endblock %}

View File

@ -53,7 +53,7 @@
'addLink': true, 'addLink': true,
'addInfo': true, 'addInfo': true,
'addAge': true, 'addAge': true,
'addAltNames': false, 'addAltNames': true,
'addCenter': true, 'addCenter': true,
'address_multiline': false, 'address_multiline': false,
'customButtons': { 'after': _self.button_person(person) } 'customButtons': { 'after': _self.button_person(person) }

View File

@ -51,6 +51,15 @@ This view should receive those arguments:
<figure class="person-details"> <figure class="person-details">
<h2 class="chill-red">{{ 'General information'|trans }}</h2> <h2 class="chill-red">{{ 'General information'|trans }}</h2>
<dl> <dl>
{% if person.civility is not null %}
<dt>{{ 'Civility'|trans }}&nbsp;:</dt>
<dd>
{% if person.civility.name|length > 0 %}
{{ person.civility.name|first }}
{% endif %}
</dd>
{% endif %}
<dt>{{ 'First name'|trans }}&nbsp;:</dt> <dt>{{ 'First name'|trans }}&nbsp;:</dt>
<dd>{{ person.firstName }}</dd> <dd>{{ person.firstName }}</dd>

View File

@ -193,8 +193,6 @@ class HouseholdMemberControllerTest extends WebTestCase
$form = $crawler->selectButton('Enregistrer') $form = $crawler->selectButton('Enregistrer')
->form(); ->form();
$form['household_member[endDate]'] = (new \DateTime('tomorrow'))
->format('Y-m-d');
$crawler = $client->submit($form); $crawler = $client->submit($form);

View File

@ -164,55 +164,6 @@ class PersonTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($r['result'], Person::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD); $this->assertEquals($r['result'], Person::ERROR_ADDIND_PERIOD_AFTER_AN_OPEN_PERIOD);
} }
public function dateProvider(): Generator
{
yield [(DateTime::createFromFormat('Y-m-d', '2021-01-05'))->settime(0, 0)];
yield [(DateTime::createFromFormat('Y-m-d', '2021-02-05'))->settime(0, 0)];
yield [(DateTime::createFromFormat('Y-m-d', '2021-03-05'))->settime(0, 0)];
}
/**
* @dataProvider dateProvider
*/
public function testGetLastAddress(DateTime $date)
{
$p = new Person($date);
// Make sure that there is no last address.
$this::assertNull($p->getLastAddress());
// Take an arbitrary date before the $date in parameter.
$addressDate = clone $date;
// 1. Smoke test: Test that the first address added is the last one.
$address1 = (new Address())->setValidFrom($addressDate->sub(new DateInterval('PT180M')));
$p->addAddress($address1);
$this::assertCount(1, $p->getAddresses());
$this::assertSame($address1, $p->getLastAddress());
// 2. Add an older address, which should not be the last address.
$addressDate2 = clone $addressDate;
$address2 = (new Address())->setValidFrom($addressDate2->sub(new DateInterval('PT30M')));
$p->addAddress($address2);
$this::assertCount(2, $p->getAddresses());
$this::assertSame($address1, $p->getLastAddress());
// 3. Add a newer address, which should be the last address.
$addressDate3 = clone $addressDate;
$address3 = (new Address())->setValidFrom($addressDate3->add(new DateInterval('PT30M')));
$p->addAddress($address3);
$this::assertCount(3, $p->getAddresses());
$this::assertSame($address3, $p->getLastAddress());
// 4. Get the last address from a specific date.
$this::assertEquals($address1, $p->getLastAddress($addressDate));
$this::assertEquals($address2, $p->getLastAddress($addressDate2));
$this::assertEquals($address3, $p->getLastAddress($addressDate3));
}
public function testIsSharingHousehold() public function testIsSharingHousehold()
{ {
$person = new Person(); $person = new Person();

View File

@ -22,7 +22,7 @@ namespace Chill\PersonBundle\Widget;
use Chill\MainBundle\Templating\Widget\WidgetInterface; use Chill\MainBundle\Templating\Widget\WidgetInterface;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr; use Doctrine\ORM\Query\Expr;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
@ -123,7 +123,7 @@ class PersonListWidget implements WidgetInterface
(new Expr())->between(':now', 'ap.openingDate', 'ap.closingDate') (new Expr())->between(':now', 'ap.openingDate', 'ap.closingDate')
); );
$and->add($or); $and->add($or);
$qb->setParameter('now', new \DateTime(), Type::DATE); $qb->setParameter('now', new \DateTime(), Types::DATE_MUTABLE);
} }

View File

@ -61,7 +61,7 @@ interface PersonFilteringInterface
public function getPersonIds(EntityManager $em, User $user) public function getPersonIds(EntityManager $em, User $user)
{ {
$rsmBuilder = new ResultSetMappingBuilder($em); $rsmBuilder = new ResultSetMappingBuilder($em);
$rsmBuilder->addScalarResult('id', 'id', Type::BIGINT); $rsmBuilder->addScalarResult('id', 'id', Types::BIGINT);
$personTable = $em->getClassMetadata('ChillPersonBundle:Person') $personTable = $em->getClassMetadata('ChillPersonBundle:Person')
->getTableName(); ->getTableName();

View File

@ -7,8 +7,9 @@ services:
Chill\PersonBundle\Form\PersonType: Chill\PersonBundle\Form\PersonType:
arguments: arguments:
- '%chill_person.person_fields%' $personFieldsConfiguration: '%chill_person.person_fields%'
- '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper' $configAltNamesHelper: '@Chill\PersonBundle\Config\ConfigPersonAltNamesHelper'
$translatableStringHelper: '@Chill\MainBundle\Templating\TranslatableStringHelper'
tags: tags:
- { name: form.type, alias: '@chill.person.form.person_creation' } - { name: form.type, alias: '@chill.person.form.person_creation' }

View File

@ -17,7 +17,6 @@ class Version20150607231010 extends AbstractMigration
. 'recorded.'; . 'recorded.';
} }
/** /**
* @param Schema $schema * @param Schema $schema
*/ */
@ -28,36 +27,26 @@ class Version20150607231010 extends AbstractMigration
'Migration can only be executed safely on \'postgresql\'.' 'Migration can only be executed safely on \'postgresql\'.'
); );
// retrieve center for setting a default center
$centers = $this->connection->fetchAll('SELECT id FROM centers');
if (count($centers) > 0) {
$defaultCenterId = $centers[0]['id'];
} else { // if no center, performs other checks
//check if there are data in person table
$nbPeople = $this->connection->fetchColumn('SELECT count(*) FROM person');
if ($nbPeople > 0) {
// we have data ! We have to create a center !
$newCenterId = $this->connection->fetchColumn('SELECT nextval(\'centers_id_seq\');');
$this->addSql(
'INSERT INTO centers (id, name) VALUES (:id, :name)',
['id' => $newCenterId, 'name' => 'Auto-created center']
);
$defaultCenterId = $newCenterId;
}
}
$this->addSql('ALTER TABLE person ADD center_id INT'); $this->addSql('ALTER TABLE person ADD center_id INT');
// retrieve center for setting a default center
$stmt = $this->connection->executeQuery("SELECT id FROM centers ORDER BY id ASC LIMIT 1");
$center = $stmt->fetchOne();
if ($center !== false) {
$defaultCenterId = $center['id'];
}
if (isset($defaultCenterId)) { if (isset($defaultCenterId)) {
$this->addSql('UPDATE person SET center_id = :id', array('id' => $defaultCenterId)); $this->addSql('UPDATE person SET center_id = :id', array('id' => $defaultCenterId));
} }
// this will fail if a person is defined, which should not happen any more at this
// time of writing (2021-11-09)
$this->addSql('ALTER TABLE person ALTER center_id SET NOT NULL');
$this->addSql('ALTER TABLE person ' $this->addSql('ALTER TABLE person '
. 'ADD CONSTRAINT FK_person_center FOREIGN KEY (center_id) ' . 'ADD CONSTRAINT FK_person_center FOREIGN KEY (center_id) '
. 'REFERENCES centers (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); . 'REFERENCES centers (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE person ALTER center_id SET NOT NULL');
$this->addSql('CREATE INDEX IDX_person_center ON person (center_id)'); $this->addSql('CREATE INDEX IDX_person_center ON person (center_id)');
} }

View File

@ -15,7 +15,6 @@ final class Version20200310090632 extends AbstractMigration
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE chill_person_closingmotive ADD parent_id INT DEFAULT NULL'); $this->addSql('ALTER TABLE chill_person_closingmotive ADD parent_id INT DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN chill_person_closingmotive.name IS \'(DC2Type:json_array)\'');
$this->addSql('ALTER TABLE chill_person_closingmotive ADD CONSTRAINT FK_92351ECE727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_person_closingmotive (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); $this->addSql('ALTER TABLE chill_person_closingmotive ADD CONSTRAINT FK_92351ECE727ACA70 FOREIGN KEY (parent_id) REFERENCES chill_person_closingmotive (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_92351ECE727ACA70 ON chill_person_closingmotive (parent_id)'); $this->addSql('CREATE INDEX IDX_92351ECE727ACA70 ON chill_person_closingmotive (parent_id)');
$this->addsql("ALTER TABLE chill_person_closingmotive ADD ordering DOUBLE PRECISION DEFAULT '0' NOT NULL;"); $this->addsql("ALTER TABLE chill_person_closingmotive ADD ordering DOUBLE PRECISION DEFAULT '0' NOT NULL;");

View File

@ -24,6 +24,5 @@ final class Version20210419112619 extends AbstractMigration
public function down(Schema $schema) : void public function down(Schema $schema) : void
{ {
$this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_closingmotive.name IS \'(DC2Type:json_array)\'');
} }
} }

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add Civility to Person
*/
final class Version20211108100849 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add Civility to Person';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_person ADD civility_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE chill_person_person ADD CONSTRAINT FK_BF210A1423D6A298 FOREIGN KEY (civility_id) REFERENCES chill_main_civility (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_BF210A1423D6A298 ON chill_person_person (civility_id)');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE chill_person_person DROP CONSTRAINT FK_BF210A1423D6A298');
$this->addSql('DROP INDEX IDX_BF210A1423D6A298');
$this->addSql('ALTER TABLE chill_person_person DROP civility_id');
}
}

View File

@ -26,6 +26,12 @@ household:
many {Montrer # anciennes ou futures appartenances} many {Montrer # anciennes ou futures appartenances}
other {Montrer # anciennes ou futures appartenances} other {Montrer # anciennes ou futures appartenances}
} }
Show accompanying periods of past or future memberships: >-
{length, plural,
one {Montrer les parcours d'une ancienne appartenance}
many {Montrer # parcours des anciennes ou futures appartenances}
other {Montrer # parcours des anciennes ou futures appartenances}
}
Hide memberships: Masquer Hide memberships: Masquer
Those members does not share address: Ces usagers ne partagent pas l'adresse du ménage. Those members does not share address: Ces usagers ne partagent pas l'adresse du ménage.
Any persons into this position: Aucune personne n'appartient au ménage à cette position. Any persons into this position: Aucune personne n'appartient au ménage à cette position.
@ -77,6 +83,7 @@ household:
Household history for %name%: Historique des ménages pour {name} Household history for %name%: Historique des ménages pour {name}
Household shared: Ménages domiciliés Household shared: Ménages domiciliés
Household not shared: Ménage non domiciliés Household not shared: Ménage non domiciliés
Members without position: Membres non positionnés
Never in any household: Membre d'aucun ménage Never in any household: Membre d'aucun ménage
Membership currently running: En cours Membership currently running: En cours
from: Depuis from: Depuis

View File

@ -47,8 +47,8 @@ Phonenumber: 'Numéro de téléphone'
phonenumber: numéro de téléphone phonenumber: numéro de téléphone
Mobilenumber: 'Numéro de téléphone portable' Mobilenumber: 'Numéro de téléphone portable'
mobilenumber: numéro de téléphone portable mobilenumber: numéro de téléphone portable
Accept short text message ?: Accepte les SMS? Accept short text message ?: La personne a donné l'autorisation d'utiliser ce no de téléphone pour l'envoi de rappel par SMS
Accept short text message: Accepte les SMS Accept short text message: La personne a donné l'autorisation d'utiliser ce no de téléphone pour l'envoi de rappel par SMS
Other phonenumber: Autre numéro de téléphone Other phonenumber: Autre numéro de téléphone
Description: description Description: description
Add new phone: Ajouter un numéro de téléphone Add new phone: Ajouter un numéro de téléphone
@ -80,6 +80,8 @@ Married: Marié(e)
'Contact information': 'Informations de contact' 'Contact information': 'Informations de contact'
'Administrative information': Administratif 'Administrative information': Administratif
File number: Dossier n° File number: Dossier n°
Civility: Civilité
choose civility: --
# dédoublonnage # dédoublonnage
Old person: Doublon Old person: Doublon

View File

@ -74,7 +74,7 @@ class Report implements HasCenterInterface, HasScopeInterface
/** /**
* @var array * @var array
* @ORM\Column(type="json_array") * @ORM\Column(type="json")
*/ */
private $cFData; private $cFData;

View File

@ -452,7 +452,7 @@ final class SingleTaskController extends AbstractController
* @return Response * @return Response
* @Route( * @Route(
* "/{_locale}/task/single-task/list/my", * "/{_locale}/task/single-task/list/my",
* name="chill_task_single_my_tasks" * name="chill_task_singletask_my_tasks"
* ) * )
*/ */
public function myTasksAction() public function myTasksAction()
@ -480,7 +480,7 @@ final class SingleTaskController extends AbstractController
] ]
); );
return $this->render('@ChillTask/SingleTask/List/index.html.twig', [ return $this->render('@ChillTask/SingleTask/List/index_my_tasks.html.twig', [
'tasks' => $tasks, 'tasks' => $tasks,
'paginator' => $paginator, 'paginator' => $paginator,
'filter_order' => $filterOrder, 'filter_order' => $filterOrder,

View File

@ -88,27 +88,27 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
if ($ended > 0) { if ($ended > 0) {
$this->addItemInMenu( $this->addItemInMenu(
$menu, $menu,
$user,
'%number% tasks over deadline', '%number% tasks over deadline',
'My tasks over deadline',
SingleTaskRepository::DATE_STATUS_ENDED,
$ended, $ended,
-15); -15,
['new', 'in_progress'],
['alert']
);
} }
if ($warning > 0) { if ($warning > 0) {
$this->addItemInMenu( $this->addItemInMenu(
$menu, $menu,
$user,
'%number% tasks near deadline', '%number% tasks near deadline',
'My tasks near deadline',
SingleTaskRepository::DATE_STATUS_WARNING,
$warning, $warning,
-14); -14,
['new', 'in_progress'],
['warning']
);
} }
$menu->addChild("My tasks", [ $menu->addChild("My tasks", [
'route' => 'chill_task_single_my_tasks' 'route' => 'chill_task_singletask_my_tasks'
]) ])
->setExtras([ ->setExtras([
'order' => -10, 'order' => -10,
@ -117,20 +117,20 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
} }
protected function addItemInMenu(MenuItem $menu, User $u, $message, $title, $status, $number, $order) protected function addItemInMenu(MenuItem $menu, $message, $number, $order, array $states = [], array $status = [])
{ {
if ($number > 0) { if ($number > 0) {
$menu->addChild( $menu->addChild(
$this->translator->transChoice($message, $number), $this->translator->transChoice($message, $number),
[ [
'route' => 'chill_task_singletask_list', 'route' => 'chill_task_singletask_my_tasks',
'routeParameters' => [ 'routeParameters' => [
'user_id' => $u->getId(), 'f' => [
'status' => [ 'checkboxes' => [
$status 'states' => $states,
], 'status' => $status
'hide_form' => true, ]
'title' => $this->translator->trans($title) ]
] ]
]) ])
->setExtras([ ->setExtras([

View File

@ -284,8 +284,8 @@ final class SingleTaskAclAwareRepository implements SingleTaskAclAwareRepository
$qb->expr()->andX( $qb->expr()->andX(
$qb->expr()->not($qb->expr()->isNull('t.endDate')), $qb->expr()->not($qb->expr()->isNull('t.endDate')),
$qb->expr()->not($qb->expr()->isNull('t.warningInterval')), $qb->expr()->not($qb->expr()->isNull('t.warningInterval')),
$qb->expr()->gte('t.endDate - t.warningInterval', ':now'), $qb->expr()->lte('t.endDate - t.warningInterval', ':now'),
$qb->expr()->lt('t.endDate', ':now') $qb->expr()->gt('t.endDate', ':now')
) )
); );
$qb $qb

View File

@ -8,7 +8,7 @@ use Doctrine\ORM\QueryBuilder;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Symfony\Component\Security\Core\Role\Role; use Symfony\Component\Security\Core\Role\Role;
use Chill\TaskBundle\Security\Authorization\TaskVoter; use Chill\TaskBundle\Security\Authorization\TaskVoter;
use Doctrine\DBAL\Types\Type; use Doctrine\DBAL\Types\Types;
use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Center;
/** /**
@ -191,7 +191,7 @@ class SingleTaskRepository extends EntityRepository
->add($this->buildNowIsAfterStartDate($qb, true)) ->add($this->buildNowIsAfterStartDate($qb, true))
; ;
} }
$qb->setParameter('now', new \DateTime('today'), Type::DATE); $qb->setParameter('now', new \DateTime('today'), Types::DATE_MUTABLE);
$qb->andWhere($andWhere); $qb->andWhere($andWhere);
} }

View File

@ -21,7 +21,7 @@
{{ chill_pagination(paginator) }} {{ chill_pagination(paginator) }}
{% if is_granted('CHILL_TASK_TASK_CREATE_FOR_COURSE', person) %} {% if is_granted('CHILL_TASK_TASK_CREATE_FOR_COURSE', accompanyingCourse) %}
<ul class="record_actions sticky-form-buttons"> <ul class="record_actions sticky-form-buttons">
<li> <li>
{% if accompanyingCourse is not null %} {% if accompanyingCourse is not null %}
@ -37,8 +37,10 @@
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }}
{{ encore_entry_link_tags('page_task_list') }} {{ encore_entry_link_tags('page_task_list') }}
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ parent() }}
{{ encore_entry_script_tags('page_task_list') }} {{ encore_entry_script_tags('page_task_list') }}
{% endblock %} {% endblock %}

View File

@ -1,6 +1,6 @@
{% extends 'ChillMainBundle::layout.html.twig' %} {% extends 'ChillMainBundle::layout.html.twig' %}
{% block title 'My tasks'|trans %} {% block title 'Tasks'|trans %}
{% block content %} {% block content %}
<div class="col-md-10 col-xxl"> <div class="col-md-10 col-xxl">
@ -25,8 +25,10 @@
{% endblock %} {% endblock %}
{% block css %} {% block css %}
{{ parent() }}
{{ encore_entry_link_tags('page_task_list') }} {{ encore_entry_link_tags('page_task_list') }}
{% endblock %} {% endblock %}
{% block js %} {% block js %}
{{ parent() }}
{{ encore_entry_script_tags('page_task_list') }} {{ encore_entry_script_tags('page_task_list') }}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,34 @@
{% extends 'ChillMainBundle::layout.html.twig' %}
{% block title 'My tasks'|trans %}
{% block content %}
<div class="col-md-10 col-xxl">
<h1>{{ block('title') }}</h1>
{{ filter_order|chill_render_filter_order_helper }}
{% if tasks|length == 0 %}
<p class="chill-no-data-statement">{{ 'Any tasks'|trans }}</p>
{% else %}
<div class="flex-table chill-task-list">
{% for task in tasks %}
{% include 'ChillTaskBundle:SingleTask/List:index_item.html.twig' with { 'showContext' : true} %}
{% endfor %}
</div>
{% endif %}
{{ chill_pagination(paginator) }}
</div>
{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('page_task_list') }}
{% endblock %}
{% block js %}
{{ parent() }}
{{ encore_entry_script_tags('page_task_list') }}
{% endblock %}

View File

@ -19,7 +19,6 @@ final class Version20190307111314 extends AbstractMigration
$this->addSql('CREATE SEQUENCE chill_third_party_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql('CREATE SEQUENCE chill_third_party_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE chill_third_party (id INT NOT NULL, name VARCHAR(255) NOT NULL, telephone VARCHAR(64) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, comment TEXT DEFAULT NULL, type JSON DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE TABLE chill_third_party (id INT NOT NULL, name VARCHAR(255) NOT NULL, telephone VARCHAR(64) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, comment TEXT DEFAULT NULL, type JSON DEFAULT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN chill_third_party.type IS \'(DC2Type:json_array)\'');
} }