Compare commits

..

12 Commits
2.7.0 ... 2.8.0

Author SHA1 Message Date
f0b3e3af14 merge changelog for 2.8.0 2023-10-05 09:48:11 +02:00
dbc19ca692 update to 2.8.0 2023-10-05 09:42:42 +02:00
cffad6219f Update CONTRIBUTING.md 2023-10-03 07:52:45 +00:00
a625260c36 Merge branch '162-change-job-on-course-when-reassigning-user' into 'master'
Reassign list: when reassigning course, the job associated to the period...

Closes #162

See merge request Chill-Projet/chill-bundles!596
2023-10-02 10:24:55 +00:00
28006374fb Merge branch '143-doublon-list-acp' into 'master'
SocialActionFilter: use a subquery "exists" instead of a where clause (many-to-many association).

Closes #143

See merge request Chill-Projet/chill-bundles!590
2023-10-02 10:24:39 +00:00
b35838f840 Merge branch '163-re-associate-person-filters' into 'master'
[exports] fix the association between aggregators and filter and the export "count persons"

Closes #138 and #163

See merge request Chill-Projet/chill-bundles!595
2023-10-02 10:24:23 +00:00
e08ab0666f Add a constraint to avoid doublons in table chill_person_accompanying_period_location_history
#143
2023-10-02 12:20:15 +02:00
217232fe4f SocialActionFilter: use a subquery "exists" instead of a where clause (many-to-many association) 2023-10-02 12:14:36 +02:00
e5a449118e Reassign list: when reassigning course, the job associated to the period becomes the job of the new referrer (user)
https://gitlab.com/Chill-Projet/chill-bundles/-/issues/162
2023-09-28 14:56:53 +02:00
9c436d5c69 Re-associate filters for export "count persons" 2023-09-28 14:32:58 +02:00
981be7b363 SocialWorkRepository: on search by accompanying period, enlarge the dateranges for take into account work with same endDate / startDate
Close https://gitlab.com/Chill-Projet/chill-bundles/-/issues/144
2023-09-28 12:36:33 +02:00
74e25e706e [ThirdParty] Fix display of civility in page "view"
https://gitlab.com/Chill-Projet/chill-bundles/-/issues/164
2023-09-28 12:12:17 +02:00
17 changed files with 203 additions and 138 deletions

19
.changes/v2.8.0.md Normal file
View File

