diff --git a/.changes/unreleased/DX-20251027-150053.yaml b/.changes/unreleased/DX-20251027-150053.yaml
deleted file mode 100644
index 880894bd4..000000000
--- a/.changes/unreleased/DX-20251027-150053.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-kind: DX
-body: |
- Send notifications log to dedicated channel, if it exists
-time: 2025-10-27T15:00:53.309372316+01:00
-custom:
- Issue: ""
- SchemaChange: No schema change
diff --git a/.changes/unreleased/Fixed-20251113-120125.yaml b/.changes/unreleased/Fixed-20251113-120125.yaml
new file mode 100644
index 000000000..a9bfc78ad
--- /dev/null
+++ b/.changes/unreleased/Fixed-20251113-120125.yaml
@@ -0,0 +1,7 @@
+kind: Fixed
+body: |
+ Improve accessibility on login page
+time: 2025-11-13T12:01:25.488478771+01:00
+custom:
+ Issue: ""
+ SchemaChange: No schema change
diff --git a/.changes/v4.7.0.md b/.changes/v4.7.0.md
new file mode 100644
index 000000000..48e6095fc
--- /dev/null
+++ b/.changes/v4.7.0.md
@@ -0,0 +1,21 @@
+## v4.7.0 - 2025-11-10
+### Feature
+* ([#385](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/385)) Create invitation list in user menu
+* ([#404](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/404)) Add columns for comments linked to an activity in the activity list export
+### Fixed
+* ([#451](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/451)) Fix: display also social actions linked to parents of the selected social issue
+* ([#453](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/453)) Fix: export actions and their results in csv even when action does not have any goals attached to it.
+* Fix the possibility to delete a workflow
+
+ **Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
+* ([#457](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/457)) Fix the fusion of thirdparty properties that are located in another schema than public for TO_ONE relations + add extra loop for MANY_TO_MANY relations where thirdparty is the source instead of the target
+* ([#428](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/428)) Fix suggestion of referrer when creating notification for accompanyingPeriodWorkDocument
+### DX
+* Send notifications log to dedicated channel, if it exists
+
+### UX
+* ([#425](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/425)) Change the terms 'cercle' and 'centre' to 'service', and 'territoire' respectively.
+* ([#542](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/542)) Improve the ux for selecting whether user wants to be notified of the final step of a workflow or all steps
+* Expand timeSpent choices for evaluation document and translate them to user locale or fallback 'fr'
+* ([#455](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/455)) Change the order of display for results and objectives in the social work/action form
+* Wrap text when it is too long within badges
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4573e5ba2..e684bb689 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,28 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie).
+## v4.7.0 - 2025-11-10
+### Feature
+* ([#385](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/385)) Create invitation list in user menu
+* ([#404](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/404)) Add columns for comments linked to an activity in the activity list export
+### Fixed
+* ([#451](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/451)) Fix: display also social actions linked to parents of the selected social issue
+* ([#453](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/453)) Fix: export actions and their results in csv even when action does not have any goals attached to it.
+* Fix the possibility to delete a workflow
+
+ **Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
+* ([#457](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/457)) Fix the fusion of thirdparty properties that are located in another schema than public for TO_ONE relations + add extra loop for MANY_TO_MANY relations where thirdparty is the source instead of the target
+* ([#428](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/428)) Fix suggestion of referrer when creating notification for accompanyingPeriodWorkDocument
+### DX
+* Send notifications log to dedicated channel, if it exists
+
+### UX
+* ([#425](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/425)) Change the terms 'cercle' and 'centre' to 'service', and 'territoire' respectively.
+* ([#542](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/542)) Improve the ux for selecting whether user wants to be notified of the final step of a workflow or all steps
+* Expand timeSpent choices for evaluation document and translate them to user locale or fallback 'fr'
+* ([#455](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/455)) Change the order of display for results and objectives in the social work/action form
+* Wrap text when it is too long within badges
+
## v4.6.1 - 2025-10-27
### Fixed
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php
diff --git a/composer.json b/composer.json
index 6740b8538..c1e235670 100644
--- a/composer.json
+++ b/composer.json
@@ -14,7 +14,7 @@
"ext-openssl": "*",
"ext-redis": "*",
"ext-zlib": "*",
- "champs-libres/wopi-bundle": "dev-master#1be045ee95310d2037683859ecefdbf3a10f7be6 as 0.4.x-dev",
+ "champs-libres/wopi-bundle": "dev-symfony-v5@dev",
"champs-libres/wopi-lib": "dev-master@dev",
"doctrine/data-fixtures": "^1.8",
"doctrine/doctrine-bundle": "^2.1",
diff --git a/config/bundles.php b/config/bundles.php
index ec11bc0b6..72b5e22f5 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -2,7 +2,6 @@
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
- loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
ChampsLibres\WopiBundle\WopiBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
@@ -37,4 +36,5 @@ return [
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
+ loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
];
diff --git a/config/packages/chill_aside_activity.yaml b/config/packages/chill_aside_activity.yaml
new file mode 100644
index 000000000..eb5c2d70e
--- /dev/null
+++ b/config/packages/chill_aside_activity.yaml
@@ -0,0 +1,2 @@
+chill_aside_activity:
+ show_concerned_persons_count: hidden
diff --git a/docs/source/_static/bundles/docStore/doc_store_classes.puml b/docs/source/_static/bundles/docStore/doc_store_classes.puml
index 431d268c0..f0c61f44c 100644
--- a/docs/source/_static/bundles/docStore/doc_store_classes.puml
+++ b/docs/source/_static/bundles/docStore/doc_store_classes.puml
@@ -23,8 +23,8 @@ class "Document" {
- text description
- ArrayCollection_DocumentCategory categories
- varchar_150 content #link to openstack
- - Center center
- - Cercle cercle
+ - Territoire territoire
+ - Service service
- User user
- DateTime date # Creation date
}
diff --git a/docs/source/development/database-principles.rst b/docs/source/development/database-principles.rst
index 455354934..3b528ae22 100644
--- a/docs/source/development/database-principles.rst
+++ b/docs/source/development/database-principles.rst
@@ -38,7 +38,7 @@ Certaines données sont historisées:
- les référents d'un parcours;
- les statuts d'un parcours;
-- la liaison entre les centres et les usagers;
+- la liaison entre les territoires et les usagers;
- etc.
Dans ces cas-là, Chill crée généralement deux colonnes, qui sont habituellement nommées :code:`startDate` et :code:`endDate`. Lorsque la colonne :code:`endDate` est à :code:`NULL`, cela signifie que la période n'est pas "fermée". La colonne :code:`startDate` n'est pas nullable.
diff --git a/docs/source/development/database/table_list.csv b/docs/source/development/database/table_list.csv
index fe688318d..be72a52ab 100644
--- a/docs/source/development/database/table_list.csv
+++ b/docs/source/development/database/table_list.csv
@@ -1,6 +1,6 @@
order,table_schema,table_name,commentaire
1,chill_3party,party_category,Catégorie de tiers
-2,chill_3party,party_center,Association entre les tiers et les centres (déprécié)
+2,chill_3party,party_center,Association entre les tiers et les territoires (déprécié)
3,chill_3party,party_profession,Profession du tiers (déprécié)
4,chill_3party,third_party,Tiers
5,chill_3party,thirdparty_category,association tiers - catégories
@@ -54,7 +54,7 @@ order,table_schema,table_name,commentaire
53,public,activitytpresence,Présence aux échanges
54,public,activitytype,Types d'échanges
55,public,activitytypecategory,Catégories de types d'échanges
-56,public,centers,"Centres (territoires, agences, etc.)"
+56,public,centers,"Territoires (territoires, agences, etc.)"
57,public,chill_activity_activity_chill_person_socialaction,
58,public,chill_activity_activity_chill_person_socialissue
59,public,chill_docgen_template,Gabarits de documents
@@ -111,7 +111,7 @@ order,table_schema,table_name,commentaire
110,public,chill_person_marital_status,Etats civils
111,public,chill_person_not_duplicate,
112,public,chill_person_person,Usagers
-113,public,chill_person_person_center_history,Historique des centres d'un usagers
+113,public,chill_person_person_center_history,Historique des territoires d'un usagers
114,public,chill_person_persons_to_addresses,Déprécié
115,public,chill_person_phone,Numéros d etéléphone supplémentaires d'un usager
116,public,chill_person_relations,Types de relations de filiation
@@ -142,7 +142,7 @@ order,table_schema,table_name,commentaire
141,public,permission_groups
142,public,permissionsgroup_rolescope
143,public,persons_spoken_languages
-144,public,regroupment,Regroupement de centres
+144,public,regroupment,Regroupement de territoires
145,public,regroupment_center,
146,public,role_scopes,
147,public,scopes,Services
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php
index dd817e7ab..c3a4428aa 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/ListActivityHelper.php
@@ -66,6 +66,9 @@ class ListActivityHelper
->leftJoin('activity.location', 'location')
->addSelect('location.name AS locationName')
->addSelect('activity.sentReceived')
+ ->addSelect('activity.comment.comment AS commentText')
+ ->addSelect('activity.comment.date AS commentDate')
+ ->addSelect('JSON_BUILD_OBJECT(\'uid\', activity.comment.userId, \'d\', activity.comment.date) AS commentUser')
->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.createdBy), \'d\', activity.createdAt) AS createdBy')
->addSelect('activity.createdAt')
->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.updatedBy), \'d\', activity.updatedAt) AS updatedBy')
@@ -87,6 +90,8 @@ class ListActivityHelper
'createdAt', 'updatedAt' => $this->dateTimeHelper->getLabel($key),
'createdBy', 'updatedBy' => $this->userHelper->getLabel($key, $values, $key),
'date' => $this->dateTimeHelper->getLabel(self::MSG_KEY.$key),
+ 'commentDate' => $this->dateTimeHelper->getLabel(self::MSG_KEY.'comment_date'),
+ 'commentUser' => $this->userHelper->getLabel($key, $values, self::MSG_KEY.'comment_user'),
'attendeeName' => function ($value) {
if ('_header' === $value) {
return 'Attendee';
@@ -176,6 +181,9 @@ class ListActivityHelper
'usersNames',
'thirdPartiesIds',
'thirdPartiesNames',
+ 'commentText',
+ 'commentDate',
+ 'commentUser',
'createdBy',
'createdAt',
'updatedBy',
diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc/CheckSocialAction.vue b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc/CheckSocialAction.vue
index 8172b2b6f..503977dbd 100644
--- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc/CheckSocialAction.vue
+++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc/CheckSocialAction.vue
@@ -43,11 +43,23 @@ export default {
span.badge {
@include badge_social($social-action-color);
font-size: 95%;
+ white-space: normal;
+ word-wrap: break-word;
+ word-break: break-word;
+ display: inline-block;
+ max-width: 100%;
margin-bottom: 5px;
margin-right: 1em;
- max-width: 100%; /* Adjust as needed */
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ text-align: left;
+ line-height: 1.2em;
+
+ &::before {
+ position: absolute;
+ left: 11px;
+ top: 0;
+ margin: 0 0.3em 0 -0.75em;
+ }
+ position: relative;
+ padding-left: 1.5em;
}
diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc/CheckSocialIssue.vue b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc/CheckSocialIssue.vue
index 9dbedf2ea..ef1c82a35 100644
--- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc/CheckSocialIssue.vue
+++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/components/SocialIssuesAcc/CheckSocialIssue.vue
@@ -43,7 +43,22 @@ export default {
span.badge {
@include badge_social($social-issue-color);
font-size: 95%;
+ white-space: normal;
+ word-wrap: break-word;
+ word-break: break-word;
+ display: inline-block;
+ max-width: 100%;
margin-bottom: 5px;
margin-right: 1em;
+ text-align: left;
+
+ &::before {
+ position: absolute;
+ left: 11px;
+ top: 0;
+ margin: 0 0.3em 0 -0.75em;
+ }
+ position: relative;
+ padding-left: 1.5em;
}
diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
index 883310df9..63c61d8e4 100644
--- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
@@ -10,7 +10,7 @@ Attendee: Présence de l'usager
attendee: présence de l'usager
list_reasons: liste des sujets
user_username: nom de l'utilisateur
-circle_name: nom du cercle
+circle_name: nom du service
Remark: Commentaire
No comments: Aucun commentaire
Add a new activity: Ajouter une nouvel échange
@@ -20,7 +20,7 @@ not present: absent
Delete: Supprimer
Update: Mettre à jour
Update activity: Modifier l'échange
-Scope: Cercle
+Scope: Service
Activity data: Données de l'échange
Activity location: Localisation de l'échange
No reason associated: Aucun sujet
@@ -398,13 +398,15 @@ export:
sent received: Envoyé ou reçu
emergency: Urgence
accompanying course id: Identifiant du parcours
- course circles: Cercles du parcours
+ course circles: Services du parcours
travelTime: Durée de déplacement
durationTime: Durée
id: Identifiant
List activities linked to an accompanying course: Liste les échanges liés à un parcours en fonction de différents filtres.
List activity linked to a course: Liste des échanges liés à un parcours
-
+ commentText: Commentaire
+ comment_date: Date de la dernière édition du commentaire
+ comment_user: Dernière édition par
filter:
activity:
diff --git a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php
index 056f29ba1..6fa123146 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/ChillAsideActivityExtension.php
@@ -25,6 +25,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']);
+ $container->setParameter('chill_aside_activity.show_concerned_persons_count', 'visible' === $config['show_concerned_persons_count']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
$loader->load('services.yaml');
@@ -38,6 +39,24 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
{
$this->prependRoute($container);
$this->prependCruds($container);
+ $this->prependTwigConfig($container);
+ }
+
+ protected function prependTwigConfig(ContainerBuilder $container)
+ {
+ // Get the configuration for this bundle
+ $chillAsideActivityConfig = $container->getExtensionConfig($this->getAlias());
+ $config = $this->processConfiguration($this->getConfiguration($chillAsideActivityConfig, $container), $chillAsideActivityConfig);
+
+ // Add configuration to twig globals
+ $twigConfig = [
+ 'globals' => [
+ 'chill_aside_activity_config' => [
+ 'show_concerned_persons_count' => 'visible' === $config['show_concerned_persons_count'],
+ ],
+ ],
+ ];
+ $container->prependExtensionConfig('twig', $twigConfig);
}
protected function prependCruds(ContainerBuilder $container)
diff --git a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php
index 241a545a8..66f3d3c86 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/DependencyInjection/Configuration.php
@@ -141,6 +141,12 @@ class Configuration implements ConfigurationInterface
->end()
->end()
->end()
+ ->end()
+ ->enumNode('show_concerned_persons_count')
+ ->values(['hidden', 'visible'])
+ ->defaultValue('hidden')
+ ->info('Show the concerned persons count field in aside activity forms and views')
+ ->end()
->end();
return $treeBuilder;
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php
index b7671c61a..0082deaf9 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Entity/AsideActivity.php
@@ -62,6 +62,10 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
#[ORM\ManyToOne(targetEntity: User::class)]
private User $updatedBy;
+ #[Assert\GreaterThanOrEqual(0)]
+ #[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true)]
+ private ?int $concernedPersonsCount = 0;
+
public function getAgent(): ?User
{
return $this->agent;
@@ -186,4 +190,16 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
return $this;
}
+
+ public function getConcernedPersonsCount(): ?int
+ {
+ return $this->concernedPersonsCount;
+ }
+
+ public function setConcernedPersonsCount(?int $concernedPersonsCount): self
+ {
+ $this->concernedPersonsCount = $concernedPersonsCount;
+
+ return $this;
+ }
}
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByConcernedPersonsCountAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByConcernedPersonsCountAggregator.php
new file mode 100644
index 000000000..444c49269
--- /dev/null
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByConcernedPersonsCountAggregator.php
@@ -0,0 +1,86 @@
+addSelect('aside.concernedPersonsCount AS by_concerned_persons_count_aggregator')
+ ->addGroupBy('by_concerned_persons_count_aggregator');
+ }
+
+ public function applyOn(): string
+ {
+ return Declarations::ASIDE_ACTIVITY_TYPE;
+ }
+
+ public function buildForm(FormBuilderInterface $builder): void
+ {
+ // No form needed
+ }
+
+ public function getNormalizationVersion(): int
+ {
+ return 1;
+ }
+
+ public function normalizeFormData(array $formData): array
+ {
+ return [];
+ }
+
+ public function denormalizeFormData(array $formData, int $fromVersion): array
+ {
+ return [];
+ }
+
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
+
+ public function getLabels($key, array $values, $data): callable
+ {
+ return function ($value): string {
+ if ('_header' === $value) {
+ return 'export.aggregator.Concerned persons count';
+ }
+
+ if (null === $value) {
+ return 'export.aggregator.No concerned persons count specified';
+ }
+
+ return (string) $value;
+ };
+ }
+
+ public function getQueryKeys($data): array
+ {
+ return ['by_concerned_persons_count_aggregator'];
+ }
+
+ public function getTitle(): string
+ {
+ return 'export.aggregator.Group by concerned persons count';
+ }
+}
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumConcernedPersonsCountAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumConcernedPersonsCountAsideActivity.php
new file mode 100644
index 000000000..ab22302c2
--- /dev/null
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumConcernedPersonsCountAsideActivity.php
@@ -0,0 +1,116 @@
+getTitle();
+
+ return static fn ($value) => $labels[$value];
+ }
+
+ public function getQueryKeys($data): array
+ {
+ return ['export_sum_concerned_persons_count'];
+ }
+
+ public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array
+ {
+ return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
+ }
+
+ public function getTitle(): string
+ {
+ return 'export.Sum concerned persons count for aside activities';
+ }
+
+ public function getType(): string
+ {
+ return Declarations::ASIDE_ACTIVITY_TYPE;
+ }
+
+ public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder
+ {
+ $qb = $this->repository->createQueryBuilder('aside');
+
+ $qb->select('SUM(COALESCE(aside.concernedPersonsCount, 0)) as export_sum_concerned_persons_count');
+
+ return $qb;
+ }
+
+ public function requiredRole(): string
+ {
+ return AsideActivityVoter::STATS;
+ }
+
+ public function supportsModifiers(): array
+ {
+ return [
+ Declarations::ASIDE_ACTIVITY_TYPE,
+ ];
+ }
+}
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php
index d6fcb821c..ea04fdc42 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Form/AsideActivityFormType.php
@@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
@@ -29,11 +30,13 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
final class AsideActivityFormType extends AbstractType
{
private readonly array $timeChoices;
+ private readonly bool $showConcernedPersonsCount;
public function __construct(
ParameterBagInterface $parameterBag,
) {
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
+ $this->showConcernedPersonsCount = $parameterBag->get('chill_aside_activity.show_concerned_persons_count');
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -76,6 +79,16 @@ final class AsideActivityFormType extends AbstractType
->add('location', PickUserLocationType::class)
;
+ if ($this->showConcernedPersonsCount) {
+ $builder->add('concernedPersonsCount', IntegerType::class, [
+ 'label' => 'Concerned persons count',
+ 'required' => false,
+ 'attr' => [
+ 'min' => 0,
+ ],
+ ]);
+ }
+
foreach (['duration'] as $fieldName) {
$builder->get($fieldName)
->addModelTransformer($durationTimeTransformer);
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig
index 0a8648749..0d06e5ba9 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig
+++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/index.html.twig
@@ -42,6 +42,11 @@
{%- if entity.location.name is defined -%}
{{ entity.location.name }}
{%- endif -%}
+
+ {%- if entity.concernedPersonsCount > 0 -%}
+ {{ entity.concernedPersonsCount }}
+ {%- endif -%}
+
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig
index ef6faa9c6..330a2ab13 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig
+++ b/src/Bundle/ChillAsideActivityBundle/src/Resources/views/asideActivity/view.html.twig
@@ -38,6 +38,11 @@
{{ 'Duration'|trans }}
{{ entity.duration|date('H:i') }}
+ {% if chill_aside_activity_config.show_concerned_persons_count == 'visible' %}
+
{{ 'Concerned persons count'|trans }}
+
{{ entity.concernedPersonsCount }}
+ {% endif %}
+
{{ 'Remark'|trans }}
{%- if entity.note is empty -%}
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Aggregator/ByConcernedPersonsCountAggregatorTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Aggregator/ByConcernedPersonsCountAggregatorTest.php
new file mode 100644
index 000000000..a1d133d8c
--- /dev/null
+++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Aggregator/ByConcernedPersonsCountAggregatorTest.php
@@ -0,0 +1,49 @@
+get(EntityManagerInterface::class);
+
+ return [
+ $em->createQueryBuilder()
+ ->select('count(aside.id)')
+ ->from(AsideActivity::class, 'aside'),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/SumConcernedPersonsCountAsideActivityTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/SumConcernedPersonsCountAsideActivityTest.php
new file mode 100644
index 000000000..499986ac1
--- /dev/null
+++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/SumConcernedPersonsCountAsideActivityTest.php
@@ -0,0 +1,50 @@
+get(AsideActivityRepository::class);
+
+ yield new SumConcernedPersonsCountAsideActivity($repository);
+ }
+
+ public static function getFormData(): array
+ {
+ return [
+ [],
+ ];
+ }
+
+ public static function getModifiersCombination(): array
+ {
+ return [
+ ['aside_activity'],
+ ];
+ }
+}
diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml
index 40cb120da..efba7a8af 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml
+++ b/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml
@@ -20,6 +20,10 @@ services:
tags:
- { name: chill.export, alias: 'avg_aside_activity_duration' }
+ Chill\AsideActivityBundle\Export\Export\SumConcernedPersonsCountAsideActivity:
+ tags:
+ - { name: chill.export, alias: 'sum_aside_activity_concerned_persons_count' }
+
## Filters
chill.aside_activity.export.date_filter:
class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter
@@ -70,3 +74,7 @@ services:
Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator:
tags:
- { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' }
+
+ Chill\AsideActivityBundle\Export\Aggregator\ByConcernedPersonsCountAggregator:
+ tags:
+ - { name: chill.export_aggregator, alias: 'aside_activity_concerned_persons_count_aggregator' }
diff --git a/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20251006113048.php b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20251006113048.php
new file mode 100644
index 000000000..8ea1dbf4c
--- /dev/null
+++ b/src/Bundle/ChillAsideActivityBundle/src/migrations/Version20251006113048.php
@@ -0,0 +1,33 @@
+addSql('ALTER TABLE chill_asideactivity.asideactivity ADD concernedPersonsCount INT DEFAULT 0');
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP concernedPersonsCount');
+ }
+}
diff --git a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml
index 7d3c7a20e..1b0e39e1b 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml
+++ b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml
@@ -27,6 +27,7 @@ Emergency: Urgent
by: "Par "
location: Lieu
Asideactivity location: Localisation de l'activité
+Concerned persons count: Nombre d'usager concernés
# Crud
crud:
@@ -177,7 +178,7 @@ export:
agent_id: Utilisateur
creator_id: Créateur
main_scope: Service principal de l'utilisateur
- main_center: Centre principal de l'utilisateur
+ main_center: Territoire principal de l'utilisateur
aside_activity_type: Catégorie d'activité annexe
date: Date
duration: Durée
@@ -190,6 +191,7 @@ export:
Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères
Average aside activities duration: Durée moyenne des activités annexes
Sum aside activities duration: Durée des activités annexes
+ Sum concerned persons count for aside activities: Nombre d'usager concernés par les activités annexes
filter:
Filter by aside activity date: Filtrer les activités annexes par date
Filter by aside activity type: Filtrer les activités annexes par type d'activité
@@ -210,6 +212,8 @@ export:
'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%"
aggregator:
Group by aside activity type: Grouper les activités annexes par type d'activité
+ Group by concerned persons count: Grouper les activités annexes par nombre d'usagers conernés
+ Concerned persons count: Nombre d'usagers concernés
Aside activity type: Type d'activité annexe
by_user_job:
Aggregate by user job: Grouper les activités annexes par métier des utilisateurs
diff --git a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php
index 94db03b1f..6705d7bb7 100644
--- a/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php
+++ b/src/Bundle/ChillCalendarBundle/Controller/CalendarController.php
@@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\Controller;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Form\CalendarType;
+use Chill\CalendarBundle\Form\CancelType;
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
use Chill\CalendarBundle\Security\Voter\CalendarVoter;
@@ -30,6 +31,7 @@ use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
+use Doctrine\ORM\EntityManagerInterface;
use http\Exception\UnexpectedValueException;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -60,6 +62,7 @@ class CalendarController extends AbstractController
private readonly UserRepositoryInterface $userRepository,
private readonly TranslatorInterface $translator,
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
+ private readonly EntityManagerInterface $em,
) {}
/**
@@ -111,6 +114,55 @@ class CalendarController extends AbstractController
]);
}
+ #[Route(path: '/{_locale}/calendar/calendar/{id}/cancel', name: 'chill_calendar_calendar_cancel')]
+ public function cancelAction(Calendar $calendar, Request $request): Response
+ {
+ // Deal with sms being sent or not
+ // Communicate cancellation with the remote calendar.
+
+ $this->denyAccessUnlessGranted(CalendarVoter::EDIT, $calendar);
+
+ [$person, $accompanyingPeriod] = [$calendar->getPerson(), $calendar->getAccompanyingPeriod()];
+
+ $form = $this->createForm(CancelType::class, $calendar);
+ $form->add('submit', SubmitType::class);
+
+ if ($accompanyingPeriod instanceof AccompanyingPeriod) {
+ $view = '@ChillCalendar/Calendar/cancelCalendarByAccompanyingCourse.html.twig';
+ $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]);
+ } elseif ($person instanceof Person) {
+ $view = '@ChillCalendar/Calendar/cancelCalendarByPerson.html.twig';
+ $redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]);
+ } else {
+ throw new \RuntimeException('nor person or accompanying period');
+ }
+
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+
+ $this->logger->notice('A calendar event has been cancelled', [
+ 'by_user' => $this->getUser()->getUsername(),
+ 'calendar_id' => $calendar->getId(),
+ ]);
+
+ $calendar->setStatus($calendar::STATUS_CANCELED);
+ $calendar->setSmsStatus($calendar::SMS_CANCEL_PENDING);
+ $this->em->flush();
+
+ $this->addFlash('success', $this->translator->trans('chill_calendar.calendar_canceled'));
+
+ return new RedirectResponse($redirectRoute);
+ }
+
+ return $this->render($view, [
+ 'calendar' => $calendar,
+ 'form' => $form->createView(),
+ 'accompanyingCourse' => $accompanyingPeriod,
+ 'person' => $person,
+ ]);
+ }
+
/**
* Edit a calendar item.
*/
@@ -266,7 +318,7 @@ class CalendarController extends AbstractController
}
if (!$this->getUser() instanceof User) {
- throw new UnauthorizedHttpException('you are not an user');
+ throw new UnauthorizedHttpException('you are not a user');
}
$view = '@ChillCalendar/Calendar/listByUser.html.twig';
diff --git a/src/Bundle/ChillCalendarBundle/Controller/MyInvitationsController.php b/src/Bundle/ChillCalendarBundle/Controller/MyInvitationsController.php
new file mode 100644
index 000000000..7af5ac18f
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Controller/MyInvitationsController.php
@@ -0,0 +1,58 @@
+denyAccessUnlessGranted('ROLE_USER');
+
+ $user = $this->getUser();
+
+ if (!$user instanceof User) {
+ throw new UnauthorizedHttpException('you are not a user');
+ }
+
+ $total = count($this->inviteRepository->findBy(['user' => $user]));
+ $paginator = $this->paginator->create($total);
+
+ $invitations = $this->inviteRepository->findBy(
+ ['user' => $user],
+ ['createdAt' => 'DESC'],
+ $paginator->getItemsPerPage(),
+ $paginator->getCurrentPageFirstItemNumber()
+ );
+
+ $view = '@ChillCalendar/Invitations/listByUser.html.twig';
+
+ return $this->render($view, [
+ 'invitations' => $invitations,
+ 'paginator' => $paginator,
+ 'templates' => $this->docGeneratorTemplateRepository->findByEntity(Calendar::class),
+ ]);
+ }
+}
diff --git a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php
index d7e552d5d..2a8e371e0 100644
--- a/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php
+++ b/src/Bundle/ChillCalendarBundle/DataFixtures/ORM/LoadCancelReason.php
@@ -35,7 +35,7 @@ class LoadCancelReason extends Fixture implements FixtureGroupInterface
$arr = [
['name' => CancelReason::CANCELEDBY_USER],
['name' => CancelReason::CANCELEDBY_PERSON],
- ['name' => CancelReason::CANCELEDBY_DONOTCOUNT],
+ ['name' => CancelReason::CANCELEDBY_OTHER],
];
foreach ($arr as $a) {
diff --git a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php
index dad302193..c194c247e 100644
--- a/src/Bundle/ChillCalendarBundle/Entity/Calendar.php
+++ b/src/Bundle/ChillCalendarBundle/Entity/Calendar.php
@@ -269,6 +269,11 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente
return $this->cancelReason;
}
+ public function isCanceled(): bool
+ {
+ return null !== $this->cancelReason;
+ }
+
public function getCenters(): ?iterable
{
return match ($this->getContext()) {
diff --git a/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php b/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php
index d4a2ed9a9..04192133c 100644
--- a/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php
+++ b/src/Bundle/ChillCalendarBundle/Entity/CancelReason.php
@@ -18,14 +18,14 @@ use Doctrine\ORM\Mapping as ORM;
#[ORM\Table(name: 'chill_calendar.cancel_reason')]
class CancelReason
{
- final public const CANCELEDBY_DONOTCOUNT = 'CANCELEDBY_DONOTCOUNT';
+ final public const CANCELEDBY_OTHER = 'CANCELEDBY_OTHER';
final public const CANCELEDBY_PERSON = 'CANCELEDBY_PERSON';
final public const CANCELEDBY_USER = 'CANCELEDBY_USER';
- #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
- private ?bool $active = null;
+ #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => true])]
+ private bool $active = true;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
private ?string $canceledBy = null;
diff --git a/src/Bundle/ChillCalendarBundle/Form/CancelReasonType.php b/src/Bundle/ChillCalendarBundle/Form/CancelReasonType.php
index c0ac4ddb0..311d3ac02 100644
--- a/src/Bundle/ChillCalendarBundle/Form/CancelReasonType.php
+++ b/src/Bundle/ChillCalendarBundle/Form/CancelReasonType.php
@@ -15,7 +15,7 @@ use Chill\CalendarBundle\Entity\CancelReason;
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
-use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -28,7 +28,14 @@ class CancelReasonType extends AbstractType
->add('active', CheckboxType::class, [
'required' => false,
])
- ->add('canceledBy', TextType::class);
+ ->add('canceledBy', ChoiceType::class, [
+ 'choices' => [
+ 'chill_calendar.canceled_by.user' => CancelReason::CANCELEDBY_USER,
+ 'chill_calendar.canceled_by.person' => CancelReason::CANCELEDBY_PERSON,
+ 'chill_calendar.canceled_by.other' => CancelReason::CANCELEDBY_OTHER,
+ ],
+ 'required' => true,
+ ]);
}
public function configureOptions(OptionsResolver $resolver)
diff --git a/src/Bundle/ChillCalendarBundle/Form/CancelType.php b/src/Bundle/ChillCalendarBundle/Form/CancelType.php
new file mode 100644
index 000000000..ad41a6105
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Form/CancelType.php
@@ -0,0 +1,42 @@
+add('cancelReason', EntityType::class, [
+ 'class' => CancelReason::class,
+ 'required' => true,
+ 'choice_label' => fn (CancelReason $cancelReason) => $this->translatableStringHelper->localize($cancelReason->getName()),
+ ]);
+ }
+
+ public function configureOptions(OptionsResolver $resolver)
+ {
+ $resolver->setDefaults([
+ 'data_class' => Calendar::class,
+
+ ]);
+ }
+}
diff --git a/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php b/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php
index 3a062f7b8..672b53460 100644
--- a/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php
+++ b/src/Bundle/ChillCalendarBundle/Menu/UserMenuBuilder.php
@@ -25,6 +25,13 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
if ($this->security->isGranted('ROLE_USER')) {
$menu->addChild('My calendar list', [
'route' => 'chill_calendar_calendar_list_my',
+ ])
+ ->setExtras([
+ 'order' => 8,
+ 'icon' => 'tasks',
+ ]);
+ $menu->addChild('invite.list.title', [
+ 'route' => 'chill_calendar_invitations_list_my',
])
->setExtras([
'order' => 9,
diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php
index 8f62fdcdb..2319fde99 100644
--- a/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php
+++ b/src/Bundle/ChillCalendarBundle/Messenger/Doctrine/CalendarEntityListener.php
@@ -21,6 +21,7 @@ namespace Chill\CalendarBundle\Messenger\Doctrine;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Messenger\Message\CalendarMessage;
use Chill\CalendarBundle\Messenger\Message\CalendarRemovedMessage;
+use Chill\MainBundle\Entity\User;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PostRemoveEventArgs;
use Doctrine\ORM\Event\PostUpdateEventArgs;
@@ -31,6 +32,17 @@ class CalendarEntityListener
{
public function __construct(private readonly MessageBusInterface $messageBus, private readonly Security $security) {}
+ private function getAuthenticatedUser(): User
+ {
+ $user = $this->security->getUser();
+
+ if (!$user instanceof User) {
+ throw new \LogicException('Expected an instance of User.');
+ }
+
+ return $user;
+ }
+
public function postPersist(Calendar $calendar, PostPersistEventArgs $args): void
{
if (!$calendar->preventEnqueueChanges) {
@@ -38,7 +50,7 @@ class CalendarEntityListener
new CalendarMessage(
$calendar,
CalendarMessage::CALENDAR_PERSIST,
- $this->security->getUser()
+ $this->getAuthenticatedUser()
)
);
}
@@ -50,7 +62,7 @@ class CalendarEntityListener
$this->messageBus->dispatch(
new CalendarRemovedMessage(
$calendar,
- $this->security->getUser()
+ $this->getAuthenticatedUser()
)
);
}
@@ -58,12 +70,19 @@ class CalendarEntityListener
public function postUpdate(Calendar $calendar, PostUpdateEventArgs $args): void
{
- if (!$calendar->preventEnqueueChanges) {
+ if ($calendar->getStatus() === $calendar::STATUS_CANCELED) {
+ $this->messageBus->dispatch(
+ new CalendarRemovedMessage(
+ $calendar,
+ $this->getAuthenticatedUser()
+ )
+ );
+ } elseif (!$calendar->preventEnqueueChanges) {
$this->messageBus->dispatch(
new CalendarMessage(
$calendar,
CalendarMessage::CALENDAR_UPDATE,
- $this->security->getUser()
+ $this->getAuthenticatedUser()
)
);
}
diff --git a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php
index 53dcea28c..7d2e88fef 100644
--- a/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php
+++ b/src/Bundle/ChillCalendarBundle/Messenger/Message/CalendarRemovedMessage.php
@@ -70,6 +70,8 @@ class CalendarRemovedMessage
public function getRemoteId(): string
{
+ dump($this->remoteId);
+
return $this->remoteId;
}
}
diff --git a/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php b/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php
index 3121854e5..e6f90f279 100644
--- a/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php
+++ b/src/Bundle/ChillCalendarBundle/Repository/CalendarRepository.php
@@ -191,6 +191,7 @@ class CalendarRepository implements ObjectRepository
$qb->expr()->eq('c.mainUser', ':user'),
$qb->expr()->gte('c.startDate', ':startDate'),
$qb->expr()->lte('c.endDate', ':endDate'),
+ $qb->expr()->isNull('c.cancelReason'),
)
)
->setParameters([
diff --git a/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php b/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php
index 8778330f8..6cb34738c 100644
--- a/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php
+++ b/src/Bundle/ChillCalendarBundle/Repository/InviteRepository.php
@@ -41,7 +41,7 @@ class InviteRepository implements ObjectRepository
/**
* @return array|Invite[]
*/
- public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null)
+ public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
{
return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset);
}
diff --git a/src/Bundle/ChillCalendarBundle/Resources/config/services/controller.yml b/src/Bundle/ChillCalendarBundle/Resources/config/services/controller.yml
index a0457c5a8..cce562d7d 100644
--- a/src/Bundle/ChillCalendarBundle/Resources/config/services/controller.yml
+++ b/src/Bundle/ChillCalendarBundle/Resources/config/services/controller.yml
@@ -1,5 +1,6 @@
services:
Chill\CalendarBundle\Controller\:
autowire: true
+ autoconfigure: true
resource: '../../../Controller'
tags: ['controller.service_arguments']
diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig
index cff5c00cc..c827ca4f4 100644
--- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig
+++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/_list.html.twig
@@ -1,17 +1,23 @@
-{# list used in context of person or accompanyingPeriod #}
+{# list used in context of person, accompanyingPeriod or user #}
-{% if calendarItems|length > 0 %}
-
-
- {% for calendar in calendarItems %}
-
-
-
-
-
+
+ {% if calendar.comment.comment is not empty
or calendar.users|length > 0
or calendar.thirdParties|length > 0
or calendar.users|length > 0 %}
@@ -76,131 +87,133 @@
} %}
-
- {% endif %}
+
+ {% endif %}
+
+ {% if calendar.comment.comment is not empty %}
+
+
+
+ {% endif %}
+
+ {% if calendar.location is not empty %}
+
+
+ {% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
+ {% endif %}
+ {% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %}
+ {% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %}
+ {% endif %}
+ {% if calendar.location.phonenumber1 is not empty %} {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
+ {% if calendar.location.phonenumber2 is not empty %} {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
+
+
+ {% endif %}
+
+ {% if calendar.documents is not empty %}
+
+
+ {{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
+
+
+ {% endif %}
+
+ {% if calendar.activity is not null %}
+
+
+
+
+
{{ 'Activity'|trans }}
+
+
+
+
+ {{ calendar.activity.type.name | localize_translatable_string }}
+
+ {% if calendar.activity.emergency %}
+ {{ 'Emergency'|trans|upper }}
+ {% endif %}
+
+
+
+
+ -
+
+ {{ 'Created by'|trans }}
+ {{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
+
+
+ {% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
+ -
+
+
+ {% endif %}
+
- {% if calendar.comment.comment is not empty %}
-
-
- {% endif %}
-
- {% if calendar.location is not empty %}
-
-
- {% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
- {% endif %}
- {% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %}
- {% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %}
- {% endif %}
- {% if calendar.location.phonenumber1 is not empty %} {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
- {% if calendar.location.phonenumber2 is not empty %} {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
-
-
- {% endif %}
-
-
-
-
- {{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
-
+
+
+ {% endif %}
- {% if calendar.activity is not null %}
-
-
-
-
-
{{ 'Activity'|trans }}
-
-
-
-
- {{ calendar.activity.type.name | localize_translatable_string }}
-
- {% if calendar.activity.emergency %}
- {{ 'Emergency'|trans|upper }}
- {% endif %}
-
-
-
-
- -
-
- {{ 'Created by'|trans }}
- {{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
-
-
- {% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
- -
-
-
- {% endif %}
-
-
-
-
+
-
+
+ {% endif %}
{% endif %}
+ {% if calendar.activity is null and (
+ (calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod))
+ or
+ (calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
+ )
+ and calendar.status is not constant('STATUS_CANCELED', calendar)
+ %}
+
+
+ {{ 'Transform to activity'|trans }}
+
+
+ {% endif %}
-
- {% endfor %}
-
- {% if calendarItems|length < paginator.getTotalItems %}
- {{ chill_pagination(paginator) }}
- {% endif %}
-
-{% endif %}
+
+
+
+
diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/cancelCalendarByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/cancelCalendarByAccompanyingCourse.html.twig
new file mode 100644
index 000000000..2f6759725
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/cancelCalendarByAccompanyingCourse.html.twig
@@ -0,0 +1,29 @@
+{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_calendar_calendar_list' %}
+
+{% block title 'chill_calendar.cancel_calendar_item'|trans %}
+
+{% block content %}
+
+ {{ form_start(form) }}
+
+ {{ form_row(form.cancelReason) }}
+
+
+
+ {{ form_end(form) }}
+
+{% endblock %}
diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/cancelCalendarByPerson.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/cancelCalendarByPerson.html.twig
new file mode 100644
index 000000000..76196b23e
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/cancelCalendarByPerson.html.twig
@@ -0,0 +1,29 @@
+{% extends "@ChillPerson/Person/layout.html.twig" %}
+
+{% set activeRouteKey = 'chill_calendar_calendar_list' %}
+
+{% block title 'chill_calendar.cancel_calendar_item'|trans %}
+
+{% block content %}
+
+ {{ form_start(form) }}
+
+ {{ form_row(form.cancelReason) }}
+
+
+
+ {{ form_end(form) }}
+
+{% endblock %}
diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig
index 7ce1003bc..96ddb3388 100644
--- a/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig
+++ b/src/Bundle/ChillCalendarBundle/Resources/views/Calendar/listByAccompanyingCourse.html.twig
@@ -34,7 +34,18 @@
{% endif %}
{% else %}
- {{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
+ {% if calendarItems|length > 0 %}
+
+ {% for calendar in calendarItems %}
+ {{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
+ {% endfor %}
+
+
+ {% if calendarItems|length < paginator.getTotalItems %}
+ {{ chill_pagination(paginator) }}
+ {% endif %}
+
+ {% endif %}
{% endif %}
-
-
-
- {{ $t("results_without_objective") }}
-
-
-
-
-
+
+
+
+
+
+ {{ $t("results_without_objective") }}
+
+
+
+
+
+
+
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/DocumentsList.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/DocumentsList.vue
index 77fa443d4..7c9892de9 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/DocumentsList.vue
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/DocumentsList.vue
@@ -97,7 +97,7 @@
@click="
goToGenerateDocumentNotification(
d,
- false,
+ true,
)
"
>
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue
index 7bdde14e9..c20c9eb9f 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue
@@ -67,37 +67,117 @@ const store = useStore();
const $toast = useToast();
-const timeSpentChoices = [
- { text: "1 minute", value: 60 },
- { text: "2 minutes", value: 120 },
- { text: "3 minutes", value: 180 },
- { text: "4 minutes", value: 240 },
- { text: "5 minutes", value: 300 },
- { text: "10 minutes", value: 600 },
- { text: "15 minutes", value: 900 },
- { text: "20 minutes", value: 1200 },
- { text: "25 minutes", value: 1500 },
- { text: "30 minutes", value: 1800 },
- { text: "45 minutes", value: 2700 },
- { text: "1 hour", value: 3600 },
- { text: "1 hour 15 minutes", value: 4500 },
- { text: "1 hour 30 minutes", value: 5400 },
- { text: "1 hour 45 minutes", value: 6300 },
- { text: "2 hours", value: 7200 },
- { text: "2 hours 30 minutes", value: 9000 },
- { text: "3 hours", value: 10800 },
- { text: "3 hours 30 minutes", value: 12600 },
- { text: "4 hours", value: 14400 },
- { text: "4 hours 30 minutes", value: 16200 },
- { text: "5 hours", value: 18000 },
- { text: "5 hours 30 minutes", value: 19800 },
- { text: "6 hours", value: 21600 },
- { text: "6 hours 30 minutes", value: 23400 },
- { text: "7 hours", value: 25200 },
- { text: "7 hours 30 minutes", value: 27000 },
- { text: "8 hours", value: 28800 },
+const timeSpentValues = [
+ 60,
+ 120,
+ 180,
+ 240,
+ 300,
+ 600,
+ 900,
+ 1200,
+ 1500,
+ 1800,
+ 2700,
+ 3600,
+ 4500,
+ 5400,
+ 6300,
+ 7200,
+ 9000,
+ 10800,
+ 12600,
+ 14400,
+ 16200,
+ 18000,
+ 19800,
+ 21600,
+ 23400,
+ 25200,
+ 27000,
+ 28800,
+ 43200,
+ 57600,
+ 72000,
+ 86400,
+ 100800,
+ 115200,
+ 129600,
+ 144000, // goes from 1 minute to 40 hours
];
+const formatDuration = (seconds, locale) => {
+ const currentLocale = locale || navigator.language || "fr";
+
+ const totalHours = Math.floor(seconds / 3600);
+ const remainingMinutes = Math.floor((seconds % 3600) / 60);
+
+ if (totalHours >= 8) {
+ const days = Math.floor(totalHours / 8);
+ const remainingHours = totalHours % 8;
+
+ const parts = [];
+
+ if (days > 0) {
+ parts.push(
+ new Intl.NumberFormat(currentLocale, {
+ style: "unit",
+ unit: "day",
+ unitDisplay: "long",
+ }).format(days),
+ );
+ }
+
+ if (remainingHours > 0) {
+ parts.push(
+ new Intl.NumberFormat(currentLocale, {
+ style: "unit",
+ unit: "hour",
+ unitDisplay: "long",
+ }).format(remainingHours),
+ );
+ }
+
+ return parts.join(" ");
+ }
+
+ // For less than 8 hours, use hour and minute format
+ const parts = [];
+
+ if (totalHours > 0) {
+ parts.push(
+ new Intl.NumberFormat(currentLocale, {
+ style: "unit",
+ unit: "hour",
+ unitDisplay: "long",
+ }).format(totalHours),
+ );
+ }
+
+ if (remainingMinutes > 0) {
+ parts.push(
+ new Intl.NumberFormat(currentLocale, {
+ style: "unit",
+ unit: "minute",
+ unitDisplay: "long",
+ }).format(remainingMinutes),
+ );
+ }
+
+ console.log(parts);
+ console.log(parts.join(" "));
+
+ return parts.join(" ");
+};
+
+const timeSpentChoices = computed(() => {
+ const locale = "fr";
+ return timeSpentValues.map((value) => ({
+ text: formatDuration(value, locale),
+ value: parseInt(value),
+ }));
+});
+
const startDate = computed({
get() {
return props.evaluation.startDate;
@@ -194,7 +274,7 @@ function updateWarningInterval(value) {
}
function updateTimeSpent(value) {
- timeSpent.value = value;
+ timeSpent.value = parseInt(value);
}
function updateComment(value) {
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js
index 289a68a34..a24e63015 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/i18n.js
@@ -50,8 +50,8 @@ const visMessages = {
return "Né·e le";
}
},
- center_id: "Identifiant du centre",
- center_type: "Type de centre",
+ center_id: "Identifiant du territoire",
+ center_type: "Type de territoire",
center_name: "Territoire", // vendée
phonenumber: "Téléphone",
mobilenumber: "Mobile",
diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts
index ba7637544..26d905a97 100644
--- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts
+++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts
@@ -25,8 +25,8 @@ const personMessages = {
return "Né·e le";
}
},
- center_id: "Identifiant du centre",
- center_type: "Type de centre",
+ center_id: "Identifiant du territoire",
+ center_type: "Type de territoire",
center_name: "Territoire", // vendée
phonenumber: "Téléphone",
mobilenumber: "Mobile",
@@ -53,8 +53,8 @@ const personMessages = {
"Un nouveau ménage va être créé. L'usager sera membre de ce ménage.",
},
center: {
- placeholder: "Choisissez un centre",
- title: "Centre",
+ placeholder: "Choisissez un territoire",
+ title: "territoire",
},
},
error_only_one_person: "Une seule personne peut être sélectionnée !",
diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig
index f35310503..9adba79ff 100644
--- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig
+++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig
@@ -2,30 +2,6 @@
# OPTIONS
# - displayContent: [short|long] default: short
#}
-{% if w.results|length > 0 %}
-
-
-
- {{ 'accompanying_course_work.goal'|trans }} |
- {{ 'accompanying_course_work.results'|trans }} |
-
-
-
- |
- {{ 'accompanying_course_work.results without objective'|trans }}
- |
-
-
- {% for r in w.results %}
- - {{ r.title|localize_translatable_string }}
- {% endfor %}
-
- |
-
-
-
-{% endif %}
-
{% if w.goals|length > 0 %}
{% endif %}
+{% if w.results|length > 0 %}
+
+
+
+ {{ 'accompanying_course_work.goal'|trans }} |
+ {{ 'accompanying_course_work.results'|trans }} |
+
+
+
+ |
+ {{ 'accompanying_course_work.results without objective'|trans }}
+ |
+
+
+ {% for r in w.results %}
+ - {{ r.title|localize_translatable_string }}
+ {% endfor %}
+
+ |
+
+
+
+{% endif %}
+
+
{% if w.accompanyingPeriodWorkEvaluations|length > 0 %}
@@ -216,9 +217,29 @@
{% if e.timeSpent is not null and e.timeSpent > 0 %}
- {% set minutes = (e.timeSpent / 60) %}
- {{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }} {{ 'duration.minute'|trans({ '{m}' : minutes }) }}
+ {% set totalHours = (e.timeSpent / 3600)|round(0, 'floor') %}
+ {% set totalMinutes = ((e.timeSpent % 3600) / 60)|round(0, 'floor') %}
+
+ {{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}
+
+ {% if totalHours >= 8 %}
+ {% set days = (totalHours / 8)|round(0, 'floor') %}
+ {% set remainingHours = totalHours % 8 %}
+
+ {% if days > 0 %}
+ {{ 'duration.day'|trans({ '{d}' : days }) }}
+ {% endif %}
+ {% if remainingHours > 0 %}
+ {{ 'duration.hour'|trans({ '{h}' : remainingHours }) }}
+ {% endif %}
+ {% else %}
+ {% if totalHours > 0 %}
+ {{ 'duration.hour'|trans({ '{h}' : totalHours }) }}
+ {% endif %}
+ {% if totalMinutes > 0 %}
+ {{ 'duration.minute'|trans({ '{m}' : totalMinutes }) }}
+ {% endif %}
+ {% endif %}
{% elseif displayContent is defined and displayContent == 'long' %}
diff --git a/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialActionCSVExportService.php b/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialActionCSVExportService.php
index 09b4442b2..71c548dd1 100644
--- a/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialActionCSVExportService.php
+++ b/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialActionCSVExportService.php
@@ -42,28 +42,41 @@ final readonly class SocialActionCSVExportService
$csv->insertOne($headers);
foreach ($actions as $action) {
- if ($action->getGoals()->isEmpty() && $action->getResults()->isEmpty() && $action->getEvaluations()->isEmpty()) {
+ $hasGoals = !$action->getGoals()->isEmpty();
+ $hasResults = !$action->getResults()->isEmpty();
+ $hasEvaluations = !$action->getEvaluations()->isEmpty();
+
+ // If action has no goals, results, or evaluations, insert a single row
+ if (!$hasGoals && !$hasResults && !$hasEvaluations) {
$csv->insertOne($this->formatRow($action));
+ continue;
}
- foreach ($action->getGoals() as $goal) {
- if ($goal->getResults()->isEmpty()) {
- $csv->insertOne($this->formatRow($action, $goal));
- }
-
- foreach ($goal->getResults() as $goalResult) {
- $csv->insertOne($this->formatRow($action, $goal, $goalResult));
+ // Process goals and their results
+ if ($hasGoals) {
+ foreach ($action->getGoals() as $goal) {
+ if ($goal->getResults()->isEmpty()) {
+ $csv->insertOne($this->formatRow($action, $goal));
+ } else {
+ foreach ($goal->getResults() as $goalResult) {
+ $csv->insertOne($this->formatRow($action, $goal, $goalResult));
+ }
+ }
}
}
- foreach ($action->getResults() as $result) {
- if ($result->getGoals()->isEmpty()) {
+ // Process results that are linked to this action (regardless of whether they have goals elsewhere)
+ if ($hasResults && !$hasGoals) {
+ foreach ($action->getResults() as $result) {
$csv->insertOne($this->formatRow($action, null, null, $result));
}
}
- foreach ($action->getEvaluations() as $evaluation) {
- $csv->insertOne($this->formatRow($action, evaluation: $evaluation));
+ // Process evaluations
+ if ($hasEvaluations) {
+ foreach ($action->getEvaluations() as $evaluation) {
+ $csv->insertOne($this->formatRow($action, evaluation: $evaluation));
+ }
}
}
diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
index cde9a6add..88b12f7bd 100644
--- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml
@@ -376,7 +376,7 @@ Create a list of people according to various filters.: Crée une liste d'usagers
Fields to include in export: Champs à inclure dans l'export
Address valid at this date: Addresse valide à cette date
Data valid at this date: Données valides à cette date
-Data regarding center, addresses, and so on will be computed at this date: Les données concernant le centre, l'adresse, le ménage, sera calculé à cette date.
+Data regarding center, addresses, and so on will be computed at this date: Les données concernant le territoire, l'adresse, le ménage, sera calculé à cette date.
List duplicates: Liste des doublons
Create a list of duplicate people: Créer la liste des usagers détectés comme doublons.
Count people participating in an accompanying course: Nombre d'usagers concernés par un parcours
@@ -1110,9 +1110,9 @@ export:
Group course by household composition: Grouper les usagers par composition familiale
Calc date: Date de calcul de la composition du ménage
by_center:
- title: Grouper les usagers par centre
- at_date: Date de calcul du centre
- center: Centre de l'usager
+ title: Grouper les usagers par territoire
+ at_date: Date de calcul du territoire
+ center: Territoire de l'usager
by_postal_code:
title: Grouper les usagers par code postal de l'adresse
at_date: Date de calcul de l'adresse
@@ -1437,7 +1437,7 @@ export:
acpParticipantPersons: Usagers concernés
acpParticipantPersonsIds: Usagers concernés (identifiants)
duration: Durée du parcours (en jours)
- centers: Centres des usagers
+ centers: Territoires des usagers
eval:
List of evaluations: Liste des évaluations
diff --git a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml
index 691fef833..c6fe0f912 100644
--- a/src/Bundle/ChillPersonBundle/translations/validators.fr.yml
+++ b/src/Bundle/ChillPersonBundle/translations/validators.fr.yml
@@ -23,7 +23,7 @@ The gender must be set: Le genre doit être renseigné
You are not allowed to perform this action: Vous n'avez pas le droit de changer cette valeur.
Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again: Désolé, mais quelqu'un d'autre a déjà modifié cette entité. Veuillez actualiser la page et appliquer à nouveau les modifications
-A center is required: Un centre est requis
+A center is required: Un territoire est requis
#export list
You must select at least one element: Vous devez sélectionner au moins un élément
diff --git a/src/Bundle/ChillReportBundle/translations/messages.fr.yml b/src/Bundle/ChillReportBundle/translations/messages.fr.yml
index c22ed17fb..a8d5ed979 100644
--- a/src/Bundle/ChillReportBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillReportBundle/translations/messages.fr.yml
@@ -9,7 +9,7 @@
'Report list': 'Liste des rapports'
Details: Détails
Person: Usager
-Scope: Cercle
+Scope: Service
Date: Date
User: Utilisateur
'Report type': 'Type de rapport'
diff --git a/src/Bundle/ChillTaskBundle/translations/messages.fr.yml b/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
index b0d155f59..341e889ec 100644
--- a/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillTaskBundle/translations/messages.fr.yml
@@ -4,7 +4,7 @@ Tasks: "Tâches"
Title: Titre
Description: Description
Assignee: "Personne assignée"
-Scope: Cercle
+Scope: Service
"Start date": "Date de début"
"End date": "Date d'échéance"
"Warning date": "Date d'avertissement"
@@ -106,7 +106,7 @@ My tasks over deadline: Mes tâches à échéance dépassée
#transition page
Apply transition on task %title%: Appliquer la transition sur la tâche %title%
-All centers: Tous les centres
+All centers: Tous les territoires
# ROLES
CHILL_TASK_TASK_CREATE: Ajouter une tâche
diff --git a/src/Bundle/ChillThirdPartyBundle/Service/ThirdpartyMergeService.php b/src/Bundle/ChillThirdPartyBundle/Service/ThirdpartyMergeService.php
index 4eecb09b4..a5aecea45 100644
--- a/src/Bundle/ChillThirdPartyBundle/Service/ThirdpartyMergeService.php
+++ b/src/Bundle/ChillThirdPartyBundle/Service/ThirdpartyMergeService.php
@@ -69,10 +69,11 @@ readonly class ThirdpartyMergeService
if (($assoc['type'] & ClassMetadata::TO_ONE) !== 0) {
$joinColumn = $meta->getSingleAssociationJoinColumnName($assoc['fieldName']);
- $suffix = (ThirdParty::class === $assoc['sourceEntity']) ? 'chill_3party.' : '';
+ $schema = $meta->getSchemaName();
+ $prefix = null !== $schema && '' !== $schema ? $schema.'.' : '';
$queries[] = [
- 'sql' => "UPDATE {$suffix}{$tableName} SET {$joinColumn} = :toKeep WHERE {$joinColumn} = :toDelete",
+ 'sql' => "UPDATE {$prefix}{$tableName} SET {$joinColumn} = :toKeep WHERE {$joinColumn} = :toDelete",
'params' => ['toKeep' => $toKeep->getId(), 'toDelete' => $toDelete->getId()],
];
} elseif (ClassMetadata::MANY_TO_MANY === $assoc['type'] && isset($assoc['joinTable'])) {
@@ -85,13 +86,36 @@ readonly class ThirdpartyMergeService
];
$queries[] = [
- 'sql' => "DELETE FROM {$joinTable} WHERE {$joinColumn} = :toDelete",
+ 'sql' => "DELETE FROM {$prefix}{$joinTable} WHERE {$joinColumn} = :toDelete",
'params' => ['toDelete' => $toDelete->getId()],
];
}
}
}
+ // Also handle many-to-many where ThirdParty is the source
+ $thirdPartyMeta = $this->em->getClassMetadata(ThirdParty::class);
+ foreach ($thirdPartyMeta->getAssociationMappings() as $assoc) {
+ if (ClassMetadata::MANY_TO_MANY === $assoc['type'] && isset($assoc['joinTable'])) {
+ $joinTable = $assoc['joinTable']['name'];
+ $prefix = null !== ($assoc['joinTable']['schema'] ?? null) ? $assoc['joinTable']['schema'].'.' : '';
+ $joinColumn = $assoc['joinTable']['joinColumns'][0]['name']; // Note: joinColumns, not inverseJoinColumns
+
+ // Get the other column name to build proper duplicate check
+ $otherColumn = $assoc['joinTable']['inverseJoinColumns'][0]['name'];
+
+ $queries[] = [
+ 'sql' => "UPDATE {$prefix}{$joinTable} SET {$joinColumn} = :toKeep WHERE {$joinColumn} = :toDelete AND NOT EXISTS (SELECT 1 FROM {$prefix}{$joinTable} AS t2 WHERE t2.{$joinColumn} = :toKeep AND t2.{$otherColumn} = {$prefix}{$joinTable}.{$otherColumn})",
+ 'params' => ['toKeep' => $toKeep->getId(), 'toDelete' => $toDelete->getId()],
+ ];
+
+ $queries[] = [
+ 'sql' => "DELETE FROM {$prefix}{$joinTable} WHERE {$joinColumn} = :toDelete",
+ 'params' => ['toDelete' => $toDelete->getId()],
+ ];
+ }
+ }
+
return $queries;
}
@@ -102,10 +126,6 @@ readonly class ThirdpartyMergeService
'sql' => 'UPDATE chill_3party.third_party SET parent_id = :toKeep WHERE parent_id = :toDelete',
'params' => ['toKeep' => $toKeep->getId(), 'toDelete' => $toDelete->getId()],
],
- [
- 'sql' => 'UPDATE chill_3party.thirdparty_category SET thirdparty_id = :toKeep WHERE thirdparty_id = :toDelete AND NOT EXISTS (SELECT 1 FROM chill_3party.thirdparty_category WHERE thirdparty_id = :toKeep)',
- 'params' => ['toKeep' => $toKeep->getId(), 'toDelete' => $toDelete->getId()],
- ],
[
'sql' => 'DELETE FROM chill_3party.third_party WHERE id = :toDelete',
'params' => ['toDelete' => $toDelete->getId()],
diff --git a/src/Bundle/ChillThirdPartyBundle/Tests/Service/ThirdpartyMergeServiceTest.php b/src/Bundle/ChillThirdPartyBundle/Tests/Service/ThirdpartyMergeServiceTest.php
index 4b2819751..4d8afbb7d 100644
--- a/src/Bundle/ChillThirdPartyBundle/Tests/Service/ThirdpartyMergeServiceTest.php
+++ b/src/Bundle/ChillThirdPartyBundle/Tests/Service/ThirdpartyMergeServiceTest.php
@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\ThirdPartyBundle\Tests\Service;
use Chill\ActivityBundle\Entity\Activity;
+use Chill\MainBundle\Entity\Center;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory;
use Chill\ThirdPartyBundle\Service\ThirdpartyMergeService;
@@ -47,19 +48,20 @@ class ThirdpartyMergeServiceTest extends KernelTestCase
$toDelete->setName('Thirdparty to delete');
$this->em->persist($toDelete);
- // Create a related entity with TO_ONE relation (thirdparty parent)
+ // Create a related entity with TO_ONE relation (thirdparty parent) - tests schema handling for TO_ONE
$relatedToOneEntity = new ThirdParty();
$relatedToOneEntity->setName('RelatedToOne thirdparty');
$relatedToOneEntity->setParent($toDelete);
$this->em->persist($relatedToOneEntity);
- // Create a related entity with TO_MANY relation (thirdparty category)
+ // Create a related entity with MANY_TO_MANY relation (thirdparty category) - tests schema handling for MANY_TO_MANY where ThirdParty is target
$thirdpartyCategory = new ThirdPartyCategory();
$thirdpartyCategory->setName(['fr' => 'Thirdparty category']);
$this->em->persist($thirdpartyCategory);
$toDelete->addCategory($thirdpartyCategory);
$this->em->persist($toDelete);
+ // Test MANY_TO_MANY relation from another bundle (Activity) - tests cross-bundle schema handling
$activity = new Activity();
$activity->setDate(new \DateTime());
$activity->addThirdParty($toDelete);
@@ -73,14 +75,55 @@ class ThirdpartyMergeServiceTest extends KernelTestCase
$this->em->refresh($relatedToOneEntity);
// Check that references were updated
- $this->assertEquals($toKeep->getId(), $relatedToOneEntity->getParent()->getId(), 'The parent thirdparty was succesfully merged');
+ // Test TO_ONE relation in chill_3party schema was properly handled
+ $this->assertEquals($toKeep->getId(), $relatedToOneEntity->getParent()->getId(), 'The parent thirdparty in chill_3party schema was successfully merged');
+
+ // Test MANY_TO_MANY relation in chill_3party schema was properly handled
$updatedRelatedManyEntity = $this->em->find(ThirdPartyCategory::class, $thirdpartyCategory->getId());
- $this->assertContains($updatedRelatedManyEntity, $toKeep->getCategories(), 'The thirdparty category was found in the toKeep entity');
+ $this->assertContains($updatedRelatedManyEntity, $toKeep->getCategories(), 'The thirdparty category in chill_3party schema was found in the toKeep entity');
+
+ // Test MANY_TO_MANY relation from different schema (Activity bundle) was properly handled
+ $this->em->refresh($activity);
+ $this->assertContains($toKeep, $activity->getThirdParties(), 'The activity relation from different schema was successfully merged');
+ $this->assertNotContains($toDelete, $activity->getThirdParties(), 'The toDelete thirdparty was removed from activity relation');
// Check that toDelete was removed
$this->em->clear();
$deletedThirdParty = $this->em->find(ThirdParty::class, $toDelete->getId());
- $this->assertNull($deletedThirdParty);
+ $this->assertNull($deletedThirdParty, 'The toDelete thirdparty was successfully removed');
+ }
+
+ public function testMergeWithSharedCenterDoesNotCauseUniqueConstraintViolation(): void
+ {
+ // Create a center that will be shared by both thirdparties
+ $sharedCenter = new Center();
+ $sharedCenter->setName('Shared Center');
+ $this->em->persist($sharedCenter);
+
+ // Create ThirdParty entities
+ $toKeep = new ThirdParty();
+ $toKeep->setName('Thirdparty to keep');
+ $toKeep->addCenter($sharedCenter); // Both thirdparties linked to same center
+ $this->em->persist($toKeep);
+
+ $toDelete = new ThirdParty();
+ $toDelete->setName('Thirdparty to delete');
+ $toDelete->addCenter($sharedCenter); // Both thirdparties linked to same center
+ $this->em->persist($toDelete);
+
+ $this->em->flush();
+
+ // This should not throw a unique constraint violation
+ $this->service->merge($toKeep, $toDelete);
+
+ // Verify that toKeep still has the shared center
+ $this->em->refresh($toKeep);
+ $this->assertContains($sharedCenter, $toKeep->getCenters(), 'The shared center is still linked to the kept thirdparty');
+
+ // Verify that toDelete was removed
+ $this->em->clear();
+ $deletedThirdParty = $this->em->find(ThirdParty::class, $toDelete->getId());
+ $this->assertNull($deletedThirdParty, 'The toDelete thirdparty was successfully removed');
}
}
diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml
index 2c5e8f262..bdd9420f7 100644
--- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml
@@ -73,8 +73,8 @@ No acronym given: Aucun sigle renseigné
No phone given: Aucun téléphone renseigné
No email given: Aucune adresse courriel renseignée
-The party is visible in those centers: Le tiers est visible dans ces centres
-The party is not visible in any center: Le tiers n'est associé à aucun centre
+The party is visible in those centers: Le tiers est visible dans ces territoires
+The party is not visible in any center: Le tiers n'est associé à aucun territoire
No third parties: Aucun tiers
Any third party selected: Aucun tiers sélectionné