@@ -0,0 +1,19 @@
## v2.8.0 - 2023-10-05
### Feature
* ([#162](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/162)) Reassigning list: when reassigning courses to a new user, the job associated with the course become the one of the new user (if any)
* Reassining list: the length of the list is increased to 100 courses
### Fixed
* ([#143](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/143)) Fix filter "accompanying course by social action" to avoid duplication in list
* ([#164](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/164)) View a third party: avoid errors when a contact has a civility
* ([#163](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/163)) Fix the filters and aggregators on exports "count peoples"
* ([#143](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/143)) From the database, avoid the creation of location history for same period and at same dates
### Traduction francophone des principaux changements
- Fonctionnalité: Réassigner les parcours en lot: lorsque des parcours sont réassignés "en lot", les parcours sont maintenant associés au métier du nouveau référent;
- Correction: certaines causes qui créaient des doublons dans les listes ont été corrigées;
- Correction des associations entre l'export "nombre de personnes" et les filtres et regroupements associés

View File

@@ -6,6 +6,26 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
## v2.8.0 - 2023-10-05
### Feature
* ([#162](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/162)) Reassigning list: when reassigning courses to a new user, the job associated with the course become the one of the new user (if any)
* Reassining list: the length of the list is increased to 100 courses
### Fixed
* ([#143](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/143)) Fix filter "accompanying course by social action" to avoid duplication in list
* ([#164](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/164)) View a third party: avoid errors when a contact has a civility
* ([#163](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/163)) Fix the filters and aggregators on exports "count peoples"
* ([#143](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/143)) From the database, avoid the creation of location history for same period and at same dates
### Traduction francophone des principaux changements
- Fonctionnalité: Réassigner les parcours en lot: lorsque des parcours sont réassignés "en lot", les parcours sont maintenant associés au métier du nouveau référent;
- Correction: certaines causes qui créaient des doublons dans les listes ont été corrigées;
- Correction des associations entre l'export "nombre de personnes" et les filtres et regroupements associés
## v2.7.0 - 2023-09-27
### Feature
* ([#155](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/155)) The regulation list load accompanying periods by exact postal code (address associated with postal code), and not by the content of the postal code (postal code with same code's string)

View File

@@ -40,7 +40,7 @@ About once a year, the core team discusses the opportunity to invite new members
### Core Membership Revocation
A Symfony Core membership can be revoked for any of the following reasons:
A Chill Core membership can be revoked for any of the following reasons:
- Refusal to follow the rules and policies stated in this document;
- Lack of activity for the past six months;

View File

@@ -17,6 +17,9 @@ use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\Export\FilterType;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations;
@@ -44,9 +47,10 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
protected TranslatorInterface $translator;
public function __construct(
TranslatableStringHelper $translatableStringHelper,
ActivityReasonRepository $activityReasonRepository,
TranslatorInterface $translator
TranslatableStringHelper $translatableStringHelper,
ActivityReasonRepository $activityReasonRepository,
TranslatorInterface $translator,
private readonly RollingDateConverterInterface $rollingDateConverter,
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->activityReasonRepository = $activityReasonRepository;
@@ -62,131 +66,93 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
{
// create a subquery for activity
$sqb = $qb->getEntityManager()->createQueryBuilder();
$sqb->select('person_person_having_activity.id')
$sqb->select('1')
->from(Activity::class, 'activity_person_having_activity')
->join('activity_person_having_activity.person', 'person_person_having_activity');
->leftJoin('activity_person_having_activity.person', 'person_person_having_activity');
// add clause between date
$sqb->where('activity_person_having_activity.date BETWEEN '
. ':person_having_activity_between_date_from'
. ' AND '
. ':person_having_activity_between_date_to');
. ':person_having_activity_between_date_to'
. ' AND '
. '(person_person_having_activity.id = person.id OR person MEMBER OF activity_person_having_activity.persons)');
// add clause activity reason
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
if (isset($data['reasons']) && [] !== $data['reasons']) {
// add clause activity reason
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
$sqb->andWhere(
$sqb->expr()->in(
'reasons_person_having_activity',
':person_having_activity_reasons'
)
);
$sqb->andWhere(
$sqb->expr()->in(
'reasons_person_having_activity',
':person_having_activity_reasons'
)
);
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('person.id', $sqb->getDQL());
if ($where instanceof Expr\Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
$qb->setParameter('person_having_activity_reasons', $data['reasons']);
}
$qb->add('where', $where);
$qb->andWhere(
$qb->expr()->exists($sqb->getDQL())
);
$qb->setParameter(
'person_having_activity_between_date_from',
$data['date_from']
$this->rollingDateConverter->convert($data['date_from_rolling'])
);
$qb->setParameter(
'person_having_activity_between_date_to',
$data['date_to']
$this->rollingDateConverter->convert($data['date_to_rolling'])
);
$qb->setParameter('person_having_activity_reasons', $data['reasons']);
}
public function applyOn(): string
{
return Declarations::PERSON_IMPLIED_IN;
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', DateType::class, [
'label' => 'Implied in an activity after this date',
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
$builder->add('date_from_rolling', PickRollingDateType::class, [
'label' => 'export.filter.activity.person_between_dates.Implied in an activity after this date',
]);
$builder->add('date_to', DateType::class, [
'label' => 'Implied in an activity before this date',
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
$builder->add('date_to_rolling', PickRollingDateType::class, [
'label' => 'export.filter.activity.person_between_dates.Implied in an activity before this date',
]);
$builder->add('reasons', EntityType::class, [
'class' => ActivityReason::class,
'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()),
'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
'multiple' => true,
'expanded' => false,
'label' => 'Activity reasons for those activities',
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var FormInterface $filterForm */
$filterForm = $event->getForm()->getParent();
$enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData();
if (true === $enabled) {
// if the filter is enabled, add some validation
$form = $event->getForm();
$date_from = $form->get('date_from')->getData();
$date_to = $form->get('date_to')->getData();
// check that fields are not empty
if (null === $date_from) {
$form->get('date_from')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')
));
}
if (null === $date_to) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')
));
}
// check that date_from is before date_to
if (
(null !== $date_from && null !== $date_to)
&& $date_from >= $date_to
) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This date '
. 'should be after the date given in "Implied in an '
. 'activity after this date" field')
));
}
}
});
if ([] !== $reasons = $this->activityReasonRepository->findAll()) {
$builder->add('reasons', EntityType::class, [
'class' => ActivityReason::class,
'choices' => $reasons,
'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()),
'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
'multiple' => true,
'expanded' => false,
'label' => 'export.filter.activity.person_between_dates.Activity reasons for those activities',
'help' => 'export.filter.activity.person_between_dates.if no reasons',
]);
}
}
public function getFormDefaultData(): array
{
return ['date_from' => new DateTime(), 'date_to' => new DateTime(), 'reasons' => $this->activityReasonRepository->findAll()];
return [
'date_from_rolling' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
'date_to_rolling' => new RollingDate(RollingDate::T_TODAY),
'reasons' => [],
];
}
public function describeAction($data, $format = 'string')
{
return [
'Filtered by person having an activity between %date_from% and '
. '%date_to% with reasons %reasons_name%',
[] === $data['reasons'] ?
'export.filter.person_between_dates.describe_action_with_no_subject'
: 'export.filter.person_between_dates.describe_action_with_subject',
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y'),
'%reasons_name%' => implode(
'date_from' => $this->rollingDateConverter->convert($data['date_from_rolling']),
'date_to' => $this->rollingDateConverter->convert($data['date_to_rolling']),
'reasons' => implode(
', ',
array_map(
fn (ActivityReason $r): string => '"' . $this->translatableStringHelper->localize($r->getName()) . '"',
@@ -199,13 +165,15 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
public function getTitle()
{
return 'Filter by person having an activity in a period';
return 'export.filter.activity.person_between_dates.title';
}
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['reasons'] || count($data['reasons']) === 0) {
$context->buildViolation('At least one reason must be chosen')
if ($this->rollingDateConverter->convert($data['date_from_rolling'])
>= $this->rollingDateConverter->convert($data['date_to_rolling'])) {
$context->buildViolation('export.filter.activity.person_between_dates.date mismatch')
->setTranslationDomain('messages')
->addViolation();
}
}

View File

@@ -3,3 +3,9 @@ export:
activity:
course_having_activity_between_date:
Only course having an activity between from and to: Seulement les parcours ayant reçu au moins un échange entre le {from, date, short} et le {to, date, short}
person_between_dates:
describe_action_with_no_subject: >-
Filtré par personne ayant eu un échange entre le {date_from, date} et le {date_to, date}
describe_action_with_subject: >-
Filtré par personne ayant eu un échange entre le {date_from, date} et le {date_to, date}, et un de ces sujets choisis: {reasons}

View File

@@ -255,11 +255,6 @@ Activities before this date: Échanges avant cette date
"Filtered by date of activity: only between %date_from% and %date_to%": "Filtré par date de l'échange: uniquement entre %date_from% et %date_to%"
This date should be after the date given in "Implied in an activity after this date" field: Cette date devrait être postérieure à la date donnée dans le champ "échanges après cette date"
Filtered by person having an activity in a period: Uniquement les usagers ayant eu un échange dans la période donnée
Implied in an activity after this date: Impliqué dans un échange après cette date
Implied in an activity before this date: Impliqué dans un échange avant cette date
Filtered by person having an activity between %date_from% and %date_to% with reasons %reasons_name%: Filtré par usager associées à un échange entre %date_from% et %date_to% avec les sujets %reasons_name%
Activity reasons for those activities: Sujets de ces échanges
Filter by activity type: Filtrer les échanges par type
@@ -379,7 +374,13 @@ export:
Receiving an activity before: Ayant reçu un échange avant le
acp_by_activity_type:
'acp_containing_at_least_one_%activitytypes%': 'Parcours filtrés: uniquement ceux qui contiennent au moins un échange d''un des types suivants: %activitytypes%'
person_between_dates:
Implied in an activity after this date: Impliqué dans un échange après cette date
Implied in an activity before this date: Impliqué dans un échange avant cette date
Activity reasons for those activities: Sujets de ces échanges
if no reasons: Si aucun sujet n'est coché, tous les sujets seront pris en compte
title: Filtrer les personnes ayant été associés à un échange au cours de la période
date mismatch: La date de fin de la période doit être supérieure à la date du début
aggregator:
activity:

View File

@@ -100,7 +100,7 @@ class ReassignAccompanyingPeriodController extends AbstractController
$total = $this->accompanyingPeriodACLAwareRepository->countByUserAndPostalCodesOpenedAccompanyingPeriod($userFrom, $postalCodes);
$paginator = $this->paginatorFactory->create($total);
$paginator->setItemsPerPage(50);
$paginator->setItemsPerPage(100);
$periods = $this->accompanyingPeriodACLAwareRepository
->findByUserAndPostalCodesOpenedAccompanyingPeriod(
$userFrom,
@@ -123,6 +123,7 @@ class ReassignAccompanyingPeriodController extends AbstractController
if ($assignForm->isSubmitted() && $assignForm->isValid()) {
$assignPeriodIds = json_decode($assignForm->get('periods')->getData(), true, 512, JSON_THROW_ON_ERROR);
/** @var User $userTo */
$userTo = $assignForm->get('userTo')->getData();
$userFrom = $assignForm->get('userFrom')->getData();
@@ -131,6 +132,10 @@ class ReassignAccompanyingPeriodController extends AbstractController
if ($period->getUser() === $userFrom) {
$period->setUser($userTo, true);
if (null !== $userTo->getUserJob() && $period->getJob() !== $userTo->getUserJob()) {
$period->setJob($userTo->getUserJob());
}
}
}

View File

@@ -22,6 +22,9 @@ abstract class Declarations
public const HOUSEHOLD_TYPE = 'household';
/**
* @deprecated consider using the PERSON_TYPE instead
*/
public const PERSON_IMPLIED_IN = 'person_implied_in';
public const PERSON_TYPE = 'person';

View File

@@ -120,9 +120,7 @@ class CountPerson implements ExportInterface, GroupedExportInterface
public function supportsModifiers()
{
return [
'abcde',
//Declarations::PERSON_TYPE,
//Declarations::PERSON_IMPLIED_IN,
Declarations::PERSON_TYPE,
];
}
}

View File

@@ -266,7 +266,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
public function supportsModifiers()
{
return [Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN];
return [Declarations::PERSON_TYPE];
}
public function validateForm($data, ExecutionContextInterface $context)

View File

@@ -205,7 +205,7 @@ class ListPersonHavingAccompanyingPeriod implements ExportElementValidatedInterf
public function supportsModifiers()
{
return [Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN, Declarations::ACP_TYPE];
return [Declarations::PERSON_TYPE, Declarations::ACP_TYPE];
}
public function validateForm($data, ExecutionContextInterface $context)

View File

@@ -149,6 +149,6 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte
public function supportsModifiers()
{
return [Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN, Declarations::ACP_TYPE];
return [Declarations::PERSON_TYPE, Declarations::ACP_TYPE];
}
}

View File

@@ -12,28 +12,17 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Form\Type\PickSocialActionType;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class SocialActionFilter implements FilterInterface
final readonly class SocialActionFilter implements FilterInterface
{
private SocialActionRender $actionRender;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
TranslatableStringHelper $translatableStringHelper,
SocialActionRender $actionRender
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->actionRender = $actionRender;
}
public function __construct(private SocialActionRender $actionRender) {}
public function addRole(): ?string
{
@@ -42,21 +31,17 @@ class SocialActionFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('acpw', $qb->getAllAliases(), true)) {
$qb->join('acp.works', 'acpw');
}
$qb->andWhere(
$qb->expr()->exists(
sprintf(
"SELECT 1 FROM %s acp_by_social_action_filter WHERE acp_by_social_action_filter.socialAction "
. "IN (:acp_by_social_action_filter_actions) AND acp_by_social_action_filter.accompanyingPeriod = acp",
AccompanyingPeriod\AccompanyingPeriodWork::class
)
)
);
if (!in_array('acpwsocialaction', $qb->getAllAliases(), true)) {
$qb->join('acpw.socialAction', 'acpwsocialaction');
}
$clause = $qb->expr()->in('acpwsocialaction.id', ':socialactions');
$qb->andWhere($clause)
->setParameter(
'socialactions',
SocialAction::getDescendantsWithThisForActions($data['accepted_socialactions'])->toArray()
);
$qb->setParameter('acp_by_social_action_filter_actions', SocialAction::getDescendantsWithThisForActions($data['accepted_socialactions']));
}
public function applyOn(): string

View File

@@ -122,7 +122,7 @@ final class AccompanyingPeriodWorkRepository implements ObjectRepository
. ")";
}
$sql .= " AND daterange(:after::date, :before::date) && daterange(w.startDate, w.endDate)";
$sql .= " AND daterange(:after::date, :before::date) && daterange(w.startDate, w.endDate, '[]')";
// if the start and end date were inversed, we inverse the order to avoid an error
if (null !== ($filters['after'] ?? null) && null !== ($filters['before']) && $filters['after'] > $filters['before']) {

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\Migrations\Person;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20231002094521 extends AbstractMigration
{
public function getDescription(): string
{
return 'Remove doublons in chill_person_accompanying_period_location_history and add a constraint on startdate / endDate by period';
}
public function up(Schema $schema): void
{
$this->addSql(
<<<'SQL'
WITH doublons_ordered AS (
SELECT h2.id AS h2id, h2.createdAt AS h2createdAt, h2.startDate AS h2start, h2.endDate AS h2end, h1.*,
rank() OVER (partition by h1.period_id ORDER BY h1.id, h2.id) AS ranking
FROM chill_person_accompanying_period_location_history h1
JOIN chill_person_accompanying_period_location_history h2 ON h1.period_id = h2.period_id AND h1.id <> h2.id
WHERE daterange(h1.startdate, h1.enddate) && daterange(h2.startdate, h2.enddate) ORDER BY h1.period_id, h1.id
),
keep_only_first AS (
SELECT id FROM doublons_ordered WHERE ranking > 1
)
DELETE FROM chill_person_accompanying_period_location_history WHERE id IN (SELECT id FROM doublons_ordered);
SQL
);
$this->addSql(
<<<'SQL'
ALTER TABLE chill_person_accompanying_period_location_history
ADD CONSTRAINT acc_period_location_history_not_overlaps
EXCLUDE USING GIST (period_id with =, tsrange(startdate, enddate) with &&)
DEFERRABLE INITIALLY DEFERRED
SQL
);
}
public function down(Schema $schema): void
{
$this->addSql(
<<<'SQL'
ALTER TABLE chill_person_accompanying_period_location_history DROP CONSTRAINT acc_period_location_history_not_overlaps
SQL
);
}
}

View File

@@ -463,7 +463,7 @@ class ReportList implements ExportElementValidatedInterface, ListInterface
public function supportsModifiers()
{
return [Declarations::PERSON_IMPLIED_IN, Declarations::PERSON_TYPE, 'report'];
return [Declarations::PERSON_TYPE, 'report'];
}
public function validateForm($data, ExecutionContextInterface $context)

View File

@@ -25,7 +25,7 @@
<dt>{{ 'Name'|trans }}</dt>
<dd>
{% if thirdParty.isLeaf == true %}{{ thirdParty.civility }}{% endif %}
{% if thirdParty.isLeaf == true and thirdParty.civility is not same as null %}{{ thirdParty.civility.name|localize_translatable_string }}{% endif %}
{{ thirdParty.firstname ~ ' ' ~ thirdParty.name }}
</dd>