From b9b4fafe14b2c80191128e439fa687fd8b7a00e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 9 Apr 2025 21:30:41 +0200 Subject: [PATCH 01/83] Adjust cronjob interval to ensure daily execution The interval for `AccompanyingPeriodStepChangeCronjob` was reduced from 24 hours to 23 hours and 45 minutes. This change guarantees at least one execution per day, addressing potential issues with timing overlaps. --- .changes/unreleased/Fixed-20250409-212958.yaml | 6 ++++++ .../Lifecycle/AccompanyingPeriodStepChangeCronjob.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changes/unreleased/Fixed-20250409-212958.yaml diff --git a/.changes/unreleased/Fixed-20250409-212958.yaml b/.changes/unreleased/Fixed-20250409-212958.yaml new file mode 100644 index 000000000..30b07baf5 --- /dev/null +++ b/.changes/unreleased/Fixed-20250409-212958.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Shorten the delay between two execution of AccompanyingPeriodStepChangeCronjob, to ensure at least one execution in a day +time: 2025-04-09T21:29:58.591267777+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Lifecycle/AccompanyingPeriodStepChangeCronjob.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Lifecycle/AccompanyingPeriodStepChangeCronjob.php index 27553444b..80f3a6081 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Lifecycle/AccompanyingPeriodStepChangeCronjob.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Lifecycle/AccompanyingPeriodStepChangeCronjob.php @@ -26,7 +26,7 @@ readonly class AccompanyingPeriodStepChangeCronjob implements CronJobInterface { $now = $this->clock->now(); - if (null !== $cronJobExecution && $now->sub(new \DateInterval('P1D')) < $cronJobExecution->getLastStart()) { + if (null !== $cronJobExecution && $now->sub(new \DateInterval('PT23H45M')) < $cronJobExecution->getLastStart()) { return false; } From 5858e05a4266f9eebd6292406cde7ef0d26f6ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 10 Apr 2025 15:31:06 +0200 Subject: [PATCH 02/83] Replace grid in person_list document to fit the whole width of the page --- .../Resources/views/GenericDoc/person_list.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/person_list.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/person_list.html.twig index 5bc9f42d1..533a075b6 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/person_list.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/GenericDoc/person_list.html.twig @@ -23,7 +23,7 @@ License * along with this program. If not, see . {{ encore_entry_link_tags("mod_document_action_buttons_group") }} {% endblock %} {% block content %} -
+

{{ 'Documents for %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}

From d58acff54148e0d2fac866d9a66c0dcf7ab4c99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 10 Apr 2025 15:33:02 +0200 Subject: [PATCH 03/83] Add css layout for badges for accompanying period work, activity and calendar --- .changes/unreleased/DX-20250410-153426.yaml | 6 ++++ .../Resources/public/chill/chillactivity.scss | 31 ++++++++++++++++ .../Resources/public/chill/scss/badge.scss | 35 ++++++++++++++++++- .../public/chill/scss/calendar-list.scss | 2 +- .../Resources/views/Dev/dev.assets.html.twig | 8 +++++ .../chill/scss/accompanying_period_work.scss | 30 ++++++++++++++++ 6 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 .changes/unreleased/DX-20250410-153426.yaml diff --git a/.changes/unreleased/DX-20250410-153426.yaml b/.changes/unreleased/DX-20250410-153426.yaml new file mode 100644 index 000000000..d86d4dfed --- /dev/null +++ b/.changes/unreleased/DX-20250410-153426.yaml @@ -0,0 +1,6 @@ +kind: DX +body: Add new chill-col style for displaying title and aside in a flex table +time: 2025-04-10T15:34:26.052138894+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss index 13de8dfc0..6658656f9 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss +++ b/src/Bundle/ChillActivityBundle/Resources/public/chill/chillactivity.scss @@ -120,3 +120,34 @@ li.document-list-item { vertical-align: baseline; } } + +.badge-activity-type-simple { + @extend .badge; + display: inline-block; + margin: 0.2rem 0; + padding-left: 0; + padding-right: 0.5rem; + + border-left: 20px groove #9acd32; + border-radius: $badge-border-radius; + + color: black; + font-weight: normal; + font-size: unset; + max-width: 100%; + background-color: $gray-100; + + overflow: hidden; + text-overflow: ellipsis; + text-indent: 5px hanging; + text-align: left; + + &::before { + margin-right: 3px; + position: relative; + left: -0.5px; + font-family: ForkAwesome; + content: '\f04b'; + color: #9acd32; + } +} diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss index ffcda8f0f..04d22bfc5 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss +++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss @@ -1,5 +1,6 @@ -@import '~ChillPersonAssets/chill/scss/mixins.scss'; @import '~ChillMainAssets/module/bootstrap/shared'; +@import '~ChillPersonAssets/chill/scss/mixins.scss'; +@import 'bootstrap/scss/_badge.scss'; .badge-calendar { display: inline-block; @@ -23,3 +24,35 @@ } } +.badge-calendar-simple { + @extend .badge; + display: inline-block; + margin: 0.2rem 0; + padding-left: 0; + padding-right: 0.5rem; + + border-left: 20px groove $chill-l-gray; + border-radius: $badge-border-radius; + + max-width: 100%; + background-color: $gray-100; + + color: black; + font-weight: normal; + overflow: hidden; + font-weight: normal; + font-size: unset; + text-overflow: ellipsis; + text-indent: 5px hanging; + text-align: left; + + &::before { + margin-right: 3px; + position: relative; + left: -0.5px; + font-family: ForkAwesome; + content: '\f04b'; + color: $chill-l-gray; + } +} + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar-list.scss b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar-list.scss index c232b78e2..6fa84f15a 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar-list.scss +++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar-list.scss @@ -16,7 +16,7 @@ div.calendar-list { } & > a.calendar-list__global { - display: inline-block;; + display: inline-block; padding: 0.2rem; min-width: 2rem; border: 1px solid var(--bs-chill-blue); diff --git a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig index 5aafb6635..6322ca68f 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig @@ -392,4 +392,12 @@ Toutes les classes btn-* de bootstrap sont fonctionnelles
+
+

Badges

+ + Action d'accompagnement + Type d'échange + Rendez-vous +
+ {% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss index e82029a1f..017b98961 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/scss/accompanying_period_work.scss @@ -20,6 +20,36 @@ } } +.badge-accompanying-work-type-simple { + @extend .badge; + display: inline-block; + margin: 0.2rem 0; + padding-left: 0; + padding-right: 0.5rem; + + border-left: 20px groove $orange; + border-radius: $badge-border-radius; + + max-width: 100%; + background-color: $gray-100; + + color: black; + font-weight: normal; + overflow: hidden; + text-overflow: ellipsis; + text-indent: 5px hanging; + text-align: left; + + &::before { + margin-right: 3px; + position: relative; + left: -0.5px; + font-family: ForkAwesome; + content: '\f04b'; + color: #e2793d; + } +} + /// AccompanyingCourse Work Pages div.accompanying-course-work { From 203a0980545338716c2a671d43c6e77005337a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 10 Apr 2025 15:33:22 +0200 Subject: [PATCH 04/83] Refactor document row layouts to use CSS grid Replaced the old 'item-col' structure with a 'item-two-col-grid' layout across multiple templates, improving consistency and responsiveness. Introduced CSS grid styles ensuring proper alignment and wrapping of titles and aside elements in different viewport sizes. This enhances the overall readability and maintainability of the views. --- .../unreleased/Fixed-20250410-153354.yaml | 6 ++ .../activity_document_row.html.twig | 58 +++++++------- .../calendar_document_row.html.twig | 72 ++++++++--------- .../views/List/list_item_row.html.twig | 80 +++++++++---------- .../public/chill/scss/flex_table.scss | 27 +++++++ .../Resources/views/Dev/dev.assets.html.twig | 53 ++++++++++++ .../evaluation_document_row.html.twig | 73 +++++++++-------- 7 files changed, 229 insertions(+), 140 deletions(-) create mode 100644 .changes/unreleased/Fixed-20250410-153354.yaml diff --git a/.changes/unreleased/Fixed-20250410-153354.yaml b/.changes/unreleased/Fixed-20250410-153354.yaml new file mode 100644 index 000000000..d5c0b5652 --- /dev/null +++ b/.changes/unreleased/Fixed-20250410-153354.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Fix display of title in document list +time: 2025-04-10T15:33:54.660510278+02:00 +custom: + Issue: "102" + SchemaChange: No schema change diff --git a/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document_row.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document_row.html.twig index 6ca871de1..93bb01f1e 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document_row.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/GenericDoc/activity_document_row.html.twig @@ -13,44 +13,44 @@ {% endif %}
-
- {% if document.isPending %} -
{{ 'docgen.Doc generation is pending'|trans }}
- {% elseif document.isFailure %} -
{{ 'docgen.Doc generation failed'|trans }}
- {% endif %} - -
- {% if activity.accompanyingPeriod is not null and context == 'person' %} - - {{ activity.accompanyingPeriod.id }} -   +
+
+ {% if document.isPending %} +
{{ 'docgen.Doc generation is pending'|trans }}
+ {% elseif document.isFailure %} +
{{ 'docgen.Doc generation failed'|trans }}
{% endif %} -
- - - {{ activity.type.name | localize_translatable_string }} + +
+
+
+ {{ activity.type.name | localize_translatable_string }} +
{% if activity.emergency %} {{ 'Emergency'|trans|upper }} {% endif %} - +
-
-
- {{ document.title|chill_print_or_message("No title") }} -
- {% if document.hasTemplate %} -
-

{{ document.template.name|localize_translatable_string }}

+
+ {{ document.title|chill_print_or_message("No title") }}
- {% endif %} -
- -
-
+ {% if document.hasTemplate %} +
+

{{ document.template.name|localize_translatable_string }}

+
+ {% endif %} +
+
{{ document.createdAt|format_date('short') }}
+ {% if activity.accompanyingPeriod is not null and context == 'person' %} +
+ + {{ activity.accompanyingPeriod.id }} +   +
+ {% endif %}
diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document_row.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document_row.html.twig index 8cf5bc4ac..8015ac600 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document_row.html.twig +++ b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document_row.html.twig @@ -6,50 +6,48 @@
-
- {% if document.storedObject.isPending %} -
{{ 'docgen.Doc generation is pending'|trans }}
- {% elseif document.storedObject.isFailure %} -
{{ 'docgen.Doc generation failed'|trans }}
- {% endif %} - -
- {% if c.accompanyingPeriod is not null and context == 'person' %} - - {{ c.accompanyingPeriod.id }} -   +
+
+ {% if document.storedObject.isPending %} +
{{ 'docgen.Doc generation is pending'|trans }}
+ {% elseif document.storedObject.isFailure %} +
{{ 'docgen.Doc generation failed'|trans }}
{% endif %} - - - - {{ 'Calendar'|trans }} - {% if c.endDate.diff(c.startDate).days >= 1 %} - {{ c.startDate|format_datetime('short', 'short') }} - - {{ c.endDate|format_datetime('short', 'short') }} - {% else %} - {{ c.startDate|format_datetime('short', 'short') }} - - {{ c.endDate|format_datetime('none', 'short') }} - {% endif %} - - -
- -
- {{ document.storedObject.title|chill_print_or_message("No title") }} -
- {% if document.storedObject.hasTemplate %}
-

{{ document.storedObject.template.name|localize_translatable_string }}

-
- {% endif %} -
-
-
+ + {{ 'Calendar'|trans }} + {% if c.endDate.diff(c.startDate).days >= 1 %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('short', 'short') }} + {% else %} + {{ c.startDate|format_datetime('short', 'short') }} + - {{ c.endDate|format_datetime('none', 'short') }} + {% endif %} + +
+ +
+ {{ document.storedObject.title|chill_print_or_message("No title") }} +
+ {% if document.storedObject.hasTemplate %} +
+

{{ document.storedObject.template.name|localize_translatable_string }}

+
+ {% endif %} +
+
{{ document.storedObject.createdAt|format_date('short') }}
+ {% if c.accompanyingPeriod is not null and context == 'person' %} +
+ + {{ c.accompanyingPeriod.id }} +   +
+ {% endif %}
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item_row.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item_row.html.twig index 21812c903..a92cea589 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item_row.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item_row.html.twig @@ -3,54 +3,54 @@ {% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
-
- {% if document.object.isPending %} -
{{ 'docgen.Doc generation is pending'|trans }}
- {% elseif document.object.isFailure %} -
{{ 'docgen.Doc generation failed'|trans }}
- {% endif %} + +
+
+ {% if document.object.isPending %} +
{{ 'docgen.Doc generation is pending'|trans }}
+ {% elseif document.object.isFailure %} +
{{ 'docgen.Doc generation failed'|trans }}
+ {% endif %} - {% if context == 'person' and accompanyingCourse is defined %} -
- - {{ accompanyingCourse.id }} -   +
+ {{ document.title|chill_print_or_message("No title") }}
- {% elseif context == 'accompanying-period' and person is defined %} + {% if document.object.type is not empty %} +
+ {{ mm.mimeIcon(document.object.type) }} +
+ {% endif %}
- - {{ 'Document from person %name%'|trans({ '%name%': document.person|chill_entity_render_string }) }} -   +

{{ document.category.name|localize_translatable_string }}

- - {% endif %} -
- {{ document.title|chill_print_or_message("No title") }} -
- {% if document.object.type is not empty %} -
- {{ mm.mimeIcon(document.object.type) }} -
- {% endif %} -
-

{{ document.category.name|localize_translatable_string }}

-
- {% if document.object.hasTemplate %} -
-

{{ document.object.template.name|localize_translatable_string }}

-
- {% endif %} -
- -
-
- {% if document.date is not null %} -
- {{ document.date|format_date('short') }} + {% if document.object.hasTemplate %} +
+

{{ document.object.template.name|localize_translatable_string }}

{% endif %}
+ {% if document.date is not null %} +
+
+ {{ document.date|format_date('short') }} +
+ {% if context == 'person' and accompanyingCourse is defined %} +
+ + {{ accompanyingCourse.id }} +   +
+ {% elseif context == 'accompanying-period' and person is defined %} +
+ + {{ document.person|chill_entity_render_string }} +   +
+ {% endif %} +
+ {% endif %}
+
{% if document.description is not empty %}
diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss index 0adf2b628..7ac52b24e 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/scss/flex_table.scss @@ -25,7 +25,34 @@ div.flex-table { div.item-col:last-child { display: flex; } + + div.item-two-col-grid { + display: grid; + width: 100%; + justify-content: stretch; + + @include media-breakpoint-up(lg) { + grid-template-areas: + "title aside"; + grid-template-columns: 1fr minmax(8rem, 1fr); + column-gap: 0.5em; + } + @include media-breakpoint-down(lg) { + grid-template-areas: + "aside" + "title"; + } + + & > div.title { + grid-area: title; + } + + & > div.aside { + grid-area: aside; + } + } } + } h2, h3, h4, dl, p { diff --git a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig index 6322ca68f..927e496f9 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig @@ -136,6 +136,59 @@
+

Fix the title in the flex table

+ +

This will fix the layout of the row, with a "title" element, and an aside element. Using css grid, this is quite safe and won't overflow

+ + + <div class="flex-table"> + <div class="item-bloc"> + <div class="item-row"> + <div class="item-two-col-grid"> + <div class="title">This is my title</div> + <div class="aside">Aside value</div> + </div> + </div> + </div> + <div class="item-bloc"> + <div class="item-row"> + <div class="item-two-col-grid"> + <div class="title"> + <div><h3>This is my title, which can be very long and take a lot of place. But it is wrapped successfully, and won't disturb the placement of the aside block</h3></div> + <div>This is a second line</div> + </div> + <div class="aside">Aside value</div> + </div> + </div> + </div> + </div> + + +

will render:

+ +
+
+
+
+
This is my title
+
Aside value
+
+
+
+
+
+
+
+

This is my title, which can be very long and take a lot of place. But it is wrapped successfully, and won't disturb the placement of the aside block

+
This is a second line
+
+
Aside value
+
+
+
+
+ +

Wrap-list

Une liste inline qui s'aligne, puis glisse sous son titre.

diff --git a/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document_row.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document_row.html.twig index 451857628..50570ddc6 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document_row.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/GenericDoc/evaluation_document_row.html.twig @@ -5,44 +5,49 @@ {% set w = document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork %}
-
- {% if document.storedObject.isPending %} -
{{ 'docgen.Doc generation is pending'|trans }}
- {% elseif document.storedObject.isFailure %} -
{{ 'docgen.Doc generation failed'|trans }}
- {% endif %} -
- {% if context == 'person' %} - - {{ w.accompanyingPeriod.id }} -   + +
+
+ {% if document.storedObject.isPending %} +
{{ 'docgen.Doc generation is pending'|trans }}
+ {% elseif document.storedObject.isFailure %} +
{{ 'docgen.Doc generation failed'|trans }}
{% endif %} -
- - {{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }} -
-
-
- {{ document.title|chill_print_or_message("No title") }} -
- {% if document.storedObject.type is not empty %}
- {{ mm.mimeIcon(document.storedObject.type) }} +
+
+ {{ w.socialAction|chill_entity_render_string }} > {{ document.accompanyingPeriodWorkEvaluation.evaluation.title|localize_translatable_string }} +
+
+
+
+ {{ document.title|chill_print_or_message("No title") }} +
+ {% if document.storedObject.type is not empty %} +
+ {{ mm.mimeIcon(document.storedObject.type) }} +
+ {% endif %} + {% if document.storedObject.hasTemplate %} +
+

{{ document.storedObject.template.name|localize_translatable_string }}

+
+ {% endif %} +
+ {% if document.storedObject.createdAt is not null %} +
+
+ {{ document.storedObject.createdAt|format_date('short') }} +
+ {% if context == 'person' %} +
+ + {{ w.accompanyingPeriod.id }} +   +
+ {% endif %}
{% endif %} - {% if document.storedObject.hasTemplate %} -
-

{{ document.storedObject.template.name|localize_translatable_string }}

-
- {% endif %} -
- -
-
-
- {{ document.storedObject.createdAt|format_date('short') }} -
-
From 40e373a9c7ca4ef4a4cfa863ac2db79b9ee8257d Mon Sep 17 00:00:00 2001 From: juminet Date: Mon, 14 Apr 2025 09:34:02 +0000 Subject: [PATCH 05/83] #365 Add works and activities counter --- .../unreleased/Feature-20250319-090004.yaml | 9 +++++++ .../Menu/AccompanyingCourseMenuBuilder.php | 15 ++++++++--- .../Menu/PersonMenuBuilder.php | 12 +++++++-- .../AccompanyingCourseController.php | 11 +++++--- .../Entity/AccompanyingPeriod.php | 8 ++++++ .../Menu/AccompanyingCourseMenuBuilder.php | 9 ++++--- .../Resources/public/chill/chillperson.scss | 9 +++++++ .../views/AccompanyingCourse/index.html.twig | 27 ++++++++++++++++++- .../translations/messages.fr.yml | 5 +++- 9 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 .changes/unreleased/Feature-20250319-090004.yaml diff --git a/.changes/unreleased/Feature-20250319-090004.yaml b/.changes/unreleased/Feature-20250319-090004.yaml new file mode 100644 index 000000000..3b4528b29 --- /dev/null +++ b/.changes/unreleased/Feature-20250319-090004.yaml @@ -0,0 +1,9 @@ +kind: Feature +body: Add counters of actions and activities, with 2 boxes to (1) show the number + of active actions on total actions and (2) show the number of activities in a accompanying + period, and pills in menus for showing the number of active actions and the number + of activities. +time: 2025-03-19T09:00:04.152359515+01:00 +custom: + Issue: "365" + SchemaChange: No schema change diff --git a/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php index b4990c0e3..5ed606637 100644 --- a/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Menu; +use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; @@ -23,22 +24,30 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface { - public function __construct(protected Security $security, protected TranslatorInterface $translator) {} + public function __construct( + protected Security $security, + protected TranslatorInterface $translator, + private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, + ) {} public function buildMenu($menuId, MenuItem $menu, array $parameters) { $period = $parameters['accompanyingCourse']; + $activities = $this->managerRegistry->getManager()->getRepository(Activity::class)->findBy( + ['accompanyingPeriod' => $period] + ); + if ( AccompanyingPeriod::STEP_DRAFT !== $period->getStep() && $this->security->isGranted(ActivityVoter::SEE, $period) ) { - $menu->addChild($this->translator->trans('Activity'), [ + $menu->addChild($this->translator->trans('Activities'), [ 'route' => 'chill_activity_activity_list', 'routeParameters' => [ 'accompanying_period_id' => $period->getId(), ], ]) - ->setExtras(['order' => 40]); + ->setExtras(['order' => 40, 'counter' => count($activities) > 0 ? count($activities) : null]); } } diff --git a/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php b/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php index 797d64eb6..5a13c5d65 100644 --- a/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php +++ b/src/Bundle/ChillActivityBundle/Menu/PersonMenuBuilder.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Menu; +use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface; use Chill\ActivityBundle\Security\Authorization\ActivityVoter; use Chill\MainBundle\Routing\LocalMenuBuilderInterface; use Chill\PersonBundle\Entity\Person; @@ -23,13 +24,20 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface { - public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private TranslatorInterface $translator) {} + public function __construct( + private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository, + private AuthorizationCheckerInterface $authorizationChecker, + private TranslatorInterface $translator, + ) {} public function buildMenu($menuId, MenuItem $menu, array $parameters) { /** @var Person $person */ $person = $parameters['person']; + + $count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE); + if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) { $menu->addChild( $this->translator->trans('Activities'), @@ -38,7 +46,7 @@ final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface 'routeParameters' => ['person_id' => $person->getId()], ] ) - ->setExtra('order', 201); + ->setExtras(['order' => 201, 'counter' => $count > 0 ? $count : null]); } } diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php index 754be808a..0cb3c1473 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php @@ -204,20 +204,25 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle ['date' => 'DESC', 'id' => 'DESC'], ); - $activities = \array_slice($activities, 0, 3); - $works = $this->workRepository->findByAccompanyingPeriod( $accompanyingCourse, ['startDate' => 'DESC', 'endDate' => 'DESC'], 3 ); + $counters = [ + 'activities' => count($activities), + 'openWorks' => count($accompanyingCourse->getOpenWorks()), + 'works' => count($works), + ]; + return $this->render('@ChillPerson/AccompanyingCourse/index.html.twig', [ 'accompanyingCourse' => $accompanyingCourse, 'withoutHousehold' => $withoutHousehold, 'participationsByHousehold' => $accompanyingCourse->actualParticipationsByHousehold(), 'works' => $works, - 'activities' => $activities, + 'activities' => \array_slice($activities, 0, 3), + 'counters' => $counters, ]); } diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index 91c8714bb..d96814e1a 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -511,6 +511,14 @@ class AccompanyingPeriod implements return $this->getParticipationsContainsPerson($person)->count() > 0; } + public function getOpenWorks(): Collection + { + return $this->getWorks()->filter( + static fn (AccompanyingPeriodWork $work): bool => null === $work->getEndDate() + or $work->getEndDate() > new \DateTimeImmutable('today') + ); + } + /** * Open a new participation for a person. */ diff --git a/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php index 79cd19db9..8c93f461d 100644 --- a/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -71,7 +71,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface ->setExtras(['order' => 30]); */ - $menu->addChild($this->translator->trans('Accompanying Course Comment'), [ + $menu->addChild($this->translator->trans('Accompanying Course Comments'), [ 'route' => 'chill_person_accompanying_period_comment_list', 'routeParameters' => [ 'accompanying_period_id' => $period->getId(), @@ -80,12 +80,15 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface } if ($this->security->isGranted(AccompanyingPeriodWorkVoter::SEE, $period)) { - $menu->addChild($this->translator->trans('Accompanying Course Action'), [ + $menu->addChild($this->translator->trans('Accompanying Course Actions'), [ 'route' => 'chill_person_accompanying_period_work_list', 'routeParameters' => [ 'id' => $period->getId(), ], ]) - ->setExtras(['order' => 40]); + ->setExtras([ + 'order' => 40, + 'counter' => count($period->getOpenWorks()) > 0 ? count($period->getOpenWorks()) : null, + ]); } $workflow = $this->registry->get($period, 'accompanying_period_lifecycle'); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss b/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss index 24f947100..afa163cf2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss +++ b/src/Bundle/ChillPersonBundle/Resources/public/chill/chillperson.scss @@ -304,5 +304,14 @@ div#dashboards { margin: 0; } } + div.count-item { + font-size: 3rem; + text-align: center; + } + div.count-item-label { + font-size: 90%; + font-variant: all-small-caps; + text-align: center; + } } } diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig index 4ceadf7a1..3637de6c8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourse/index.html.twig @@ -201,7 +201,7 @@ {% endif %} {% if accompanyingCourse.step != 'DRAFT' %} -
+

{{ 'notification.Notifications'|trans }}

{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod', accompanyingCourse.id) %} @@ -238,6 +238,31 @@
{% endif %} + + {% if counters.activities > 0 %} +
+
+
{{ counters.activities }}
+
+ {% if counters.activities == 1 %} + {{ 'Activity'|trans }} + {% else %} + {{ 'Activities'|trans }} + {% endif %} +
+
+
+ {% endif %} + + {% if counters.works > 0 %} +
+
+
{{ counters.openWorks }} / {{ counters.works }}
+
{{ 'accompanying_course_work.On-going works over total'|trans }}
+
+
+ {% endif %} +
+
+ + +
+
{{ 'thirdparty.No_phonenumber'|trans }} {% endif %} + {% if thirdparty.telephone2 is not null %} + {% if thirdparty.telephone is not null %}, {% endif %} + {{ thirdparty.telephone2|chill_format_phonenumber }} + {% endif %}
  • @@ -135,8 +139,14 @@ }) }}
  • - {% if thirdparty.telephone %} - {{ thirdparty.telephone|chill_format_phonenumber }} + {% if thirdparty.telephone or thirdparty.telephone2 %} + {% if thirdparty.telephone is not null %} + {{ thirdparty.telephone|chill_format_phonenumber }} + {% endif %} + {% if thirdparty.telephone2 is not null %} + {% if thirdparty.telephone is not null %}, {% endif %} + {{ thirdparty.telephone2|chill_format_phonenumber }} + {% endif %} {% else %} {{ 'thirdparty.No_phonenumber'|trans }} {% endif %} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig index 34da002f0..dd4dd488d 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form.html.twig @@ -22,6 +22,7 @@ {{ form_row(form.typesAndCategories) }} {{ form_row(form.telephone) }} +{{ form_row(form.telephone2) }} {{ form_row(form.email) }} {% if form.contactDataAnonymous is defined %} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig index 81dd4997f..fc8e60928 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/_form_thirdparty_children.html.twig @@ -24,17 +24,24 @@
  • -
    +
    {{ form_widget(form.telephone) }} {{ form_errors(form.telephone) }} {{ form_label(form.telephone) }}
    -
    +
    + {{ form_widget(form.telephone2) }} + {{ form_errors(form.telephone2) }} + {{ form_label(form.telephone2) }} +
    +
    +
    +
    {{ form_widget(form.email) }} {{ form_errors(form.email) }} {{ form_label(form.email) }}
    -
    +
    {{ form_widget(form.contactDataAnonymous) }} {{ form_label(form.contactDataAnonymous) }} {{ form_errors(form.contactDataAnonymous) }} diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig index 4e7fd159d..bc5fc3325 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig +++ b/src/Bundle/ChillThirdPartyBundle/Resources/views/ThirdParty/view.html.twig @@ -76,6 +76,18 @@ {% endif %} + +
    {{ 'Phonenumber2'|trans }}
    +
    + {% if thirdParty.telephone2 == null %} + {{ 'thirdparty.No_phonenumber'|trans }} + {% else %} + + {{ thirdParty.telephone2|chill_format_phonenumber }} + + {% endif %} +
    +
    {{ 'email'|trans }}
    {% if thirdParty.email == null %} diff --git a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php index 26f175705..929b97c1e 100644 --- a/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php +++ b/src/Bundle/ChillThirdPartyBundle/Serializer/Normalizer/ThirdPartyNormalizer.php @@ -55,6 +55,7 @@ class ThirdPartyNormalizer implements NormalizerAwareInterface, NormalizerInterf 'profession' => $this->normalizer->normalize($thirdParty->getProfession(), $format, $context), 'address' => $this->normalizer->normalize($thirdParty->getAddress(), $format, ['address_rendering' => 'short']), 'telephone' => $this->normalizer->normalize($thirdParty->getTelephone(), $format, $context), + 'telephone2' => $this->normalizer->normalize($thirdParty->getTelephone2(), $format, $context), 'email' => $thirdParty->getEmail(), 'isChild' => $thirdParty->isChild(), 'parent' => $this->normalizer->normalize($thirdParty->getParent(), $format, $context), diff --git a/src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml b/src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml index 5477075e0..dfb9155f7 100644 --- a/src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml @@ -28,6 +28,8 @@ components: type: string telephone: type: string + telephone2: + type: string address: $ref: "#/components/schemas/Address" Address: diff --git a/src/Bundle/ChillThirdPartyBundle/migrations/Version20250325085950.php b/src/Bundle/ChillThirdPartyBundle/migrations/Version20250325085950.php new file mode 100644 index 000000000..a2dc8b5ce --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/migrations/Version20250325085950.php @@ -0,0 +1,34 @@ +addSql('ALTER TABLE chill_3party.third_party ADD telephone2 VARCHAR(35) DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN chill_3party.third_party.telephone2 IS \'(DC2Type:phone_number)\''); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_3party.third_party DROP telephone2'); + } +} diff --git a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml index 52bae6fee..c5a5a110f 100644 --- a/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillThirdPartyBundle/translations/messages.fr.yml @@ -4,6 +4,7 @@ third parties: tiers firstname: Prénom name: Nom telephone: Téléphone +telephone2: Autre numéro de téléphone adress: Adresse email: Courriel comment: Commentaire @@ -39,7 +40,7 @@ thirdparty.A contact: Une personne physique thirdparty.contact: Personne physique thirdparty.Contact of: Contact de thirdparty.a_company_explanation: >- - Les personnes morales peuvent compter un ou plusieurs contacts, interne à l'instution. Il est également possible de + Les personnes morales peuvent compter un ou plusieurs contacts, interne à l'institution. Il est également possible de leur associer un acronyme, et le nom d'un service. thirdparty.a_contact_explanation: >- Les personnes physiques ne disposent pas d'acronyme, de service, ou de contacts sous-jacents. Il est possible de leur @@ -149,6 +150,8 @@ Contact id: Identifiant du contact Contact name: Nom du contact Contact firstname: Prénom du contact Contact phone: Téléphone du contact +Contact phone2: Autre téléphone du contact +Telephone2: Autre téléphone Contact email: Courrier électronique du contact Contact address: Adresse du contact Contact profession: Profession du contact From 224e0bae433c2bb16a882dc519caff3dc1819955 Mon Sep 17 00:00:00 2001 From: LenaertsJ Date: Tue, 15 Apr 2025 13:09:54 +0000 Subject: [PATCH 07/83] Add button unique signature zone --- .../unreleased/Feature-20250415-150324.yaml | 6 + .../public/vuejs/DocumentSignature/App.vue | 176 ++++++++++++------ .../translations/messages.fr.yml | 27 +++ 3 files changed, 152 insertions(+), 57 deletions(-) create mode 100644 .changes/unreleased/Feature-20250415-150324.yaml diff --git a/.changes/unreleased/Feature-20250415-150324.yaml b/.changes/unreleased/Feature-20250415-150324.yaml new file mode 100644 index 000000000..1daf7d5e4 --- /dev/null +++ b/.changes/unreleased/Feature-20250415-150324.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: 'Signature: add a button to go directly to the signature zone, even if there is only one' +time: 2025-04-15T15:03:24.436112828+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/App.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/App.vue index 6392a2b49..3f1cdbe06 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/App.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/App.vue @@ -2,26 +2,28 @@ @@ -82,28 +84,39 @@ @change="toggleMultiPage" />
    +
    +
    + |
    @@ -112,9 +125,9 @@ :hidden="!userSignatureZone" @click="undoSign" v-if="signature.zones.length > 1" - :title="$t('choose_another_signature')" + :title="trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE)" > - {{ $t("another_zone") }} + {{ trans(SIGNATURES_ANOTHER_ZONE) }}
    - | -
    | +
    +
    + +
    +
    + + | +
    @@ -237,7 +272,7 @@ @click="undoSign" v-if="signature.zones.length > 1" > - {{ $t("choose_another_signature") }} + {{ trans(SIGNATURES_CHOOSE_ANOTHER_SIGNATURE) }}
    +
  • +
    + + +
    +  {{ + $t("warning_previous_persons") + }} +
  • @@ -497,6 +520,8 @@ const i18n = { notification_notify_referrer: "Notifier le référent", notification_notify_any: "Notifier d'autres utilisateurs", notification_send: "Envoyer une notification", + warning_previous_persons: + "Cet usager n'est désormais plus concerné par le parcours, bien qu'il ait été associé à l'action par le passé.", }, }, }; @@ -583,6 +608,7 @@ export default { "hasHandlingThirdParty", "hasThirdParties", "hasReferrers", + "getPreviousPersons", ]), classicEditor: () => ClassicEditor, editorConfig: () => classicEditorConfig, diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js index e2105f314..a820d0816 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js @@ -87,6 +87,11 @@ const store = createStore({ return []; }, + getPreviousPersons(state) { + return state.personsPicked.filter( + (p) => !state.personsReachables.map((pr) => pr.id).includes(p.id), + ); + }, buildPayload(state) { return { type: "accompanying_period_work", @@ -607,8 +612,7 @@ const store = createStore({ submit({ getters, state, commit }, callback) { let payload = getters.buildPayload, params = new URLSearchParams({ entity_version: state.version }), - url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json?${params}`, - errors = []; + url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json?${params}`; commit("setIsPosting", true); // console.log('the social action', payload); From 6d8e2ad8250bf593ef1c35ae21c8a18f30113973 Mon Sep 17 00:00:00 2001 From: LenaertsJ Date: Tue, 13 May 2025 08:59:01 +0000 Subject: [PATCH 17/83] =?UTF-8?q?Resolve=20"Module=20t=C3=A2che:=20enlever?= =?UTF-8?q?=20les=20filtres=20par=20d=C3=A9faut"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changes/unreleased/UX-20250423-172624.yaml | 6 ++++++ .../ChillTaskBundle/Controller/SingleTaskController.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changes/unreleased/UX-20250423-172624.yaml diff --git a/.changes/unreleased/UX-20250423-172624.yaml b/.changes/unreleased/UX-20250423-172624.yaml new file mode 100644 index 000000000..2a17e4195 --- /dev/null +++ b/.changes/unreleased/UX-20250423-172624.yaml @@ -0,0 +1,6 @@ +kind: UX +body: Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page +time: 2025-04-23T17:26:24.45777387+02:00 +custom: + Issue: "374" + SchemaChange: No schema change diff --git a/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php b/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php index 9b97809dd..152184570 100644 --- a/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php +++ b/src/Bundle/ChillTaskBundle/Controller/SingleTaskController.php @@ -624,7 +624,7 @@ final class SingleTaskController extends AbstractController ->addCheckbox('status', $statuses, $statuses, $statusTrans); $states = $this->singleTaskStateRepository->findAllExistingStates(); - $checked = array_values(array_filter($states, fn (string $state) => !in_array($state, ['closed', 'canceled', 'validated'], true))); + $checked = array_values(array_filter($states, fn (string $state) => !in_array($state, ['in_progress', 'closed', 'canceled', 'validated'], true))); if ([] !== $states) { $filterBuilder From af36eccfafcbed686f16770d409193e2ecd80df9 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 14 May 2025 13:56:36 +0200 Subject: [PATCH 18/83] Allow more characters for maritalstatus id --- .../Entity/MaritalStatus.php | 2 +- .../migrations/Version20250514115009.php | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/Bundle/ChillPersonBundle/migrations/Version20250514115009.php diff --git a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php index b64416113..5f88906bd 100644 --- a/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php +++ b/src/Bundle/ChillPersonBundle/Entity/MaritalStatus.php @@ -22,7 +22,7 @@ use Doctrine\ORM\Mapping as ORM; class MaritalStatus { #[ORM\Id] - #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 7)] + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 15)] private ?string $id; #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON)] diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20250514115009.php b/src/Bundle/ChillPersonBundle/migrations/Version20250514115009.php new file mode 100644 index 000000000..364456281 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/migrations/Version20250514115009.php @@ -0,0 +1,36 @@ +addSql(<<<'SQL' + ALTER TABLE chill_person_marital_status ALTER id TYPE VARCHAR(15) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_person_person ALTER maritalstatus_id TYPE VARCHAR(15) + SQL); + } + + public function down(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE chill_person_person ALTER maritalStatus_id TYPE VARCHAR(7) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_person_marital_status ALTER id TYPE VARCHAR(7) + SQL); + } +} From f6c98aa0d52641db3ac1df2913ee95e817588002 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 14 May 2025 14:52:51 +0200 Subject: [PATCH 19/83] Add missing translation for user_group.no_user_groups --- .changes/unreleased/Fixed-20250514-145339.yaml | 6 ++++++ src/Bundle/ChillMainBundle/translations/messages.fr.yml | 1 + 2 files changed, 7 insertions(+) create mode 100644 .changes/unreleased/Fixed-20250514-145339.yaml diff --git a/.changes/unreleased/Fixed-20250514-145339.yaml b/.changes/unreleased/Fixed-20250514-145339.yaml new file mode 100644 index 000000000..862ea764b --- /dev/null +++ b/.changes/unreleased/Fixed-20250514-145339.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Add missing translation for user_group.no_user_groups +time: 2025-05-14T14:53:39.53927329+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 6c52d962f..f70f6d03a 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -59,6 +59,7 @@ user_group: inactive: Inactif with_users: Membres no_users: Aucun utilisateur associé + no_user_groups: Aucune groupe d'utilisateurs no_admin_users: Aucun administrateur Label: Nom du groupe BackgroundColor: Couleur de fond du badge From 4c5dee5f0af888a8a1c981fae26c95a43aba6bbc Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 14 May 2025 17:31:48 +0200 Subject: [PATCH 20/83] Fix pipeline --- .../ChillPersonBundle/migrations/Version20250514115009.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20250514115009.php b/src/Bundle/ChillPersonBundle/migrations/Version20250514115009.php index 364456281..719366d92 100644 --- a/src/Bundle/ChillPersonBundle/migrations/Version20250514115009.php +++ b/src/Bundle/ChillPersonBundle/migrations/Version20250514115009.php @@ -2,6 +2,13 @@ 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; From b0959f8cc5af02c9c197a5807794b9123a4003f3 Mon Sep 17 00:00:00 2001 From: LenaertsJ Date: Fri, 16 May 2025 08:33:56 +0000 Subject: [PATCH 21/83] =?UTF-8?q?Resolve=20"Code=20mort:=20module=20wopi-l?= =?UTF-8?q?ink=20semble=20inutilis=C3=A9"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changes/unreleased/DX-20250430-144550.yaml | 6 + package.json | 1 + .../public/module/wopi-link/index.js | 45 ---- .../public/vuejs/_components/OpenWopiLink.vue | 214 ------------------ .../Resources/views/Workflow/index.html.twig | 2 - .../ChillMainBundle/chill.webpack.config.js | 4 - 6 files changed, 7 insertions(+), 265 deletions(-) create mode 100644 .changes/unreleased/DX-20250430-144550.yaml delete mode 100644 src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js delete mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OpenWopiLink.vue diff --git a/.changes/unreleased/DX-20250430-144550.yaml b/.changes/unreleased/DX-20250430-144550.yaml new file mode 100644 index 000000000..7d9f0c32b --- /dev/null +++ b/.changes/unreleased/DX-20250430-144550.yaml @@ -0,0 +1,6 @@ +kind: DX +body: Remove dead code for wopi-link module +time: 2025-04-30T14:45:50.406111606+02:00 +custom: + Issue: "352" + SchemaChange: No schema change diff --git a/package.json b/package.json index 9493bd5dc..15697def7 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@hotwired/stimulus": "^3.0.0", "@luminateone/eslint-baseline": "^1.0.9", "@symfony/stimulus-bridge": "^3.2.0", + "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/webpack-encore": "^4.1.0", "@tsconfig/node20": "^20.1.4", "@types/dompurify": "^3.0.5", diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js deleted file mode 100644 index 856c12ef4..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js +++ /dev/null @@ -1,45 +0,0 @@ -import { createApp } from "vue"; -import OpenWopiLink from "ChillMainAssets/vuejs/_components/OpenWopiLink"; -import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n"; - -const i18n = _createI18n({}); - -//TODO move to chillDocStore or ChillWopi - -/* - -tags to load module: - - - -*/ - -window.addEventListener("DOMContentLoaded", function (e) { - document - .querySelectorAll('span[data-module="wopi-link"]') - .forEach(function (el) { - createApp({ - template: - '', - components: { - OpenWopiLink, - }, - data() { - return { - wopiUrl: el.dataset.wopiUrl, - type: el.dataset.docType, - options: - el.dataset.options !== "null" - ? JSON.parse(el.dataset.options) - : {}, - }; - }, - }) - .use(i18n) - .mount(el); - }); -}); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OpenWopiLink.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OpenWopiLink.vue deleted file mode 100644 index f323107fa..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/OpenWopiLink.vue +++ /dev/null @@ -1,214 +0,0 @@ - - - - - diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig index 989f2fa72..27af88296 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig @@ -9,7 +9,6 @@ {{ encore_entry_script_tags('mod_pickentity_type') }} {{ encore_entry_script_tags('mod_entity_workflow_subscribe') }} {{ encore_entry_script_tags('page_workflow_show') }} - {{ encore_entry_script_tags('mod_wopi_link') }} {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {{ encore_entry_script_tags('mod_workflow_attachment') }} {% endblock %} @@ -19,7 +18,6 @@ {{ encore_entry_link_tags('mod_pickentity_type') }} {{ encore_entry_link_tags('mod_entity_workflow_subscribe') }} {{ encore_entry_link_tags('page_workflow_show') }} - {{ encore_entry_link_tags('mod_wopi_link') }} {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {{ encore_entry_link_tags('mod_workflow_attachment') }} {% endblock %} diff --git a/src/Bundle/ChillMainBundle/chill.webpack.config.js b/src/Bundle/ChillMainBundle/chill.webpack.config.js index 88b537932..6cc890797 100644 --- a/src/Bundle/ChillMainBundle/chill.webpack.config.js +++ b/src/Bundle/ChillMainBundle/chill.webpack.config.js @@ -86,10 +86,6 @@ module.exports = function (encore, entries) { "mod_entity_workflow_pick", __dirname + "/Resources/public/module/entity-workflow-pick/index.js", ); - encore.addEntry( - "mod_wopi_link", - __dirname + "/Resources/public/module/wopi-link/index.js", - ); encore.addEntry( "mod_pick_postal_code", __dirname + "/Resources/public/module/pick-postal-code/index.js", From 2faf194b15196b452b6bd88bccf9c0c6806cd6aa Mon Sep 17 00:00:00 2001 From: juminet Date: Fri, 16 May 2025 14:40:19 +0000 Subject: [PATCH 22/83] #365 correct works counter in acc course summary --- .../Controller/AccompanyingCourseController.php | 3 +-- .../ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php index 0cb3c1473..79b0b8046 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseController.php @@ -207,7 +207,6 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle $works = $this->workRepository->findByAccompanyingPeriod( $accompanyingCourse, ['startDate' => 'DESC', 'endDate' => 'DESC'], - 3 ); $counters = [ @@ -220,7 +219,7 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle 'accompanyingCourse' => $accompanyingCourse, 'withoutHousehold' => $withoutHousehold, 'participationsByHousehold' => $accompanyingCourse->actualParticipationsByHousehold(), - 'works' => $works, + 'works' => \array_slice($works, 0, 3), 'activities' => \array_slice($activities, 0, 3), 'counters' => $counters, ]); diff --git a/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php b/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php index 8c93f461d..7eb4b63bc 100644 --- a/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php +++ b/src/Bundle/ChillPersonBundle/Menu/AccompanyingCourseMenuBuilder.php @@ -87,7 +87,7 @@ class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface ], ]) ->setExtras([ 'order' => 40, - 'counter' => count($period->getOpenWorks()) > 0 ? count($period->getOpenWorks()) : null, + 'counter' => count($period->getWorks()) > 0 ? count($period->getWorks()) : null, ]); } From b6d454691ab41d638e0918a39596f69076d2ffb3 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 20 May 2025 09:38:39 +0200 Subject: [PATCH 23/83] Git add desactivation date for social action csv export --- src/Bundle/ChillMainBundle/translations/messages.fr.yml | 1 + .../Service/SocialWork/SocialActionCSVExportService.php | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index f70f6d03a..4313da429 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -740,6 +740,7 @@ export: id: Identifiant de l'action social_issue_id: Identifiant de la problématique sociale social_issue: Problématique sociale + desactivation_date: Date de désactivation social_issue_ordering: Ordre de la problématique sociale action_label: Action d'accompagnement action_ordering: Ordre diff --git a/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialActionCSVExportService.php b/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialActionCSVExportService.php index adff4a588..d19b01382 100644 --- a/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialActionCSVExportService.php +++ b/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialActionCSVExportService.php @@ -30,9 +30,6 @@ final readonly class SocialActionCSVExportService private TranslatorInterface $translator, ) {} - /** - * @param list $actions - */ public function generateCsv(array $actions): Writer { // CSV headers @@ -84,7 +81,8 @@ final readonly class SocialActionCSVExportService 'action_id' => $action->getId(), 'social_issue_id' => $action->getIssue()?->getId(), 'problematique_label' => null !== $action->getIssue() ? $this->socialIssueRender->renderString($action->getIssue(), []) : null, - 'social_issue_ordering' => null !== $action->getIssue() ? $action->getIssue()->getOrdering() : null, + 'desactivation_date' => $action->getDesactivationDate()?->format('Y-m-d'), + 'social_issue_ordering' => $action->getIssue()?->getOrdering(), 'action_label' => $this->socialActionRender->renderString($action, []), 'action_ordering' => $action->getOrdering(), 'goal_label' => null !== $goal ? $this->stringHelper->localize($goal->getTitle()) : null, From 6a364705f22326388daa89a40494b9f64566e859 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 20 May 2025 09:55:58 +0200 Subject: [PATCH 24/83] Git add desactivation date for social issue csv export --- .changes/unreleased/Feature-20250520-095628.yaml | 6 ++++++ .../Service/SocialWork/SocialIssueCSVExportService.php | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 .changes/unreleased/Feature-20250520-095628.yaml diff --git a/.changes/unreleased/Feature-20250520-095628.yaml b/.changes/unreleased/Feature-20250520-095628.yaml new file mode 100644 index 000000000..4b1fba30c --- /dev/null +++ b/.changes/unreleased/Feature-20250520-095628.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Add desactivation date for social action and issue csv export +time: 2025-05-20T09:56:28.108941934+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialIssueCSVExportService.php b/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialIssueCSVExportService.php index be4cc0706..ce403d192 100644 --- a/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialIssueCSVExportService.php +++ b/src/Bundle/ChillPersonBundle/Service/SocialWork/SocialIssueCSVExportService.php @@ -44,6 +44,7 @@ readonly class SocialIssueCSVExportService 'Id', 'Label', 'Social issue', + 'export.social_action_list.desactivation_date', 'socialIssue.isParent?', 'socialIssue.Parent id', ] @@ -66,6 +67,7 @@ readonly class SocialIssueCSVExportService 'id' => $issue->getId(), 'label' => $this->stringHelper->localize($issue->getTitle()), 'title' => $this->socialIssueRender->renderString($issue, []), + 'export.social_action_list.desactivation_date' => $issue->getDesactivationDate()?->format('Y-m-d'), 'isParent' => $issue->hasChildren() ? 'X' : '', 'parent_id' => null !== $issue->getParent() ? $issue->getParent()->getId() : '', ]; From dc44c466672917ff07bebe7af32279aa9ad83cdc Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 21 May 2025 09:29:25 +0200 Subject: [PATCH 25/83] Fix SocialActionCSVExporterTest.php --- .../Exporters/SocialActionCSVExporterTest.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Tests/Exporters/SocialActionCSVExporterTest.php b/src/Bundle/ChillPersonBundle/Tests/Exporters/SocialActionCSVExporterTest.php index d450c9690..d6f6cda64 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Exporters/SocialActionCSVExporterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Exporters/SocialActionCSVExporterTest.php @@ -52,6 +52,7 @@ class SocialActionCsvExporterTest extends TestCase // Création d'une instance réelle de SocialAction sans objectifs ni résultats $actionWithoutGoalsOrResults = new SocialAction(); $actionWithoutGoalsOrResults->setIssue($socialIssue); + $actionWithoutGoalsOrResults->setDesactivationDate(new \DateTime('2025-05-21')); $actionWithoutGoalsOrResults->setTitle(['fr' => 'Action without goals or results']); // Création d'une instance réelle de SocialAction avec des objectifs et des résultats @@ -61,6 +62,7 @@ class SocialActionCsvExporterTest extends TestCase $actionWithGoalsAndResults = new SocialAction(); $actionWithGoalsAndResults->setIssue($socialIssue); + $actionWithGoalsAndResults->setDesactivationDate(new \DateTime('2025-05-21')); $actionWithGoalsAndResults->setTitle(['fr' => 'Action with goals and results']); $actionWithGoalsAndResults->addGoal($goalWithResult); @@ -68,6 +70,7 @@ class SocialActionCsvExporterTest extends TestCase $goalWithoutResult = new Goal(); $actionWithGoalsNoResults = new SocialAction(); $actionWithGoalsNoResults->setIssue($socialIssue); + $actionWithGoalsNoResults->setDesactivationDate(new \DateTime('2025-05-21')); $actionWithGoalsNoResults->setTitle(['fr' => 'Action with goals and no results']); $actionWithGoalsNoResults->addGoal($goalWithoutResult); @@ -76,6 +79,7 @@ class SocialActionCsvExporterTest extends TestCase $resultWithNoAction->setTitle(['fr' => 'Result without objectives']); $actionWithResultsNoGoals = new SocialAction(); $actionWithResultsNoGoals->setIssue($socialIssue); + $actionWithResultsNoGoals->setDesactivationDate(new \DateTime('2025-05-21')); $actionWithResultsNoGoals->setTitle(['fr' => 'Action with results and no goals']); $actionWithResultsNoGoals->addResult($resultWithNoAction); @@ -91,11 +95,11 @@ class SocialActionCsvExporterTest extends TestCase $this->assertStringContainsString('Action with results and no goals', $content); self::assertEquals(<<<'CSV' - export.social_action_list.action_id,export.social_action_list.social_issue_id,export.social_action_list.problematique_label,export.social_action_list.social_issue_ordering,export.social_action_list.action_label,export.social_action_list.action_ordering,export.social_action_list.goal_label,export.social_action_list.goal_id,export.social_action_list.goal_result_label,export.social_action_list.goal_result_id,export.social_action_list.result_without_goal_label,export.social_action_list.result_without_goal_id,export.social_action_list.evaluation_title,export.social_action_list.evaluation_id,export.social_action_list.evaluation_url,export.social_action_list.evaluation_delay_month,export.social_action_list.evaluation_delay_week,export.social_action_list.evaluation_delay_day - ,,"Issue Title",0,"Action with goals and results",0,"not found",,"not found",,,,,,,,, - ,,"Issue Title",0,"Action without goals or results",0,,,,,,,,,,,, - ,,"Issue Title",0,"Action with goals and no results",0,"not found",,,,,,,,,,, - ,,"Issue Title",0,"Action with results and no goals",0,,,,,"Result without objectives",,,,,,, + export.social_action_list.action_id,export.social_action_list.social_issue_id,export.social_action_list.problematique_label,export.social_action_list.desactivation_date,export.social_action_list.social_issue_ordering,export.social_action_list.action_label,export.social_action_list.action_ordering,export.social_action_list.goal_label,export.social_action_list.goal_id,export.social_action_list.goal_result_label,export.social_action_list.goal_result_id,export.social_action_list.result_without_goal_label,export.social_action_list.result_without_goal_id,export.social_action_list.evaluation_title,export.social_action_list.evaluation_id,export.social_action_list.evaluation_url,export.social_action_list.evaluation_delay_month,export.social_action_list.evaluation_delay_week,export.social_action_list.evaluation_delay_day + ,,"Issue Title",2025-05-21,0,"Action with goals and results",0,"not found",,"not found",,,,,,,,, + ,,"Issue Title",2025-05-21,0,"Action without goals or results",0,,,,,,,,,,,, + ,,"Issue Title",2025-05-21,0,"Action with goals and no results",0,"not found",,,,,,,,,,, + ,,"Issue Title",2025-05-21,0,"Action with results and no goals",0,,,,,"Result without objectives",,,,,,, CSV, $content); } From 8b2af35e9730f29c43f8c08f4acdeb4b403728e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 21 May 2025 17:57:35 +0200 Subject: [PATCH 26/83] Fix typo --- .../notification_location_user_on_period_has_moved.fr.txt.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig index df932067c..85bce211f 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/notification_location_user_on_period_has_moved.fr.txt.twig @@ -8,7 +8,7 @@ L'usager {{ oldPersonLocation|chill_entity_render_string }} a déménagé. Son adresse était utilisée pour localiser le parcours n°{{ period.id }}, dont vous êtes le référent. -En conséquence de ce déménage, le parcours est toujours localisé à cette adresse, mais à l'aide d'une +En conséquence de ce déménagement, le parcours est toujours localisé à cette adresse, mais à l'aide d'une adresse temporaire. Si vous continuez à suivre le parcours, vous pouvez le localiser à nouveau auprès de l'adresse de From 44a8ddeba458e3d6149263e8b72f4b53466a640f Mon Sep 17 00:00:00 2001 From: LenaertsJ Date: Wed, 21 May 2025 16:13:43 +0000 Subject: [PATCH 27/83] Resolve "Reorganise page 'Mes parcours'" --- .../UserAccompanyingPeriodController.php | 112 +++++++++++++++--- .../AccompanyingPeriodACLAwareRepository.php | 86 ++++++++++++++ ...nyingPeriodACLAwareRepositoryInterface.php | 16 +++ .../AccompanyingPeriodRepository.php | 22 ++-- .../user_periods_list.html.twig | 32 ++++- .../translations/messages.fr.yml | 8 ++ 6 files changed, 244 insertions(+), 32 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php b/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php index 3a2b1be6e..30608072a 100644 --- a/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php +++ b/src/Bundle/ChillPersonBundle/Controller/UserAccompanyingPeriodController.php @@ -11,46 +11,97 @@ declare(strict_types=1); namespace Chill\PersonBundle\Controller; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Pagination\PaginatorFactory; +use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository; use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; +use Doctrine\ORM\NonUniqueResultException; +use Doctrine\ORM\NoResultException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Contracts\Translation\TranslatorInterface; class UserAccompanyingPeriodController extends AbstractController { - public function __construct(private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, private readonly PaginatorFactory $paginatorFactory) {} + public function __construct( + private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository, + private readonly PaginatorFactory $paginatorFactory, + private readonly AccompanyingPeriodACLAwareRepository $accompanyingPeriodACLAwareRepository, + private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory, + private readonly TranslatorInterface $translator, + ) {} + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ #[Route(path: '/{_locale}/person/accompanying-periods/my', name: 'chill_person_accompanying_period_user')] public function listAction(Request $request): Response { - $active = $request->query->getBoolean('active', true); - $steps = match ($active) { - true => [ + $filter = (int) $request->query->get('filter', 2); + $user = $this->getUser(); + + if (!$user instanceof User) { + throw new \LogicException('Expected an instance of Chill\MainBundle\Entity\User.'); + } + + $activeTab = match ($filter) { + 2 => 'referrer', + 4 => 'referrer_to_works', + 6 => 'both', + 8 => 'intervening', + default => 'referrer', + }; + + $statusAndDateFilter = $this->buildStatusAndDateFilter($filter); + + $status = $statusAndDateFilter->getCheckboxData('statusFilter'); + $from = null; + $to = null; + + if ('intervening' === $activeTab) { + $interventionBetweenDates = $statusAndDateFilter->getDateRangeData('interventionBetweenDates'); + $from = $interventionBetweenDates['from']; + $to = $interventionBetweenDates['to']; + } + + $steps = []; + + if (in_array('is_open', $status, true)) { + $steps[] = [ + ...$steps, AccompanyingPeriod::STEP_CONFIRMED, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, - ], - false => [ - AccompanyingPeriod::STEP_CLOSED, - ], - }; + ]; + } - $total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => $steps]); - $pagination = $this->paginatorFactory->create($total); - $accompanyingPeriods = $this->accompanyingPeriodRepository->findBy( - ['user' => $this->getUser(), 'step' => $steps], - ['openingDate' => 'DESC'], - $pagination->getItemsPerPage(), - $pagination->getCurrentPageFirstItemNumber() + if (in_array('is_closed', $status, true)) { + $steps[] = AccompanyingPeriod::STEP_CLOSED; + } + + $total = $this->accompanyingPeriodACLAwareRepository->countByUserAssociation($user, $steps, $from, $to, $filter); + $paginator = $this->paginatorFactory->create($total); + $accompanyingPeriods = $this->accompanyingPeriodACLAwareRepository->findByUserAssociation( + $user, + $steps, + $from, + $to, + $filter, + $paginator->getCurrentPageFirstItemNumber(), + $paginator->getItemsPerPage(), ); return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [ 'accompanyingPeriods' => $accompanyingPeriods, - 'pagination' => $pagination, - 'active' => $active, + 'pagination' => $paginator, + 'activeTab' => $activeTab, + 'filter' => $filter, + 'statusFilter' => $statusAndDateFilter, ]); } @@ -71,4 +122,29 @@ class UserAccompanyingPeriodController extends AbstractController 'pagination' => $pagination, ]); } + + public function buildStatusAndDateFilter(int $filter) + { + $filterBuilder = $this->filterOrderHelperFactory + ->create(self::class) + ->addCheckbox( + 'statusFilter', + ['is_open', 'is_closed'], + ['is_open'], + array_map( + static fn (string $s) => 'my_parcours_filters.'.$s, + ['is_open', 'is_closed'] + ) + ); + + if (8 === $filter) { + $filterBuilder->addDateRange( + 'interventionBetweenDates', + $this->translator->trans('Since'), + new \DateTimeImmutable('-6 months'), + ); + } + + return $filterBuilder->build(); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php index ebd72e0b4..4ba50e912 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php @@ -365,4 +365,90 @@ final readonly class AccompanyingPeriodACLAwareRepository implements Accompanyin return $qb; } + + public function findByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter, ?int $start = 0, ?int $limit = 1000): array + { + $qb = $this->buildQueryByUserAssociation($user, $steps, $from, $to, $filter); + + $qb->addOrderBy('acp.openingDate', 'DESC'); + + if (null !== $start) { + $qb->setFirstResult($start); + } + if (null !== $limit) { + $qb->setMaxResults($limit); + } + + return $qb->getQuery()->getResult(); + } + + public function countByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter): int + { + $qb = $this->buildQueryByUserAssociation($user, $steps, $from, $to, $filter); + + $qb->select('COUNT(DISTINCT acp.id)'); + + return $qb->getQuery()->getSingleScalarResult(); + } + + public function buildQueryByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter): QueryBuilder + { + $qb = $this->accompanyingPeriodRepository->createQueryBuilder('acp'); + + // Create an andX expression to hold the user association conditions + $whereUserAssociation = $qb->expr()->andX(); + + if (($filter & self::USER_IS_REFERRER) > 0) { + $whereUserAssociation->add($qb->expr()->eq('acp.user', ':user')); + } + + if (($filter & self::USER_IS_WORK_REFERRER) > 0) { + $whereUserAssociation->add( + $qb->expr()->exists( + 'SELECT 1 + FROM '.AccompanyingPeriod\AccompanyingPeriodWork::class.' subw + JOIN subw.referrersHistory subw_ref_history + WHERE subw.id = acpw.id + AND subw_ref_history.user = :user + AND subw_ref_history.endDate IS NULL' + ) + ); + + $qb->innerJoin('acp.works', 'acpw'); + } + + if (($filter & self::USER_IS_INTERVENING) > 0) { + + $expr = 'SELECT 1 + FROM '.AccompanyingPeriod\AccompanyingPeriodInfo::class.' info + WHERE info.accompanyingPeriod = acp + AND info.user = :user'; + + if (null !== $from) { + $expr .= ' AND info.infoDate >= :from'; + $qb->setParameter('from', $from); + } + + if (null !== $to) { + $expr .= ' AND info.infoDate <= :to'; + $qb->setParameter('to', $to); + } + + $whereUserAssociation->add( + $qb->expr()->exists($expr) + ); + } + + // Apply the compound condition to the query builder + $qb->andWhere($whereUserAssociation); + + // Apply the steps condition + $qb->andWhere($qb->expr()->in('acp.step', ':steps')); + + // Set the remaining parameters + $qb->setParameter('user', $user) + ->setParameter('steps', $steps); + + return $qb; + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php index 560917ac8..bb4887a85 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepositoryInterface.php @@ -21,6 +21,22 @@ use Chill\PersonBundle\Entity\Person; interface AccompanyingPeriodACLAwareRepositoryInterface { + public const USER_IS_REFERRER = 0b0010; // 2 in decimal + public const USER_IS_WORK_REFERRER = 0b0100; // 4 in decimal + public const USER_IS_INTERVENING = 0b1000; // 8 in decimal + + /** + * Finds associations for a given user within a specific date range and step filters. + * + * @param \DateTimeImmutable|null $from the start date for filtering when intervention in accompanying period took place + * @param \DateTimeImmutable|null $to the end date for filtering when intervention in accompanying period took place + * + * @return array the list of user associations matching the given criteria + */ + public function findByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter, ?int $start = 0, ?int $limit = 1000): array; + + public function countByUserAssociation(User $user, array $steps, ?\DateTimeImmutable $from, ?\DateTimeImmutable $to, int $filter): int; + /** * @param array|UserJob[] $jobs * @param array|Scope[] $services diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php index 73f1beecb..a3e49c657 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php @@ -62,6 +62,18 @@ final readonly class AccompanyingPeriodRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } + public function findOneBy(array $criteria): ?AccompanyingPeriod + { + return $this->findOneBy($criteria); + } + + public function getClassName() + { + return AccompanyingPeriod::class; + } + + // CUSTOM FIND BY METHODS + /** * @return array|AccompanyingPeriod[] */ @@ -87,16 +99,6 @@ final readonly class AccompanyingPeriodRepository implements ObjectRepository return $qb; } - public function findOneBy(array $criteria): ?AccompanyingPeriod - { - return $this->findOneBy($criteria); - } - - public function getClassName() - { - return AccompanyingPeriod::class; - } - private function buildQueryByRecentUserHistory(User $user, \DateTimeImmutable $since): QueryBuilder { $qb = $this->repository->createQueryBuilder('a'); diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig index 38e055378..078a445bd 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriod/user_periods_list.html.twig @@ -19,14 +19,38 @@ -

    {{ 'Number of periods'|trans }}: {{ pagination.totalItems }}

    + {{ statusFilter|chill_render_filter_order_helper }} + +

    {{ 'Number of periods'|trans }}: {{ pagination.totalItems }}

    {% for period in accompanyingPeriods %} diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index c2aee2cf0..9bce912a0 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -1497,3 +1497,11 @@ entity_display_title: Evaluation (n°%eval%): "Évaluation (n°%eval%)" Work (n°%w%): "Action d'accompagnement (n°%w%)" Accompanying Course (n°%w%): "Parcours d'accompagnement (n°%w%)" + +my_parcours_filters: + referrer_parcours_and_acpw: Agent traitant ou réferent + referrer_acpw: Agent traitant d'une action + referrer_parcours: Réferent + parcours_intervening: Intervenant + is_open: Parcours ouverts + is_closed: Parcours clôturés From 5c9396077db5eac3fe6e2679c70aa87618a4957f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 23 May 2025 11:56:34 +0200 Subject: [PATCH 28/83] remove "faked" entity and simplify template string in Activity index.js Replaced multi-line template string with a cleaner single-line backtick template. This improves readability and maintains consistent formatting in the code. --- .../Resources/public/vuejs/Activity/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/index.js b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/index.js index 2c0a903e5..44f0192df 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/index.js +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/index.js @@ -41,9 +41,7 @@ const i18nGendoc = _createI18n({}); document.querySelectorAll("div[data-docgen-template-picker]").forEach((el) => { fetchTemplates(el.dataset.entityClass).then((templates) => { const picker = { - template: - '', + template: ``, components: { PickTemplate, }, @@ -54,7 +52,7 @@ document.querySelectorAll("div[data-docgen-template-picker]").forEach((el) => { }; }, methods: { - generateDoc({ event, link, template }) { + generateDoc({ link, template }) { console.log("generateDoc"); console.log("link", link); console.log("template", template); From 976f293f28bdc7d872c33e1cb4c3485c4114224f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 23 May 2025 13:33:47 +0200 Subject: [PATCH 29/83] Add Emoji and Fullscreen features to CKEditor configuration Integrated Emoji, Mention (required for Emoji), and Fullscreen plugins into the CKEditor setup. Updated the toolbar to include respective buttons, enhancing functionality and user interaction. --- .changes/unreleased/Feature-20250523-133341.yaml | 6 ++++++ .../public/module/ckeditor5/editor_config.ts | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 .changes/unreleased/Feature-20250523-133341.yaml diff --git a/.changes/unreleased/Feature-20250523-133341.yaml b/.changes/unreleased/Feature-20250523-133341.yaml new file mode 100644 index 000000000..1a5513a0d --- /dev/null +++ b/.changes/unreleased/Feature-20250523-133341.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Add Emoji and Fullscreen feature to ckeditor configuration +time: 2025-05-23T13:33:41.645095128+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts index cddb5707f..a396da295 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts @@ -8,6 +8,9 @@ import { Heading, Link, List, + Emoji, + Mention, + Fullscreen, } from "ckeditor5"; import coreTranslations from "ckeditor5/translations/fr.js"; @@ -26,6 +29,11 @@ export default { Link, List, Paragraph, + // both Emoji and Mention are required for Emoji feature + Emoji, + Mention, + // to enable fullscreen + Fullscreen, ], toolbar: { items: [ @@ -37,8 +45,13 @@ export default { "bulletedList", "numberedList", "blockQuote", + "|", + "emoji", + "|", "undo", "redo", + "|", + "fullscreen", ], }, translations: [coreTranslations], From 19dd4667f2cb1af98f5e105facb3a503b98eab66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 23 May 2025 13:34:50 +0200 Subject: [PATCH 30/83] Integrate Vue-based editor with rich and simple mode toggle Replaced CKEditor initialization with a Vue-based editor component. Introduced a toggle to switch between rich and simple editing modes, persisting the state in local storage. Updated CKEditor dependency to version 45.1.0. --- .../unreleased/Feature-20250523-133434.yaml | 6 + package.json | 2 +- .../public/module/ckeditor5/index.ts | 34 ++++- .../CommentEditor/CommentEditor.vue | 121 ++++++++++++++++++ .../translations/messages.fr.yml | 4 + 5 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 .changes/unreleased/Feature-20250523-133434.yaml create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/CommentEditor/CommentEditor.vue diff --git a/.changes/unreleased/Feature-20250523-133434.yaml b/.changes/unreleased/Feature-20250523-133434.yaml new file mode 100644 index 000000000..31dced03a --- /dev/null +++ b/.changes/unreleased/Feature-20250523-133434.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Create editor which allow us to toggle between rich and simple text editor +time: 2025-05-23T13:34:34.56795603+02:00 +custom: + Issue: "321" + SchemaChange: No schema change diff --git a/package.json b/package.json index 15697def7..f8489c27d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "bindings": "^1.5.0", "bootstrap": "5.2.3", "chokidar": "^3.5.1", - "ckeditor5": "^44.1.0", + "ckeditor5": "^45.1.0", "dompurify": "^3.1.0", "eslint": "^9.14.0", "eslint-config-prettier": "^9.1.0", diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts index 9a14cec01..163ab012f 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts @@ -1,12 +1,32 @@ -import config from "./editor_config"; -import { ClassicEditor } from "ckeditor5"; +import { createApp } from "vue"; +import CommentEditor from "ChillMainAssets/vuejs/_components/CommentEditor/CommentEditor.vue"; const ckeditorFields: NodeListOf = document.querySelectorAll("textarea[ckeditor]"); ckeditorFields.forEach((field: HTMLTextAreaElement): void => { - ClassicEditor.create(field, config).catch((error) => { - console.error(error.stack); - throw error; - }); + const content = field.value; +const div = document.createElement("div"); + + if (field.parentNode !== null) { + field.parentNode.insertBefore(div, field); + } else { + throw "parent is null"; + } + + createApp({ + components: { CommentEditor }, + template: ``, + data() { + return { + content, + }; + }, + methods: { + handleInput() { + field.value = this.content; + }, + }, + }).mount(div); + + field.style.display = "none"; }); -//Fields.push.apply(Fields, document.querySelectorAll('.cf-fields textarea')); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/CommentEditor/CommentEditor.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/CommentEditor/CommentEditor.vue new file mode 100644 index 000000000..7ebd78cf1 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/CommentEditor/CommentEditor.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 4313da429..c883cb91c 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -915,3 +915,7 @@ multiselect: select_group_label: Appuyer sur "Entrée" pour sélectionner ce groupe deselect_group_label: Appuyer sur "Entrée" pour désélectionner ce groupe selected_label: Sélectionné' + +editor: + switch_to_simple: Éditeur simple + switch_to_complex: Éditeur riche From bb41af4ed2914d834b01a6e849ef46d4c2a94c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 23 May 2025 13:51:44 +0200 Subject: [PATCH 31/83] replace ck-editor by new CommentEditor on every vuejs components --- .../public/module/ckeditor5/index.ts | 2 +- .../AccompanyingCourse/components/Comment.vue | 15 +++----------- .../components/Resources/WriteComment.vue | 15 +++----------- .../vuejs/AccompanyingCourseWorkEdit/App.vue | 20 ++++--------------- .../components/FormEvaluation.vue | 16 +++------------ .../components/MemberDetails.vue | 13 +++--------- .../components/PersonComment.vue | 19 +++--------------- 7 files changed, 20 insertions(+), 80 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts index 163ab012f..25ab1f55e 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts @@ -5,7 +5,7 @@ const ckeditorFields: NodeListOf = document.querySelectorAll("textarea[ckeditor]"); ckeditorFields.forEach((field: HTMLTextAreaElement): void => { const content = field.value; -const div = document.createElement("div"); + const div = document.createElement("div"); if (field.parentNode !== null) { field.parentNode.insertBefore(div, field); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue index f063b7b8f..ec8cf1444 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue @@ -13,14 +13,7 @@ $t("comment.label") }} - +
    diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue index 230676800..fee6a89ad 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue @@ -11,7 +11,7 @@ :value="s" />
    @@ -24,6 +24,7 @@ @@ -75,62 +75,62 @@ export default { $chill-accourse-context: #718596; div#accompanying-course { - div.vue-component { - h2 { - margin: 1em 0.7em; - position: relative; - &:before { - position: absolute; - content: "\f142"; //ellipsis-v - font-family: "ForkAwesome"; - color: tint-color($chill-accourse-context, 10%); - left: -22px; - top: 4px; - } - a[id^="section"] { - position: absolute; - top: -2.5em; // reference for stickNav - } - } - padding: 0em 0em; - margin: 1em 0; - border-radius: 5px; - border: 1px dotted tint-color($chill-accourse-context, 10%); - border-left: 1px dotted tint-color($chill-accourse-context, 10%); - border-right: 1px dotted tint-color($chill-accourse-context, 10%); - dd { - margin-left: 1em; - } - & > div { - margin: 1em 3em 0; - - &.flex-table, - &.flex-bloc { - margin: 1em 0 0; - } - &.alert.to-confirm { - margin: 1em 0 0; - padding: 1em 3em; - } - } - - div.flex-table { - div.item-row { - div.item-col:first-child { - flex-basis: 33%; + div.vue-component { + h2 { + margin: 1em 0.7em; + position: relative; + &:before { + position: absolute; + content: "\f142"; //ellipsis-v + font-family: "ForkAwesome"; + color: tint-color($chill-accourse-context, 10%); + left: -22px; + top: 4px; + } + a[id^="section"] { + position: absolute; + top: -2.5em; // reference for stickNav + } } - } - } + padding: 0em 0em; + margin: 1em 0; + border-radius: 5px; + border: 1px dotted tint-color($chill-accourse-context, 10%); + border-left: 1px dotted tint-color($chill-accourse-context, 10%); + border-right: 1px dotted tint-color($chill-accourse-context, 10%); + dd { + margin-left: 1em; + } + & > div { + margin: 1em 3em 0; - &.errors { - //display: flex; - //position: sticky; - //bottom: 0.3em; - //z-index: 1000; - margin: 1em 0; - padding: 1em; - border-radius: 0; + &.flex-table, + &.flex-bloc { + margin: 1em 0 0; + } + &.alert.to-confirm { + margin: 1em 0 0; + padding: 1em 3em; + } + } + + div.flex-table { + div.item-row { + div.item-col:first-child { + flex-basis: 33%; + } + } + } + + &.errors { + //display: flex; + //position: sticky; + //bottom: 0.3em; + //z-index: 1000; + margin: 1em 0; + padding: 1em; + border-radius: 0; + } } - } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/AdminLocation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/AdminLocation.vue index a91cd1488..9b1bc632e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/AdminLocation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/AdminLocation.vue @@ -1,35 +1,38 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue index 097b746b1..47a586e7d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner.vue @@ -1,106 +1,132 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/PersonsAssociated.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/PersonsAssociated.vue index 2e084f72f..048dbd1df 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/PersonsAssociated.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/PersonsAssociated.vue @@ -1,123 +1,129 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue index 2d3c7d031..9bbe4e513 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/SocialIssue.vue @@ -1,11 +1,11 @@ @@ -14,9 +14,9 @@ export default { @import "ChillPersonAssets/chill/scss/mixins"; @import "ChillMainAssets/chill/scss/chill_variables"; span.badge { - @include badge_social($social-issue-color); - font-size: 95%; - margin-bottom: 5px; - margin-right: 1em; + @include badge_social($social-issue-color); + font-size: 95%; + margin-bottom: 5px; + margin-right: 1em; } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue index 3e405295e..b759dccd2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Banner/ToggleFlags.vue @@ -1,142 +1,158 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/ButtonLocation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/ButtonLocation.vue index 1e17a1cd2..e84d5a9f2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/ButtonLocation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/ButtonLocation.vue @@ -1,36 +1,38 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue index ec8cf1444..b5b2a4466 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Comment.vue @@ -1,56 +1,56 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue index 3ce902daa..408852711 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue @@ -1,120 +1,127 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue index af985e502..f781eca0b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue @@ -1,85 +1,95 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue index 053eb058f..641c9ef37 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue @@ -1,33 +1,33 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue index 67d3f8999..d6979928b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue @@ -1,106 +1,121 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue index c2dbd6e0c..61a9d2571 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue @@ -1,97 +1,98 @@ - - + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue index 05c1a0a73..dbb21fa40 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue @@ -1,111 +1,116 @@ - - - + -
    - + - + +
    - +
    +
      +
    • + +
    • +
    +
    - - - +
    + {{ $t("job.not_valid") }} +
    - -
    -
      -
    • - -
    • -
    -
    - -
    - {{ $t("job.not_valid") }} -
    -
    diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue index 5858cdb00..c2b9acfc4 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue @@ -1,239 +1,263 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue index fa600bfdd..1ef9bfe45 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue @@ -1,57 +1,57 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue index 87daacf47..8a52748d4 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue @@ -1,107 +1,107 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue index 416c6085c..26b561c5d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue @@ -1,32 +1,34 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue index fee6a89ad..1d05e0bbe 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue @@ -1,25 +1,25 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue index 21c290f48..02d3b0c84 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue @@ -1,30 +1,30 @@ @@ -91,20 +96,20 @@ export default { @import "ChillPersonAssets/chill/scss/mixins"; @import "ChillMainAssets/chill/scss/chill_variables"; div#accompanying-course { - span.multiselect__tag { - @include badge_social($social-issue-color); - background: $chill-l-gray; - color: $dark; - } - span.multiselect__option--highlight { - &::after { - background: $green; + span.multiselect__tag { + @include badge_social($social-issue-color); + background: $chill-l-gray; + color: $dark; } - &.multiselect__option--selected { - &::after { - background: $red; - } + span.multiselect__option--highlight { + &::after { + background: $green; + } + &.multiselect__option--selected { + &::after { + background: $red; + } + } } - } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue index 99bced828..0bfd87b18 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue @@ -1,22 +1,22 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue index 1d5644609..65e980fb0 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue @@ -1,195 +1,207 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue index ecdf3026c..8e175f72b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue @@ -1,22 +1,26 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue index 929b4edd8..904d4ff42 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue @@ -1,146 +1,155 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/composer.json b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/composer.json deleted file mode 100644 index d7e9b335b..000000000 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/composer.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "vendor_name/AccompanyingCourse", - "description": "description", - "minimum-stability": "stable", - "license": "proprietary", - "authors": [ - { - "name": "mat", - "email": "email@example.com" - } - ], - "require": { - } -} diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue index f8b5389f4..745317211 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue @@ -1,135 +1,152 @@ @@ -267,46 +288,46 @@ export default { @import "ChillPersonAssets/chill/scss/mixins"; @import "ChillMainAssets/chill/scss/chill_variables"; span.badge { - @include badge_social($social-issue-color); - font-size: 95%; - margin-bottom: 5px; - margin-right: 1em; - margin-left: 1em; + @include badge_social($social-issue-color); + font-size: 95%; + margin-bottom: 5px; + margin-right: 1em; + margin-left: 1em; } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index e505bc396..f3404e2de 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -1,452 +1,482 @@ @@ -803,172 +841,172 @@ export default { @import "~ChillMainAssets/chill/scss/mixins"; div#workEditor { - display: grid; - grid-template-columns: 50%; - column-gap: 0rem; - grid-template-areas: - "title title" - "startDate endDate" - "comment comment" - "privateComment privateComment" - "objectives objectives" - "evaluations evaluations" - "persons persons" - "referrers referrers" - "handling handling" - "tparties tparties" - "errors errors"; + display: grid; + grid-template-columns: 50%; + column-gap: 0rem; + grid-template-areas: + "title title" + "startDate endDate" + "comment comment" + "privateComment privateComment" + "objectives objectives" + "evaluations evaluations" + "persons persons" + "referrers referrers" + "handling handling" + "tparties tparties" + "errors errors"; - #title { - grid-area: title; - } - #startDate { - grid-area: startDate; - } - #endDate { - grid-area: endDate; - } - #comment { - grid-area: comment; - } - #privateComment { - grid-area: privateComment; - } - #objectives { - grid-area: objectives; - } - #evaluations { - grid-area: evaluations; - } - #persons { - grid-area: persons; - } - #handlingThirdParty { - grid-area: handling; - } - #thirdParties { - grid-area: tparties; - } - #referrers { - grid-area: referrers; - } - #errors { - grid-area: errors; - } - - div.action-row { - @include border-collapse; - padding: 1em; - - &#title { - label { - margin-bottom: 0; - } - p { - margin-top: 0; - font-weight: bold; - font-size: 1rem; - } + #title { + grid-area: title; + } + #startDate { + grid-area: startDate; + } + #endDate { + grid-area: endDate; + } + #comment { + grid-area: comment; + } + #privateComment { + grid-area: privateComment; + } + #objectives { + grid-area: objectives; + } + #evaluations { + grid-area: evaluations; + } + #persons { + grid-area: persons; + } + #handlingThirdParty { + grid-area: handling; + } + #thirdParties { + grid-area: tparties; + } + #referrers { + grid-area: referrers; + } + #errors { + grid-area: errors; } - &#objectives { - & > div { - display: grid; - grid-template-columns: 50%; - column-gap: 0rem; - grid-template-areas: "obj res"; - - & > div { - @include border-collapse; - padding: 1em; - - &:nth-child(1) { - grid-area: obj; - } - - &:nth-child(2) { - grid-area: res; - } - } - - & > div.results_without_objective { - background: repeating-linear-gradient( - 45deg, - $gray-200, - $gray-200 10px, - $gray-100 10px, - $gray-100 20px - ); - text-align: center; - font-weight: 700; - padding-top: 1.5rem; - } - } - } - - &#evaluations { - & > div { + div.action-row { @include border-collapse; padding: 1em; - } - } - &#objectives, - &#evaluations { - padding: 0; - - & > div.title { - background-color: $gray-200; - color: $gray-700; - - h3 { - text-align: center; + &#title { + label { + margin-bottom: 0; + } + p { + margin-top: 0; + font-weight: bold; + font-size: 1rem; + } } - } - .item-title { - font-weight: bold; - } - .item-details { - margin: 1em 2em; - font-size: 85%; - } + &#objectives { + & > div { + display: grid; + grid-template-columns: 50%; + column-gap: 0rem; + grid-template-areas: "obj res"; - i.fa { - padding: 0.25rem; - color: $white; + & > div { + @include border-collapse; + padding: 1em; - &.fa-times { - color: $red; + &:nth-child(1) { + grid-area: obj; + } + + &:nth-child(2) { + grid-area: res; + } + } + + & > div.results_without_objective { + background: repeating-linear-gradient( + 45deg, + $gray-200, + $gray-200 10px, + $gray-100 10px, + $gray-100 20px + ); + text-align: center; + font-weight: 700; + padding-top: 1.5rem; + } + } + } + + &#evaluations { + & > div { + @include border-collapse; + padding: 1em; + } + } + + &#objectives, + &#evaluations { + padding: 0; + + & > div.title { + background-color: $gray-200; + color: $gray-700; + + h3 { + text-align: center; + } + } + + .item-title { + font-weight: bold; + } + .item-details { + margin: 1em 2em; + font-size: 85%; + } + + i.fa { + padding: 0.25rem; + color: $white; + + &.fa-times { + color: $red; + } + } + } + + &#persons { + margin-top: 1.5em; + } + + ul.record_actions { + margin-bottom: 0; } - } } - &#persons { - margin-top: 1.5em; + div#errors { + &.alert { + margin-top: 2em; + } } - - ul.record_actions { - margin-bottom: 0; - } - } - - div#errors { - &.alert { - margin-top: 2em; - } - } } .accordion-item:first-of-type, .accordion-item:last-of-type { - border-radius: 0rem; - border: 0px; - .accordion-button { - padding: 0.25rem; - border: 1px solid rgba(17, 17, 17, 0.125); - margin-top: 20px; - margin-bottom: 20px; - } + border-radius: 0rem; + border: 0px; + .accordion-button { + padding: 0.25rem; + border: 1px solid rgba(17, 17, 17, 0.125); + margin-top: 20px; + margin-bottom: 20px; + } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue index 2ee770e41..77b6810bb 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue @@ -1,67 +1,70 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddResult.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddResult.vue index f25de72c8..aa8948a4e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddResult.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddResult.vue @@ -1,167 +1,179 @@ 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 f960e1aa5..326e810e9 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -1,11 +1,11 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue index 862c81926..25877aa7a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue @@ -1,447 +1,445 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue index a58b8a98a..b8e8ee8c4 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue @@ -1,53 +1,53 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Concerned.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Concerned.vue index ae422f150..d4bacd855 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Concerned.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Concerned.vue @@ -1,74 +1,88 @@ @@ -78,61 +92,62 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue"; import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue"; export default { - name: "Concerned", - components: { - AddPersons, - PersonText, - }, - computed: { - ...mapState(["concerned", "household"]), - ...mapGetters(["persons"]), - noPerson() { - return this.$store.getters.persons.length === 0; + name: "Concerned", + components: { + AddPersons, + PersonText, }, - concernedPersonsWithHouseholds() { - if (this.$store.state.household) { - return this.$store.state.concerned.filter( - (c) => - c.person.current_household_id !== null && - c.person.current_household_id !== this.$store.state.household.id, - ); - } else { - return []; - } - }, - }, - data() { - return { - addPersons: { - key: "household_members_editor_concerned", - options: { - type: ["person"], - priority: null, - uniq: false, + computed: { + ...mapState(["concerned", "household"]), + ...mapGetters(["persons"]), + noPerson() { + return this.$store.getters.persons.length === 0; + }, + concernedPersonsWithHouseholds() { + if (this.$store.state.household) { + return this.$store.state.concerned.filter( + (c) => + c.person.current_household_id !== null && + c.person.current_household_id !== + this.$store.state.household.id, + ); + } else { + return []; + } }, - }, - }; - }, - methods: { - addNewPersons({ selected, modal }) { - selected.forEach(function (item) { - this.$store.dispatch("addConcerned", item.result); - }, this); - this.$refs.addPersons.resetSearch(); // to cast child method - modal.showModal = false; }, - removeConcerned(concerned) { - console.log("removedconcerned", concerned); + data() { + return { + addPersons: { + key: "household_members_editor_concerned", + options: { + type: ["person"], + priority: null, + uniq: false, + }, + }, + }; + }, + methods: { + addNewPersons({ selected, modal }) { + selected.forEach(function (item) { + this.$store.dispatch("addConcerned", item.result); + }, this); + this.$refs.addPersons.resetSearch(); // to cast child method + modal.showModal = false; + }, + removeConcerned(concerned) { + console.log("removedconcerned", concerned); - if (!concerned.allowRemove) { - return; - } + if (!concerned.allowRemove) { + return; + } - this.$store.dispatch("removePerson", concerned.person); + this.$store.dispatch("removePerson", concerned.person); + }, + makeHouseholdLink(id) { + return `/fr/person/household/${id}/summary`; + }, }, - makeHouseholdLink(id) { - return `/fr/person/household/${id}/summary`; - }, - }, }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Confirmation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Confirmation.vue index 5441bc1af..902e95648 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Confirmation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Confirmation.vue @@ -1,20 +1,20 @@ @@ -23,14 +23,14 @@ import { mapState } from "vuex"; export default { - name: "Confirmation", - computed: { - ...mapState({ - hasWarnings: (state) => - state.warnings.length > 0 || state.errors.length > 0, - warnings: (state) => state.warnings, - errors: (state) => state.errors, - }), - }, + name: "Confirmation", + computed: { + ...mapState({ + hasWarnings: (state) => + state.warnings.length > 0 || state.errors.length > 0, + warnings: (state) => state.warnings, + errors: (state) => state.errors, + }), + }, }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue index 3ae8c1ab6..34002972c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue @@ -1,35 +1,37 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue index 1139d2fe9..8757f23bc 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue @@ -1,83 +1,83 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue index 56798341a..9773d178e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue @@ -1,116 +1,130 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/HouseholdAddress.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/HouseholdAddress.vue index c7d9d8aac..af6f00d84 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/HouseholdAddress.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/HouseholdAddress.vue @@ -1,26 +1,30 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue index d514ab511..13515efec 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue @@ -1,97 +1,104 @@ @@ -101,41 +108,44 @@ import PersonRenderBox from "ChillPersonAssets/vuejs/_components/Entity/PersonRe import CommentEditor from "ChillMainAssets/vuejs/_components/CommentEditor/CommentEditor.vue"; export default { - name: "MemberDetails", - components: { - PersonRenderBox, - CommentEditor, - }, - props: ["conc"], - computed: { - ...mapGetters(["concByPersonId"]), - classicEditor: () => ClassicEditor, - editorConfig: () => classicEditorConfig, - isHolder() { - return this.conc.holder; + name: "MemberDetails", + components: { + PersonRenderBox, + CommentEditor, }, - comment: { - get() { - return this.conc.comment; - }, - set(text) { - console.log("set comment"); - console.log("comment", text); + props: ["conc"], + computed: { + ...mapGetters(["concByPersonId"]), + classicEditor: () => ClassicEditor, + editorConfig: () => classicEditorConfig, + isHolder() { + return this.conc.holder; + }, + comment: { + get() { + return this.conc.comment; + }, + set(text) { + console.log("set comment"); + console.log("comment", text); - this.$store.dispatch("setComment", { conc: this.conc, comment: text }); - }, + this.$store.dispatch("setComment", { + conc: this.conc, + comment: text, + }); + }, + }, }, - }, - methods: { - toggleHolder() { - this.$store.dispatch("toggleHolder", this.conc); + methods: { + toggleHolder() { + this.$store.dispatch("toggleHolder", this.conc); + }, + removePosition() { + this.$store.dispatch("removePosition", this.conc); + }, + removeConcerned() { + this.$store.dispatch("removeConcerned", this.conc); + }, }, - removePosition() { - this.$store.dispatch("removePosition", this.conc); - }, - removeConcerned() { - this.$store.dispatch("removeConcerned", this.conc); - }, - }, }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue index 99fb3ebfa..6630a72f1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue @@ -1,30 +1,30 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue index ceaff2acf..a19d80c7a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue @@ -1,55 +1,57 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue index fb14c371b..8359ad90c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue @@ -1,180 +1,211 @@ + + + + +
      +
    • + - {{ layer.label }} - -
    -
    -
    - - - - - - - - - -
      -
    • - -
    • -
    + + @@ -637,21 +689,21 @@ export default { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/accompanyingCourseWorkEvaluationDocument.ts b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/accompanyingCourseWorkEvaluationDocument.ts index 577c4b52b..ddb37fab1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/accompanyingCourseWorkEvaluationDocument.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/accompanyingCourseWorkEvaluationDocument.ts @@ -2,10 +2,10 @@ import { AccompanyingPeriodWorkEvaluationDocument } from "../../types"; import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; export const duplicate = async ( - id: number, + id: number, ): Promise => { - return makeFetch( - "POST", - `/api/1.0/person/accompanying-course-work-evaluation-document/${id}/duplicate`, - ); + return makeFetch( + "POST", + `/api/1.0/person/accompanying-course-work-evaluation-document/${id}/duplicate`, + ); }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue index fe4cdae74..068b2068b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue @@ -1,52 +1,52 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue index 9d05f6883..6f55b6636 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue @@ -1,106 +1,123 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue index bef3967b5..bb90557b7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue @@ -1,46 +1,49 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue index 63b39ebc7..86a58737a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue @@ -1,14 +1,14 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue index 773609316..259029f73 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue @@ -1,21 +1,21 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue index bbcc553d0..8c97d2e29 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue @@ -1,33 +1,33 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue index 35759a810..56b3afe17 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue @@ -1,12 +1,12 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue index cc8c6ca66..1daf921e1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue @@ -1,30 +1,32 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue index a9d937250..ad996a375 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue @@ -1,69 +1,69 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue index dcb0c45e2..cdc143f76 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue @@ -1,288 +1,362 @@ @@ -378,38 +456,38 @@ export default { @import "ChillMainAssets/chill/scss/chill_variables"; .lastname:before { - content: " "; + content: " "; } div.flex-table { - div.item-bloc { - div.item-row { - div.item-col:first-child { - width: 33%; - } + div.item-bloc { + div.item-row { + div.item-col:first-child { + width: 33%; + } - @include media-breakpoint-down(sm) { - div.item-col:first-child { - width: unset; + @include media-breakpoint-down(sm) { + div.item-col:first-child { + width: unset; + } + } + + div.item-col:last-child { + justify-content: flex-start; + } } - } - - div.item-col:last-child { - justify-content: flex-start; - } } - } } .age { - margin-left: 0.5em; + margin-left: 0.5em; - &:before { - content: "("; - } + &:before { + content: "("; + } - &:after { - content: ")"; - } + &:after { + content: ")"; + } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue index f3005f235..f157fac6a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue @@ -1,66 +1,75 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue index 9abd03991..ff4b312f1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue @@ -1,505 +1,530 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts index 088ff11ea..ba7637544 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts @@ -1,63 +1,64 @@ const personMessages = { - fr: { - add_persons: { - title: "Ajouter des usagers", - suggested_counter: "Pas de résultats | 1 résultat | {count} résultats", - selected_counter: " 1 sélectionné | {count} sélectionnés", - search_some_persons: "Rechercher des personnes..", + fr: { + add_persons: { + title: "Ajouter des usagers", + suggested_counter: + "Pas de résultats | 1 résultat | {count} résultats", + selected_counter: " 1 sélectionné | {count} sélectionnés", + search_some_persons: "Rechercher des personnes..", + }, + item: { + type_person: "Usager", + type_user: "TMS", + type_thirdparty: "Tiers professionnel", + type_household: "Ménage", + }, + person: { + firstname: "Prénom", + lastname: "Nom", + born: (ctx: { gender: "man" | "woman" | "neutral" }) => { + if (ctx.gender === "man") { + return "Né le"; + } else if (ctx.gender === "woman") { + return "Née le"; + } else { + return "Né·e le"; + } + }, + center_id: "Identifiant du centre", + center_type: "Type de centre", + center_name: "Territoire", // vendée + phonenumber: "Téléphone", + mobilenumber: "Mobile", + altnames: "Autres noms", + email: "Courriel", + gender: { + title: "Genre", + placeholder: "Choisissez le genre de l'usager", + woman: "Féminin", + man: "Masculin", + neutral: "Neutre, non binaire", + unknown: "Non renseigné", + undefined: "Non renseigné", + }, + civility: { + title: "Civilité", + placeholder: "Choisissez la civilité", + }, + address: { + create_address: "Ajouter une adresse", + show_address_form: + "Ajouter une adresse pour un usager non suivi et seul dans un ménage", + warning: + "Un nouveau ménage va être créé. L'usager sera membre de ce ménage.", + }, + center: { + placeholder: "Choisissez un centre", + title: "Centre", + }, + }, + error_only_one_person: "Une seule personne peut être sélectionnée !", }, - item: { - type_person: "Usager", - type_user: "TMS", - type_thirdparty: "Tiers professionnel", - type_household: "Ménage", - }, - person: { - firstname: "Prénom", - lastname: "Nom", - born: (ctx: { gender: "man" | "woman" | "neutral" }) => { - if (ctx.gender === "man") { - return "Né le"; - } else if (ctx.gender === "woman") { - return "Née le"; - } else { - return "Né·e le"; - } - }, - center_id: "Identifiant du centre", - center_type: "Type de centre", - center_name: "Territoire", // vendée - phonenumber: "Téléphone", - mobilenumber: "Mobile", - altnames: "Autres noms", - email: "Courriel", - gender: { - title: "Genre", - placeholder: "Choisissez le genre de l'usager", - woman: "Féminin", - man: "Masculin", - neutral: "Neutre, non binaire", - unknown: "Non renseigné", - undefined: "Non renseigné", - }, - civility: { - title: "Civilité", - placeholder: "Choisissez la civilité", - }, - address: { - create_address: "Ajouter une adresse", - show_address_form: - "Ajouter une adresse pour un usager non suivi et seul dans un ménage", - warning: - "Un nouveau ménage va être créé. L'usager sera membre de ce ménage.", - }, - center: { - placeholder: "Choisissez un centre", - title: "Centre", - }, - }, - error_only_one_person: "Une seule personne peut être sélectionnée !", - }, }; export { personMessages }; diff --git a/src/Bundle/ChillPersonBundle/apigen.neon b/src/Bundle/ChillPersonBundle/apigen.neon deleted file mode 100644 index 201e80edb..000000000 --- a/src/Bundle/ChillPersonBundle/apigen.neon +++ /dev/null @@ -1,15 +0,0 @@ -# configuration for apigen - - -source: - - . - -accessLevels: ["public", "protected"] - -exclude: - - vendor/* - - Resources/test/* - - Tests/Fixtures/* - -title: Chill Activity Bundle - diff --git a/src/Bundle/ChillPersonBundle/composer.json b/src/Bundle/ChillPersonBundle/composer.json deleted file mode 100644 index b6e7804aa..000000000 --- a/src/Bundle/ChillPersonBundle/composer.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "chill-project/person", - "license": "AGPL-3.0", - "type": "symfony-bundle", - "description": "A bundle to deal with persons", - "keywords" : ["chill", "social work", "persons"], - "homepage" : "https://github.com/Chill-project/Person", - "autoload": { - "psr-4": { "Chill\\PersonBundle\\": "" } - }, - "autoload-dev": { - "classmap": [ "Resources/test/Fixtures/App/app/AppKernel.php" ] - }, - "authors" : [ - { - "name": "Champs-Libres", - "email": "info@champs-libres.coop", - "homepage": "http://www.champs-libres.coop" - } - ], - "require": { - - }, - "require-dev": { - - }, - "scripts": { - "post-install-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ], - "post-update-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ] - }, - "extra": { - "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations", - "symfony-app-dir": "Tests/Fixtures/App/" - } -} diff --git a/src/Bundle/ChillPersonBundle/phpunit.xml.dist b/src/Bundle/ChillPersonBundle/phpunit.xml.dist deleted file mode 100644 index 2d5dca36d..000000000 --- a/src/Bundle/ChillPersonBundle/phpunit.xml.dist +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - ./Tests - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - - - - - - diff --git a/src/Bundle/ChillReportBundle/.gitignore b/src/Bundle/ChillReportBundle/.gitignore deleted file mode 100644 index edf97d24e..000000000 --- a/src/Bundle/ChillReportBundle/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/vendor/ -composer.lock -Tests/Fixtures/App/app/config/parameters.yml -*~ -auth.json -/nbproject/private/ \ No newline at end of file diff --git a/src/Bundle/ChillReportBundle/.gitlab-ci.yml b/src/Bundle/ChillReportBundle/.gitlab-ci.yml deleted file mode 100644 index 126ed8194..000000000 --- a/src/Bundle/ChillReportBundle/.gitlab-ci.yml +++ /dev/null @@ -1,54 +0,0 @@ -.test_definition: &test_definition - services: - - chill/database:latest - - before_script: - - composer config github-oauth.github.com $GITHUB_TOKEN - - php -d memory_limit=-1 /usr/local/bin/composer install --no-interaction - - cp Resources/test/Fixtures/App/app/config/parameters.gitlab-ci.yml Resources/test/Fixtures/App/app/config/parameters.yml - - php Resources/test/Fixtures/App/app/console --env=test cache:warmup - - php Resources/test/Fixtures/App/app/console doctrine:migrations:migrate --env=test --no-interaction - - php Resources/test/Fixtures/App/app/console doctrine:fixtures:load --env=test --no-interaction - -stages: - - test - - build-doc - - deploy-doc - -test:php-7.2: - stage: test - image: chill/ci-image:php-7.2 - <<: *test_definition - script: php vendor/bin/phpunit - -# deploy documentation -api-doc-build: - stage: build-doc - environment: api-doc - image: chill/ci-image:php-7.2 - before_script: - - mkdir api-doc - script: apigen generate --destination api-doc/$CI_BUILD_REF_NAME/$CI_PROJECT_NAME - artifacts: - paths: - - "api-doc/" - name: api - expire_in: '2h' - only: - - master - - tags - -api-doc-deploy: - stage: deploy-doc - image: pallet/swiftclient:latest - before_script: - # test that CONTAINER_API variable is set - - if [ -z ${CONTAINER_API+x} ]; then echo "Please set CONTAINER_API variable"; exit -1; fi - # go to api-doc to have and url with PROJECT/BUILD - - cd api-doc - # upload, and keep files during 1 year - script: "swift upload --header \"X-Delete-After: 31536000\" $CONTAINER_API $CI_BUILD_REF_NAME/$CI_PROJECT_NAME" - only: - - master - - tags - diff --git a/src/Bundle/ChillReportBundle/CHANGELOG.md b/src/Bundle/ChillReportBundle/CHANGELOG.md deleted file mode 100644 index e0ed03792..000000000 --- a/src/Bundle/ChillReportBundle/CHANGELOG.md +++ /dev/null @@ -1,18 +0,0 @@ - -Version 1.5.1 -============= - -- [list export] fix error "all custom fields are shown" - -Version 1.5.2 -============= - -- add privacy events to report list / view -- add privacy events to report edit / update - -Master branch -============= - - - - diff --git a/src/Bundle/ChillReportBundle/LICENSE b/src/Bundle/ChillReportBundle/LICENSE deleted file mode 100644 index dba13ed2d..000000000 --- a/src/Bundle/ChillReportBundle/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/src/Bundle/ChillReportBundle/apigen.neon b/src/Bundle/ChillReportBundle/apigen.neon deleted file mode 100644 index 6fd31b419..000000000 --- a/src/Bundle/ChillReportBundle/apigen.neon +++ /dev/null @@ -1,11 +0,0 @@ -# configuration for apigen - -source: - - . - -exclude: - - vendor/* - - Test* - - Resources/test - -title: Chill Report Bundle diff --git a/src/Bundle/ChillReportBundle/composer.json b/src/Bundle/ChillReportBundle/composer.json deleted file mode 100644 index a5c17a2ee..000000000 --- a/src/Bundle/ChillReportBundle/composer.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "chill-project/report", - "description": "The bundle for reports", - "type": "symfony-bundle", - "keywords" : ["chill", "social work"], - "license": "AGPL-3.0", - "homepage" : "https://github.com/Chill-project/Report", - "autoload": { - "psr-4": { "Chill\\ReportBundle\\": "" } - }, - "autoload-dev": { - "classmap": [ "Resources/test/Fixtures/App/app/AppKernel.php" ] - }, - "authors" : [ - { - "name": "Champs-Libres", - "email": "info@champs-libres.coop", - "homepage": "http://www.champs-libres.coop" - } - ], - "require": { - }, - "require-dev": { - }, - "scripts": { - "post-install-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ], - "post-update-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ] - }, - "extra": { - "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations", - "symfony-app-dir": "Tests/Fixtures/App/app" - }, - "minimum-stability": "dev", - "prefer-stable": true -} diff --git a/src/Bundle/ChillReportBundle/console.sh b/src/Bundle/ChillReportBundle/console.sh deleted file mode 100755 index 609ce9cde..000000000 --- a/src/Bundle/ChillReportBundle/console.sh +++ /dev/null @@ -1 +0,0 @@ -php Tests/Fixtures/App/console $1 $2 $3 $4 $5 \ No newline at end of file diff --git a/src/Bundle/ChillReportBundle/phpunit.xml.dist b/src/Bundle/ChillReportBundle/phpunit.xml.dist deleted file mode 100644 index 5e67beade..000000000 --- a/src/Bundle/ChillReportBundle/phpunit.xml.dist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - ./Tests - - - - - ./ - - ./Resources - ./Tests - ./vendor - - - - - - - - diff --git a/src/Bundle/ChillTaskBundle/.gitignore b/src/Bundle/ChillTaskBundle/.gitignore deleted file mode 100644 index 57872d0f1..000000000 --- a/src/Bundle/ChillTaskBundle/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/vendor/ diff --git a/src/Bundle/ChillTaskBundle/.gitlab-ci.yml b/src/Bundle/ChillTaskBundle/.gitlab-ci.yml deleted file mode 100644 index e9826c3fb..000000000 --- a/src/Bundle/ChillTaskBundle/.gitlab-ci.yml +++ /dev/null @@ -1,25 +0,0 @@ -.test_definition: &test_definition - services: - - chill/database:latest - before_script: - - composer config github-oauth.github.com $GITHUB_TOKEN - - composer install - - cp Resources/test/Fixtures/App/app/config/parameters.gitlab-ci.yml Resources/test/Fixtures/App/app/config/parameters.yml - - php Resources/test/Fixtures/App/app/console --env=test cache:warmup - - php Resources/test/Fixtures/App/app/console doctrine:migrations:migrate --env=test --no-interaction - - php Resources/test/Fixtures/App/app/console doctrine:fixtures:load --env=test --no-interaction - - -stages: - - deploy - -deploy-packagist: - stage: deploy - image: chill/ci-image:php-7.2 - before_script: - # test that PACKAGIST USERNAME and PACKAGIST_TOKEN variable are set - - if [ -z ${PACKAGIST_USERNAME+x} ]; then echo "Please set PACKAGIST_USERNAME variable"; exit -1; fi - - if [ -z ${PACKAGIST_TOKEN+x} ]; then echo "Please set PACKAGIST_TOKEN variable"; exit -1; fi - script: - - STATUSCODE=$(curl -XPOST -H'content-type:application/json' "https://packagist.org/api/update-package?username=$PACKAGIST_USERNAME&apiToken=$PACKAGIST_TOKEN" -d"{\"repository\":{\"url\":\"$CI_PROJECT_URL.git\"}}" --silent --output /dev/stderr --write-out "%{http_code}") - - if [ $STATUSCODE = "202" ]; then exit 0; else exit $STATUSCODE; fi diff --git a/src/Bundle/ChillTaskBundle/CHANGELOG.md b/src/Bundle/ChillTaskBundle/CHANGELOG.md deleted file mode 100644 index c4662ce9d..000000000 --- a/src/Bundle/ChillTaskBundle/CHANGELOG.md +++ /dev/null @@ -1,52 +0,0 @@ -Version 1.5.1 -============= - -- fix bug in filter task form: allow to show the list of users, which was hidden when the user had access to multiple centers; -- add assignee in task list; -- fix some translation; -- add a filtering by center on list; -- add color in boxes for task statuses; - -Version 1.5.4 -============= - -- adding indexes on place event and task - -Version 1.5.5 -============= - -- Fix error on the "see more" link which was not showed -- Layout of the task list - -Version 1.5.6 -============= - -- fix: validation error on warning date interval is not shown -- add privacy events to task show / list; -- add privacy events to task edit / update; - -Version 1.5.7 -============== - -- fix error when showing task list without person in context (issue #3) ; - -Version 1.5.8 -============= - -- add returnPath to page Show and List for Single tasks ; - -Version 1.5.9 -============= - -- better exception description when task workflow is not found ; - -Version 1.5.10 -============== - -- load webpack config using a `configure` function ; - -Version 1.5.11 -============== - -- [task] fix loading of chill task list ; - diff --git a/src/Bundle/ChillTaskBundle/composer.json b/src/Bundle/ChillTaskBundle/composer.json deleted file mode 100644 index ecd4a91e9..000000000 --- a/src/Bundle/ChillTaskBundle/composer.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "chill-project/task", - "description": "Associate task with peoples in chill.social", - "type": "symfony-bundle", - "keywords": ["chill", "social work", "tasks"], - "homepage": "https://framagit.org/Chill-project/Chill-Task", - "autoload": { - "psr-4": { "Chill\\TaskBundle\\" : "" } - }, - "autoload-dev": { - "classmap": [ "Resources/test/Fixtures/App/app/AppKernel.php" ] - }, - "require": { - - }, - "require-dev": { - }, - "license": "AGPL-3.0-or-later", - "authors": [ - { - "name": "Champs-Libres", - "email": "info@champs-libres.coop" - } - ], - "scripts": { - "post-install-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ], - "post-update-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ] - }, - "extra": { - "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations", - "symfony-app-dir": "Resources/test/Fixtures/App/" - } -} diff --git a/src/Bundle/ChillTaskBundle/composer.lock b/src/Bundle/ChillTaskBundle/composer.lock deleted file mode 100644 index 2ad9ca212..000000000 --- a/src/Bundle/ChillTaskBundle/composer.lock +++ /dev/null @@ -1,4403 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "c40de51f6aab3bbe16dc8bd7becbfbe8", - "packages": [ - { - "name": "champs-libres/composer-bundle-migration", - "version": "1.0.6", - "source": { - "type": "git", - "url": "https://github.com/Champs-Libres/ComposerBundleMigration.git", - "reference": "556eecdf6de3a0bd7f7e7807dca00be5bb2babf0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Champs-Libres/ComposerBundleMigration/zipball/556eecdf6de3a0bd7f7e7807dca00be5bb2babf0", - "reference": "556eecdf6de3a0bd7f7e7807dca00be5bb2babf0", - "shasum": "" - }, - "require": { - "php": ">5.4" - }, - "suggest": { - "doctrine/doctrine-migrations-bundle": "doctrine migrations for symfony app", - "doctrine/migrations": "the original doctrine migration bundle" - }, - "type": "library", - "autoload": { - "psr-4": { - "ComposerBundleMigration\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "AGPL-3.0" - ], - "authors": [ - { - "name": "Julien Fastré", - "email": "julien.fastre@champs-libres.coop" - }, - { - "name": "Champs-Libres", - "email": "info@champs-libres.coop" - } - ], - "description": "Move DoctrineMigrations files from installed bundle to root package.", - "keywords": [ - "doctrine", - "doctrine migrations", - "symfony" - ], - "time": "2015-02-23T08:33:12+00:00" - }, - { - "name": "chill-project/custom-fields", - "version": "dev-upgrade-sf3", - "source": { - "type": "git", - "url": "/var/www/app/vendor/chill-project/custom-fields", - "reference": "7950ccfddb230cad5191594785710505ec8ad49e" - }, - "require": { - "chill-project/main": "dev-upgrade-sf3@dev", - "chill-project/person": "dev-upgrade-sf3@dev" - }, - "require-dev": { - "doctrine/doctrine-fixtures-bundle": "~2.2", - "fzaninotto/faker": "~1", - "phpunit/phpunit": "^6.2" - }, - "type": "symfony-bundle", - "extra": { - "symfony-app-dir": "Tests/Fixtures/App/app", - "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations" - }, - "autoload": { - "psr-4": { - "Chill\\CustomFieldsBundle\\": "" - } - }, - "autoload-dev": { - "classmap": [ - "Resources/test/Fixtures/App/app/AppKernel.php" - ] - }, - "scripts": { - "post-install-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ], - "post-update-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ] - }, - "license": [ - "AGPL-3.0" - ], - "authors": [ - { - "name": "Champs-Libres", - "email": "info@champs-libres.coop", - "homepage": "http://www.champs-libres.coop" - } - ], - "description": "This bundle allow to add custom fields on entities.", - "homepage": "https://github.com/Chill-project/CustomFields", - "keywords": [ - "chill", - "social work" - ], - "time": "2018-04-24T12:31:41+00:00" - }, - { - "name": "chill-project/main", - "version": "dev-upgrade-sf3", - "source": { - "type": "git", - "url": "/var/www/app/vendor/chill-project/main", - "reference": "19e30d90c5bc5d3f3058b6f1914df8f683edfbb6" - }, - "require": { - "champs-libres/composer-bundle-migration": "~1.0", - "doctrine/common": "~2.8", - "doctrine/dbal": "~2.7", - "doctrine/doctrine-bundle": "~1.9", - "doctrine/doctrine-migrations-bundle": "~1.3", - "doctrine/migrations": "~1.0", - "doctrine/orm": "~2.6", - "php": "~7.2", - "phpoffice/phpspreadsheet": "~1.2", - "sensio/distribution-bundle": "^5.0", - "symfony/assetic-bundle": "~2.8", - "symfony/monolog-bundle": "~3.2", - "symfony/symfony": "~3.4", - "twig/extensions": "~1.5" - }, - "require-dev": { - "doctrine/doctrine-fixtures-bundle": "~3.0", - "phpunit/phpunit": "~5.6", - "symfony/dom-crawler": "~3.4", - "symfony/phpunit-bridge": "~3.4" - }, - "type": "symfony-bundle", - "extra": { - "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations", - "symfony-app-dir": "Tests/Fixtures/App/" - }, - "autoload": { - "psr-4": { - "Chill\\MainBundle\\": "" - } - }, - "autoload-dev": { - "classmap": [ - "Resources/test/Fixtures/App/app/AppKernel.php" - ] - }, - "scripts": { - "post-install-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ], - "post-update-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ] - }, - "license": [ - "AGPL-3.0" - ], - "authors": [ - { - "name": "Champs-Libres", - "email": "info@champs-libres.coop", - "homepage": "http://www.champs-libres.coop" - } - ], - "description": "The main bundle for the Chill App", - "homepage": "http://chill.social", - "keywords": [ - "chill", - "social work", - "software for social service" - ], - "support": { - "email": "dev@lists.chill.social", - "issues": "https://git.framasoft.org/Chill-project/Chill-Main/issues", - "sources": "https://git.framasoft.org/Chill-project/Chill-Main", - "docs": "http://docs.chill.social" - }, - "time": "2018-04-24T12:21:11+00:00" - }, - { - "name": "chill-project/person", - "version": "dev-upgrade-sf3", - "source": { - "type": "git", - "url": "/var/www/app/vendor/chill-project/person", - "reference": "c847be8534ddd7522ea5e231da658097e6e573b1" - }, - "require": { - "chill-project/custom-fields": "dev-upgrade-sf3@dev", - "chill-project/main": "dev-upgrade-sf3" - }, - "require-dev": { - "fzaninotto/faker": "~1", - "phpunit/phpunit": "~5.6", - "symfony/phpunit-bridge": "~3" - }, - "type": "symfony-bundle", - "extra": { - "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations", - "symfony-app-dir": "Tests/Fixtures/App/" - }, - "autoload": { - "psr-4": { - "Chill\\PersonBundle\\": "" - } - }, - "autoload-dev": { - "classmap": [ - "Resources/test/Fixtures/App/app/AppKernel.php" - ] - }, - "scripts": { - "post-install-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ], - "post-update-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ] - }, - "license": [ - "AGPL-3.0" - ], - "authors": [ - { - "name": "Champs-Libres", - "email": "info@champs-libres.coop", - "homepage": "http://www.champs-libres.coop" - } - ], - "description": "A bundle to deal with persons", - "homepage": "https://github.com/Chill-project/Person", - "keywords": [ - "chill", - "persons", - "social work" - ], - "time": "2018-04-24T12:33:27+00:00" - }, - { - "name": "composer/ca-bundle", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/composer/ca-bundle.git", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169", - "shasum": "" - }, - "require": { - "ext-openssl": "*", - "ext-pcre": "*", - "php": "^5.3.2 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", - "psr/log": "^1.0", - "symfony/process": "^2.5 || ^3.0 || ^4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\CaBundle\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", - "keywords": [ - "cabundle", - "cacert", - "certificate", - "ssl", - "tls" - ], - "time": "2018-03-29T19:57:20+00:00" - }, - { - "name": "doctrine/annotations", - "version": "v1.6.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "php": "^7.1" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2017-12-06T07:11:42+00:00" - }, - { - "name": "doctrine/cache", - "version": "v1.7.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/b3217d58609e9c8e661cd41357a54d926c4a2a1a", - "reference": "b3217d58609e9c8e661cd41357a54d926c4a2a1a", - "shasum": "" - }, - "require": { - "php": "~7.1" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "alcaeus/mongo-php-adapter": "^1.1", - "mongodb/mongodb": "^1.1", - "phpunit/phpunit": "^5.7", - "predis/predis": "~1.0" - }, - "suggest": { - "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Caching library offering an object-oriented API for many cache backends", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "cache", - "caching" - ], - "time": "2017-08-25T07:02:50+00:00" - }, - { - "name": "doctrine/collections", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "doctrine/coding-standard": "~0.1@dev", - "phpunit/phpunit": "^5.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Collections\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Collections Abstraction library", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "array", - "collections", - "iterator" - ], - "time": "2017-07-22T10:37:32+00:00" - }, - { - "name": "doctrine/common", - "version": "v2.8.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/common.git", - "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/common/zipball/f68c297ce6455e8fd794aa8ffaf9fa458f6ade66", - "reference": "f68c297ce6455e8fd794aa8ffaf9fa458f6ade66", - "shasum": "" - }, - "require": { - "doctrine/annotations": "1.*", - "doctrine/cache": "1.*", - "doctrine/collections": "1.*", - "doctrine/inflector": "1.*", - "doctrine/lexer": "1.*", - "php": "~7.1" - }, - "require-dev": { - "phpunit/phpunit": "^5.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "lib/Doctrine/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Common Library for Doctrine projects", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "collections", - "eventmanager", - "persistence", - "spl" - ], - "time": "2017-08-31T08:43:38+00:00" - }, - { - "name": "doctrine/dbal", - "version": "v2.7.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "11037b4352c008373561dc6fc836834eed80c3b5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/11037b4352c008373561dc6fc836834eed80c3b5", - "reference": "11037b4352c008373561dc6fc836834eed80c3b5", - "shasum": "" - }, - "require": { - "doctrine/common": "^2.7.1", - "ext-pdo": "*", - "php": "^7.1" - }, - "require-dev": { - "doctrine/coding-standard": "^4.0", - "phpunit/phpunit": "^7.0", - "phpunit/phpunit-mock-objects": "!=3.2.4,!=3.2.5", - "symfony/console": "^2.0.5||^3.0", - "symfony/phpunit-bridge": "^3.4.5|^4.0.5" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\DBAL\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Database Abstraction Layer", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "database", - "dbal", - "persistence", - "queryobject" - ], - "time": "2018-04-07T18:44:18+00:00" - }, - { - "name": "doctrine/doctrine-bundle", - "version": "1.9.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "703fad32e4c8cbe609caf45a71a1d4266c830f0f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/703fad32e4c8cbe609caf45a71a1d4266c830f0f", - "reference": "703fad32e4c8cbe609caf45a71a1d4266c830f0f", - "shasum": "" - }, - "require": { - "doctrine/dbal": "^2.5.12", - "doctrine/doctrine-cache-bundle": "~1.2", - "jdorn/sql-formatter": "^1.2.16", - "php": "^5.5.9|^7.0", - "symfony/console": "~2.7|~3.0|~4.0", - "symfony/dependency-injection": "~2.7|~3.0|~4.0", - "symfony/doctrine-bridge": "~2.7|~3.0|~4.0", - "symfony/framework-bundle": "^2.7.22|~3.0|~4.0" - }, - "conflict": { - "symfony/http-foundation": "<2.6" - }, - "require-dev": { - "doctrine/orm": "~2.4", - "phpunit/phpunit": "^4.8.36|^5.7|^6.4", - "satooshi/php-coveralls": "^1.0", - "symfony/phpunit-bridge": "~2.7|~3.0|~4.0", - "symfony/property-info": "~2.8|~3.0|~4.0", - "symfony/validator": "~2.7|~3.0|~4.0", - "symfony/web-profiler-bundle": "~2.7|~3.0|~4.0", - "symfony/yaml": "~2.7|~3.0|~4.0", - "twig/twig": "~1.26|~2.0" - }, - "suggest": { - "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", - "symfony/web-profiler-bundle": "To use the data collector." - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "1.8.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Bundle\\DoctrineBundle\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Doctrine Project", - "homepage": "http://www.doctrine-project.org/" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony DoctrineBundle", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "database", - "dbal", - "orm", - "persistence" - ], - "time": "2018-04-19T14:07:39+00:00" - }, - { - "name": "doctrine/doctrine-cache-bundle", - "version": "1.3.3", - "source": { - "type": "git", - "url": "https://github.com/doctrine/DoctrineCacheBundle.git", - "reference": "4c8e363f96427924e7e519c5b5119b4f54512697" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineCacheBundle/zipball/4c8e363f96427924e7e519c5b5119b4f54512697", - "reference": "4c8e363f96427924e7e519c5b5119b4f54512697", - "shasum": "" - }, - "require": { - "doctrine/cache": "^1.4.2", - "doctrine/inflector": "~1.0", - "php": ">=5.3.2", - "symfony/doctrine-bridge": "~2.7|~3.3|~4.0" - }, - "require-dev": { - "instaclick/coding-standard": "~1.1", - "instaclick/object-calisthenics-sniffs": "dev-master", - "instaclick/symfony2-coding-standard": "dev-remaster", - "phpunit/phpunit": "~4|~5", - "predis/predis": "~0.8", - "satooshi/php-coveralls": "^1.0", - "squizlabs/php_codesniffer": "~1.5", - "symfony/console": "~2.7|~3.3|~4.0", - "symfony/finder": "~2.7|~3.3|~4.0", - "symfony/framework-bundle": "~2.7|~3.3|~4.0", - "symfony/phpunit-bridge": "~2.7|~3.3|~4.0", - "symfony/security-acl": "~2.7|~3.3", - "symfony/validator": "~2.7|~3.3|~4.0", - "symfony/yaml": "~2.7|~3.3|~4.0" - }, - "suggest": { - "symfony/security-acl": "For using this bundle to cache ACLs" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Bundle\\DoctrineCacheBundle\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Fabio B. Silva", - "email": "fabio.bat.silva@gmail.com" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@hotmail.com" - }, - { - "name": "Doctrine Project", - "homepage": "http://www.doctrine-project.org/" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony Bundle for Doctrine Cache", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "cache", - "caching" - ], - "time": "2018-03-27T09:22:12+00:00" - }, - { - "name": "doctrine/doctrine-migrations-bundle", - "version": "v1.3.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", - "reference": "a9e506369f931351a2a6dd2aef588a822802b1b7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/a9e506369f931351a2a6dd2aef588a822802b1b7", - "reference": "a9e506369f931351a2a6dd2aef588a822802b1b7", - "shasum": "" - }, - "require": { - "doctrine/doctrine-bundle": "~1.0", - "doctrine/migrations": "^1.1", - "php": ">=5.4.0", - "symfony/framework-bundle": "~2.7|~3.3|~4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.36" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Bundle\\MigrationsBundle\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Doctrine Project", - "homepage": "http://www.doctrine-project.org" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony DoctrineMigrationsBundle", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "dbal", - "migrations", - "schema" - ], - "time": "2017-11-01T09:13:26+00:00" - }, - { - "name": "doctrine/inflector", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/inflector.git", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/5527a48b7313d15261292c149e55e26eae771b0a", - "reference": "5527a48b7313d15261292c149e55e26eae771b0a", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^6.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Common String Manipulations with regard to casing and singular/plural rules.", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "inflection", - "pluralize", - "singularize", - "string" - ], - "time": "2018-01-09T20:05:19+00:00" - }, - { - "name": "doctrine/instantiator", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2017-07-22T11:58:36+00:00" - }, - { - "name": "doctrine/lexer", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "lexer", - "parser" - ], - "time": "2014-09-09T13:34:57+00:00" - }, - { - "name": "doctrine/migrations", - "version": "v1.6.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/migrations.git", - "reference": "e3faf7c96b8a6084045dedcaf51f74c7834644d4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/e3faf7c96b8a6084045dedcaf51f74c7834644d4", - "reference": "e3faf7c96b8a6084045dedcaf51f74c7834644d4", - "shasum": "" - }, - "require": { - "doctrine/dbal": "~2.6", - "ocramius/proxy-manager": "^1.0|^2.0", - "php": "^7.1", - "symfony/console": "~3.3|^4.0", - "symfony/yaml": "~3.3|^4.0" - }, - "require-dev": { - "doctrine/coding-standard": "^1.0", - "doctrine/orm": "~2.5", - "jdorn/sql-formatter": "~1.1", - "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "~6.2", - "squizlabs/php_codesniffer": "^3.0" - }, - "suggest": { - "jdorn/sql-formatter": "Allows to generate formatted SQL with the diff command." - }, - "bin": [ - "bin/doctrine-migrations" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "v1.6.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\Migrations\\": "lib/Doctrine/DBAL/Migrations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1" - ], - "authors": [ - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Michael Simonson", - "email": "contact@mikesimonson.com" - } - ], - "description": "Database Schema migrations using Doctrine DBAL", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "database", - "migrations" - ], - "time": "2017-11-24T14:13:17+00:00" - }, - { - "name": "doctrine/orm", - "version": "v2.6.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/doctrine2.git", - "reference": "87ee409783a4a322b5597ebaae558661404055a7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/doctrine2/zipball/87ee409783a4a322b5597ebaae558661404055a7", - "reference": "87ee409783a4a322b5597ebaae558661404055a7", - "shasum": "" - }, - "require": { - "doctrine/annotations": "~1.5", - "doctrine/cache": "~1.6", - "doctrine/collections": "^1.4", - "doctrine/common": "^2.7.1", - "doctrine/dbal": "^2.6", - "doctrine/instantiator": "~1.1", - "ext-pdo": "*", - "php": "^7.1", - "symfony/console": "~3.0|~4.0" - }, - "require-dev": { - "doctrine/coding-standard": "^1.0", - "phpunit/phpunit": "^6.5", - "squizlabs/php_codesniffer": "^3.2", - "symfony/yaml": "~3.4|~4.0" - }, - "suggest": { - "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" - }, - "bin": [ - "bin/doctrine" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\ORM\\": "lib/Doctrine/ORM" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Object-Relational-Mapper for PHP", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "database", - "orm" - ], - "time": "2018-02-27T07:30:56+00:00" - }, - { - "name": "fig/link-util", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/link-util.git", - "reference": "1a07821801a148be4add11ab0603e4af55a72fac" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/link-util/zipball/1a07821801a148be4add11ab0603e4af55a72fac", - "reference": "1a07821801a148be4add11ab0603e4af55a72fac", - "shasum": "" - }, - "require": { - "php": ">=5.5.0", - "psr/link": "~1.0@dev" - }, - "require-dev": { - "phpunit/phpunit": "^5.1", - "squizlabs/php_codesniffer": "^2.3.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Fig\\Link\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common utility implementations for HTTP links", - "keywords": [ - "http", - "http-link", - "link", - "psr", - "psr-13", - "rest" - ], - "time": "2016-10-17T18:31:11+00:00" - }, - { - "name": "jdorn/sql-formatter", - "version": "v1.2.17", - "source": { - "type": "git", - "url": "https://github.com/jdorn/sql-formatter.git", - "reference": "64990d96e0959dff8e059dfcdc1af130728d92bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jdorn/sql-formatter/zipball/64990d96e0959dff8e059dfcdc1af130728d92bc", - "reference": "64990d96e0959dff8e059dfcdc1af130728d92bc", - "shasum": "" - }, - "require": { - "php": ">=5.2.4" - }, - "require-dev": { - "phpunit/phpunit": "3.7.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "lib" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jeremy Dorn", - "email": "jeremy@jeremydorn.com", - "homepage": "http://jeremydorn.com/" - } - ], - "description": "a PHP SQL highlighting library", - "homepage": "https://github.com/jdorn/sql-formatter/", - "keywords": [ - "highlight", - "sql" - ], - "time": "2014-01-12T16:20:24+00:00" - }, - { - "name": "kriswallsmith/assetic", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/kriswallsmith/assetic.git", - "reference": "e911c437dbdf006a8f62c2f59b15b2d69a5e0aa1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kriswallsmith/assetic/zipball/e911c437dbdf006a8f62c2f59b15b2d69a5e0aa1", - "reference": "e911c437dbdf006a8f62c2f59b15b2d69a5e0aa1", - "shasum": "" - }, - "require": { - "php": ">=5.3.1", - "symfony/process": "~2.1|~3.0" - }, - "conflict": { - "twig/twig": "<1.27" - }, - "require-dev": { - "leafo/lessphp": "^0.3.7", - "leafo/scssphp": "~0.1", - "meenie/javascript-packer": "^1.1", - "mrclay/minify": "<2.3", - "natxet/cssmin": "3.0.4", - "patchwork/jsqueeze": "~1.0|~2.0", - "phpunit/phpunit": "~4.8 || ^5.6", - "psr/log": "~1.0", - "ptachoire/cssembed": "~1.0", - "symfony/phpunit-bridge": "~2.7|~3.0", - "twig/twig": "~1.23|~2.0", - "yfix/packager": "dev-master" - }, - "suggest": { - "leafo/lessphp": "Assetic provides the integration with the lessphp LESS compiler", - "leafo/scssphp": "Assetic provides the integration with the scssphp SCSS compiler", - "leafo/scssphp-compass": "Assetic provides the integration with the SCSS compass plugin", - "patchwork/jsqueeze": "Assetic provides the integration with the JSqueeze JavaScript compressor", - "ptachoire/cssembed": "Assetic provides the integration with phpcssembed to embed data uris", - "twig/twig": "Assetic provides the integration with the Twig templating engine" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "psr-0": { - "Assetic": "src/" - }, - "files": [ - "src/functions.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kris Wallsmith", - "email": "kris.wallsmith@gmail.com", - "homepage": "http://kriswallsmith.net/" - } - ], - "description": "Asset Management for PHP", - "homepage": "https://github.com/kriswallsmith/assetic", - "keywords": [ - "assets", - "compression", - "minification" - ], - "time": "2016-11-11T18:43:20+00:00" - }, - { - "name": "monolog/monolog", - "version": "1.23.0", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" - }, - "provide": { - "psr/log-implementation": "1.0.0" - }, - "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Monolog\\": "src/Monolog" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "time": "2017-06-19T01:22:40+00:00" - }, - { - "name": "ocramius/package-versions", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/4489d5002c49d55576fa0ba786f42dbb009be46f", - "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0.0", - "php": "^7.1.0" - }, - "require-dev": { - "composer/composer": "^1.6.3", - "ext-zip": "*", - "infection/infection": "^0.7.1", - "phpunit/phpunit": "^7.0.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PackageVersions\\Installer", - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "PackageVersions\\": "src/PackageVersions" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "time": "2018-02-05T13:05:30+00:00" - }, - { - "name": "ocramius/proxy-manager", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/Ocramius/ProxyManager.git", - "reference": "81d53b2878f1d1c40ad27270e64b51798485dfc5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/81d53b2878f1d1c40ad27270e64b51798485dfc5", - "reference": "81d53b2878f1d1c40ad27270e64b51798485dfc5", - "shasum": "" - }, - "require": { - "ocramius/package-versions": "^1.1.3", - "php": "^7.2.0", - "zendframework/zend-code": "^3.3.0" - }, - "require-dev": { - "couscous/couscous": "^1.6.1", - "ext-phar": "*", - "humbug/humbug": "1.0.0-RC.0@RC", - "nikic/php-parser": "^3.1.1", - "padraic/phpunit-accelerator": "dev-master@DEV", - "phpbench/phpbench": "^0.12.2", - "phpstan/phpstan": "dev-master#856eb10a81c1d27c701a83f167dc870fd8f4236a as 0.9.999", - "phpstan/phpstan-phpunit": "dev-master#5629c0a1f4a9c417cb1077cf6693ad9753895761", - "phpunit/phpunit": "^6.4.3", - "squizlabs/php_codesniffer": "^2.9.1" - }, - "suggest": { - "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects", - "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)", - "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)", - "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "ProxyManager\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.io/" - } - ], - "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", - "homepage": "https://github.com/Ocramius/ProxyManager", - "keywords": [ - "aop", - "lazy loading", - "proxy", - "proxy pattern", - "service proxies" - ], - "time": "2017-11-16T23:22:31+00:00" - }, - { - "name": "paragonie/random_compat", - "version": "v2.0.12", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "shasum": "" - }, - "require": { - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "pseudorandom", - "random" - ], - "time": "2018-04-04T21:24:14+00:00" - }, - { - "name": "phpoffice/phpspreadsheet", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "36acc372875c4d894dc093825ce4f62209db5a76" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/36acc372875c4d894dc093825ce4f62209db5a76", - "reference": "36acc372875c4d894dc093825ce4f62209db5a76", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-iconv": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-xml": "*", - "ext-xmlreader": "*", - "ext-xmlwriter": "*", - "ext-zip": "*", - "ext-zlib": "*", - "php": "^5.6|^7.0", - "psr/simple-cache": "^1.0" - }, - "require-dev": { - "dompdf/dompdf": "^0.8.0", - "friendsofphp/php-cs-fixer": "@stable", - "jpgraph/jpgraph": "^4.0", - "mpdf/mpdf": "^7.0.0", - "phpunit/phpunit": "^5.7", - "squizlabs/php_codesniffer": "^2.7", - "tecnickcom/tcpdf": "^6.2" - }, - "suggest": { - "dompdf/dompdf": "Option for rendering PDF with PDF Writer", - "jpgraph/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", - "mpdf/mpdf": "Option for rendering PDF with PDF Writer", - "tecnick.com/tcpdf": "Option for rendering PDF with PDF Writer" - }, - "type": "library", - "autoload": { - "psr-4": { - "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-or-later" - ], - "authors": [ - { - "name": "Maarten Balliauw", - "homepage": "http://blog.maartenballiauw.be" - }, - { - "name": "Erik Tilt" - }, - { - "name": "Franck Lefevre", - "homepage": "http://rootslabs.net" - }, - { - "name": "Mark Baker", - "homepage": "http://markbakeruk.net" - } - ], - "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", - "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", - "keywords": [ - "OpenXML", - "excel", - "gnumeric", - "ods", - "php", - "spreadsheet", - "xls", - "xlsx" - ], - "time": "2018-04-10T03:53:16+00:00" - }, - { - "name": "psr/cache", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "time": "2016-08-06T20:24:11+00:00" - }, - { - "name": "psr/container", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "time": "2017-02-14T16:28:37+00:00" - }, - { - "name": "psr/link", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/link.git", - "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/link/zipball/eea8e8662d5cd3ae4517c9b864493f59fca95562", - "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Link\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for HTTP links", - "keywords": [ - "http", - "http-link", - "link", - "psr", - "psr-13", - "rest" - ], - "time": "2016-10-28T16:06:13+00:00" - }, - { - "name": "psr/log", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "time": "2016-10-10T12:19:37+00:00" - }, - { - "name": "psr/simple-cache", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for simple caching", - "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" - ], - "time": "2017-10-23T01:57:42+00:00" - }, - { - "name": "sensio/distribution-bundle", - "version": "v5.0.21", - "source": { - "type": "git", - "url": "https://github.com/sensiolabs/SensioDistributionBundle.git", - "reference": "eb6266b3b472e4002538610b28a0a04bcf94891a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/SensioDistributionBundle/zipball/eb6266b3b472e4002538610b28a0a04bcf94891a", - "reference": "eb6266b3b472e4002538610b28a0a04bcf94891a", - "shasum": "" - }, - "require": { - "php": ">=5.3.9", - "sensiolabs/security-checker": "~3.0|~4.0", - "symfony/class-loader": "~2.3|~3.0", - "symfony/config": "~2.3|~3.0", - "symfony/dependency-injection": "~2.3|~3.0", - "symfony/filesystem": "~2.3|~3.0", - "symfony/http-kernel": "~2.3|~3.0", - "symfony/process": "~2.3|~3.0" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Sensio\\Bundle\\DistributionBundle\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Base bundle for Symfony Distributions", - "keywords": [ - "configuration", - "distribution" - ], - "time": "2017-08-25T16:55:44+00:00" - }, - { - "name": "sensiolabs/security-checker", - "version": "v4.1.8", - "source": { - "type": "git", - "url": "https://github.com/sensiolabs/security-checker.git", - "reference": "dc270d5fec418cc6ac983671dba5d80ffaffb142" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/dc270d5fec418cc6ac983671dba5d80ffaffb142", - "reference": "dc270d5fec418cc6ac983671dba5d80ffaffb142", - "shasum": "" - }, - "require": { - "composer/ca-bundle": "^1.0", - "symfony/console": "~2.7|~3.0|~4.0" - }, - "bin": [ - "security-checker" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.1-dev" - } - }, - "autoload": { - "psr-0": { - "SensioLabs\\Security": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien.potencier@gmail.com" - } - ], - "description": "A security checker for your composer.lock", - "time": "2018-02-28T22:10:01+00:00" - }, - { - "name": "symfony/assetic-bundle", - "version": "v2.8.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/assetic-bundle.git", - "reference": "2e0a23a4874838e26de6f025e02fc63328921a4c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/assetic-bundle/zipball/2e0a23a4874838e26de6f025e02fc63328921a4c", - "reference": "2e0a23a4874838e26de6f025e02fc63328921a4c", - "shasum": "" - }, - "require": { - "kriswallsmith/assetic": "~1.4", - "php": ">=5.3.0", - "symfony/console": "~2.3|~3.0", - "symfony/dependency-injection": "~2.3|~3.0", - "symfony/framework-bundle": "~2.3|~3.0", - "symfony/yaml": "~2.3|~3.0" - }, - "conflict": { - "kriswallsmith/spork": "<=0.2", - "twig/twig": "<1.27" - }, - "require-dev": { - "kriswallsmith/spork": "~0.3", - "patchwork/jsqueeze": "~1.0", - "symfony/class-loader": "~2.3|~3.0", - "symfony/css-selector": "~2.3|~3.0", - "symfony/dom-crawler": "~2.3|~3.0", - "symfony/phpunit-bridge": "~2.7|~3.0", - "symfony/twig-bundle": "~2.3|~3.0" - }, - "suggest": { - "kriswallsmith/spork": "to be able to dump assets in parallel", - "symfony/twig-bundle": "to use the Twig integration" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Bundle\\AsseticBundle\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kris Wallsmith", - "email": "kris.wallsmith@gmail.com", - "homepage": "http://kriswallsmith.net/" - } - ], - "description": "Integrates Assetic into Symfony2", - "homepage": "https://github.com/symfony/AsseticBundle", - "keywords": [ - "assets", - "compression", - "minification" - ], - "time": "2017-07-14T07:26:46+00:00" - }, - { - "name": "symfony/monolog-bundle", - "version": "v3.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/monolog-bundle.git", - "reference": "8781649349fe418d51d194f8c9d212c0b97c40dd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/8781649349fe418d51d194f8c9d212c0b97c40dd", - "reference": "8781649349fe418d51d194f8c9d212c0b97c40dd", - "shasum": "" - }, - "require": { - "monolog/monolog": "~1.22", - "php": ">=5.3.2", - "symfony/config": "~2.7|~3.0|~4.0", - "symfony/dependency-injection": "~2.7|~3.0|~4.0", - "symfony/http-kernel": "~2.7|~3.0|~4.0", - "symfony/monolog-bridge": "~2.7|~3.0|~4.0" - }, - "require-dev": { - "symfony/console": "~2.3|~3.0|~4.0", - "symfony/phpunit-bridge": "^3.3|^4.0", - "symfony/yaml": "~2.3|~3.0|~4.0" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Bundle\\MonologBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - }, - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Symfony MonologBundle", - "homepage": "http://symfony.com", - "keywords": [ - "log", - "logging" - ], - "time": "2018-03-05T14:51:36+00:00" - }, - { - "name": "symfony/polyfill-apcu", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-apcu.git", - "reference": "e8ae2136ddb53dea314df56fcd88e318ab936c00" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/e8ae2136ddb53dea314df56fcd88e318ab936c00", - "reference": "e8ae2136ddb53dea314df56fcd88e318ab936c00", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Apcu\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "apcu", - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2018-01-30T19:27:44+00:00" - }, - { - "name": "symfony/polyfill-intl-icu", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "254919c03761d46c29291616576ed003f10e91c1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/254919c03761d46c29291616576ed003f10e91c1", - "reference": "254919c03761d46c29291616576ed003f10e91c1", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/intl": "~2.3|~3.0|~4.0" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7-dev" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's ICU-related data and classes", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "icu", - "intl", - "polyfill", - "portable", - "shim" - ], - "time": "2018-01-30T19:27:44+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", - "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "time": "2018-01-30T19:27:44+00:00" - }, - { - "name": "symfony/polyfill-php56", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "ebc999ce5f14204c5150b9bd15f8f04e621409d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/ebc999ce5f14204c5150b9bd15f8f04e621409d8", - "reference": "ebc999ce5f14204c5150b9bd15f8f04e621409d8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "symfony/polyfill-util": "~1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php56\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2018-01-30T19:27:44+00:00" - }, - { - "name": "symfony/polyfill-php70", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/3532bfcd8f933a7816f3a0a59682fc404776600f", - "reference": "3532bfcd8f933a7816f3a0a59682fc404776600f", - "shasum": "" - }, - "require": { - "paragonie/random_compat": "~1.0|~2.0", - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2018-01-30T19:27:44+00:00" - }, - { - "name": "symfony/polyfill-util", - "version": "v1.7.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-util.git", - "reference": "e17c808ec4228026d4f5a8832afa19be85979563" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/e17c808ec4228026d4f5a8832afa19be85979563", - "reference": "e17c808ec4228026d4f5a8832afa19be85979563", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Util\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony utilities for portability of PHP codes", - "homepage": "https://symfony.com", - "keywords": [ - "compat", - "compatibility", - "polyfill", - "shim" - ], - "time": "2018-01-31T18:08:44+00:00" - }, - { - "name": "symfony/symfony", - "version": "v3.4.8", - "source": { - "type": "git", - "url": "https://github.com/symfony/symfony.git", - "reference": "5304a36c5efbb01af7efe2bb5b1953dbaeebc293" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/symfony/zipball/5304a36c5efbb01af7efe2bb5b1953dbaeebc293", - "reference": "5304a36c5efbb01af7efe2bb5b1953dbaeebc293", - "shasum": "" - }, - "require": { - "doctrine/common": "~2.4", - "ext-xml": "*", - "fig/link-util": "^1.0", - "php": "^5.5.9|>=7.0.8", - "psr/cache": "~1.0", - "psr/container": "^1.0", - "psr/link": "^1.0", - "psr/log": "~1.0", - "psr/simple-cache": "^1.0", - "symfony/polyfill-apcu": "~1.1", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php56": "~1.0", - "symfony/polyfill-php70": "~1.6", - "twig/twig": "^1.35|^2.4.4" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "<3.0||>=3.2.0,<3.2.2", - "phpdocumentor/type-resolver": "<0.2.1", - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" - }, - "provide": { - "psr/cache-implementation": "1.0", - "psr/container-implementation": "1.0", - "psr/log-implementation": "1.0", - "psr/simple-cache-implementation": "1.0" - }, - "replace": { - "symfony/asset": "self.version", - "symfony/browser-kit": "self.version", - "symfony/cache": "self.version", - "symfony/class-loader": "self.version", - "symfony/config": "self.version", - "symfony/console": "self.version", - "symfony/css-selector": "self.version", - "symfony/debug": "self.version", - "symfony/debug-bundle": "self.version", - "symfony/dependency-injection": "self.version", - "symfony/doctrine-bridge": "self.version", - "symfony/dom-crawler": "self.version", - "symfony/dotenv": "self.version", - "symfony/event-dispatcher": "self.version", - "symfony/expression-language": "self.version", - "symfony/filesystem": "self.version", - "symfony/finder": "self.version", - "symfony/form": "self.version", - "symfony/framework-bundle": "self.version", - "symfony/http-foundation": "self.version", - "symfony/http-kernel": "self.version", - "symfony/inflector": "self.version", - "symfony/intl": "self.version", - "symfony/ldap": "self.version", - "symfony/lock": "self.version", - "symfony/monolog-bridge": "self.version", - "symfony/options-resolver": "self.version", - "symfony/process": "self.version", - "symfony/property-access": "self.version", - "symfony/property-info": "self.version", - "symfony/proxy-manager-bridge": "self.version", - "symfony/routing": "self.version", - "symfony/security": "self.version", - "symfony/security-bundle": "self.version", - "symfony/security-core": "self.version", - "symfony/security-csrf": "self.version", - "symfony/security-guard": "self.version", - "symfony/security-http": "self.version", - "symfony/serializer": "self.version", - "symfony/stopwatch": "self.version", - "symfony/templating": "self.version", - "symfony/translation": "self.version", - "symfony/twig-bridge": "self.version", - "symfony/twig-bundle": "self.version", - "symfony/validator": "self.version", - "symfony/var-dumper": "self.version", - "symfony/web-link": "self.version", - "symfony/web-profiler-bundle": "self.version", - "symfony/web-server-bundle": "self.version", - "symfony/workflow": "self.version", - "symfony/yaml": "self.version" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/annotations": "~1.0", - "doctrine/cache": "~1.6", - "doctrine/data-fixtures": "1.0.*", - "doctrine/dbal": "~2.4", - "doctrine/doctrine-bundle": "~1.4", - "doctrine/orm": "~2.4,>=2.4.5", - "egulias/email-validator": "~1.2,>=1.2.8|~2.0", - "monolog/monolog": "~1.11", - "ocramius/proxy-manager": "~0.4|~1.0|~2.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", - "predis/predis": "~1.0", - "symfony/phpunit-bridge": "~3.4|~4.0", - "symfony/security-acl": "~2.8|~3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Bridge\\Doctrine\\": "src/Symfony/Bridge/Doctrine/", - "Symfony\\Bridge\\Monolog\\": "src/Symfony/Bridge/Monolog/", - "Symfony\\Bridge\\ProxyManager\\": "src/Symfony/Bridge/ProxyManager/", - "Symfony\\Bridge\\Twig\\": "src/Symfony/Bridge/Twig/", - "Symfony\\Bundle\\": "src/Symfony/Bundle/", - "Symfony\\Component\\": "src/Symfony/Component/" - }, - "classmap": [ - "src/Symfony/Component/Intl/Resources/stubs" - ], - "exclude-from-classmap": [ - "**/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "The Symfony PHP framework", - "homepage": "https://symfony.com", - "keywords": [ - "framework" - ], - "time": "2018-04-06T15:20:04+00:00" - }, - { - "name": "twig/extensions", - "version": "v1.5.1", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig-extensions.git", - "reference": "d188c76168b853481cc75879ea045bf93d718e9c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig-extensions/zipball/d188c76168b853481cc75879ea045bf93d718e9c", - "reference": "d188c76168b853481cc75879ea045bf93d718e9c", - "shasum": "" - }, - "require": { - "twig/twig": "~1.27|~2.0" - }, - "require-dev": { - "symfony/phpunit-bridge": "~3.3@dev", - "symfony/translation": "~2.3|~3.0" - }, - "suggest": { - "symfony/translation": "Allow the time_diff output to be translated" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5-dev" - } - }, - "autoload": { - "psr-0": { - "Twig_Extensions_": "lib/" - }, - "psr-4": { - "Twig\\Extensions\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Common additional features for Twig that do not directly belong in core", - "homepage": "http://twig.sensiolabs.org/doc/extensions/index.html", - "keywords": [ - "i18n", - "text" - ], - "time": "2017-06-08T18:19:53+00:00" - }, - { - "name": "twig/twig", - "version": "v2.4.8", - "source": { - "type": "git", - "url": "https://github.com/twigphp/Twig.git", - "reference": "7b604c89da162034bdf4bb66310f358d313dd16d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/7b604c89da162034bdf4bb66310f358d313dd16d", - "reference": "7b604c89da162034bdf4bb66310f358d313dd16d", - "shasum": "" - }, - "require": { - "php": "^7.0", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "psr/container": "^1.0", - "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - } - }, - "autoload": { - "psr-0": { - "Twig_": "lib/" - }, - "psr-4": { - "Twig\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" - }, - { - "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" - }, - { - "name": "Twig Team", - "homepage": "http://twig.sensiolabs.org/contributors", - "role": "Contributors" - } - ], - "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "http://twig.sensiolabs.org", - "keywords": [ - "templating" - ], - "time": "2018-04-02T09:24:19+00:00" - }, - { - "name": "zendframework/zend-code", - "version": "3.3.0", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-code.git", - "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/6b1059db5b368db769e4392c6cb6cc139e56640d", - "reference": "6b1059db5b368db769e4392c6cb6cc139e56640d", - "shasum": "" - }, - "require": { - "php": "^7.1", - "zendframework/zend-eventmanager": "^2.6 || ^3.0" - }, - "require-dev": { - "doctrine/annotations": "~1.0", - "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "zendframework/zend-coding-standard": "^1.0.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "suggest": { - "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "zendframework/zend-stdlib": "Zend\\Stdlib component" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev", - "dev-develop": "3.3-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\Code\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "provides facilities to generate arbitrary code using an object oriented interface", - "homepage": "https://github.com/zendframework/zend-code", - "keywords": [ - "code", - "zf2" - ], - "time": "2017-10-20T15:21:32+00:00" - }, - { - "name": "zendframework/zend-eventmanager", - "version": "3.2.0", - "source": { - "type": "git", - "url": "https://github.com/zendframework/zend-eventmanager.git", - "reference": "9d72db10ceb6e42fb92350c0cb54460da61bd79c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/9d72db10ceb6e42fb92350c0cb54460da61bd79c", - "reference": "9d72db10ceb6e42fb92350c0cb54460da61bd79c", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "athletic/athletic": "^0.1", - "container-interop/container-interop": "^1.1.0", - "phpunit/phpunit": "^6.0.7 || ^5.7.14", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-stdlib": "^2.7.3 || ^3.0" - }, - "suggest": { - "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", - "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev", - "dev-develop": "3.3-dev" - } - }, - "autoload": { - "psr-4": { - "Zend\\EventManager\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Trigger and listen to events within a PHP application", - "homepage": "https://github.com/zendframework/zend-eventmanager", - "keywords": [ - "event", - "eventmanager", - "events", - "zf2" - ], - "time": "2017-07-11T19:17:22+00:00" - } - ], - "packages-dev": [ - { - "name": "fzaninotto/faker", - "version": "v1.7.1", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", - "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.0 || ^5.0", - "squizlabs/php_codesniffer": "^1.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2017-08-15T16:48:10+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.7.0", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, - "files": [ - "src/DeepCopy/deep_copy.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2017-10-19T19:58:43+00:00" - }, - { - "name": "phar-io/manifest", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "phar-io/version": "^1.0.1", - "php": "^5.6 || ^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" - }, - { - "name": "phar-io/version", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2017-09-11T18:02:19+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", - "shasum": "" - }, - "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" - }, - "require-dev": { - "doctrine/instantiator": "~1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", - "shasum": "" - }, - "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "time": "2017-07-14T14:27:02+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "1.7.6", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.7.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2018-04-18T13:57:24+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "6.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "774a82c0c5da4c1c7701790c262035d235ab7856" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/774a82c0c5da4c1c7701790c262035d235ab7856", - "reference": "774a82c0c5da4c1c7701790c262035d235ab7856", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": "^7.1", - "phpunit/php-file-iterator": "^1.4.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.1", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "suggest": { - "ext-xdebug": "^2.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2018-04-06T15:39:20+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2017-11-27T13:52:08+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21T13:50:34+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2018-02-01T13:07:23+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/21ad88bbba7c3d93530d93994e0a33cd45f02ace", - "reference": "21ad88bbba7c3d93530d93994e0a33cd45f02ace", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2018-02-01T13:16:43+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "7.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "6d51299e307dc510149e0b7cd1931dd11770e1cb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/6d51299e307dc510149e0b7cd1931dd11770e1cb", - "reference": "6d51299e307dc510149e0b7cd1931dd11770e1cb", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", - "php": "^7.1", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^6.0.1", - "phpunit/php-file-iterator": "^1.4.3", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.0", - "phpunit/phpunit-mock-objects": "^6.1.1", - "sebastian/comparator": "^2.1 || ^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^3.1", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", - "sebastian/version": "^2.0.1" - }, - "require-dev": { - "ext-pdo": "*" - }, - "suggest": { - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2018-04-18T13:41:53+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "6.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/70c740bde8fd9ea9ea295be1cd875dd7b267e157", - "reference": "70c740bde8fd9ea9ea295be1cd875dd7b267e157", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.5", - "php": "^7.1", - "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2018-04-11T04:50:36+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" - }, - { - "name": "sebastian/comparator", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/ed5fd2281113729f1ebcc64d101ad66028aeb3d5", - "reference": "ed5fd2281113729f1ebcc64d101ad66028aeb3d5", - "shasum": "" - }, - "require": { - "php": "^7.1", - "sebastian/diff": "^3.0", - "sebastian/exporter": "^3.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2018-04-18T13:33:00+00:00" - }, - { - "name": "sebastian/diff", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/e09160918c66281713f1c324c1f4c4c3037ba1e8", - "reference": "e09160918c66281713f1c324c1f4c4c3037ba1e8", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "phpunit/phpunit": "^7.0", - "symfony/process": "^2 || ^3.3 || ^4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "time": "2018-02-01T13:45:15+00:00" - }, - { - "name": "sebastian/environment", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2017-07-01T08:51:00+00:00" - }, - { - "name": "sebastian/exporter", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", - "shasum": "" - }, - "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2017-04-03T13:19:02+00:00" - }, - { - "name": "sebastian/global-state", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2017-04-27T15:39:26+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "shasum": "" - }, - "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "shasum": "" - }, - "require": { - "php": ">=5.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" - }, - { - "name": "sebastian/version", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2018-01-29T19:49:41+00:00" - } - ], - "aliases": [], - "minimum-stability": "dev", - "stability-flags": { - "chill-project/person": 20 - }, - "prefer-stable": true, - "prefer-lowest": false, - "platform": { - "php": "~7.2" - }, - "platform-dev": [] -} diff --git a/src/Bundle/ChillThirdPartyBundle/.gitignore b/src/Bundle/ChillThirdPartyBundle/.gitignore deleted file mode 100644 index f443cb81e..000000000 --- a/src/Bundle/ChillThirdPartyBundle/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -composer.lock -vendor/* -parameters.yml -*~ -*.DS_Store -*.sass-cache -Resources/node_modules/ -Tests/Fixtures/App/app/config/parameters.yml -/nbproject/private/ -Resources/test/Fixtures/App/bootstrap.php.cache - diff --git a/src/Bundle/ChillThirdPartyBundle/.gitlab-ci.yml b/src/Bundle/ChillThirdPartyBundle/.gitlab-ci.yml deleted file mode 100644 index cfbaa42e3..000000000 --- a/src/Bundle/ChillThirdPartyBundle/.gitlab-ci.yml +++ /dev/null @@ -1,64 +0,0 @@ -.test_definition: &test_definition - services: - - chill/database:latest - before_script: - - if [ -z ${GITHUB_TOKEN+x} ]; then composer config github-oauth.github.com $GITHUB_TOKEN; fi - - php -d memory_limit=-1 /usr/local/bin/composer install --no-interaction - - cp Resources/test/Fixtures/App/app/config/parameters.gitlab-ci.yml Resources/test/Fixtures/App/app/config/parameters.yml - - php Resources/test/Fixtures/App/app/console --env=test cache:warmup - - php Resources/test/Fixtures/App/app/console doctrine:migrations:migrate --env=test --no-interaction - - php Resources/test/Fixtures/App/app/console doctrine:fixtures:load --env=test --no-interaction - -stages: - - deploy - - test - - build-doc - - deploy-doc - -test:php-7.2: - image: chill/ci-image:php-7.2 - stage: test - <<: *test_definition - script: APP_ENV=test php vendor/bin/phpunit - -deploy-packagist: - stage: deploy - image: chill/ci-image:php-7.2 - before_script: - # test that PACKAGIST USERNAME and PACKAGIST_TOKEN variable are set - - if [ -z ${PACKAGIST_USERNAME+x} ]; then echo "Please set PACKAGIST_USERNAME variable"; exit -1; fi - - if [ -z ${PACKAGIST_TOKEN+x} ]; then echo "Please set PACKAGIST_TOKEN variable"; exit -1; fi - script: - - STATUSCODE=$(curl -XPOST -H'content-type:application/json' "https://packagist.org/api/update-package?username=$PACKAGIST_USERNAME&apiToken=$PACKAGIST_TOKEN" -d"{\"repository\":{\"url\":\"$CI_PROJECT_URL.git\"}}" --silent --output /dev/stderr --write-out "%{http_code}") - - if [ $STATUSCODE = "202" ]; then exit 0; else exit $STATUSCODE; fi - -# deploy documentation -api-doc-build: - stage: build-doc - environment: api-doc - image: chill/ci-image:php-7.2 - before_script: - - mkdir api-doc - script: apigen generate --destination api-doc/$CI_BUILD_REF_NAME/$CI_PROJECT_NAME - artifacts: - paths: - - "api-doc/" - name: api - expire_in: '2h' - only: - - master - - tags - -api-doc-deploy: - stage: deploy-doc - image: pallet/swiftclient:latest - before_script: - # test that CONTAINER_API variable is set - - if [ -z ${CONTAINER_API+x} ]; then echo "Please set CONTAINER_API variable"; exit -1; fi - # go to api-doc to have and url with PROJECT/BUILD - - cd api-doc - # upload, and keep files during 1 year - script: "swift upload --header \"X-Delete-After: 31536000\" $CONTAINER_API $CI_BUILD_REF_NAME/$CI_PROJECT_NAME" - only: - - master - - tags diff --git a/src/Bundle/ChillThirdPartyBundle/CHANGELOG.md b/src/Bundle/ChillThirdPartyBundle/CHANGELOG.md deleted file mode 100644 index 5653415df..000000000 --- a/src/Bundle/ChillThirdPartyBundle/CHANGELOG.md +++ /dev/null @@ -1,8 +0,0 @@ - -branch master -============= - -* initial commit -* add a render template for entity ThirdParty ; -* remove dump messages ; - diff --git a/src/Bundle/ChillThirdPartyBundle/LICENSE b/src/Bundle/ChillThirdPartyBundle/LICENSE deleted file mode 100644 index ddfcbd34e..000000000 --- a/src/Bundle/ChillThirdPartyBundle/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ -GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - -Copyright (C) 2007 Free Software Foundation, Inc. -Everyone is permitted to copy and distribute verbatim copies -of this license document, but changing it is not allowed. - - Preamble - -The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - -The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - -When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - -Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - -A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - -The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - -An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - -The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - -0. Definitions. - -"This License" refers to version 3 of the GNU Affero General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based -on the Program. - -To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -1. Source Code. - -The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - -A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - -The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - -The Corresponding Source for a work in source code form is that -same work. - -2. Basic Permissions. - -All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - -3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - -When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - -4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - -5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified -it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is -released under this License and any conditions added under section -7. This requirement modifies the requirement in section 4 to -"keep intact all notices". - -c) You must license the entire work, as a whole, under this -License to anyone who comes into possession of a copy. This -License will therefore apply, along with any applicable section 7 -additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no -permission to license the work in any other way, but it does not -invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display -Appropriate Legal Notices; however, if the Program has interactive -interfaces that do not display Appropriate Legal Notices, your -work need not make them do so. - -A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - -6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - -a) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by the -Corresponding Source fixed on a durable physical medium -customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product -(including a physical distribution medium), accompanied by a -written offer, valid for at least three years and valid for as -long as you offer spare parts or customer support for that product -model, to give anyone who possesses the object code either (1) a -copy of the Corresponding Source for all the software in the -product that is covered by this License, on a durable physical -medium customarily used for software interchange, for a price no -more than your reasonable cost of physically performing this -conveying of source, or (2) access to copy the -Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the -written offer to provide the Corresponding Source. This -alternative is allowed only occasionally and noncommercially, and -only if you received the object code with such an offer, in accord -with subsection 6b. - -d) Convey the object code by offering access from a designated -place (gratis or for a charge), and offer equivalent access to the -Corresponding Source in the same way through the same place at no -further charge. You need not require recipients to copy the -Corresponding Source along with the object code. If the place to -copy the object code is a network server, the Corresponding Source -may be on a different server (operated by you or a third party) -that supports equivalent copying facilities, provided you maintain -clear directions next to the object code saying where to find the -Corresponding Source. Regardless of what server hosts the -Corresponding Source, you remain obligated to ensure that it is -available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided -you inform other peers where the object code and Corresponding -Source of the work are being offered to the general public at no -charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - -If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - -The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - -7. Additional Terms. - -"Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the -terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or -author attributions in that material or in the Appropriate Legal -Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or -requiring that modified versions of such material be marked in -reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or -authors of the material; or - -e) Declining to grant rights under trademark law for use of some -trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that -material by anyone who conveys the material (or modified versions of -it) with contractual assumptions of liability to the recipient, for -any liability that these contractual assumptions directly impose on -those licensors and authors. - -All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - -8. Termination. - -You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - -However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - -9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - -10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - -11. Patents. - -A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - -If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - -A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - -12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - -13. Remote Network Interaction; Use with the GNU General Public License. - -Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - -Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - -14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - -Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - -15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - -17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - -END OF TERMS AND CONDITIONS - -How to Apply These Terms to Your New Programs - -If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - -To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - -Copyright (C) - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - -If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - -You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/src/Bundle/ChillThirdPartyBundle/composer.json b/src/Bundle/ChillThirdPartyBundle/composer.json deleted file mode 100644 index d54112504..000000000 --- a/src/Bundle/ChillThirdPartyBundle/composer.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "chill-project/third-party", - "license": "AGPL-3.0", - "type": "symfony-bundle", - "description": "A bundle to manage Third Party contact categories", - "keywords" : ["chill", "social work", "contacts"], - "homepage" : "https://framagit.org/Chill-project/Chill-ThirdParty", - "autoload": { - "psr-4": { "Chill\\ThirdPartyBundle\\": "" } - }, - "autoload-dev": { - "classmap": [ "Resources/test/Fixtures/App/app/AppKernel.php" ] - }, - "authors" : [ - { - "name": "Champs-Libres", - "email": "info@champs-libres.coop", - "homepage": "http://www.champs-libres.coop" - } - ], - "require": { - }, - "require-dev": { - }, - "scripts": { - "post-install-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ], - "post-update-cmd": [ - "ComposerBundleMigration\\Composer\\Migrations::synchronizeMigrations", - "Sensio\\Bundle\\DistributionBundle\\Composer\\ScriptHandler::buildBootstrap" - ] - }, - "extra": { - "app-migrations-dir": "Resources/test/Fixtures/App/app/DoctrineMigrations", - "symfony-app-dir": "Tests/Fixtures/App/" - } -} diff --git a/src/Bundle/ChillWopiBundle/.editorconfig b/src/Bundle/ChillWopiBundle/.editorconfig deleted file mode 100644 index d769b46a4..000000000 --- a/src/Bundle/ChillWopiBundle/.editorconfig +++ /dev/null @@ -1,19 +0,0 @@ -; top-most EditorConfig file -root = true - -; Unix-style newlines -[*] -charset = utf-8 -end_of_line = LF -insert_final_newline = true -trim_trailing_whitespace = true - -[*.{php,html,twig}] -indent_style = space -indent_size = 4 - -[*.md] -max_line_length = 80 - -[COMMIT_EDITMSG] -max_line_length = 0 diff --git a/src/Bundle/ChillWopiBundle/.gitignore b/src/Bundle/ChillWopiBundle/.gitignore deleted file mode 100644 index f443cb81e..000000000 --- a/src/Bundle/ChillWopiBundle/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -composer.lock -vendor/* -parameters.yml -*~ -*.DS_Store -*.sass-cache -Resources/node_modules/ -Tests/Fixtures/App/app/config/parameters.yml -/nbproject/private/ -Resources/test/Fixtures/App/bootstrap.php.cache - diff --git a/src/Bundle/ChillWopiBundle/LICENSE b/src/Bundle/ChillWopiBundle/LICENSE deleted file mode 100644 index be3f7b28e..000000000 --- a/src/Bundle/ChillWopiBundle/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/src/Bundle/ChillWopiBundle/composer.json b/src/Bundle/ChillWopiBundle/composer.json deleted file mode 100644 index 41737c8b1..000000000 --- a/src/Bundle/ChillWopiBundle/composer.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "chill-project/chill-wopi-bundle", - "type": "symfony-bundle", - "description": "TODO", - "keywords": [ - "chill", - "wopi" - ], - "homepage": "http://www.champs-libres.coop", - "license": "AGPL-3.0", - "authors": [ - { - "name": "Champs-Libres", - "email": "info@champs-libres.coop", - "homepage": "http://www.champs-libres.coop" - } - ], - "require": { - "php": ">= 7.4", - "champs-libres/wopi-bundle": "dev-master", - "nyholm/psr7": "^1.4", - "symfony/mime": "^4 || ^5" - }, - "autoload": { - "psr-4": { - "Chill\\WopiBundle\\": "src/" - } - } -} diff --git a/src/Bundle/ChillWopiBundle/grumphp.yml b/src/Bundle/ChillWopiBundle/grumphp.yml deleted file mode 100644 index c6fe2e7f5..000000000 --- a/src/Bundle/ChillWopiBundle/grumphp.yml +++ /dev/null @@ -1,9 +0,0 @@ -imports: - - { resource: vendor/drupol/php-conventions/config/php73/grumphp.yml } - -parameters: - tasks.license.holder: Champs-Libres - tasks.license.date_from: 2021 - tasks.license.name: AGPL-3.0 - extra_tasks: - phpunit: ~ diff --git a/src/Bundle/ChillWopiBundle/psalm.xml b/src/Bundle/ChillWopiBundle/psalm.xml deleted file mode 100644 index 30258a709..000000000 --- a/src/Bundle/ChillWopiBundle/psalm.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - diff --git a/src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts b/src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts index 74082c224..98dbac4d4 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts +++ b/src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts @@ -1,40 +1,40 @@ import { is_object_ready } from "../../../../../../ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers"; import { - StoredObject, - StoredObjectStatus, - StoredObjectStatusChange, + StoredObject, + StoredObjectStatus, + StoredObjectStatusChange, } from "../../../../../../ChillDocStoreBundle/Resources/public/types"; async function reload_if_needed( - stored_object: StoredObject, - i: number, + stored_object: StoredObject, + i: number, ): Promise { - const current_status = await is_object_ready(stored_object); + const current_status = await is_object_ready(stored_object); - if (stored_object.status !== current_status.status) { - window.location.reload(); - } - wait_before_reload(stored_object, i + 1); - return Promise.resolve(); + if (stored_object.status !== current_status.status) { + window.location.reload(); + } + wait_before_reload(stored_object, i + 1); + return Promise.resolve(); } function wait_before_reload(stored_object: StoredObject, i: number): void { - /** - * value of the timeout. Set to 5 sec during the first 10 minutes, then every 1 minute - */ - const timeout = i < 1200 ? 5000 : 60000; + /** + * value of the timeout. Set to 5 sec during the first 10 minutes, then every 1 minute + */ + const timeout = i < 1200 ? 5000 : 60000; - setTimeout(reload_if_needed, timeout, stored_object, i); + setTimeout(reload_if_needed, timeout, stored_object, i); } window.addEventListener("DOMContentLoaded", async function (e) { - if (undefined === (window as any).stored_object) { - console.error("window.stored_object is undefined"); - throw Error("window.stored_object is undefined"); - } + if (undefined === (window as any).stored_object) { + console.error("window.stored_object is undefined"); + throw Error("window.stored_object is undefined"); + } - const stored_object = JSON.parse( - (window as any).stored_object, - ) as StoredObject; - reload_if_needed(stored_object, 0); + const stored_object = JSON.parse( + (window as any).stored_object, + ) as StoredObject; + reload_if_needed(stored_object, 0); }); diff --git a/tests/Kernel.php b/tests/Kernel.php deleted file mode 100644 index 2956c899b..000000000 --- a/tests/Kernel.php +++ /dev/null @@ -1,34 +0,0 @@ -getConfigDir().'/bundles.php'; - } - - private function getConfigDir(): string - { - return $this->getProjectDir().'/tests/app/config'; - } -} diff --git a/tests/app/config/packages/wopi.yaml b/tests/app/config/packages/wopi.yaml deleted file mode 100644 index e02694625..000000000 --- a/tests/app/config/packages/wopi.yaml +++ /dev/null @@ -1,2 +0,0 @@ -wopi: - server: "%env(resolve:EDITOR_SERVER)%" diff --git a/tests/app/config/packages/workflow_chill.yaml b/tests/app/config/packages/workflow_chill.yaml deleted file mode 100644 index 82662461b..000000000 --- a/tests/app/config/packages/workflow_chill.yaml +++ /dev/null @@ -1,294 +0,0 @@ -framework: - workflows: - vendee_internal: - type: state_machine - metadata: - related_entity: - - Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument - - Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork - - Chill\DocStoreBundle\Entity\AccompanyingCourseDocument - label: - fr: 'Suivi' - support_strategy: Chill\MainBundle\Workflow\RelatedEntityWorkflowSupportsStrategy - initial_marking: 'initial' - marking_store: - property: step - type: method - places: - initial: - metadata: - label: - fr: Étape initiale - attenteModification: - metadata: - label: - fr: En attente de modification du document - validationFilterInputLabels: - forward: {fr: Modification effectuée} - backward: {fr: Pas de modification effectuée} - neutral: {fr: Autre} - attenteMiseEnForme: - metadata: - label: - fr: En attente de mise en forme - validationFilterInputLabels: - forward: {fr: Mise en forme terminée} - backward: {fr: Pas de mise en forme effectuée} - neutral: {fr: Autre} - attenteVisa: - metadata: - label: - fr: En attente de visa - validationFilterInputLabels: - forward: {fr: Visa accordé} - backward: {fr: Visa refusé} - neutral: {fr: Autre} - attenteSignature: - metadata: - label: - fr: En attente de signature - validationFilterInputLabels: - forward: {fr: Signature accordée} - backward: {fr: Signature refusée} - neutral: {fr: Autre} - attenteTraitement: - metadata: - label: - fr: En attente de traitement - validationFilterInputLabels: - forward: {fr: Traitement terminé favorablement} - backward: {fr: Traitement terminé défavorablement} - neutral: {fr: Autre} - attenteEnvoi: - metadata: - label: - fr: En attente d'envoi - validationFilterInputLabels: - forward: {fr: Document envoyé} - backward: {fr: Document non envoyé} - neutral: {fr: Autre} - attenteValidationMiseEnForme: - metadata: - label: - fr: En attente de validation de la mise en forme - validationFilterInputLabels: - forward: {fr: Validation de la mise en forme} - backward: {fr: Refus de validation de la mise en forme} - neutral: {fr: Autre} - annule: - metadata: - isFinal: true - isFinalPositive: false - label: - fr: Annulé - final: - metadata: - isFinal: true - isFinalPositive: true - label: - fr: Finalisé - transitions: - # transition qui avancent - demandeModificationDocument: - from: - - initial - to: attenteModification - metadata: - label: - fr: Demande de modification du document - isForward: true - demandeMiseEnForme: - from: - - initial - - attenteModification - to: attenteMiseEnForme - metadata: - label: - fr: Demande de mise en forme - isForward: true - demandeValidationMiseEnForme: - from: - - attenteMiseEnForme - to: attenteValidationMiseEnForme - metadata: - label: - fr: Demande de validation de la mise en forme - isForward: true - demandeVisa: - from: - - initial - - attenteModification - - attenteMiseEnForme - - attenteValidationMiseEnForme - to: attenteVisa - metadata: - label: - fr: Demande de visa - isForward: true - demandeSignature: - from: - - initial - - attenteModification - - attenteMiseEnForme - - attenteValidationMiseEnForme - - attenteVisa - to: attenteSignature - metadata: - label: {fr: Demande de signature} - isForward: true - demandeTraitement: - from: - - initial - - attenteModification - - attenteMiseEnForme - - attenteValidationMiseEnForme - - attenteVisa - - attenteSignature - to: attenteTraitement - metadata: - label: {fr: Demande de traitement} - isForward: true - demandeEnvoi: - from: - - initial - - attenteModification - - attenteMiseEnForme - - attenteValidationMiseEnForme - - attenteVisa - - attenteSignature - - attenteTraitement - to: attenteEnvoi - metadata: - label: {fr: Demande d'envoi} - isForward: true - annulation: - from: - - initial - - attenteModification - - attenteMiseEnForme - - attenteValidationMiseEnForme - - attenteVisa - - attenteSignature - - attenteTraitement - - attenteEnvoi - to: annule - metadata: - label: {fr: Annulation} - isForward: false - # transitions qui répètent l'étape - demandeMiseEnFormeSupplementaire: - from: - - attenteMiseEnForme - - attenteValidationMiseEnForme - to: attenteMiseEnForme - metadata: - label: {fr: Demande de mise en forme supplémentaire} - demandeVisaSupplementaire: - from: - - attenteVisa - to: attenteVisa - metadata: - label: {fr: Demande de visa supplémentaire} - isForward: true - demandeSignatureSupplementaire: - from: - - attenteSignature - to: attenteSignature - metadata: - label: {fr: Demande de signature supplémentaire} - demandeTraitementSupplementaire: - from: - - attenteTraitement - to: attenteTraitement - metadata: - label: {fr: Demande de traitement supplémentaire} - # transitions qui renvoient vers une étape précédente - refusEtModificationDocument: - from: - - attenteVisa - - attenteSignature - - attenteTraitement - - attenteEnvoi - to: attenteModification - metadata: - label: - fr: Refus et demande de modification du document - isForward: false - refusEtDemandeMiseEnForme: - from: - - attenteVisa - - attenteSignature - - attenteTraitement - - attenteEnvoi - to: attenteMiseEnForme - metadata: - label: {fr: Refus et demande de mise en forme} - isForward: false - refusEtDemandeVisa: - from: - - attenteSignature - - attenteTraitement - - attenteEnvoi - to: attenteVisa - metadata: - label: {fr: Refus et demande de visa} - isForward: false - refusEtDemandeSignature: - from: - - attenteTraitement - - attenteEnvoi - to: attenteSignature - metadata: - label: {fr: Refus et demande de signature} - isForward: false - refusEtDemandeTraitement: - from: - - attenteEnvoi - to: attenteTraitement - metadata: - label: {fr: Refus et demande de traitement} - isForward: false - # transition vers final - initialToFinal: - from: - - initial - to: final - metadata: - label: {fr: Clotûre immédiate et cloture positive} - isForward: true - attenteMiseEnFormeToFinal: - from: - - attenteMiseEnForme - - attenteValidationMiseEnForme - to: final - metadata: - label: {fr: Mise en forme terminée et cloture positive} - isForward: true - attenteVisaToFinal: - from: - - attenteVisa - to: final - metadata: - label: {fr: Accorde le visa et cloture positive} - isForward: true - attenteSignatureToFinal: - from: - - attenteSignature - to: final - metadata: - label: {fr: Accorde la signature et cloture positive} - isForward: true - attenteTraitementToFinal: - from: - - attenteTraitement - to: final - metadata: - label: {fr: Traitement terminé et cloture postive} - isForward: true - attenteEnvoiToFinal: - from: - - attenteEnvoi - to: final - metadata: - label: {fr: Envoyé et cloture postive} - isForward: true From a35d45630839c335dfddaabed6899edd721c2225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 4 Jun 2025 16:54:44 +0200 Subject: [PATCH 41/83] Fix participant condition in list_with_period.html.twig Updated the condition to properly handle cases where the first participation's person is not the current person. --- .../Resources/views/Person/list_with_period.html.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig index 7d06c51c3..670ce2ec7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig @@ -96,7 +96,7 @@
    {% endif %} - {% if acp.currentParticipations|length > 1 %} + {% if acp.currentParticipations|length > 1 or (acp.currentParticipations|first).person is not same as person %}

    From fca10ada717b84bfe9dce44ddb36c7a1f3592ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 4 Jun 2025 16:54:58 +0200 Subject: [PATCH 42/83] Fix translation keys and participant pluralization in list_with_period.html.twig Updated French translations for "Participants" and improved pluralization handling in accompanying_period keys. Modified list_with_period.html.twig to dynamically translate "Participants" based on the count of current participations. --- .changes/unreleased/Fixed-20250604-165550.yaml | 6 ++++++ .../Resources/views/Person/list_with_period.html.twig | 2 +- .../translations/messages+intl-icu.fr.yaml | 8 ++++++++ src/Bundle/ChillPersonBundle/translations/messages.fr.yml | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 .changes/unreleased/Fixed-20250604-165550.yaml diff --git a/.changes/unreleased/Fixed-20250604-165550.yaml b/.changes/unreleased/Fixed-20250604-165550.yaml new file mode 100644 index 000000000..0544c9402 --- /dev/null +++ b/.changes/unreleased/Fixed-20250604-165550.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Display the list of participant in the results, even if there is only one participant and that the search result display the requestor +time: 2025-06-04T16:55:50.107852336+02:00 +custom: + Issue: "390" + SchemaChange: No schema change diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig index 670ce2ec7..41bc51864 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Person/list_with_period.html.twig @@ -100,7 +100,7 @@

    - {{ 'Participants'|trans }} + {{ 'accompanying_period.Participants_without_count'|trans({count: acp.currentParticipations|length}) }}

    diff --git a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml index 830e21d2d..6e42a362b 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml @@ -13,6 +13,14 @@ Requestor: >- neutral {Demandeur·euse} } +accompanying_period: + Participants_without_count: >- + {count, plural, + =0 {Participant} + =1 {Participant} + other {Participants} + } + person: from_the: depuis le And himself: >- diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 9bce912a0..0f62e4f5b 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -208,7 +208,7 @@ Pediod closing form is not valid: Le formulaire n'est pas valide Accompanying user: Accompagnant No accompanying user: Aucun accompagnant No data given: Pas d'information -Participants: Usagers impliquées +Participants: Usagers impliqués Create an accompanying course: Créer un parcours Accompanying courses of users: Parcours des utilisateurs This accompanying course is still a draft: Ce parcours est encore à l'état brouillon. From 47cf83ef93cafe9c92249d7a26b7e97970aa5d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 5 Jun 2025 11:30:01 +0200 Subject: [PATCH 43/83] Update CI configuration to use `chill/base-image:8.3-edge` instead of the old PHP 8.2 base image --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 02c01640c..355524fa4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -46,7 +46,7 @@ stages: build: stage: Composer install - image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 + image: chill/base-image:8.3-edge before_script: - composer config -g cache-dir "$(pwd)/.cache" script: @@ -61,7 +61,7 @@ build: code_style: stage: Tests - image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 + image: chill/base-image:8.3-edge script: - php-cs-fixer fix --dry-run -v --show-progress=none cache: @@ -74,7 +74,7 @@ code_style: phpstan_tests: stage: Tests - image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 + image: chill/base-image:8.3-edge variables: COMPOSER_MEMORY_LIMIT: 3G before_script: @@ -91,7 +91,7 @@ phpstan_tests: rector_tests: stage: Tests - image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 + image: chill/base-image:8.3-edge before_script: - bin/console cache:clear --env=dev script: @@ -132,7 +132,7 @@ lint: unit_tests: stage: Tests - image: gitea.champs-libres.be/chill-project/chill-skeleton-basic/base-image:php82 + image: chill/base-image:8.3-edge variables: COMPOSER_MEMORY_LIMIT: 3G before_script: From 7d0fe0665184a008ec3213b7cf849f8ff6d3fa70 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 11 Jun 2025 16:46:04 +0200 Subject: [PATCH 44/83] Fix admin entity edit actions for event admin entities and activity reason (category) entities --- .../ActivityReasonCategoryController.php | 28 ++----------------- .../Controller/ActivityReasonController.php | 24 +--------------- .../ActivityReasonCategory/index.html.twig | 2 +- .../Controller/EventTypeController.php | 4 +-- .../Controller/RoleController.php | 2 +- .../Controller/StatusController.php | 2 +- 6 files changed, 8 insertions(+), 54 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php index 0d337416b..b5af48674 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php @@ -48,28 +48,6 @@ class ActivityReasonCategoryController extends AbstractController ]); } - /** - * Displays a form to edit an existing ActivityReasonCategory entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/edit', name: 'chill_activity_activityreasoncategory_edit')] - public function editAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(ActivityReasonCategory::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.'); - } - - $editForm = $this->createEditForm($entity); - - return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [ - 'entity' => $entity, - 'edit_form' => $editForm->createView(), - ]); - } - /** * Lists all ActivityReasonCategory entities. */ @@ -122,7 +100,7 @@ class ActivityReasonCategoryController extends AbstractController /** * Edits an existing ActivityReasonCategory entity. */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/update', name: 'chill_activity_activityreasoncategory_update', methods: ['POST', 'PUT'])] + #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/update', name: 'chill_activity_activityreasoncategory_update')] public function updateAction(Request $request, mixed $id) { $em = $this->managerRegistry->getManager(); @@ -139,7 +117,7 @@ class ActivityReasonCategoryController extends AbstractController if ($editForm->isSubmitted() && $editForm->isValid()) { $em->flush(); - return $this->redirectToRoute('chill_activity_activityreasoncategory_edit', ['id' => $id]); + return $this->redirectToRoute('chill_activity_activityreasoncategory', ['id' => $id]); } return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [ @@ -178,7 +156,7 @@ class ActivityReasonCategoryController extends AbstractController { $form = $this->createForm(ActivityReasonCategoryType::class, $entity, [ 'action' => $this->generateUrl('chill_activity_activityreasoncategory_update', ['id' => $entity->getId()]), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php index 37d04d367..77dd6c9d4 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php @@ -50,28 +50,6 @@ class ActivityReasonController extends AbstractController ]); } - /** - * Displays a form to edit an existing ActivityReason entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/edit', name: 'chill_activity_activityreason_edit')] - public function editAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(ActivityReason::class)->find($id); - - if (null === $entity) { - throw new NotFoundHttpException('Unable to find ActivityReason entity.'); - } - - $editForm = $this->createEditForm($entity); - - return $this->render('@ChillActivity/ActivityReason/edit.html.twig', [ - 'entity' => $entity, - 'edit_form' => $editForm->createView(), - ]); - } - /** * Lists all ActivityReason entities. */ @@ -180,7 +158,7 @@ class ActivityReasonController extends AbstractController { $form = $this->createForm(ActivityReasonType::class, $entity, [ 'action' => $this->generateUrl('chill_activity_activityreason_update', ['id' => $entity->getId()]), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); diff --git a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig index 5f48180b3..e56c874b1 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig @@ -26,7 +26,7 @@
  • - +
  • diff --git a/src/Bundle/ChillEventBundle/Controller/EventTypeController.php b/src/Bundle/ChillEventBundle/Controller/EventTypeController.php index f7b1b205c..97719d64d 100644 --- a/src/Bundle/ChillEventBundle/Controller/EventTypeController.php +++ b/src/Bundle/ChillEventBundle/Controller/EventTypeController.php @@ -87,12 +87,10 @@ class EventTypeController extends AbstractController } $editForm = $this->createEditForm($entity); - $deleteForm = $this->createDeleteForm($id); return $this->render('@ChillEvent/EventType/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -228,7 +226,7 @@ class EventTypeController extends AbstractController 'chill_eventtype_admin_update', ['id' => $entity->getId()] ), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); diff --git a/src/Bundle/ChillEventBundle/Controller/RoleController.php b/src/Bundle/ChillEventBundle/Controller/RoleController.php index 1ff205dfc..10e978a6b 100644 --- a/src/Bundle/ChillEventBundle/Controller/RoleController.php +++ b/src/Bundle/ChillEventBundle/Controller/RoleController.php @@ -226,7 +226,7 @@ class RoleController extends AbstractController 'chill_event_admin_role_update', ['id' => $entity->getId()] ), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); diff --git a/src/Bundle/ChillEventBundle/Controller/StatusController.php b/src/Bundle/ChillEventBundle/Controller/StatusController.php index 3b302500d..f83fa9480 100644 --- a/src/Bundle/ChillEventBundle/Controller/StatusController.php +++ b/src/Bundle/ChillEventBundle/Controller/StatusController.php @@ -222,7 +222,7 @@ class StatusController extends AbstractController { $form = $this->createForm(StatusType::class, $entity, [ 'action' => $this->generateUrl('chill_event_admin_status_update', ['id' => $entity->getId()]), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); From f680a35f49b580fcbb7e942a8fa8feb7f56017b0 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 11 Jun 2025 17:10:56 +0200 Subject: [PATCH 45/83] Improve admin templates for event admin entities + activity reason (category) entities and remove delete and show actions for coherence --- .../unreleased/Fixed-20250611-164623.yaml | 6 ++ .../ActivityReasonCategoryController.php | 19 ------ .../Controller/ActivityReasonController.php | 21 +----- .../views/ActivityReason/index.html.twig | 7 +- .../ActivityReasonCategory/index.html.twig | 5 +- .../Controller/EventTypeController.php | 64 ------------------- .../Controller/RoleController.php | 64 ------------------- .../Controller/StatusController.php | 63 ------------------ .../Resources/views/EventType/edit.html.twig | 2 +- .../Resources/views/EventType/index.html.twig | 7 +- .../Resources/views/EventType/new.html.twig | 2 +- .../Resources/views/EventType/show.html.twig | 7 +- .../Resources/views/Role/edit.html.twig | 4 +- .../Resources/views/Role/index.html.twig | 7 +- .../Resources/views/Role/new.html.twig | 2 +- .../Resources/views/Role/show.html.twig | 7 +- .../Resources/views/Status/edit.html.twig | 2 +- .../Resources/views/Status/index.html.twig | 7 +- .../Resources/views/Status/new.html.twig | 2 +- .../Resources/views/Status/show.html.twig | 7 +- 20 files changed, 26 insertions(+), 279 deletions(-) create mode 100644 .changes/unreleased/Fixed-20250611-164623.yaml diff --git a/.changes/unreleased/Fixed-20250611-164623.yaml b/.changes/unreleased/Fixed-20250611-164623.yaml new file mode 100644 index 000000000..8bb956c34 --- /dev/null +++ b/.changes/unreleased/Fixed-20250611-164623.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Fix admin entity edit actions for event admin entities and activity reason (category) entities +time: 2025-06-11T16:46:23.113506434+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php index b5af48674..6ccddcc97 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php @@ -78,25 +78,6 @@ class ActivityReasonCategoryController extends AbstractController ]); } - /** - * Finds and displays a ActivityReasonCategory entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/show', name: 'chill_activity_activityreasoncategory_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(ActivityReasonCategory::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.'); - } - - return $this->render('@ChillActivity/ActivityReasonCategory/show.html.twig', [ - 'entity' => $entity, - ]); - } - /** * Edits an existing ActivityReasonCategory entity. */ diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php index 77dd6c9d4..0167273c5 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php @@ -80,29 +80,10 @@ class ActivityReasonController extends AbstractController ]); } - /** - * Finds and displays a ActivityReason entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/show', name: 'chill_activity_activityreason_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(ActivityReason::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find ActivityReason entity.'); - } - - return $this->render('@ChillActivity/ActivityReason/show.html.twig', [ - 'entity' => $entity, - ]); - } - /** * Edits an existing ActivityReason entity. */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/update', name: 'chill_activity_activityreason_update', methods: ['POST', 'PUT'])] + #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/update', name: 'chill_activity_activityreason_update')] public function updateAction(Request $request, mixed $id) { $em = $this->managerRegistry->getManager(); diff --git a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig index 855c9386d..ee36958c3 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig @@ -3,7 +3,7 @@ {% block admin_content %}

    {{ 'ActivityReason list'|trans }}

    - +
    @@ -29,10 +29,7 @@ diff --git a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig index e56c874b1..f64ce359d 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig @@ -3,7 +3,7 @@ {% block admin_content %}

    {{ 'ActivityReasonCategory list'|trans }}

    -
    {{ 'Name'|trans }}
    • - -
    • -
    • - +
    +
    @@ -22,9 +22,6 @@ {% for entity in entities %} - + - +
    {{ 'Name'|trans }}
      -
    • - -
    • diff --git a/src/Bundle/ChillEventBundle/Controller/EventTypeController.php b/src/Bundle/ChillEventBundle/Controller/EventTypeController.php index 97719d64d..969e60cb7 100644 --- a/src/Bundle/ChillEventBundle/Controller/EventTypeController.php +++ b/src/Bundle/ChillEventBundle/Controller/EventTypeController.php @@ -48,30 +48,6 @@ class EventTypeController extends AbstractController ]); } - /** - * Deletes a EventType entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/event_type/{id}/delete', name: 'chill_eventtype_admin_delete', methods: ['POST', 'DELETE'])] - public function deleteAction(Request $request, mixed $id) - { - $form = $this->createDeleteForm($id); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $em = $this->managerRegistry->getManager(); - $entity = $em->getRepository(EventType::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find EventType entity.'); - } - - $em->remove($entity); - $em->flush(); - } - - return $this->redirectToRoute('chill_eventtype_admin'); - } - /** * Displays a form to edit an existing EventType entity. */ @@ -124,28 +100,6 @@ class EventTypeController extends AbstractController ]); } - /** - * Finds and displays a EventType entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/event_type/{id}/show', name: 'chill_eventtype_admin_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(EventType::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find EventType entity.'); - } - - $deleteForm = $this->createDeleteForm($id); - - return $this->render('@ChillEvent/EventType/show.html.twig', [ - 'entity' => $entity, - 'delete_form' => $deleteForm->createView(), - ]); - } - /** * Edits an existing EventType entity. */ @@ -160,7 +114,6 @@ class EventTypeController extends AbstractController throw $this->createNotFoundException('Unable to find EventType entity.'); } - $deleteForm = $this->createDeleteForm($id); $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); @@ -173,7 +126,6 @@ class EventTypeController extends AbstractController return $this->render('@ChillEvent/EventType/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -196,22 +148,6 @@ class EventTypeController extends AbstractController return $form; } - /** - * Creates a form to delete a EventType entity by id. - * - * @return \Symfony\Component\Form\FormInterface The form - */ - private function createDeleteForm(mixed $id) - { - return $this->createFormBuilder() - ->setAction($this->generateUrl( - 'chill_eventtype_admin_delete', - ['id' => $id] - )) - ->add('submit', SubmitType::class, ['label' => 'Delete']) - ->getForm(); - } - /** * Creates a form to edit a EventType entity. * diff --git a/src/Bundle/ChillEventBundle/Controller/RoleController.php b/src/Bundle/ChillEventBundle/Controller/RoleController.php index 10e978a6b..9d0c38c92 100644 --- a/src/Bundle/ChillEventBundle/Controller/RoleController.php +++ b/src/Bundle/ChillEventBundle/Controller/RoleController.php @@ -48,30 +48,6 @@ class RoleController extends AbstractController ]); } - /** - * Deletes a Role entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/role/{id}/delete', name: 'chill_event_admin_role_delete', methods: ['POST', 'DELETE'])] - public function deleteAction(Request $request, mixed $id) - { - $form = $this->createDeleteForm($id); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $em = $this->managerRegistry->getManager(); - $entity = $em->getRepository(Role::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find Role entity.'); - } - - $em->remove($entity); - $em->flush(); - } - - return $this->redirectToRoute('chill_event_admin_role'); - } - /** * Displays a form to edit an existing Role entity. */ @@ -87,12 +63,10 @@ class RoleController extends AbstractController } $editForm = $this->createEditForm($entity); - $deleteForm = $this->createDeleteForm($id); return $this->render('@ChillEvent/Role/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -126,28 +100,6 @@ class RoleController extends AbstractController ]); } - /** - * Finds and displays a Role entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/role/{id}/show', name: 'chill_event_admin_role_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(Role::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find Role entity.'); - } - - $deleteForm = $this->createDeleteForm($id); - - return $this->render('@ChillEvent/Role/show.html.twig', [ - 'entity' => $entity, - 'delete_form' => $deleteForm->createView(), - ]); - } - /** * Edits an existing Role entity. */ @@ -162,7 +114,6 @@ class RoleController extends AbstractController throw $this->createNotFoundException('Unable to find Role entity.'); } - $deleteForm = $this->createDeleteForm($id); $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); @@ -175,7 +126,6 @@ class RoleController extends AbstractController return $this->render('@ChillEvent/Role/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -198,20 +148,6 @@ class RoleController extends AbstractController return $form; } - /** - * Creates a form to delete a Role entity by id. - * - * @return \Symfony\Component\Form\FormInterface The form - */ - private function createDeleteForm(mixed $id) - { - return $this->createFormBuilder() - ->setAction($this->generateUrl('chill_event_admin_role_delete', ['id' => $id])) - ->setMethod('DELETE') - ->add('submit', SubmitType::class, ['label' => 'Delete']) - ->getForm(); - } - /** * Creates a form to edit a Role entity. * diff --git a/src/Bundle/ChillEventBundle/Controller/StatusController.php b/src/Bundle/ChillEventBundle/Controller/StatusController.php index f83fa9480..1001fe269 100644 --- a/src/Bundle/ChillEventBundle/Controller/StatusController.php +++ b/src/Bundle/ChillEventBundle/Controller/StatusController.php @@ -48,30 +48,6 @@ class StatusController extends AbstractController ]); } - /** - * Deletes a Status entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/status/{id}/delete', name: 'chill_event_admin_status_delete', methods: ['POST', 'DELETE'])] - public function deleteAction(Request $request, mixed $id) - { - $form = $this->createDeleteForm($id); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $em = $this->managerRegistry->getManager(); - $entity = $em->getRepository(Status::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find Status entity.'); - } - - $em->remove($entity); - $em->flush(); - } - - return $this->redirectToRoute('chill_event_admin_status'); - } - /** * Displays a form to edit an existing Status entity. */ @@ -87,12 +63,10 @@ class StatusController extends AbstractController } $editForm = $this->createEditForm($entity); - $deleteForm = $this->createDeleteForm($id); return $this->render('@ChillEvent/Status/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -126,28 +100,6 @@ class StatusController extends AbstractController ]); } - /** - * Finds and displays a Status entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/status/{id}/show', name: 'chill_event_admin_status_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(Status::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find Status entity.'); - } - - $deleteForm = $this->createDeleteForm($id); - - return $this->render('@ChillEvent/Status/show.html.twig', [ - 'entity' => $entity, - 'delete_form' => $deleteForm->createView(), - ]); - } - /** * Edits an existing Status entity. */ @@ -162,7 +114,6 @@ class StatusController extends AbstractController throw $this->createNotFoundException('Unable to find Status entity.'); } - $deleteForm = $this->createDeleteForm($id); $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); @@ -175,7 +126,6 @@ class StatusController extends AbstractController return $this->render('@ChillEvent/Status/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -198,19 +148,6 @@ class StatusController extends AbstractController return $form; } - /** - * Creates a form to delete a Status entity by id. - * - * @return \Symfony\Component\Form\FormInterface The form - */ - private function createDeleteForm(mixed $id) - { - return $this->createFormBuilder() - ->setAction($this->generateUrl('chill_event_admin_status_delete', ['id' => $id])) - ->add('submit', SubmitType::class, ['label' => 'Delete']) - ->getForm(); - } - /** * Creates a form to edit a Status entity. * diff --git a/src/Bundle/ChillEventBundle/Resources/views/EventType/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/EventType/edit.html.twig index 151a9ec0c..28a5b22e8 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/EventType/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/EventType/edit.html.twig @@ -8,7 +8,7 @@ {{ form_row(edit_form.name) }} {{ form_row(edit_form.active) }} -
        +
        • {{ 'Back to the list'|trans }}
        • diff --git a/src/Bundle/ChillEventBundle/Resources/views/EventType/index.html.twig b/src/Bundle/ChillEventBundle/Resources/views/EventType/index.html.twig index 0a0154b3a..e55122d96 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/EventType/index.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/EventType/index.html.twig @@ -16,14 +16,11 @@
    {{ entity.id }}{{ entity.id }} {{ entity.name|localize_translatable_string }}{{ entity.active }}
      -
    • - -
    • diff --git a/src/Bundle/ChillEventBundle/Resources/views/EventType/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/EventType/new.html.twig index 3474ddd01..778f2cf73 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/EventType/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/EventType/new.html.twig @@ -8,7 +8,7 @@ {{ form_row(form.name) }} {{ form_row(form.active) }} -
        +
        • {{ 'Back to the list'|trans }}
        • diff --git a/src/Bundle/ChillEventBundle/Resources/views/EventType/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/EventType/show.html.twig index d50a97f15..ce879a719 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/EventType/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/EventType/show.html.twig @@ -21,17 +21,12 @@
    -
      + {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Role/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Role/edit.html.twig index 41ad81b90..04dab6188 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Role/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Role/edit.html.twig @@ -8,12 +8,12 @@ {{ form_row(edit_form.type) }} {{ form_row(edit_form.active) }} -
        +
        • {{ 'Back to the list'|trans }}
        • - {{ form_row(edit_form.submit, { 'attr': { 'class' : 'btn btn-edit' }}) }} + {{ form_row(edit_form.submit, { 'attr': { 'class' : 'btn btn-update' }}) }}
        diff --git a/src/Bundle/ChillEventBundle/Resources/views/Role/index.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Role/index.html.twig index e7e9afe1b..67654bd34 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Role/index.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Role/index.html.twig @@ -17,15 +17,12 @@ {% for entity in entities %} - {{ entity.id }} + {{ entity.id }} {{ entity.name|localize_translatable_string }} {{ entity.type.name|localize_translatable_string }} - {{ entity.active }} +
          -
        • - -
        • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Role/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Role/new.html.twig index 15363c02c..df9cf1dcf 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Role/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Role/new.html.twig @@ -9,7 +9,7 @@ {{ form_row(form.type) }} {{ form_row(form.active) }} -
            +
            • {{ 'Back to the list'|trans }}
            • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Role/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Role/show.html.twig index 9fe7e3adf..4452b96e4 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Role/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Role/show.html.twig @@ -25,17 +25,12 @@ -
                + {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Status/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Status/edit.html.twig index 00ee2d25c..408af3b79 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Status/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Status/edit.html.twig @@ -9,7 +9,7 @@ {{ form_row(edit_form.type) }} {{ form_row(edit_form.active) }} -
                  +
                  • {{ 'Back to the list'|trans }}
                  • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Status/index.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Status/index.html.twig index 64336b0d1..2f71e0d2e 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Status/index.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Status/index.html.twig @@ -17,15 +17,12 @@ {% for entity in entities %} - {{ entity.id }} + {{ entity.id }} {{ entity.name|localize_translatable_string }} {{ entity.type.name|localize_translatable_string }} - {{ entity.active }} +
                      -
                    • - -
                    • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Status/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Status/new.html.twig index 0e0b788ad..162de1d48 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Status/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Status/new.html.twig @@ -9,7 +9,7 @@ {{ form_row(form.type) }} {{ form_row(form.active) }} -
                        +
                        • {{ 'Back to the list'|trans }}
                        • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Status/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Status/show.html.twig index ce0aaae98..ca5371100 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Status/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Status/show.html.twig @@ -25,17 +25,12 @@ -
                            + {% endblock %} From bdf1cf71bac75336dbf82ff5b02982cdacac0b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 17 Jun 2025 10:55:30 +0200 Subject: [PATCH 46/83] Fix argument usage in `localizeString` method for `UserRenderBoxBadge` component Adjusted `localizeString` method to accept a `label` parameter, ensuring proper localization of `user_job.label` and `main_scope.name`. Also made minor syntax adjustments to improve readability. --- .../vuejs/_components/Entity/UserRenderBoxBadge.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue index 2ce48978c..4645f322d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue @@ -2,10 +2,10 @@ {{ user.label }} ({{ localizeString(user.user_job.label) }}) ({{ localizeString(user.user_job.label) }}) ({{ localizeString(user.main_scope.name) }}) ({{ localizeString(user.main_scope.name) }}) Date: Tue, 17 Jun 2025 19:26:23 +0200 Subject: [PATCH 47/83] Improve UX with better labeling in person resource form --- .changes/unreleased/UX-20250617-192650.yaml | 6 ++++++ .../ChillPersonBundle/Form/PersonResourceType.php | 10 ++++------ .../Resources/views/PersonResource/form.html.twig | 12 ++++++------ .../ChillPersonBundle/translations/messages.fr.yml | 4 ++++ 4 files changed, 20 insertions(+), 12 deletions(-) create mode 100644 .changes/unreleased/UX-20250617-192650.yaml diff --git a/.changes/unreleased/UX-20250617-192650.yaml b/.changes/unreleased/UX-20250617-192650.yaml new file mode 100644 index 000000000..810758b10 --- /dev/null +++ b/.changes/unreleased/UX-20250617-192650.yaml @@ -0,0 +1,6 @@ +kind: UX +body: Improve labeling of fields in person resource creation form +time: 2025-06-17T19:26:50.599703116+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php index 2063b0e21..1412733bc 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php @@ -16,10 +16,8 @@ use Chill\MainBundle\Form\Type\CommentType; use Chill\PersonBundle\Entity\Person\PersonResource; use Chill\PersonBundle\Entity\Person\PersonResourceKind; use Chill\PersonBundle\Form\Type\PickPersonDynamicType; -use Chill\PersonBundle\Templating\Entity\PersonRenderInterface; use Chill\PersonBundle\Templating\Entity\ResourceKindRender; use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType; -use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; @@ -29,7 +27,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; final class PersonResourceType extends AbstractType { - public function __construct(private readonly ResourceKindRender $resourceKindRender, private readonly PersonRenderInterface $personRender, private readonly ThirdPartyRender $thirdPartyRender, private readonly TranslatorInterface $translator) {} + public function __construct(private readonly ResourceKindRender $resourceKindRender, private readonly TranslatorInterface $translator) {} public function buildForm(FormBuilderInterface $builder, array $options) { @@ -52,13 +50,13 @@ final class PersonResourceType extends AbstractType }, ]) ->add('person', PickPersonDynamicType::class, [ - 'label' => 'Usager', + 'label' => $this->translator->trans('person_resource.person_non_prof'), ]) ->add('thirdparty', PickThirdpartyDynamicType::class, [ - 'label' => 'Tiers', + 'label' => $this->translator->trans('person_resource.thirdparty_prof'), ]) ->add('freetext', ChillTextareaType::class, [ - 'label' => 'Description libre', + 'label' => $this->translator->trans('person_resource.freetext'), 'required' => false, ]) ->add('comment', CommentType::class, [ diff --git a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/form.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/form.html.twig index 9bda2856b..5542074fa 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/form.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/form.html.twig @@ -10,28 +10,28 @@
                            {% if resource is defined and resource.person is not null %} - + {% else %} - + {% endif %}
                            {% if resource is defined and resource.thirdparty is not null %} - + {% else %} - + {% endif %}
                            {% if resource is defined and resource.freeText is not null %} - + {% else %} - + {% endif %}
    diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 9bce912a0..94e6083ca 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -265,6 +265,10 @@ no comment found: "Aucun commentaire" Select a type: "Choisissez un type" Select a person: "Choisissez un usager" Kind: "Type" +person_resource: + person_non_prof: "Usager/ Tiers non-professionnel" + thirdparty_prof: "Tiers professionnel" + freetext: "Description libre" # pickAPersonType From af74f7860be069c207a25752d45249f7ca7a0c6f Mon Sep 17 00:00:00 2001 From: nobohan Date: Thu, 19 Jun 2025 17:53:33 +0200 Subject: [PATCH 48/83] Fixed nullable content for NewsItem in setContent signature #392 --- .changes/unreleased/Fixed-20250619-170142.yaml | 7 +++++++ src/Bundle/ChillMainBundle/Entity/NewsItem.php | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changes/unreleased/Fixed-20250619-170142.yaml diff --git a/.changes/unreleased/Fixed-20250619-170142.yaml b/.changes/unreleased/Fixed-20250619-170142.yaml new file mode 100644 index 000000000..d9ee78e1b --- /dev/null +++ b/.changes/unreleased/Fixed-20250619-170142.yaml @@ -0,0 +1,7 @@ +kind: Fixed +body: | + Allow null and cast as string to setContent method for NewsItem +time: 2025-06-19T17:01:42.125730402+02:00 +custom: + Issue: "392" + SchemaChange: No schema change diff --git a/src/Bundle/ChillMainBundle/Entity/NewsItem.php b/src/Bundle/ChillMainBundle/Entity/NewsItem.php index 7acf2de86..371cc7f15 100644 --- a/src/Bundle/ChillMainBundle/Entity/NewsItem.php +++ b/src/Bundle/ChillMainBundle/Entity/NewsItem.php @@ -70,9 +70,9 @@ class NewsItem implements TrackCreationInterface, TrackUpdateInterface return $this->content; } - public function setContent(string $content): void + public function setContent(string|null $content): void { - $this->content = $content; + $this->content = (string) $content; } public function getStartDate(): ?\DateTimeImmutable From 9158e33854a5df7748bf4ff3a10460273f1e1905 Mon Sep 17 00:00:00 2001 From: nobohan Date: Thu, 19 Jun 2025 21:29:43 +0200 Subject: [PATCH 49/83] #392 php cs-fixer --- src/Bundle/ChillMainBundle/Entity/NewsItem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillMainBundle/Entity/NewsItem.php b/src/Bundle/ChillMainBundle/Entity/NewsItem.php index 371cc7f15..cc1c77bac 100644 --- a/src/Bundle/ChillMainBundle/Entity/NewsItem.php +++ b/src/Bundle/ChillMainBundle/Entity/NewsItem.php @@ -70,7 +70,7 @@ class NewsItem implements TrackCreationInterface, TrackUpdateInterface return $this->content; } - public function setContent(string|null $content): void + public function setContent(?string $content): void { $this->content = (string) $content; } From a38116cca4ab1550f62b1fc5eb4a2e5433de8c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 20 Jun 2025 17:31:13 +0200 Subject: [PATCH 50/83] fix cs --- .../Controller/ActivityControllerTest.php | 92 ++-- .../ActivityACLAwareRepositoryTest.php | 116 ++--- .../Authorization/ActivityVoterTest.php | 80 +-- .../AsideActivityControllerTest.php | 24 +- .../Controller/CalendarControllerTest.php | 52 +- .../MSGraph/MSUserAbsenceReaderTest.php | 28 +- .../DefaultRangeGeneratorTest.php | 36 +- .../CustomFields/CustomFieldsChoiceTest.php | 148 +++--- .../Serializer/Encoder/DocGenEncoderTest.php | 60 +-- .../TempUrlLocalStorageGeneratorTest.php | 126 ++--- .../StoredObjectManagerTest.php | 96 ++-- .../TempUrlOpenstackGeneratorTest.php | 64 +-- ...ectContentToLocalStorageControllerTest.php | 98 ++-- .../Tests/Controller/WebdavControllerTest.php | 114 ++-- .../PersonDocumentACLAwareRepositoryTest.php | 20 +- .../Authorization/StoredObjectVoterTest.php | 26 +- .../RemoveOldVersionCronJobTest.php | 46 +- .../Test/Export/AbstractAggregatorTest.php | 202 ++++---- .../Test/Export/AbstractExportTest.php | 42 +- .../Test/Export/AbstractFilterTest.php | 110 ++-- .../Controller/AddressControllerTest.php | 22 +- .../AddressReferenceApiControllerTest.php | 32 +- ...ddressToReferenceMatcherControllerTest.php | 40 +- .../Controller/NewsItemControllerTest.php | 38 +- .../NewsItemsHistoryControllerTest.php | 42 +- .../NotificationApiControllerTest.php | 54 +- .../Controller/SearchApiControllerTest.php | 22 +- .../Tests/Controller/UserControllerTest.php | 38 +- .../Tests/Doctrine/DQL/AgeTest.php | 32 +- .../Tests/Doctrine/DQL/JsonExtractTest.php | 14 +- .../Tests/Entity/NotificationTest.php | 40 +- .../Tests/Pagination/PageTest.php | 68 +-- .../Tests/Pagination/PaginatorTest.php | 134 ++--- .../Phonenumber/PhonenumberHelperTest.php | 38 +- .../Utils/ExtractDateFromPatternTest.php | 34 +- .../ExtractPhonenumberFromPatternTest.php | 30 +- .../Authorization/AuthorizationHelperTest.php | 94 ++-- .../Normalizer/DateNormalizerTest.php | 16 +- .../DoctrineExistingEntityNormalizerTest.php | 22 +- .../Normalizer/PhonenumberNormalizerTest.php | 18 +- .../Normalizer/UserNormalizerTest.php | 56 +- ...ssWithReferenceOrPostalCodeCronJobTest.php | 16 +- .../RollingDate/RollingDateConverterTest.php | 62 +-- .../CancelStaleWorkflowCronJobTest.php | 52 +- .../Templating/Entity/AddressRenderTest.php | 272 +++++----- .../EntityWorkflowGuardTransitionTest.php | 28 +- ...ityWorkflowGuardUnsignedTransitionTest.php | 78 +-- ...kflowRelatedEntityPermissionHelperTest.php | 86 +-- .../Tests/Action/Remove/PersonMoveTest.php | 150 +++--- .../AccompanyingCourseApiControllerTest.php | 490 +++++++++--------- .../AccompanyingCourseControllerTest.php | 52 +- .../Controller/HouseholdApiControllerTest.php | 104 ++-- .../Controller/HouseholdControllerTest.php | 54 +- .../HouseholdMemberControllerTest.php | 144 ++--- .../Controller/PersonApiControllerTest.php | 82 +-- ...onControllerUpdateWithHiddenFieldsTest.php | 36 +- .../PersonDuplicateControllerViewTest.php | 64 +-- .../RelationshipApiControllerTest.php | 72 +-- .../SocialWorkEvaluationApiControllerTest.php | 36 +- .../EventListener/PersonCreateEventTest.php | 48 +- .../Normalizer/PersonDocGenNormalizerTest.php | 50 +- ...ngPeriodCalendarGenericDocProviderTest.php | 36 +- .../TimelineAccompanyingPeriodTest.php | 54 +- .../Household/MaxHolderValidatorTest.php | 28 +- .../Authorization/ReportVoterTest.php | 38 +- 65 files changed, 2298 insertions(+), 2298 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityControllerTest.php b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityControllerTest.php index 31012569a..62a885937 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityControllerTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Controller/ActivityControllerTest.php @@ -22,6 +22,52 @@ use Symfony\Component\Security\Core\Role\Role; */ final class ActivityControllerTest extends WebTestCase { + /** + * @dataProvider getSecuredPagesUnauthenticated + */ + public function testAccessIsDeniedForUnauthenticated(mixed $url) + { + $client = $this->createClient(); + + $client->request('GET', $url); + + $this->assertEquals(302, $client->getResponse()->getStatusCode()); + $this->assertTrue( + $client->getResponse()->isRedirect('http://localhost/login'), + sprintf('the page "%s" does not redirect to http://localhost/login', $url) + ); + } + + /** + * Provide a client unauthenticated and. + */ + public function getSecuredPagesUnauthenticated() + { + self::bootKernel(); + $person = $this->getPersonFromFixtures(); + $activities = $this->getActivitiesForPerson($person); + + return [ + [sprintf('fr/person/%d/activity/', $person->getId())], + [sprintf('fr/person/%d/activity/new', $person->getId())], + [sprintf('fr/person/%d/activity/%d/show', $person->getId(), $activities[0]->getId())], + [sprintf('fr/person/%d/activity/%d/edit', $person->getId(), $activities[0]->getId())], + ]; + } + + /** + * @dataProvider getSecuredPagesAuthenticated + * + * @param type $client + * @param type $url + */ + public function testAccessIsDeniedForUnauthorized($client, $url) + { + $client->request('GET', $url); + + $this->assertEquals(403, $client->getResponse()->getStatusCode()); + } + public function getSecuredPagesAuthenticated() { self::bootKernel(); @@ -55,52 +101,6 @@ final class ActivityControllerTest extends WebTestCase ]; } - /** - * Provide a client unauthenticated and. - */ - public function getSecuredPagesUnauthenticated() - { - self::bootKernel(); - $person = $this->getPersonFromFixtures(); - $activities = $this->getActivitiesForPerson($person); - - return [ - [sprintf('fr/person/%d/activity/', $person->getId())], - [sprintf('fr/person/%d/activity/new', $person->getId())], - [sprintf('fr/person/%d/activity/%d/show', $person->getId(), $activities[0]->getId())], - [sprintf('fr/person/%d/activity/%d/edit', $person->getId(), $activities[0]->getId())], - ]; - } - - /** - * @dataProvider getSecuredPagesUnauthenticated - */ - public function testAccessIsDeniedForUnauthenticated(mixed $url) - { - $client = $this->createClient(); - - $client->request('GET', $url); - - $this->assertEquals(302, $client->getResponse()->getStatusCode()); - $this->assertTrue( - $client->getResponse()->isRedirect('http://localhost/login'), - sprintf('the page "%s" does not redirect to http://localhost/login', $url) - ); - } - - /** - * @dataProvider getSecuredPagesAuthenticated - * - * @param type $client - * @param type $url - */ - public function testAccessIsDeniedForUnauthorized($client, $url) - { - $client->request('GET', $url); - - $this->assertEquals(403, $client->getResponse()->getStatusCode()); - } - public function testCompleteScenario() { // Create a new client to browse the application diff --git a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php index de4a3312d..a42ab6049 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityACLAwareRepositoryTest.php @@ -137,6 +137,64 @@ class ActivityACLAwareRepositoryTest extends KernelTestCase self::assertIsArray($actual); } + public function provideDataFindByAccompanyingPeriod(): iterable + { + $this->setUp(); + + if (null === $period = $this->entityManager + ->createQueryBuilder() + ->select('a') + ->from(AccompanyingPeriod::class, 'a') + ->setMaxResults(1) + ->getQuery() + ->getSingleResult()) { + throw new \RuntimeException('no period found'); + } + + if ([] === $types = $this->entityManager + ->createQueryBuilder() + ->select('t') + ->from(ActivityType::class, 't') + ->setMaxResults(2) + ->getQuery() + ->getResult()) { + throw new \RuntimeException('no types'); + } + + if ([] === $jobs = $this->entityManager + ->createQueryBuilder() + ->select('j') + ->from(UserJob::class, 'j') + ->setMaxResults(2) + ->getQuery() + ->getResult() + ) { + $job = new UserJob(); + $job->setLabel(['fr' => 'test']); + $this->entityManager->persist($job); + $this->entityManager->flush(); + } + + if (null === $user = $this->entityManager + ->createQueryBuilder() + ->select('u') + ->from(User::class, 'u') + ->setMaxResults(1) + ->getQuery() + ->getSingleResult() + ) { + throw new \RuntimeException('no user found'); + } + + yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], []]; + yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['my_activities' => true]]; + yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['types' => $types]]; + yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['jobs' => $jobs]]; + yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago')]]; + yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]]; + yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]]; + } + /** * @dataProvider provideDataFindByPerson */ @@ -291,62 +349,4 @@ class ActivityACLAwareRepositoryTest extends KernelTestCase yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]]; yield [$person, $user, $centers, $scopes, ActivityVoter::SEE, 0, 5, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]]; } - - public function provideDataFindByAccompanyingPeriod(): iterable - { - $this->setUp(); - - if (null === $period = $this->entityManager - ->createQueryBuilder() - ->select('a') - ->from(AccompanyingPeriod::class, 'a') - ->setMaxResults(1) - ->getQuery() - ->getSingleResult()) { - throw new \RuntimeException('no period found'); - } - - if ([] === $types = $this->entityManager - ->createQueryBuilder() - ->select('t') - ->from(ActivityType::class, 't') - ->setMaxResults(2) - ->getQuery() - ->getResult()) { - throw new \RuntimeException('no types'); - } - - if ([] === $jobs = $this->entityManager - ->createQueryBuilder() - ->select('j') - ->from(UserJob::class, 'j') - ->setMaxResults(2) - ->getQuery() - ->getResult() - ) { - $job = new UserJob(); - $job->setLabel(['fr' => 'test']); - $this->entityManager->persist($job); - $this->entityManager->flush(); - } - - if (null === $user = $this->entityManager - ->createQueryBuilder() - ->select('u') - ->from(User::class, 'u') - ->setMaxResults(1) - ->getQuery() - ->getSingleResult() - ) { - throw new \RuntimeException('no user found'); - } - - yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], []]; - yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['my_activities' => true]]; - yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['types' => $types]]; - yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['jobs' => $jobs]]; - yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago')]]; - yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['before' => new \DateTimeImmutable('1 year ago')]]; - yield [$period, $user, ActivityVoter::SEE, 0, 10, ['date' => 'DESC'], ['after' => new \DateTimeImmutable('1 year ago'), 'before' => new \DateTimeImmutable('1 month ago')]]; - } } diff --git a/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php b/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php index 0a9e7d8ba..e4cae588c 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Security/Authorization/ActivityVoterTest.php @@ -57,6 +57,46 @@ final class ActivityVoterTest extends KernelTestCase $this->prophet = new \Prophecy\Prophet(); } + public function testNullUser() + { + $token = $this->prepareToken(); + $center = $this->prepareCenter(1, 'center'); + $person = $this->preparePerson($center); + $scope = $this->prepareScope(1, 'default'); + $activity = $this->prepareActivity($scope, $person); + + $this->assertEquals( + VoterInterface::ACCESS_DENIED, + $this->voter->vote($token, $activity, ['CHILL_ACTIVITY_SEE']), + 'assert that a null user is not allowed to see' + ); + } + + /** + * @dataProvider dataProvider_testVoteAction + * + * @param type $expectedResult + * @param string $attribute + * @param string $message + */ + public function testVoteAction( + $expectedResult, + User $user, + Scope $scope, + Center $center, + $attribute, + $message, + ) { + $token = $this->prepareToken($user); + $activity = $this->prepareActivity($scope, $this->preparePerson($center)); + + $this->assertEquals( + $expectedResult, + $this->voter->vote($token, $activity, [$attribute]), + $message + ); + } + public function dataProvider_testVoteAction() { $centerA = $this->prepareCenter(1, 'center A'); @@ -110,46 +150,6 @@ final class ActivityVoterTest extends KernelTestCase ]; } - public function testNullUser() - { - $token = $this->prepareToken(); - $center = $this->prepareCenter(1, 'center'); - $person = $this->preparePerson($center); - $scope = $this->prepareScope(1, 'default'); - $activity = $this->prepareActivity($scope, $person); - - $this->assertEquals( - VoterInterface::ACCESS_DENIED, - $this->voter->vote($token, $activity, ['CHILL_ACTIVITY_SEE']), - 'assert that a null user is not allowed to see' - ); - } - - /** - * @dataProvider dataProvider_testVoteAction - * - * @param type $expectedResult - * @param string $attribute - * @param string $message - */ - public function testVoteAction( - $expectedResult, - User $user, - Scope $scope, - Center $center, - $attribute, - $message, - ) { - $token = $this->prepareToken($user); - $activity = $this->prepareActivity($scope, $this->preparePerson($center)); - - $this->assertEquals( - $expectedResult, - $this->voter->vote($token, $activity, [$attribute]), - $message - ); - } - /** * prepare a token interface with correct rights. * diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php index 21e78e6cf..a4c68a9b0 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Controller/AsideActivityControllerTest.php @@ -30,6 +30,18 @@ final class AsideActivityControllerTest extends WebTestCase self::ensureKernelShutdown(); } + /** + * @dataProvider generateAsideActivityId + */ + public function testEditWithoutUsers(int $asideActivityId) + { + self::ensureKernelShutdown(); + $client = $this->getClientAuthenticated(); + $client->request('GET', "/fr/asideactivity/{$asideActivityId}/edit"); + + $this->assertEquals(200, $client->getResponse()->getStatusCode()); + } + public static function generateAsideActivityId(): iterable { self::bootKernel(); @@ -58,18 +70,6 @@ final class AsideActivityControllerTest extends WebTestCase self::ensureKernelShutdown(); } - /** - * @dataProvider generateAsideActivityId - */ - public function testEditWithoutUsers(int $asideActivityId) - { - self::ensureKernelShutdown(); - $client = $this->getClientAuthenticated(); - $client->request('GET', "/fr/asideactivity/{$asideActivityId}/edit"); - - $this->assertEquals(200, $client->getResponse()->getStatusCode()); - } - public function testIndexWithoutUsers() { self::ensureKernelShutdown(); diff --git a/src/Bundle/ChillCalendarBundle/Tests/Controller/CalendarControllerTest.php b/src/Bundle/ChillCalendarBundle/Tests/Controller/CalendarControllerTest.php index ef2597367..31af5a921 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Controller/CalendarControllerTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Controller/CalendarControllerTest.php @@ -42,6 +42,32 @@ final class CalendarControllerTest extends WebTestCase self::ensureKernelShutdown(); } + /** + * @dataProvider provideAccompanyingPeriod + */ + public function testList(int $accompanyingPeriodId) + { + $this->client->request( + Request::METHOD_GET, + sprintf('/fr/calendar/calendar/by-period/%d', $accompanyingPeriodId) + ); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + } + + /** + * @dataProvider provideAccompanyingPeriod + */ + public function testNew(int $accompanyingPeriodId) + { + $this->client->request( + Request::METHOD_GET, + sprintf('/fr/calendar/calendar/new?accompanying_period_id=%d', $accompanyingPeriodId) + ); + + $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); + } + public static function provideAccompanyingPeriod(): iterable { self::bootKernel(); @@ -82,30 +108,4 @@ final class CalendarControllerTest extends WebTestCase self::ensureKernelShutdown(); } - - /** - * @dataProvider provideAccompanyingPeriod - */ - public function testList(int $accompanyingPeriodId) - { - $this->client->request( - Request::METHOD_GET, - sprintf('/fr/calendar/calendar/by-period/%d', $accompanyingPeriodId) - ); - - $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - } - - /** - * @dataProvider provideAccompanyingPeriod - */ - public function testNew(int $accompanyingPeriodId) - { - $this->client->request( - Request::METHOD_GET, - sprintf('/fr/calendar/calendar/new?accompanying_period_id=%d', $accompanyingPeriodId) - ); - - $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); - } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php index 3d3a71854..3c9c0d2ff 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php @@ -45,20 +45,6 @@ class MSUserAbsenceReaderTest extends TestCase self::assertEquals($expected, $absenceReader->isUserAbsent($user), $message); } - public function testIsUserAbsentWithoutRemoteId(): void - { - $user = new User(); - $client = new MockHttpClient(); - - $mapUser = $this->prophesize(MapCalendarToUser::class); - $mapUser->getUserId($user)->willReturn(null); - $clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00')); - - $absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock); - - self::assertNull($absenceReader->isUserAbsent($user), 'when no user found, absence should be null'); - } - public static function provideDataTestUserAbsence(): iterable { // contains data that was retrieved from microsoft graph api on 2023-07-06 @@ -173,4 +159,18 @@ class MSUserAbsenceReaderTest extends TestCase 'User is absent: absence is always enabled', ]; } + + public function testIsUserAbsentWithoutRemoteId(): void + { + $user = new User(); + $client = new MockHttpClient(); + + $mapUser = $this->prophesize(MapCalendarToUser::class); + $mapUser->getUserId($user)->willReturn(null); + $clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00')); + + $absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock); + + self::assertNull($absenceReader->isUserAbsent($user), 'when no user found, absence should be null'); + } } diff --git a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultRangeGeneratorTest.php b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultRangeGeneratorTest.php index 1d28c43c0..4fb6e99d6 100644 --- a/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultRangeGeneratorTest.php +++ b/src/Bundle/ChillCalendarBundle/Tests/Service/ShortMessageNotification/DefaultRangeGeneratorTest.php @@ -28,6 +28,24 @@ use PHPUnit\Framework\TestCase; */ final class DefaultRangeGeneratorTest extends TestCase { + /** + * @dataProvider generateData + */ + public function testGenerateRange(\DateTimeImmutable $date, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate) + { + $generator = new DefaultRangeGenerator(); + + ['startDate' => $actualStartDate, 'endDate' => $actualEndDate] = $generator->generateRange($date); + + if (null === $startDate) { + $this->assertNull($actualStartDate); + $this->assertNull($actualEndDate); + } else { + $this->assertEquals($startDate->format(\DateTimeImmutable::ATOM), $actualStartDate->format(\DateTimeImmutable::ATOM)); + $this->assertEquals($endDate->format(\DateTimeImmutable::ATOM), $actualEndDate->format(\DateTimeImmutable::ATOM)); + } + } + /** * * Lundi => Envoi des rdv du mardi et mercredi. * * Mardi => Envoi des rdv du jeudi. @@ -79,22 +97,4 @@ final class DefaultRangeGeneratorTest extends TestCase null, ]; } - - /** - * @dataProvider generateData - */ - public function testGenerateRange(\DateTimeImmutable $date, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate) - { - $generator = new DefaultRangeGenerator(); - - ['startDate' => $actualStartDate, 'endDate' => $actualEndDate] = $generator->generateRange($date); - - if (null === $startDate) { - $this->assertNull($actualStartDate); - $this->assertNull($actualEndDate); - } else { - $this->assertEquals($startDate->format(\DateTimeImmutable::ATOM), $actualStartDate->format(\DateTimeImmutable::ATOM)); - $this->assertEquals($endDate->format(\DateTimeImmutable::ATOM), $actualEndDate->format(\DateTimeImmutable::ATOM)); - } - } } diff --git a/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFields/CustomFieldsChoiceTest.php b/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFields/CustomFieldsChoiceTest.php index 6f6ba394e..453b8ee64 100644 --- a/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFields/CustomFieldsChoiceTest.php +++ b/src/Bundle/ChillCustomFieldsBundle/Tests/CustomFields/CustomFieldsChoiceTest.php @@ -49,80 +49,6 @@ final class CustomFieldsChoiceTest extends KernelTestCase parent::tearDown(); } - /** - * provide empty data in different possible representations. - * Those data are supposed to be deserialized. - * - * @return array - */ - public static function emptyDataProvider() - { - return [ - // 0 - [ - // signle - '', - ], - // 1 - [ - // single - null, - ], - // 2 - [ - // signle with allow other - ['_other' => 'something', '_choices' => ''], - ], - // 3 - [ - // multiple - [], - ], - // 4 - [ - // multiple with allow other - ['_other' => 'something', '_choices' => []], - ], - // 5 - [ - // multiple with allow other - ['_other' => '', '_choices' => []], - ], - // 6 - [ - // empty - ['_other' => null, '_choices' => null], - ], - // 7 - [ - // empty - [null], - ], - ]; - } - - public static function serializedRepresentationDataProvider() - { - return [ - [ - // multiple => false, allow_other => false - 'my-value', - ], - [ - // multiple => true, allow_ther => false - ['my-value'], - ], - [ - // multiple => false, allow_other => true, current value not in other - ['_other' => '', '_choices' => 'my-value'], - ], - [ - // multiple => true, allow_other => true, current value not in other - ['_other' => '', '_choices' => ['my-value']], - ], - ]; - } - /** * Test if the representation of the data is deserialized to an array text * with an "allow_other" field. @@ -412,6 +338,58 @@ final class CustomFieldsChoiceTest extends KernelTestCase $this->assertTrue($isEmpty); } + /** + * provide empty data in different possible representations. + * Those data are supposed to be deserialized. + * + * @return array + */ + public static function emptyDataProvider() + { + return [ + // 0 + [ + // signle + '', + ], + // 1 + [ + // single + null, + ], + // 2 + [ + // signle with allow other + ['_other' => 'something', '_choices' => ''], + ], + // 3 + [ + // multiple + [], + ], + // 4 + [ + // multiple with allow other + ['_other' => 'something', '_choices' => []], + ], + // 5 + [ + // multiple with allow other + ['_other' => '', '_choices' => []], + ], + // 6 + [ + // empty + ['_other' => null, '_choices' => null], + ], + // 7 + [ + // empty + [null], + ], + ]; + } + // /////////////////////////////////////// // // test function isEmptyValue @@ -435,6 +413,28 @@ final class CustomFieldsChoiceTest extends KernelTestCase $this->assertFalse($isEmpty); } + public static function serializedRepresentationDataProvider() + { + return [ + [ + // multiple => false, allow_other => false + 'my-value', + ], + [ + // multiple => true, allow_ther => false + ['my-value'], + ], + [ + // multiple => false, allow_other => true, current value not in other + ['_other' => '', '_choices' => 'my-value'], + ], + [ + // multiple => true, allow_other => true, current value not in other + ['_other' => '', '_choices' => ['my-value']], + ], + ]; + } + /** * @param array $options * diff --git a/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php index b48f19719..237c16fb4 100644 --- a/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php +++ b/src/Bundle/ChillDocGeneratorBundle/tests/Serializer/Encoder/DocGenEncoderTest.php @@ -31,6 +31,36 @@ final class DocGenEncoderTest extends TestCase $this->encoder = new DocGenEncoder(); } + public function testEmbeddedLoopsThrowsException() + { + $this->expectException(UnexpectedValueException::class); + + $data = [ + 'data' => [ + ['item' => 'one'], + [ + 'embedded' => [ + [ + ['subitem' => 'two'], + ['subitem' => 'three'], + ], + ], + ], + ], + ]; + + $this->encoder->encode($data, 'docgen'); + } + + /** + * @dataProvider generateEncodeData + */ + public function testEncode(mixed $expected, mixed $data, string $msg) + { + $generated = $this->encoder->encode($data, 'docgen'); + $this->assertEquals($expected, $generated, $msg); + } + public static function generateEncodeData() { yield [['tests' => 'ok'], ['tests' => 'ok'], 'A simple test with a simple array']; @@ -93,34 +123,4 @@ final class DocGenEncoderTest extends TestCase 'a longer list, with near real data inside and embedded associative arrays', ]; } - - public function testEmbeddedLoopsThrowsException() - { - $this->expectException(UnexpectedValueException::class); - - $data = [ - 'data' => [ - ['item' => 'one'], - [ - 'embedded' => [ - [ - ['subitem' => 'two'], - ['subitem' => 'three'], - ], - ], - ], - ], - ]; - - $this->encoder->encode($data, 'docgen'); - } - - /** - * @dataProvider generateEncodeData - */ - public function testEncode(mixed $expected, mixed $data, string $msg) - { - $generated = $this->encoder->encode($data, 'docgen'); - $this->assertEquals($expected, $generated, $msg); - } } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGeneratorTest.php b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGeneratorTest.php index d85d3094a..a4a6aef0c 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGeneratorTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/LocalStorage/TempUrlLocalStorageGeneratorTest.php @@ -85,6 +85,69 @@ class TempUrlLocalStorageGeneratorTest extends TestCase self::assertEquals($expected, $urlGenerator->validateSignature($signature, $method, $objectName, $expiration), $message); } + public static function generateValidateSignatureData(): iterable + { + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'GET', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + true, + 'Valid signature, not expired', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('HEAD', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'HEAD', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + true, + 'Valid signature, not expired', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180).'A', + 'GET', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Invalid signature', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'GET', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration + 1)), + false, + 'Signature expired', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'GET', + $object_name.'____', + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Invalid object name', + ]; + + yield [ + TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180), + 'POST', + $object_name, + $expiration, + \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), + false, + 'Wrong method', + ]; + } + /** * @dataProvider generateValidateSignaturePostData */ @@ -164,69 +227,6 @@ class TempUrlLocalStorageGeneratorTest extends TestCase ]; } - public static function generateValidateSignatureData(): iterable - { - yield [ - TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180), - 'GET', - $object_name, - $expiration, - \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), - true, - 'Valid signature, not expired', - ]; - - yield [ - TempUrlLocalStorageGeneratorTest::expectedSignature('HEAD', $object_name = 'testABC', $expiration = 1734307200 + 180), - 'HEAD', - $object_name, - $expiration, - \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), - true, - 'Valid signature, not expired', - ]; - - yield [ - TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180).'A', - 'GET', - $object_name, - $expiration, - \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), - false, - 'Invalid signature', - ]; - - yield [ - TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180), - 'GET', - $object_name, - $expiration, - \DateTimeImmutable::createFromFormat('U', (string) ($expiration + 1)), - false, - 'Signature expired', - ]; - - yield [ - TempUrlLocalStorageGeneratorTest::expectedSignature('GET', $object_name = 'testABC', $expiration = 1734307200 + 180), - 'GET', - $object_name.'____', - $expiration, - \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), - false, - 'Invalid object name', - ]; - - yield [ - TempUrlLocalStorageGeneratorTest::expectedSignature('POST', $object_name = 'testABC', $expiration = 1734307200 + 180), - 'POST', - $object_name, - $expiration, - \DateTimeImmutable::createFromFormat('U', (string) ($expiration - 10)), - false, - 'Wrong method', - ]; - } - private function buildGenerator(?UrlGeneratorInterface $urlGenerator = null, ?ClockInterface $clock = null): TempUrlLocalStorageGenerator { return new TempUrlLocalStorageGenerator( diff --git a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/StoredObjectManagerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/StoredObjectManagerTest.php index d0cb6cb02..7b9e71838 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/StoredObjectManagerTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/StoredObjectManagerTest.php @@ -31,6 +31,20 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; */ final class StoredObjectManagerTest extends TestCase { + /** + * @dataProvider getDataProviderForRead + */ + public function testRead(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null) + { + if (null !== $exceptionClass) { + $this->expectException($exceptionClass); + } + + $storedObjectManager = $this->getSubject($storedObject, $encodedContent); + + self::assertEquals($clearContent, $storedObjectManager->read($storedObject)); + } + public static function getDataProviderForRead(): \Generator { /* HAPPY SCENARIO */ @@ -96,6 +110,40 @@ final class StoredObjectManagerTest extends TestCase ]; } + /** + * @dataProvider getDataProviderForWrite + */ + public function testWrite(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null, ?int $errorCode = null) + { + if (null !== $exceptionClass) { + $this->expectException($exceptionClass); + } + + $previousVersion = $storedObject->getCurrentVersion(); + $previousFilename = $previousVersion->getFilename(); + + $client = new MockHttpClient(function ($method, $url, $options) use ($encodedContent, $previousFilename, $errorCode) { + self::assertEquals('PUT', $method); + self::assertStringStartsWith('https://example.com/', $url); + self::assertStringNotContainsString($previousFilename, $url, 'test that the PUT operation is not performed on the same file'); + self::assertArrayHasKey('body', $options); + self::assertEquals($encodedContent, $options['body']); + + if (-1 === $errorCode) { + throw new TransportException(); + } + + return new MockResponse('', ['http_code' => $errorCode ?? 201]); + }); + + $storedObjectManager = new StoredObjectManager($client, $this->getTempUrlGenerator($storedObject)); + + $newVersion = $storedObjectManager->write($storedObject, $clearContent); + + self::assertNotSame($previousVersion, $newVersion); + self::assertSame($storedObject->getCurrentVersion(), $newVersion); + } + public static function getDataProviderForWrite(): \Generator { /* HAPPY SCENARIO */ @@ -150,54 +198,6 @@ final class StoredObjectManagerTest extends TestCase ]; } - /** - * @dataProvider getDataProviderForRead - */ - public function testRead(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null) - { - if (null !== $exceptionClass) { - $this->expectException($exceptionClass); - } - - $storedObjectManager = $this->getSubject($storedObject, $encodedContent); - - self::assertEquals($clearContent, $storedObjectManager->read($storedObject)); - } - - /** - * @dataProvider getDataProviderForWrite - */ - public function testWrite(StoredObject $storedObject, string $encodedContent, string $clearContent, ?string $exceptionClass = null, ?int $errorCode = null) - { - if (null !== $exceptionClass) { - $this->expectException($exceptionClass); - } - - $previousVersion = $storedObject->getCurrentVersion(); - $previousFilename = $previousVersion->getFilename(); - - $client = new MockHttpClient(function ($method, $url, $options) use ($encodedContent, $previousFilename, $errorCode) { - self::assertEquals('PUT', $method); - self::assertStringStartsWith('https://example.com/', $url); - self::assertStringNotContainsString($previousFilename, $url, 'test that the PUT operation is not performed on the same file'); - self::assertArrayHasKey('body', $options); - self::assertEquals($encodedContent, $options['body']); - - if (-1 === $errorCode) { - throw new TransportException(); - } - - return new MockResponse('', ['http_code' => $errorCode ?? 201]); - }); - - $storedObjectManager = new StoredObjectManager($client, $this->getTempUrlGenerator($storedObject)); - - $newVersion = $storedObjectManager->write($storedObject, $clearContent); - - self::assertNotSame($previousVersion, $newVersion); - self::assertSame($storedObject->getCurrentVersion(), $newVersion); - } - public function testDelete(): void { $storedObject = new StoredObject(); diff --git a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php index 347e490e7..f6bd15195 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/AsyncUpload/Driver/OpenstackObjectStore/TempUrlOpenstackGeneratorTest.php @@ -82,6 +82,38 @@ class TempUrlOpenstackGeneratorTest extends KernelTestCase self::assertEquals($expected, $signedUrl); } + public static function dataProviderGenerate(): iterable + { + $now = \DateTimeImmutable::createFromFormat('U', '1702041743'); + $expireDelay = 1800; + $baseUrls = [ + 'https://objectstore.example/v1/my_account/container/', + 'https://objectstore.example/v1/my_account/container', + ]; + $objectName = 'object'; + $method = 'GET'; + $key = 'MYKEY'; + + $signedUrl = new SignedUrl( + 'GET', + 'https://objectstore.example/v1/my_account/container/object?temp_url_sig=0aeef353a5f6e22d125c76c6ad8c644a59b222ba1b13eaeb56bf3d04e28b081d11dfcb36601ab3aa7b623d79e1ef03017071bbc842fb7b34afec2baff895bf80&temp_url_expires=1702043543', + \DateTimeImmutable::createFromFormat('U', '1702043543'), + $objectName + ); + + foreach ($baseUrls as $baseUrl) { + yield [ + $baseUrl, + $now, + $key, + $method, + $objectName, + $expireDelay, + $signedUrl, + ]; + } + } + /** * @dataProvider dataProviderGeneratePost */ @@ -125,38 +157,6 @@ class TempUrlOpenstackGeneratorTest extends KernelTestCase self::assertGreaterThanOrEqual(20, strlen($signedUrl->prefix)); } - public static function dataProviderGenerate(): iterable - { - $now = \DateTimeImmutable::createFromFormat('U', '1702041743'); - $expireDelay = 1800; - $baseUrls = [ - 'https://objectstore.example/v1/my_account/container/', - 'https://objectstore.example/v1/my_account/container', - ]; - $objectName = 'object'; - $method = 'GET'; - $key = 'MYKEY'; - - $signedUrl = new SignedUrl( - 'GET', - 'https://objectstore.example/v1/my_account/container/object?temp_url_sig=0aeef353a5f6e22d125c76c6ad8c644a59b222ba1b13eaeb56bf3d04e28b081d11dfcb36601ab3aa7b623d79e1ef03017071bbc842fb7b34afec2baff895bf80&temp_url_expires=1702043543', - \DateTimeImmutable::createFromFormat('U', '1702043543'), - $objectName - ); - - foreach ($baseUrls as $baseUrl) { - yield [ - $baseUrl, - $now, - $key, - $method, - $objectName, - $expireDelay, - $signedUrl, - ]; - } - } - public static function dataProviderGeneratePost(): iterable { $now = \DateTimeImmutable::createFromFormat('U', '1702041743'); diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectContentToLocalStorageControllerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectContentToLocalStorageControllerTest.php index c680bd0b3..dcdc16546 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectContentToLocalStorageControllerTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/Controller/StoredObjectContentToLocalStorageControllerTest.php @@ -61,6 +61,55 @@ class StoredObjectContentToLocalStorageControllerTest extends TestCase $controller->contentOperate($request); } + public static function generateOperateContentWithExceptionDataProvider(): iterable + { + yield [ + new Request(['object_name' => '', 'sig' => '', 'exp' => 0]), + BadRequestHttpException::class, + 'Object name parameter is missing', + false, + '', + false, + ]; + + yield [ + new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => 0]), + BadRequestHttpException::class, + 'Expiration is not set or equal to zero', + false, + '', + false, + ]; + + yield [ + new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => (new \DateTimeImmutable())->getTimestamp()]), + BadRequestHttpException::class, + 'Signature is not set or is a blank string', + false, + '', + false, + ]; + + yield [ + new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]), + AccessDeniedHttpException::class, + 'Invalid signature', + false, + '', + false, + ]; + + + yield [ + new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]), + NotFoundHttpException::class, + 'Object does not exists on disk', + false, + '', + true, + ]; + } + public function testOperateContentGetHappyScenario(): void { $objectName = 'testABC'; @@ -286,53 +335,4 @@ class StoredObjectContentToLocalStorageControllerTest extends TestCase 'Filename does not start with signed prefix', ]; } - - public static function generateOperateContentWithExceptionDataProvider(): iterable - { - yield [ - new Request(['object_name' => '', 'sig' => '', 'exp' => 0]), - BadRequestHttpException::class, - 'Object name parameter is missing', - false, - '', - false, - ]; - - yield [ - new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => 0]), - BadRequestHttpException::class, - 'Expiration is not set or equal to zero', - false, - '', - false, - ]; - - yield [ - new Request(['object_name' => 'testABC', 'sig' => '', 'exp' => (new \DateTimeImmutable())->getTimestamp()]), - BadRequestHttpException::class, - 'Signature is not set or is a blank string', - false, - '', - false, - ]; - - yield [ - new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]), - AccessDeniedHttpException::class, - 'Invalid signature', - false, - '', - false, - ]; - - - yield [ - new Request(['object_name' => 'testABC', 'sig' => '1234', 'exp' => (new \DateTimeImmutable())->getTimestamp()]), - NotFoundHttpException::class, - 'Object does not exists on disk', - false, - '', - true, - ]; - } } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Controller/WebdavControllerTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Controller/WebdavControllerTest.php index 9d469369d..ec25ff5d6 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/Controller/WebdavControllerTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/Controller/WebdavControllerTest.php @@ -136,63 +136,6 @@ class WebdavControllerTest extends KernelTestCase self::assertXmlStringEqualsXmlString($expectedXmlResponse, $response->getContent(), $message); } - /** - * @dataProvider generateDataPropfindDirectory - */ - public function testPropfindDirectory(string $requestContent, int $expectedStatusCode, string $expectedXmlResponse, string $message): void - { - $controller = $this->buildController(); - - $request = new Request([], [], [], [], [], [], $requestContent); - $request->setMethod('PROPFIND'); - $request->headers->add(['Depth' => '0']); - $response = $controller->propfindDirectory($this->buildDocument(), '1234', $request); - - self::assertEquals($expectedStatusCode, $response->getStatusCode()); - self::assertContains('content-type', $response->headers->keys()); - self::assertStringContainsString('text/xml', $response->headers->get('content-type')); - self::assertTrue((new \DOMDocument())->loadXML($response->getContent()), $message.' test that the xml response is a valid xml'); - self::assertXmlStringEqualsXmlString($expectedXmlResponse, $response->getContent(), $message); - } - - public function testHeadDocument(): void - { - $controller = $this->buildController(); - $response = $controller->headDocument($this->buildDocument()); - - self::assertEquals(200, $response->getStatusCode()); - self::assertContains('content-length', $response->headers->keys()); - self::assertContains('content-type', $response->headers->keys()); - self::assertContains('etag', $response->headers->keys()); - self::assertEquals('ab56b4d92b40713acc5af89985d4b786', $response->headers->get('etag')); - self::assertEquals('application/vnd.oasis.opendocument.text', $response->headers->get('content-type')); - self::assertEquals(5, $response->headers->get('content-length')); - } - - public function testPutDocument(): void - { - $document = $this->buildDocument(); - $entityManager = $this->createMock(EntityManagerInterface::class); - $storedObjectManager = $this->createMock(StoredObjectManagerInterface::class); - - // entity manager must be flushed - $entityManager->expects($this->once()) - ->method('flush'); - - // object must be written by StoredObjectManager - $storedObjectManager->expects($this->once()) - ->method('write') - ->with($this->identicalTo($document), $this->identicalTo('1234')); - - $controller = $this->buildController($entityManager, $storedObjectManager); - - $request = new Request(content: '1234'); - $response = $controller->putDocument($document, $request); - - self::assertEquals(204, $response->getStatusCode()); - self::assertEquals('', $response->getContent()); - } - public static function generateDataPropfindDocument(): iterable { $content = @@ -347,6 +290,25 @@ class WebdavControllerTest extends KernelTestCase ]; } + /** + * @dataProvider generateDataPropfindDirectory + */ + public function testPropfindDirectory(string $requestContent, int $expectedStatusCode, string $expectedXmlResponse, string $message): void + { + $controller = $this->buildController(); + + $request = new Request([], [], [], [], [], [], $requestContent); + $request->setMethod('PROPFIND'); + $request->headers->add(['Depth' => '0']); + $response = $controller->propfindDirectory($this->buildDocument(), '1234', $request); + + self::assertEquals($expectedStatusCode, $response->getStatusCode()); + self::assertContains('content-type', $response->headers->keys()); + self::assertStringContainsString('text/xml', $response->headers->get('content-type')); + self::assertTrue((new \DOMDocument())->loadXML($response->getContent()), $message.' test that the xml response is a valid xml'); + self::assertXmlStringEqualsXmlString($expectedXmlResponse, $response->getContent(), $message); + } + public static function generateDataPropfindDirectory(): iterable { yield [ @@ -414,6 +376,44 @@ class WebdavControllerTest extends KernelTestCase 'test creatableContentsInfo', ]; } + + public function testHeadDocument(): void + { + $controller = $this->buildController(); + $response = $controller->headDocument($this->buildDocument()); + + self::assertEquals(200, $response->getStatusCode()); + self::assertContains('content-length', $response->headers->keys()); + self::assertContains('content-type', $response->headers->keys()); + self::assertContains('etag', $response->headers->keys()); + self::assertEquals('ab56b4d92b40713acc5af89985d4b786', $response->headers->get('etag')); + self::assertEquals('application/vnd.oasis.opendocument.text', $response->headers->get('content-type')); + self::assertEquals(5, $response->headers->get('content-length')); + } + + public function testPutDocument(): void + { + $document = $this->buildDocument(); + $entityManager = $this->createMock(EntityManagerInterface::class); + $storedObjectManager = $this->createMock(StoredObjectManagerInterface::class); + + // entity manager must be flushed + $entityManager->expects($this->once()) + ->method('flush'); + + // object must be written by StoredObjectManager + $storedObjectManager->expects($this->once()) + ->method('write') + ->with($this->identicalTo($document), $this->identicalTo('1234')); + + $controller = $this->buildController($entityManager, $storedObjectManager); + + $request = new Request(content: '1234'); + $response = $controller->putDocument($document, $request); + + self::assertEquals(204, $response->getStatusCode()); + self::assertEquals('', $response->getContent()); + } } class MockedStoredObjectManager implements StoredObjectManagerInterface diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php index b5d030c35..a8b17d130 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/Repository/PersonDocumentACLAwareRepositoryTest.php @@ -87,6 +87,16 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase self::assertIsInt($nb, 'test that the query could be executed'); } + public static function provideDataBuildFetchQueryForPerson(): iterable + { + yield [null, null, null]; + yield [new \DateTimeImmutable('1 year ago'), null, null]; + yield [null, new \DateTimeImmutable('1 year ago'), null]; + yield [new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), null]; + yield [null, null, 'test']; + yield [new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), 'test']; + } + /** * @dataProvider provideDateForFetchQueryForAccompanyingPeriod */ @@ -142,14 +152,4 @@ class PersonDocumentACLAwareRepositoryTest extends KernelTestCase yield [$period, null, null, 'test']; yield [$period, new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), 'test']; } - - public static function provideDataBuildFetchQueryForPerson(): iterable - { - yield [null, null, null]; - yield [new \DateTimeImmutable('1 year ago'), null, null]; - yield [null, new \DateTimeImmutable('1 year ago'), null]; - yield [new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), null]; - yield [null, null, 'test']; - yield [new \DateTimeImmutable('2 years ago'), new \DateTimeImmutable('1 year ago'), 'test']; - } } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Security/Authorization/StoredObjectVoterTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Security/Authorization/StoredObjectVoterTest.php index 7180a14d8..9ce41f3a4 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/Security/Authorization/StoredObjectVoterTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/Security/Authorization/StoredObjectVoterTest.php @@ -50,19 +50,6 @@ class StoredObjectVoterTest extends TestCase self::assertEquals($expected, $voter->vote($token, $subject, [$attribute])); } - private function buildStoredObjectVoter(bool $supportsIsCalled, bool $supports, bool $voteOnAttribute): StoredObjectVoterInterface - { - $storedObjectVoter = $this->createMock(StoredObjectVoterInterface::class); - $storedObjectVoter->expects($supportsIsCalled ? $this->once() : $this->never())->method('supports') - ->with(self::isInstanceOf(StoredObjectRoleEnum::class), $this->isInstanceOf(StoredObject::class)) - ->willReturn($supports); - $storedObjectVoter->expects($supportsIsCalled && $supports ? $this->once() : $this->never())->method('voteOnAttribute') - ->with(self::isInstanceOf(StoredObjectRoleEnum::class), $this->isInstanceOf(StoredObject::class), $this->isInstanceOf(TokenInterface::class)) - ->willReturn($voteOnAttribute); - - return $storedObjectVoter; - } - public static function provideDataVote(): iterable { yield [ @@ -120,4 +107,17 @@ class StoredObjectVoterTest extends TestCase VoterInterface::ACCESS_GRANTED, ]; } + + private function buildStoredObjectVoter(bool $supportsIsCalled, bool $supports, bool $voteOnAttribute): StoredObjectVoterInterface + { + $storedObjectVoter = $this->createMock(StoredObjectVoterInterface::class); + $storedObjectVoter->expects($supportsIsCalled ? $this->once() : $this->never())->method('supports') + ->with(self::isInstanceOf(StoredObjectRoleEnum::class), $this->isInstanceOf(StoredObject::class)) + ->willReturn($supports); + $storedObjectVoter->expects($supportsIsCalled && $supports ? $this->once() : $this->never())->method('voteOnAttribute') + ->with(self::isInstanceOf(StoredObjectRoleEnum::class), $this->isInstanceOf(StoredObject::class), $this->isInstanceOf(TokenInterface::class)) + ->willReturn($voteOnAttribute); + + return $storedObjectVoter; + } } diff --git a/src/Bundle/ChillDocStoreBundle/Tests/Service/StoredObjectCleaner/RemoveOldVersionCronJobTest.php b/src/Bundle/ChillDocStoreBundle/Tests/Service/StoredObjectCleaner/RemoveOldVersionCronJobTest.php index df75eea93..771727d88 100644 --- a/src/Bundle/ChillDocStoreBundle/Tests/Service/StoredObjectCleaner/RemoveOldVersionCronJobTest.php +++ b/src/Bundle/ChillDocStoreBundle/Tests/Service/StoredObjectCleaner/RemoveOldVersionCronJobTest.php @@ -40,29 +40,6 @@ class RemoveOldVersionCronJobTest extends KernelTestCase self::assertEquals($expected, $cronJob->canRun($cronJobExecution)); } - public function testRun(): void - { - // we create a clock in the future. This led us a chance to having stored object to delete - $clock = new MockClock(new \DateTimeImmutable('2024-01-01 00:00:00', new \DateTimeZone('+00:00'))); - $repository = $this->createMock(StoredObjectVersionRepository::class); - $repository->expects($this->once()) - ->method('findIdsByVersionsOlderThanDateAndNotLastVersionAndNotPointInTime') - ->with(new \DateTime('2023-10-03 00:00:00', new \DateTimeZone('+00:00'))) - ->willReturnCallback(function ($arg) { - yield 1; - yield 3; - yield 2; - }) - ; - - $cronJob = new RemoveOldVersionCronJob($clock, $this->buildMessageBus(true), $repository); - - $results = $cronJob->run([]); - - self::assertArrayHasKey('last-deleted-stored-object-version-id', $results); - self::assertIsInt($results['last-deleted-stored-object-version-id']); - } - public static function buildTestCanRunData(): iterable { yield [ @@ -86,6 +63,29 @@ class RemoveOldVersionCronJobTest extends KernelTestCase ]; } + public function testRun(): void + { + // we create a clock in the future. This led us a chance to having stored object to delete + $clock = new MockClock(new \DateTimeImmutable('2024-01-01 00:00:00', new \DateTimeZone('+00:00'))); + $repository = $this->createMock(StoredObjectVersionRepository::class); + $repository->expects($this->once()) + ->method('findIdsByVersionsOlderThanDateAndNotLastVersionAndNotPointInTime') + ->with(new \DateTime('2023-10-03 00:00:00', new \DateTimeZone('+00:00'))) + ->willReturnCallback(function ($arg) { + yield 1; + yield 3; + yield 2; + }) + ; + + $cronJob = new RemoveOldVersionCronJob($clock, $this->buildMessageBus(true), $repository); + + $results = $cronJob->run([]); + + self::assertArrayHasKey('last-deleted-stored-object-version-id', $results); + self::assertIsInt($results['last-deleted-stored-object-version-id']); + } + private function buildMessageBus(bool $expectDistpatchAtLeastOnce = false): MessageBusInterface { $messageBus = $this->createMock(MessageBusInterface::class); diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php index 12182b96b..724620eb9 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractAggregatorTest.php @@ -32,107 +32,6 @@ abstract class AbstractAggregatorTest extends KernelTestCase self::ensureKernelShutdown(); } - /** - * provide data for `testAliasDidNotDisappears`. - */ - public static function dataProviderAliasDidNotDisappears() - { - $datas = static::getFormData(); - - if (!\is_array($datas)) { - $datas = iterator_to_array($datas); - } - - foreach (static::getQueryBuilders() as $qb) { - if ([] === $datas) { - yield [clone $qb, []]; - } else { - foreach (static::getFormData() as $data) { - yield [clone $qb, $data]; - } - } - } - } - - /** - * provide data for `testAlterQuery`. - */ - public static function dataProviderAlterQuery() - { - $datas = static::getFormData(); - - if (!\is_array($datas)) { - $datas = iterator_to_array($datas); - } - - foreach (static::getQueryBuilders() as $qb) { - if ([] === $datas) { - yield [clone $qb, []]; - } else { - foreach (static::getFormData() as $data) { - yield [clone $qb, $data]; - } - } - } - } - - public static function dataProviderQueryExecution(): iterable - { - $datas = static::getFormData(); - - if (!\is_array($datas)) { - $datas = iterator_to_array($datas); - } - - foreach (static::getQueryBuilders() as $qb) { - if ([] === $datas) { - yield [clone $qb, []]; - } else { - foreach (static::getFormData() as $data) { - yield [clone $qb, $data]; - } - } - } - } - - /** - * prepare data for `testGetQueryKeys`. - */ - public static function dataProviderGetQueryKeys() - { - $datas = static::getFormData(); - - if (!\is_array($datas)) { - $datas = iterator_to_array($datas); - } - - foreach ($datas as $data) { - yield [$data]; - } - } - - /** - * prepare date for method `testGetResultsAndLabels`. - */ - public static function dataProviderGetResultsAndLabels() - { - $datas = static::getFormData(); - - if (!\is_array($datas)) { - $datas = iterator_to_array($datas); - } - - foreach (static::getQueryBuilders() as $qb) { - if ([] === $datas) { - yield [clone $qb, []]; - } else { - foreach ($datas as $data) { - yield [clone $qb, $data]; - } - } - } - } - /** * Create an aggregator instance which will be used in tests. * @@ -187,6 +86,28 @@ abstract class AbstractAggregatorTest extends KernelTestCase } } + /** + * provide data for `testAliasDidNotDisappears`. + */ + public static function dataProviderAliasDidNotDisappears() + { + $datas = static::getFormData(); + + if (!\is_array($datas)) { + $datas = iterator_to_array($datas); + } + + foreach (static::getQueryBuilders() as $qb) { + if ([] === $datas) { + yield [clone $qb, []]; + } else { + foreach (static::getFormData() as $data) { + yield [clone $qb, $data]; + } + } + } + } + /** * @dataProvider dataProviderQueryExecution * @@ -201,6 +122,25 @@ abstract class AbstractAggregatorTest extends KernelTestCase self::assertIsArray($actual); } + public static function dataProviderQueryExecution(): iterable + { + $datas = static::getFormData(); + + if (!\is_array($datas)) { + $datas = iterator_to_array($datas); + } + + foreach (static::getQueryBuilders() as $qb) { + if ([] === $datas) { + yield [clone $qb, []]; + } else { + foreach (static::getFormData() as $data) { + yield [clone $qb, $data]; + } + } + } + } + /** * test the alteration of query by the filter. * @@ -239,6 +179,28 @@ abstract class AbstractAggregatorTest extends KernelTestCase ); } + /** + * provide data for `testAlterQuery`. + */ + public static function dataProviderAlterQuery() + { + $datas = static::getFormData(); + + if (!\is_array($datas)) { + $datas = iterator_to_array($datas); + } + + foreach (static::getQueryBuilders() as $qb) { + if ([] === $datas) { + yield [clone $qb, []]; + } else { + foreach (static::getFormData() as $data) { + yield [clone $qb, $data]; + } + } + } + } + /** * Test the `applyOn` method. */ @@ -282,6 +244,22 @@ abstract class AbstractAggregatorTest extends KernelTestCase ); } + /** + * prepare data for `testGetQueryKeys`. + */ + public static function dataProviderGetQueryKeys() + { + $datas = static::getFormData(); + + if (!\is_array($datas)) { + $datas = iterator_to_array($datas); + } + + foreach ($datas as $data) { + yield [$data]; + } + } + /** * Test that. * @@ -346,6 +324,28 @@ abstract class AbstractAggregatorTest extends KernelTestCase } } + /** + * prepare date for method `testGetResultsAndLabels`. + */ + public static function dataProviderGetResultsAndLabels() + { + $datas = static::getFormData(); + + if (!\is_array($datas)) { + $datas = iterator_to_array($datas); + } + + foreach (static::getQueryBuilders() as $qb) { + if ([] === $datas) { + yield [clone $qb, []]; + } else { + foreach ($datas as $data) { + yield [clone $qb, $data]; + } + } + } + } + /** * test the `getTitle` method. */ diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php index 51403f51c..9e9a87833 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php @@ -31,27 +31,6 @@ abstract class AbstractExportTest extends WebTestCase { use PrepareClientTrait; - public static function dataProviderGetQueryKeys() - { - foreach (static::getFormData() as $data) { - yield [$data]; - } - } - - /** - * create data for `ìnitiateQuery` method. - */ - public static function dataProviderInitiateQuery() - { - $acl = static::getAcl(); - - foreach (static::getModifiersCombination() as $modifiers) { - foreach (static::getFormData() as $data) { - yield [$modifiers, $acl, $data]; - } - } - } - /** * Return an array usable as ACL. * @@ -198,6 +177,13 @@ abstract class AbstractExportTest extends WebTestCase } } + public static function dataProviderGetQueryKeys() + { + foreach (static::getFormData() as $data) { + yield [$data]; + } + } + /** * Test that. * @@ -382,4 +368,18 @@ abstract class AbstractExportTest extends WebTestCase } } } + + /** + * create data for `ìnitiateQuery` method. + */ + public static function dataProviderInitiateQuery() + { + $acl = static::getAcl(); + + foreach (static::getModifiersCombination() as $modifiers) { + foreach (static::getFormData() as $data) { + yield [$modifiers, $acl, $data]; + } + } + } } diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php index 5c38515fc..05b7231b3 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php @@ -43,61 +43,6 @@ abstract class AbstractFilterTest extends KernelTestCase self::ensureKernelShutdown(); } - /** - * provide data for `testAliasDidNotDisappears`. - */ - public static function dataProviderAliasDidNotDisappears() - { - $datas = static::getFormData(); - - foreach (static::getQueryBuilders() as $qb) { - if ([] === $datas) { - yield [clone $qb, []]; - } else { - foreach ($datas as $data) { - yield [clone $qb, $data]; - } - } - } - } - - public static function dataProviderAlterQuery() - { - $datas = static::getFormData(); - - foreach (static::getQueryBuilders() as $qb) { - if ([] === $datas) { - yield [clone $qb, []]; - } else { - foreach ($datas as $data) { - yield [clone $qb, $data]; - } - } - } - } - - public static function dataProvideQueryExecution(): iterable - { - $datas = static::getFormData(); - - foreach (static::getQueryBuilders() as $qb) { - if ([] === $datas) { - yield [clone $qb, []]; - } else { - foreach ($datas as $data) { - yield [clone $qb, $data]; - } - } - } - } - - public static function dataProviderDescriptionAction() - { - foreach (static::getFormData() as $data) { - yield [$data]; - } - } - /** * Create a filter which will be used in tests. * @@ -149,6 +94,24 @@ abstract class AbstractFilterTest extends KernelTestCase } } + /** + * provide data for `testAliasDidNotDisappears`. + */ + public static function dataProviderAliasDidNotDisappears() + { + $datas = static::getFormData(); + + foreach (static::getQueryBuilders() as $qb) { + if ([] === $datas) { + yield [clone $qb, []]; + } else { + foreach ($datas as $data) { + yield [clone $qb, $data]; + } + } + } + } + /** * test the alteration of query by the filter. * @@ -187,6 +150,21 @@ abstract class AbstractFilterTest extends KernelTestCase ); } + public static function dataProviderAlterQuery() + { + $datas = static::getFormData(); + + foreach (static::getQueryBuilders() as $qb) { + if ([] === $datas) { + yield [clone $qb, []]; + } else { + foreach ($datas as $data) { + yield [clone $qb, $data]; + } + } + } + } + /** * @dataProvider dataProvideQueryExecution */ @@ -199,6 +177,21 @@ abstract class AbstractFilterTest extends KernelTestCase self::assertIsArray($actual); } + public static function dataProvideQueryExecution(): iterable + { + $datas = static::getFormData(); + + foreach (static::getQueryBuilders() as $qb) { + if ([] === $datas) { + yield [clone $qb, []]; + } else { + foreach ($datas as $data) { + yield [clone $qb, $data]; + } + } + } + } + public function testApplyOn() { $filter = $this->getFilter(); @@ -263,6 +256,13 @@ abstract class AbstractFilterTest extends KernelTestCase } } + public static function dataProviderDescriptionAction() + { + foreach (static::getFormData() as $data) { + yield [$data]; + } + } + public function testGetTitle() { $title = $this->getFilter()->getTitle(); diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/AddressControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/AddressControllerTest.php index 4a65c434c..ac711d66b 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/AddressControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/AddressControllerTest.php @@ -32,6 +32,17 @@ final class AddressControllerTest extends \Symfony\Bundle\FrameworkBundle\Test\W self::ensureKernelShutdown(); } + /** + * @dataProvider generateAddressIds + */ + public function testDuplicate(int $addressId) + { + $this->client = $this->getClientAuthenticated(); + $this->client->request('POST', "/api/1.0/main/address/{$addressId}/duplicate.json"); + + $this->assertResponseIsSuccessful('test that duplicate is successful'); + } + public static function generateAddressIds(): iterable { self::bootKernel(); @@ -49,15 +60,4 @@ final class AddressControllerTest extends \Symfony\Bundle\FrameworkBundle\Test\W self::ensureKernelShutdown(); } - - /** - * @dataProvider generateAddressIds - */ - public function testDuplicate(int $addressId) - { - $this->client = $this->getClientAuthenticated(); - $this->client->request('POST', "/api/1.0/main/address/{$addressId}/duplicate.json"); - - $this->assertResponseIsSuccessful('test that duplicate is successful'); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/AddressReferenceApiControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/AddressReferenceApiControllerTest.php index 335f6f29b..29d9eff48 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/AddressReferenceApiControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/AddressReferenceApiControllerTest.php @@ -25,6 +25,22 @@ final class AddressReferenceApiControllerTest extends WebTestCase { use PrepareClientTrait; + /** + * @dataProvider provideData + */ + public function testSearch(int $postCodeId, string $pattern) + { + $client = $this->getClientAuthenticated(); + + $client->request( + 'GET', + "/api/1.0/main/address-reference/by-postal-code/{$postCodeId}/search.json", + ['q' => $pattern] + ); + + $this->assertResponseIsSuccessful(); + } + public static function provideData() { self::bootKernel(); @@ -42,20 +58,4 @@ final class AddressReferenceApiControllerTest extends WebTestCase yield [$postalCode->getId(), 'rue']; } - - /** - * @dataProvider provideData - */ - public function testSearch(int $postCodeId, string $pattern) - { - $client = $this->getClientAuthenticated(); - - $client->request( - 'GET', - "/api/1.0/main/address-reference/by-postal-code/{$postCodeId}/search.json", - ['q' => $pattern] - ); - - $this->assertResponseIsSuccessful(); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/AddressToReferenceMatcherControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/AddressToReferenceMatcherControllerTest.php index ec4d3fa09..87c9dc8da 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/AddressToReferenceMatcherControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/AddressToReferenceMatcherControllerTest.php @@ -52,26 +52,6 @@ class AddressToReferenceMatcherControllerTest extends WebTestCase $this->assertEquals(Address::ADDR_REFERENCE_STATUS_REVIEWED, $address->getRefStatus()); } - /** - * @dataProvider addressUnsyncedProvider - */ - public function testSyncAddressWithReference(int $addressId): void - { - $client = $this->getClientAuthenticated(); - - $client->request('POST', "/api/1.0/main/address/reference-match/{$addressId}/sync-with-reference"); - - $this->assertResponseIsSuccessful(); - - $this->addressRepository = self::getContainer()->get(AddressRepository::class); - $address = $this->addressRepository->find($addressId); - - $this->assertEquals(Address::ADDR_REFERENCE_STATUS_MATCH, $address->getRefStatus()); - $this->assertEquals($address->getAddressReference()->getStreet(), $address->getStreet()); - $this->assertEquals($address->getAddressReference()->getStreetNumber(), $address->getStreetNumber()); - $this->assertEquals($address->getAddressReference()->getPoint()->toWKT(), $address->getPoint()->toWKT()); - } - public static function addressToReviewProvider(): iterable { self::bootKernel(); @@ -98,6 +78,26 @@ class AddressToReferenceMatcherControllerTest extends WebTestCase self::ensureKernelShutdown(); } + /** + * @dataProvider addressUnsyncedProvider + */ + public function testSyncAddressWithReference(int $addressId): void + { + $client = $this->getClientAuthenticated(); + + $client->request('POST', "/api/1.0/main/address/reference-match/{$addressId}/sync-with-reference"); + + $this->assertResponseIsSuccessful(); + + $this->addressRepository = self::getContainer()->get(AddressRepository::class); + $address = $this->addressRepository->find($addressId); + + $this->assertEquals(Address::ADDR_REFERENCE_STATUS_MATCH, $address->getRefStatus()); + $this->assertEquals($address->getAddressReference()->getStreet(), $address->getStreet()); + $this->assertEquals($address->getAddressReference()->getStreetNumber(), $address->getStreetNumber()); + $this->assertEquals($address->getAddressReference()->getPoint()->toWKT(), $address->getPoint()->toWKT()); + } + public static function addressUnsyncedProvider(): iterable { self::bootKernel(); diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/NewsItemControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/NewsItemControllerTest.php index 3881188e9..05c3e54f3 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/NewsItemControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/NewsItemControllerTest.php @@ -55,6 +55,25 @@ class NewsItemControllerTest extends WebTestCase $em->flush(); } + public function testList() + { + $client = $this->getClientAuthenticated('admin', 'password'); + $client->request('GET', '/fr/admin/news_item'); + + self::assertResponseIsSuccessful('News item admin page shows'); + } + + /** + * @dataProvider generateNewsItemIds + */ + public function testShowSingleItem(NewsItem $newsItem) + { + $client = $this->getClientAuthenticated('admin', 'password'); + $client->request('GET', "/fr/admin/news_item/{$newsItem->getId()}/view"); + + self::assertResponseIsSuccessful('Single news item admin page loads successfully'); + } + public static function generateNewsItemIds(): iterable { self::bootKernel(); @@ -74,23 +93,4 @@ class NewsItemControllerTest extends WebTestCase yield [$newsItem]; } - - public function testList() - { - $client = $this->getClientAuthenticated('admin', 'password'); - $client->request('GET', '/fr/admin/news_item'); - - self::assertResponseIsSuccessful('News item admin page shows'); - } - - /** - * @dataProvider generateNewsItemIds - */ - public function testShowSingleItem(NewsItem $newsItem) - { - $client = $this->getClientAuthenticated('admin', 'password'); - $client->request('GET', "/fr/admin/news_item/{$newsItem->getId()}/view"); - - self::assertResponseIsSuccessful('Single news item admin page loads successfully'); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/NewsItemsHistoryControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/NewsItemsHistoryControllerTest.php index d36fca40c..0bd8d798c 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/NewsItemsHistoryControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/NewsItemsHistoryControllerTest.php @@ -51,27 +51,6 @@ class NewsItemsHistoryControllerTest extends WebTestCase self::ensureKernelShutdown(); } - public static function generateNewsItemIds(): iterable - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - - $news = new NewsItem(); - - $news->setContent('test content'); - $news->setTitle('Title'); - $news->setStartDate(new \DateTimeImmutable('yesterday')); - - $em->persist($news); - $em->flush(); - - static::$toDelete[] = [NewsItem::class, $news]; - - self::ensureKernelShutdown(); - - yield [$news->getId()]; - } - public function testList() { self::ensureKernelShutdown(); @@ -94,4 +73,25 @@ class NewsItemsHistoryControllerTest extends WebTestCase $this->assertResponseIsSuccessful('test that single news item page loads successfully'); } + + public static function generateNewsItemIds(): iterable + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + + $news = new NewsItem(); + + $news->setContent('test content'); + $news->setTitle('Title'); + $news->setStartDate(new \DateTimeImmutable('yesterday')); + + $em->persist($news); + $em->flush(); + + static::$toDelete[] = [NewsItem::class, $news]; + + self::ensureKernelShutdown(); + + yield [$news->getId()]; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/NotificationApiControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/NotificationApiControllerTest.php index 1c19a1466..59242843c 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/NotificationApiControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/NotificationApiControllerTest.php @@ -44,33 +44,6 @@ final class NotificationApiControllerTest extends WebTestCase self::$toDelete = []; } - public static function generateDataMarkAsRead() - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - $userRepository = self::getContainer()->get(UserRepository::class); - $userA = $userRepository->findOneBy(['username' => 'center a_social']); - $userB = $userRepository->findOneBy(['username' => 'center b_social']); - - $notification = new Notification(); - $notification - ->setMessage('Test generated') - ->setRelatedEntityClass(AccompanyingPeriod::class) - ->setRelatedEntityId(0) - ->setSender($userB) - ->addAddressee($userA) - ->setUpdatedAt(new \DateTimeImmutable()); - $em->persist($notification); - $em->refresh($notification); - $em->flush(); - - self::$toDelete[] = [Notification::class, $notification->getId()]; - - self::ensureKernelShutdown(); - - yield [$notification->getId()]; - } - /** * @dataProvider generateDataMarkAsRead */ @@ -99,4 +72,31 @@ final class NotificationApiControllerTest extends WebTestCase $this->assertFalse($notification->isReadBy($user)); } + + public static function generateDataMarkAsRead() + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $userRepository = self::getContainer()->get(UserRepository::class); + $userA = $userRepository->findOneBy(['username' => 'center a_social']); + $userB = $userRepository->findOneBy(['username' => 'center b_social']); + + $notification = new Notification(); + $notification + ->setMessage('Test generated') + ->setRelatedEntityClass(AccompanyingPeriod::class) + ->setRelatedEntityId(0) + ->setSender($userB) + ->addAddressee($userA) + ->setUpdatedAt(new \DateTimeImmutable()); + $em->persist($notification); + $em->refresh($notification); + $em->flush(); + + self::$toDelete[] = [Notification::class, $notification->getId()]; + + self::ensureKernelShutdown(); + + yield [$notification->getId()]; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/SearchApiControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/SearchApiControllerTest.php index 4f265658b..289a0fa27 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/SearchApiControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/SearchApiControllerTest.php @@ -24,17 +24,6 @@ final class SearchApiControllerTest extends WebTestCase { use PrepareClientTrait; - public static function generateSearchData() - { - yield ['per', ['person', 'thirdparty']]; - - yield ['per', ['thirdparty']]; - - yield ['per', ['person']]; - - yield ['fjklmeqjfkdqjklrmefdqjklm', ['person', 'thirdparty']]; - } - /** * @dataProvider generateSearchData */ @@ -50,4 +39,15 @@ final class SearchApiControllerTest extends WebTestCase $this->assertResponseIsSuccessful(); } + + public static function generateSearchData() + { + yield ['per', ['person', 'thirdparty']]; + + yield ['per', ['thirdparty']]; + + yield ['per', ['person']]; + + yield ['fjklmeqjfkdqjklrmefdqjklm', ['person', 'thirdparty']]; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php index 483211020..ba76df8ca 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/UserControllerTest.php @@ -27,25 +27,6 @@ final class UserControllerTest extends WebTestCase { use PrepareClientTrait; - public static function dataGenerateUserId() - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - /** @var UserPasswordHasherInterface::class $passwordHasher */ - $passwordHasher = self::getContainer()->get(UserPasswordHasherInterface::class); - - $user = new User(); - $user->setUsername('Test_user '.uniqid()); - $user->setPassword($passwordHasher->hashPassword($user, 'password')); - - $em->persist($user); - $em->flush(); - - self::ensureKernelShutdown(); - - yield [$user->getId(), $user->getUsername()]; - } - public function testList() { $client = $this->getClientAuthenticatedAsAdmin(); @@ -135,6 +116,25 @@ final class UserControllerTest extends WebTestCase $this->isPasswordValid($username, $newPassword); } + public static function dataGenerateUserId() + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + /** @var UserPasswordHasherInterface::class $passwordHasher */ + $passwordHasher = self::getContainer()->get(UserPasswordHasherInterface::class); + + $user = new User(); + $user->setUsername('Test_user '.uniqid()); + $user->setPassword($passwordHasher->hashPassword($user, 'password')); + + $em->persist($user); + $em->flush(); + + self::ensureKernelShutdown(); + + yield [$user->getId(), $user->getUsername()]; + } + protected function isPasswordValid($username, $password) { /** @var \Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher $passwordEncoder */ diff --git a/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/AgeTest.php b/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/AgeTest.php index cdb24b4b7..71b16413c 100644 --- a/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/AgeTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/AgeTest.php @@ -31,6 +31,22 @@ final class AgeTest extends KernelTestCase $this->entityManager = self::getContainer()->get(EntityManagerInterface::class); } + /** + * @dataProvider generateQueries + */ + public function testWorking(string $dql, array $args) + { + $dql = $this->entityManager->createQuery($dql)->setMaxResults(3); + + foreach ($args as $key => $value) { + $dql->setParameter($key, $value); + } + + $results = $dql->getResult(); + + $this->assertIsArray($results); + } + public static function generateQueries(): iterable { yield [ @@ -60,20 +76,4 @@ final class AgeTest extends KernelTestCase ], ]; } - - /** - * @dataProvider generateQueries - */ - public function testWorking(string $dql, array $args) - { - $dql = $this->entityManager->createQuery($dql)->setMaxResults(3); - - foreach ($args as $key => $value) { - $dql->setParameter($key, $value); - } - - $results = $dql->getResult(); - - $this->assertIsArray($results); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/JsonExtractTest.php b/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/JsonExtractTest.php index dec63a64c..312b3950a 100644 --- a/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/JsonExtractTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Doctrine/DQL/JsonExtractTest.php @@ -31,13 +31,6 @@ final class JsonExtractTest extends KernelTestCase $this->em = self::getContainer()->get(EntityManagerInterface::class); } - public static function dataGenerateDql(): iterable - { - yield ['SELECT JSON_EXTRACT(c.name, \'fr\') FROM '.Country::class.' c', []]; - - yield ['SELECT JSON_EXTRACT(c.name, :lang) FROM '.Country::class.' c', ['lang' => 'fr']]; - } - /** * @dataProvider dataGenerateDql */ @@ -50,4 +43,11 @@ final class JsonExtractTest extends KernelTestCase $this->assertIsArray($results, 'simply test that the query return a result'); } + + public static function dataGenerateDql(): iterable + { + yield ['SELECT JSON_EXTRACT(c.name, \'fr\') FROM '.Country::class.' c', []]; + + yield ['SELECT JSON_EXTRACT(c.name, :lang) FROM '.Country::class.' c', ['lang' => 'fr']]; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php index 26a9b5980..2e945dcb4 100644 --- a/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Entity/NotificationTest.php @@ -44,26 +44,6 @@ final class NotificationTest extends KernelTestCase $em->flush(); } - public static function generateNotificationData() - { - self::bootKernel(); - $userRepository = self::getContainer()->get(UserRepository::class); - - $senderId = $userRepository - ->findOneBy(['username' => 'center b_social']) - ->getId(); - - $addressesIds = []; - $addressesIds[] = $userRepository - ->findOneBy(['username' => 'center b_direction']) - ->getId(); - - yield [ - $senderId, - $addressesIds, - ]; - } - public function testAddAddresseeStoreAnUread() { $notification = new Notification(); @@ -139,4 +119,24 @@ final class NotificationTest extends KernelTestCase $this->assertContains($addresseeId, $unreadIds); } } + + public static function generateNotificationData() + { + self::bootKernel(); + $userRepository = self::getContainer()->get(UserRepository::class); + + $senderId = $userRepository + ->findOneBy(['username' => 'center b_social']) + ->getId(); + + $addressesIds = []; + $addressesIds[] = $userRepository + ->findOneBy(['username' => 'center b_direction']) + ->getId(); + + yield [ + $senderId, + $addressesIds, + ]; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Pagination/PageTest.php b/src/Bundle/ChillMainBundle/Tests/Pagination/PageTest.php index 787a5833a..99e81ce65 100644 --- a/src/Bundle/ChillMainBundle/Tests/Pagination/PageTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Pagination/PageTest.php @@ -33,6 +33,23 @@ final class PageTest extends KernelTestCase $this->prophet = new \Prophecy\Prophet(); } + /** + * @param int $number + * @param int $itemPerPage + * @param int $expectedItemPerPage + * + * @dataProvider generateGetFirstItemNumber + */ + public function testGetFirstItemNumber( + $number, + $itemPerPage, + $expectedItemPerPage, + ) { + $page = $this->generatePage($number, $itemPerPage); + + $this->assertEquals($expectedItemPerPage, $page->getFirstItemNumber()); + } + /** * return a set of element to testGetFirstItemNumber. * @@ -51,6 +68,23 @@ final class PageTest extends KernelTestCase ]; } + /** + * @param int $number + * @param int $itemPerPage + * @param int $expectedItemPerPage + * + * @dataProvider generateGetLastItemNumber + */ + public function testGetLastItemNumber( + $number, + $itemPerPage, + $expectedItemPerPage, + ) { + $page = $this->generatePage($number, $itemPerPage); + + $this->assertEquals($expectedItemPerPage, $page->getLastItemNumber()); + } + /** * return a set of element to testGetLastItemNumber. * @@ -69,40 +103,6 @@ final class PageTest extends KernelTestCase ]; } - /** - * @param int $number - * @param int $itemPerPage - * @param int $expectedItemPerPage - * - * @dataProvider generateGetFirstItemNumber - */ - public function testGetFirstItemNumber( - $number, - $itemPerPage, - $expectedItemPerPage, - ) { - $page = $this->generatePage($number, $itemPerPage); - - $this->assertEquals($expectedItemPerPage, $page->getFirstItemNumber()); - } - - /** - * @param int $number - * @param int $itemPerPage - * @param int $expectedItemPerPage - * - * @dataProvider generateGetLastItemNumber - */ - public function testGetLastItemNumber( - $number, - $itemPerPage, - $expectedItemPerPage, - ) { - $page = $this->generatePage($number, $itemPerPage); - - $this->assertEquals($expectedItemPerPage, $page->getLastItemNumber()); - } - public function testPageNumber() { $page = $this->generatePage(1); diff --git a/src/Bundle/ChillMainBundle/Tests/Pagination/PaginatorTest.php b/src/Bundle/ChillMainBundle/Tests/Pagination/PaginatorTest.php index f32a772eb..28a6d426b 100644 --- a/src/Bundle/ChillMainBundle/Tests/Pagination/PaginatorTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Pagination/PaginatorTest.php @@ -34,73 +34,6 @@ final class PaginatorTest extends KernelTestCase $this->prophet = new \Prophecy\Prophet(); } - /** - * generate an array with a set of page with : - * - total items ; - * - item per page ; - * - current page number ; - * - expected hasNextPage result. - * - * @return array - */ - public static function generateHasNextPage() - { - return [ - [10, 10, 1, false], - [20, 10, 1, true], - [12, 10, 1, true], - [12, 10, 2, false], - ]; - } - - public static function generateHasPage() - { - return [ - [10, 10, -1, false], - [12, 10, 1, true], - [12, 10, 2, true], - [12, 10, 3, false], - ]; - } - - /** - * generate an array with a set of page with : - * - total items ; - * - item per page ; - * - current page number ; - * - expected hasPreviousPage result. - * - * @return array - */ - public static function generateHasPreviousPage() - { - return [ - [10, 10, 1, false], - [20, 10, 1, false], - [12, 10, 1, false], - [12, 10, 2, true], - ]; - } - - /** - * generate a set of pages with different maxItem, itemPerPage, and - * expected page number. - * - * @return array - */ - public static function generatePageNumber() - { - return [ - [12, 10, 2], - [20, 10, 2], - [21, 10, 3], - [19, 10, 2], - [1, 10, 1], - [0, 10, 1], - [10, 10, 1], - ]; - } - public function testGetPage() { $paginator = $this->generatePaginator(105, 10); @@ -127,6 +60,25 @@ final class PaginatorTest extends KernelTestCase $this->assertEquals($expectedHasNextPage, $paginator->hasNextPage()); } + /** + * generate an array with a set of page with : + * - total items ; + * - item per page ; + * - current page number ; + * - expected hasNextPage result. + * + * @return array + */ + public static function generateHasNextPage() + { + return [ + [10, 10, 1, false], + [20, 10, 1, true], + [12, 10, 1, true], + [12, 10, 2, false], + ]; + } + /** * test the HasPage function. * @@ -148,6 +100,16 @@ final class PaginatorTest extends KernelTestCase $this->assertEquals($expectedHasPage, $paginator->hasPage($pageNumber)); } + public static function generateHasPage() + { + return [ + [10, 10, -1, false], + [12, 10, 1, true], + [12, 10, 2, true], + [12, 10, 3, false], + ]; + } + /** * @param int $totalItems * @param int $itemPerPage @@ -166,6 +128,25 @@ final class PaginatorTest extends KernelTestCase $this->assertEquals($expectedHasNextPage, $paginator->hasPreviousPage()); } + /** + * generate an array with a set of page with : + * - total items ; + * - item per page ; + * - current page number ; + * - expected hasPreviousPage result. + * + * @return array + */ + public static function generateHasPreviousPage() + { + return [ + [10, 10, 1, false], + [20, 10, 1, false], + [12, 10, 1, false], + [12, 10, 2, true], + ]; + } + /** * Test that the countPages method (and his alias `count`) return * valid results. @@ -184,6 +165,25 @@ final class PaginatorTest extends KernelTestCase $this->assertEquals($expectedPageNumber, \count($paginator)); } + /** + * generate a set of pages with different maxItem, itemPerPage, and + * expected page number. + * + * @return array + */ + public static function generatePageNumber() + { + return [ + [12, 10, 2], + [20, 10, 2], + [21, 10, 3], + [19, 10, 2], + [1, 10, 1], + [0, 10, 1], + [10, 10, 1], + ]; + } + public function testPagesGenerator() { $paginator = $this->generatePaginator(105, 10); diff --git a/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php index 6f65c612f..182980dd0 100644 --- a/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php @@ -25,6 +25,25 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; */ final class PhonenumberHelperTest extends KernelTestCase { + /** + * @dataProvider formatPhonenumbers + */ + public function testFormatPhonenumbers(string $defaultCarrierCode, string $phoneNumber, string $expected) + { + $util = PhoneNumberUtil::getInstance(); + $subject = new PhonenumberHelper( + new ArrayAdapter(), + new ParameterBag([ + 'chill_main.phone_helper' => [ + 'default_carrier_code' => $defaultCarrierCode, + ], + ]), + new NullLogger() + ); + + $this->assertEquals($expected, $subject->format($util->parse($phoneNumber))); + } + public static function formatPhonenumbers() { yield [ @@ -51,23 +70,4 @@ final class PhonenumberHelperTest extends KernelTestCase '00 33 6 23 12 45 54', ]; } - - /** - * @dataProvider formatPhonenumbers - */ - public function testFormatPhonenumbers(string $defaultCarrierCode, string $phoneNumber, string $expected) - { - $util = PhoneNumberUtil::getInstance(); - $subject = new PhonenumberHelper( - new ArrayAdapter(), - new ParameterBag([ - 'chill_main.phone_helper' => [ - 'default_carrier_code' => $defaultCarrierCode, - ], - ]), - new NullLogger() - ); - - $this->assertEquals($expected, $subject->format($util->parse($phoneNumber))); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php index 00339b100..f5ed33bd0 100644 --- a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractDateFromPatternTest.php @@ -21,23 +21,6 @@ use PHPUnit\Framework\TestCase; */ final class ExtractDateFromPatternTest extends TestCase { - public static function provideSubjects() - { - yield ['15/06/1981', '', 1, '1981-06-15']; - - yield ['15/06/1981 30/12/1987', '', 2, '1981-06-15', '1987-12-30']; - - yield ['diallo 15/06/1981', 'diallo', 1, '1981-06-15']; - - yield ['diallo 31/03/1981', 'diallo', 1, '1981-03-31']; - - yield ['diallo 15-06-1981', 'diallo', 1, '1981-06-15']; - - yield ['diallo 1981-12-08', 'diallo', 1, '1981-12-08']; - - yield ['diallo', 'diallo', 0]; - } - /** * @dataProvider provideSubjects */ @@ -59,4 +42,21 @@ final class ExtractDateFromPatternTest extends TestCase $this->assertContains($date, $dates); } } + + public static function provideSubjects() + { + yield ['15/06/1981', '', 1, '1981-06-15']; + + yield ['15/06/1981 30/12/1987', '', 2, '1981-06-15', '1987-12-30']; + + yield ['diallo 15/06/1981', 'diallo', 1, '1981-06-15']; + + yield ['diallo 31/03/1981', 'diallo', 1, '1981-03-31']; + + yield ['diallo 15-06-1981', 'diallo', 1, '1981-06-15']; + + yield ['diallo 1981-12-08', 'diallo', 1, '1981-12-08']; + + yield ['diallo', 'diallo', 0]; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php index 96d7d4622..3f5950a4c 100644 --- a/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Search/Utils/ExtractPhonenumberFromPatternTest.php @@ -22,6 +22,21 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; */ final class ExtractPhonenumberFromPatternTest extends KernelTestCase { + /** + * @dataProvider provideData + */ + public function testExtract(string $defaultCarrierCode, mixed $subject, mixed $expectedCount, mixed $expected, mixed $filteredSubject, mixed $msg) + { + $extractor = new ExtractPhonenumberFromPattern(new ParameterBag(['chill_main' => [ + 'phone_helper' => ['default_carrier_code' => $defaultCarrierCode], + ]])); + $result = $extractor->extractPhonenumber($subject); + + $this->assertCount($expectedCount, $result->getFound()); + $this->assertEquals($filteredSubject, $result->getFilteredSubject()); + $this->assertEquals($expected, $result->getFound()); + } + public static function provideData() { yield ['BE', 'Diallo', 0, [], 'Diallo', 'no phonenumber']; @@ -44,19 +59,4 @@ final class ExtractPhonenumberFromPatternTest extends KernelTestCase yield ['FR', 'Diallo +32486 123 456', 1, ['+32486123456'], 'Diallo', 'a phonenumber and a name']; } - - /** - * @dataProvider provideData - */ - public function testExtract(string $defaultCarrierCode, mixed $subject, mixed $expectedCount, mixed $expected, mixed $filteredSubject, mixed $msg) - { - $extractor = new ExtractPhonenumberFromPattern(new ParameterBag(['chill_main' => [ - 'phone_helper' => ['default_carrier_code' => $defaultCarrierCode], - ]])); - $result = $extractor->extractPhonenumber($subject); - - $this->assertCount($expectedCount, $result->getFound()); - $this->assertEquals($filteredSubject, $result->getFilteredSubject()); - $this->assertEquals($expected, $result->getFound()); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Security/Authorization/AuthorizationHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Security/Authorization/AuthorizationHelperTest.php index 4ce7034ce..b4538ce95 100644 --- a/src/Bundle/ChillMainBundle/Tests/Security/Authorization/AuthorizationHelperTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Security/Authorization/AuthorizationHelperTest.php @@ -45,6 +45,29 @@ final class AuthorizationHelperTest extends KernelTestCase self::bootKernel(); } + /** + * @group legacy + */ + public function testGetParentRoles() + { + $parentRoles = $this->getAuthorizationHelper() + ->getParentRoles('CHILL_INHERITED_ROLE_1'); + + $this->assertContains( + 'CHILL_MASTER_ROLE', + $parentRoles, + 'Assert that `CHILL_MASTER_ROLE` is a parent of `CHILL_INHERITED_ROLE_1`' + ); + } + + /** + * @dataProvider dataProvider_getReachableCenters + */ + public function testGetReachableCenters(mixed $test, mixed $result, mixed $msg) + { + $this->assertEquals($test, $result, $msg); + } + public static function dataProvider_getReachableCenters() { self::bootKernel(); @@ -143,6 +166,30 @@ final class AuthorizationHelperTest extends KernelTestCase ]; } + /** + * @dataProvider dataProvider_getReachableScopes + * + * @param bool $expectedResult + * @param string $message + */ + public function testGetReachableScopes( + $expectedResult, + Scope $testedScope, + User $user, + string $role, + Center $center, + $message, + ) { + $reachableScopes = $this->getAuthorizationHelper() + ->getReachableScopes($user, $role, $center); + + $this->assertEquals( + $expectedResult, + \in_array($testedScope, $reachableScopes, true), + $message + ); + } + public static function dataProvider_getReachableScopes() { $centerA = self::prepareCenter(1, 'center A'); @@ -198,53 +245,6 @@ final class AuthorizationHelperTest extends KernelTestCase ]; } - /** - * @group legacy - */ - public function testGetParentRoles() - { - $parentRoles = $this->getAuthorizationHelper() - ->getParentRoles('CHILL_INHERITED_ROLE_1'); - - $this->assertContains( - 'CHILL_MASTER_ROLE', - $parentRoles, - 'Assert that `CHILL_MASTER_ROLE` is a parent of `CHILL_INHERITED_ROLE_1`' - ); - } - - /** - * @dataProvider dataProvider_getReachableCenters - */ - public function testGetReachableCenters(mixed $test, mixed $result, mixed $msg) - { - $this->assertEquals($test, $result, $msg); - } - - /** - * @dataProvider dataProvider_getReachableScopes - * - * @param bool $expectedResult - * @param string $message - */ - public function testGetReachableScopes( - $expectedResult, - Scope $testedScope, - User $user, - string $role, - Center $center, - $message, - ) { - $reachableScopes = $this->getAuthorizationHelper() - ->getReachableScopes($user, $role, $center); - - $this->assertEquals( - $expectedResult, - \in_array($testedScope, $reachableScopes, true), - $message - ); - } - public function testtestUserHasAccessUserShouldHaveAccessEntityWithScope() { $center = $this->prepareCenter(1, 'center'); diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php index 74f44d05e..16de1fab3 100644 --- a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DateNormalizerTest.php @@ -32,6 +32,14 @@ final class DateNormalizerTest extends KernelTestCase $this->prophet = new Prophet(); } + /** + * @dataProvider generateDataNormalize + */ + public function testNormalize(mixed $expected, mixed $date, mixed $format, mixed $locale, mixed $msg) + { + $this->assertEquals($expected, $this->buildDateNormalizer($locale)->normalize($date, $format, []), $msg); + } + public static function generateDataNormalize() { $datetime = \DateTime::createFromFormat('Y-m-d H:i:sO', '2021-06-05 15:05:01+02:00'); @@ -63,14 +71,6 @@ final class DateNormalizerTest extends KernelTestCase ]; } - /** - * @dataProvider generateDataNormalize - */ - public function testNormalize(mixed $expected, mixed $date, mixed $format, mixed $locale, mixed $msg) - { - $this->assertEquals($expected, $this->buildDateNormalizer($locale)->normalize($date, $format, []), $msg); - } - public function testSupports() { $this->assertTrue($this->buildDateNormalizer()->supportsNormalization(new \DateTime(), 'json')); diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php index da1adbf89..691e32f26 100644 --- a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/DoctrineExistingEntityNormalizerTest.php @@ -35,6 +35,17 @@ final class DoctrineExistingEntityNormalizerTest extends KernelTestCase $this->normalizer = new DoctrineExistingEntityNormalizer($em, $serializerFactory); } + /** + * @dataProvider dataProviderUserId + */ + public function testGetMappedClass(mixed $userId) + { + $data = ['type' => 'user', 'id' => $userId]; + $supports = $this->normalizer->supportsDenormalization($data, User::class); + + $this->assertTrue($supports); + } + public static function dataProviderUserId() { self::bootKernel(); @@ -49,15 +60,4 @@ final class DoctrineExistingEntityNormalizerTest extends KernelTestCase yield [$userIds[0]['id']]; } - - /** - * @dataProvider dataProviderUserId - */ - public function testGetMappedClass(mixed $userId) - { - $data = ['type' => 'user', 'id' => $userId]; - $supports = $this->normalizer->supportsDenormalization($data, User::class); - - $this->assertTrue($supports); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php index b36abdd3f..606bdda05 100644 --- a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/PhonenumberNormalizerTest.php @@ -28,15 +28,6 @@ final class PhonenumberNormalizerTest extends TestCase { use ProphecyTrait; - public static function dataProviderNormalizePhonenumber() - { - $phonenumberUtil = PhoneNumberUtil::getInstance(); - - yield [$phonenumberUtil->parse('+32486123465'), 'docgen', ['docgen:expects' => PhoneNumber::class], '0486 12 34 65']; - - yield [null, 'docgen', ['docgen:expects' => PhoneNumber::class], '']; - } - /** * @dataProvider dataProviderNormalizePhonenumber */ @@ -48,4 +39,13 @@ final class PhonenumberNormalizerTest extends TestCase $this->assertEquals($expected, $normalizer->normalize($phonenumber, $format, $context)); } + + public static function dataProviderNormalizePhonenumber() + { + $phonenumberUtil = PhoneNumberUtil::getInstance(); + + yield [$phonenumberUtil->parse('+32486123465'), 'docgen', ['docgen:expects' => PhoneNumber::class], '0486 12 34 65']; + + yield [null, 'docgen', ['docgen:expects' => PhoneNumber::class], '']; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/UserNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/UserNormalizerTest.php index 488495027..dacdace88 100644 --- a/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/UserNormalizerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Serializer/Normalizer/UserNormalizerTest.php @@ -38,6 +38,34 @@ final class UserNormalizerTest extends TestCase { use ProphecyTrait; + /** + * @dataProvider dataProviderUserNormalizer + * + * @throws ExceptionInterface + */ + public function testNormalize(?User $user, mixed $format, mixed $context, mixed $expected) + { + $userRender = $this->prophesize(UserRender::class); + $userRender->renderString(Argument::type(User::class), Argument::type('array'))->willReturn($user ? $user->getLabel() : ''); + + $clock = new MockClock(new \DateTimeImmutable('now')); + + $normalizer = new UserNormalizer($userRender->reveal(), $clock); + $normalizer->setNormalizer(new class () implements NormalizerInterface { + public function normalize($object, ?string $format = null, array $context = []) + { + return ['context' => $context['docgen:expects'] ?? null]; + } + + public function supportsNormalization($data, ?string $format = null) + { + return true; + } + }); + + $this->assertEquals($expected, $normalizer->normalize($user, $format, $context)); + } + /** * @throws NumberParseException */ @@ -112,32 +140,4 @@ final class UserNormalizerTest extends TestCase 'main_center' => ['context' => Center::class], ]]; } - - /** - * @dataProvider dataProviderUserNormalizer - * - * @throws ExceptionInterface - */ - public function testNormalize(?User $user, mixed $format, mixed $context, mixed $expected) - { - $userRender = $this->prophesize(UserRender::class); - $userRender->renderString(Argument::type(User::class), Argument::type('array'))->willReturn($user ? $user->getLabel() : ''); - - $clock = new MockClock(new \DateTimeImmutable('now')); - - $normalizer = new UserNormalizer($userRender->reveal(), $clock); - $normalizer->setNormalizer(new class () implements NormalizerInterface { - public function normalize($object, ?string $format = null, array $context = []) - { - return ['context' => $context['docgen:expects'] ?? null]; - } - - public function supportsNormalization($data, ?string $format = null) - { - return true; - } - }); - - $this->assertEquals($expected, $normalizer->normalize($user, $format, $context)); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Services/AddressGeographicalUnit/CollateAddressWithReferenceOrPostalCodeCronJobTest.php b/src/Bundle/ChillMainBundle/Tests/Services/AddressGeographicalUnit/CollateAddressWithReferenceOrPostalCodeCronJobTest.php index e3a29e285..9565ac72d 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/AddressGeographicalUnit/CollateAddressWithReferenceOrPostalCodeCronJobTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/AddressGeographicalUnit/CollateAddressWithReferenceOrPostalCodeCronJobTest.php @@ -45,6 +45,14 @@ class CollateAddressWithReferenceOrPostalCodeCronJobTest extends TestCase self::assertEquals($expected, $job->canRun($execution)); } + public static function provideDataCanRun(): iterable + { + yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-10T11:00:00'), false]; + yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-10T05:00:00'), true]; + yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-01T12:00:00'), true]; + yield [new \DateTimeImmutable('2023-07-10T12:00:00'), null, true]; + } + public function testRun(): void { $clock = new MockClock(); @@ -57,12 +65,4 @@ class CollateAddressWithReferenceOrPostalCodeCronJobTest extends TestCase $actual = $job->run(['last-max-id' => 0]); self::assertEquals(['last-max-id' => 1], $actual); } - - public static function provideDataCanRun(): iterable - { - yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-10T11:00:00'), false]; - yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-10T05:00:00'), true]; - yield [new \DateTimeImmutable('2023-07-10T12:00:00'), new \DateTimeImmutable('2023-07-01T12:00:00'), true]; - yield [new \DateTimeImmutable('2023-07-10T12:00:00'), null, true]; - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php index 5b2dfb954..bb255f5e7 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php @@ -29,37 +29,6 @@ final class RollingDateConverterTest extends TestCase $this->converter = new RollingDateConverter(); } - public static function generateDataConversionDate(): iterable - { - $format = 'Y-m-d His'; - - yield [RollingDate::T_MONTH_CURRENT_START, '2022-11-01 000000', $format]; - - yield [RollingDate::T_MONTH_NEXT_START, '2022-12-01 000000', $format]; - - yield [RollingDate::T_MONTH_PREVIOUS_START, '2022-10-01 000000', $format]; - - yield [RollingDate::T_QUARTER_CURRENT_START, '2022-10-01 000000', $format]; - - yield [RollingDate::T_QUARTER_NEXT_START, '2023-01-01 000000', $format]; - - yield [RollingDate::T_QUARTER_PREVIOUS_START, '2022-07-01 000000', $format]; - - yield [RollingDate::T_TODAY, '2022-11-07 000000', $format]; - - yield [RollingDate::T_WEEK_CURRENT_START, '2022-11-07 000000', $format]; - - yield [RollingDate::T_WEEK_NEXT_START, '2022-11-14 000000', $format]; - - yield [RollingDate::T_WEEK_PREVIOUS_START, '2022-10-31 000000', $format]; - - yield [RollingDate::T_YEAR_CURRENT_START, '2022-01-01 000000', $format]; - - yield [RollingDate::T_YEAR_NEXT_START, '2023-01-01 000000', $format]; - - yield [RollingDate::T_YEAR_PREVIOUS_START, '2021-01-01 000000', $format]; - } - public function testConversionFixedDate() { $rollingDate = new RollingDate(RollingDate::T_FIXED_DATE, new \DateTimeImmutable('2022-01-01')); @@ -97,4 +66,35 @@ final class RollingDateConverterTest extends TestCase $this->converter->convert($rollingDate) ); } + + public static function generateDataConversionDate(): iterable + { + $format = 'Y-m-d His'; + + yield [RollingDate::T_MONTH_CURRENT_START, '2022-11-01 000000', $format]; + + yield [RollingDate::T_MONTH_NEXT_START, '2022-12-01 000000', $format]; + + yield [RollingDate::T_MONTH_PREVIOUS_START, '2022-10-01 000000', $format]; + + yield [RollingDate::T_QUARTER_CURRENT_START, '2022-10-01 000000', $format]; + + yield [RollingDate::T_QUARTER_NEXT_START, '2023-01-01 000000', $format]; + + yield [RollingDate::T_QUARTER_PREVIOUS_START, '2022-07-01 000000', $format]; + + yield [RollingDate::T_TODAY, '2022-11-07 000000', $format]; + + yield [RollingDate::T_WEEK_CURRENT_START, '2022-11-07 000000', $format]; + + yield [RollingDate::T_WEEK_NEXT_START, '2022-11-14 000000', $format]; + + yield [RollingDate::T_WEEK_PREVIOUS_START, '2022-10-31 000000', $format]; + + yield [RollingDate::T_YEAR_CURRENT_START, '2022-01-01 000000', $format]; + + yield [RollingDate::T_YEAR_NEXT_START, '2023-01-01 000000', $format]; + + yield [RollingDate::T_YEAR_PREVIOUS_START, '2021-01-01 000000', $format]; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowCronJobTest.php b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowCronJobTest.php index 7f9246a81..f6bcde7ca 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowCronJobTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowCronJobTest.php @@ -48,6 +48,32 @@ class CancelStaleWorkflowCronJobTest extends TestCase self::assertEquals($expected, $cronJob->canRun($cronJobExecution)); } + /** + * @throws \Exception + */ + public static function buildTestCanRunData(): iterable + { + yield [ + (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-31 00:00:00', new \DateTimeZone('+00:00'))), + true, + ]; + + yield [ + (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-30 23:59:59', new \DateTimeZone('+00:00'))), + true, + ]; + + yield [ + (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-31 00:00:01', new \DateTimeZone('+00:00'))), + false, + ]; + + yield [ + null, + true, + ]; + } + /** * @throws \DateMalformedStringException * @throws \DateInvalidTimeZoneException @@ -85,32 +111,6 @@ class CancelStaleWorkflowCronJobTest extends TestCase return $entityWorkflow; } - /** - * @throws \Exception - */ - public static function buildTestCanRunData(): iterable - { - yield [ - (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-31 00:00:00', new \DateTimeZone('+00:00'))), - true, - ]; - - yield [ - (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-30 23:59:59', new \DateTimeZone('+00:00'))), - true, - ]; - - yield [ - (new CronJobExecution('last-canceled-workflow-id'))->setLastEnd(new \DateTimeImmutable('2023-12-31 00:00:01', new \DateTimeZone('+00:00'))), - false, - ]; - - yield [ - null, - true, - ]; - } - private function buildMessageBus(bool $expectDispatchAtLeastOnce = false): MessageBusInterface { $messageBus = $this->createMock(MessageBusInterface::class); diff --git a/src/Bundle/ChillMainBundle/Tests/Templating/Entity/AddressRenderTest.php b/src/Bundle/ChillMainBundle/Tests/Templating/Entity/AddressRenderTest.php index 0fbc69eab..06b5d8af7 100644 --- a/src/Bundle/ChillMainBundle/Tests/Templating/Entity/AddressRenderTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Templating/Entity/AddressRenderTest.php @@ -30,84 +30,16 @@ final class AddressRenderTest extends KernelTestCase self::bootKernel(); } - public static function addressDataProviderBEWithBuilding(): \Iterator + /** + * @dataProvider complexAddressDataProviderBE + */ + public function testRenderComplexAddressBE(Address $addr, string $expectedString): void { - $addr = new Address(); - $country = (new Country()) - ->setName(['fr' => 'Belgium']) - ->setCountryCode('BE'); - $postCode = new PostalCode(); - $postCode->setName('Locality') - ->setCode('012345') - ->setCountry($country); + $engine = self::getContainer()->get(\Twig\Environment::class); + $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); + $renderer = new AddressRender($engine, $translatableStringHelper); - $addr->setStreet('Rue ABC') - ->setStreetNumber('5') - ->setPostcode($postCode); - - $addr->setBuildingName('Résidence "Les Bleuets"'); - - yield [$addr, 'Résidence "Les Bleuets" — Rue ABC, 5 — 012345 Locality — Belgium']; - } - - public static function addressDataProviderBEWithSteps(): \Iterator - { - $addr = new Address(); - $country = (new Country()) - ->setName(['fr' => 'Belgium']) - ->setCountryCode('BE'); - $postCode = new PostalCode(); - $postCode->setName('Locality') - ->setCode('012345') - ->setCountry($country); - - $addr->setStreet('Rue ABC') - ->setStreetNumber('5') - ->setPostcode($postCode); - - $addr->setSteps('4'); - - yield [$addr, 'esc 4 — Rue ABC, 5 — 012345 Locality — Belgium']; - } - - public static function addressDataProviderFRWithBuilding(): \Iterator - { - $addr = new Address(); - $country = (new Country()) - ->setName(['fr' => 'France']) - ->setCountryCode('FR'); - $postCode = new PostalCode(); - $postCode->setName('Locality') - ->setCode('012345') - ->setCountry($country); - - $addr->setStreet('Rue ABC') - ->setStreetNumber('5') - ->setPostcode($postCode); - - $addr->setBuildingName('Résidence "Les Bleuets"'); - - yield [$addr, 'Résidence "Les Bleuets" — 5, Rue ABC — 012345 Locality — France']; - } - - public static function addressDataProviderFRWithSteps(): \Iterator - { - $addr = new Address(); - $country = (new Country()) - ->setName(['fr' => 'France']) - ->setCountryCode('FR'); - $postCode = new PostalCode(); - $postCode->setName('Locality') - ->setCode('012345') - ->setCountry($country); - - $addr->setStreet('Rue ABC') - ->setStreetNumber('5') - ->setPostcode($postCode); - - $addr->setSteps('4'); - - yield [$addr, 'esc 4 — 5, Rue ABC — 012345 Locality — France']; + $this->assertEquals($expectedString, $renderer->renderString($addr, [])); } public static function complexAddressDataProviderBE(): \Iterator @@ -134,6 +66,18 @@ final class AddressRenderTest extends KernelTestCase yield [$addr, 'Résidence "Les Bleuets" - appart 1 - ét 2 - coul 3 - esc 4 — Rue ABC, 5 — 012345 Locality — Belgium']; } + /** + * @dataProvider complexAddressDataProviderFR + */ + public function testRenderComplexAddressFR(Address $addr, string $expectedString): void + { + $engine = self::getContainer()->get(\Twig\Environment::class); + $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); + $renderer = new AddressRender($engine, $translatableStringHelper); + + $this->assertEquals($expectedString, $renderer->renderString($addr, [])); + } + public static function complexAddressDataProviderFR(): \Iterator { $addr = new Address(); @@ -160,6 +104,18 @@ final class AddressRenderTest extends KernelTestCase yield [$addr, 'appart 1 - ét 2 - coul 3 - esc 4 — Résidence "Les Bleuets" — 5, Rue ABC — A droite de la porte — 012345 Locality CEDEX — France']; } + /** + * @dataProvider noFullAddressDataProviderBE + */ + public function testRenderNoFullAddressBE(Address $addr, string $expectedString): void + { + $engine = self::getContainer()->get(\Twig\Environment::class); + $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); + $renderer = new AddressRender($engine, $translatableStringHelper); + + $this->assertEquals($expectedString, $renderer->renderString($addr, [])); + } + public static function noFullAddressDataProviderBE(): \Iterator { $addr = new Address(); @@ -177,6 +133,18 @@ final class AddressRenderTest extends KernelTestCase yield [$addr, '012345 Locality — Belgium']; } + /** + * @dataProvider simpleAddressDataProviderBE + */ + public function testRenderStringSimpleAddressBE(Address $addr, string $expectedString): void + { + $engine = self::getContainer()->get(\Twig\Environment::class); + $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); + $renderer = new AddressRender($engine, $translatableStringHelper); + + $this->assertEquals($expectedString, $renderer->renderString($addr, [])); + } + public static function simpleAddressDataProviderBE(): \Iterator { $addr = new Address(); @@ -195,6 +163,18 @@ final class AddressRenderTest extends KernelTestCase yield [$addr, 'Rue ABC, 5 — 012345 Locality — Belgium']; } + /** + * @dataProvider simpleAddressDataProviderFR + */ + public function testRenderStringSimpleAddressFR(Address $addr, string $expectedString): void + { + $engine = self::getContainer()->get(\Twig\Environment::class); + $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); + $renderer = new AddressRender($engine, $translatableStringHelper); + + $this->assertEquals($expectedString, $renderer->renderString($addr, [])); + } + public static function simpleAddressDataProviderFR(): \Iterator { $addr = new Address(); @@ -213,66 +193,6 @@ final class AddressRenderTest extends KernelTestCase yield [$addr, '5, Rue ABC — 012345 Locality — France']; } - /** - * @dataProvider complexAddressDataProviderBE - */ - public function testRenderComplexAddressBE(Address $addr, string $expectedString): void - { - $engine = self::getContainer()->get(\Twig\Environment::class); - $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); - $renderer = new AddressRender($engine, $translatableStringHelper); - - $this->assertEquals($expectedString, $renderer->renderString($addr, [])); - } - - /** - * @dataProvider complexAddressDataProviderFR - */ - public function testRenderComplexAddressFR(Address $addr, string $expectedString): void - { - $engine = self::getContainer()->get(\Twig\Environment::class); - $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); - $renderer = new AddressRender($engine, $translatableStringHelper); - - $this->assertEquals($expectedString, $renderer->renderString($addr, [])); - } - - /** - * @dataProvider noFullAddressDataProviderBE - */ - public function testRenderNoFullAddressBE(Address $addr, string $expectedString): void - { - $engine = self::getContainer()->get(\Twig\Environment::class); - $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); - $renderer = new AddressRender($engine, $translatableStringHelper); - - $this->assertEquals($expectedString, $renderer->renderString($addr, [])); - } - - /** - * @dataProvider simpleAddressDataProviderBE - */ - public function testRenderStringSimpleAddressBE(Address $addr, string $expectedString): void - { - $engine = self::getContainer()->get(\Twig\Environment::class); - $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); - $renderer = new AddressRender($engine, $translatableStringHelper); - - $this->assertEquals($expectedString, $renderer->renderString($addr, [])); - } - - /** - * @dataProvider simpleAddressDataProviderFR - */ - public function testRenderStringSimpleAddressFR(Address $addr, string $expectedString): void - { - $engine = self::getContainer()->get(\Twig\Environment::class); - $translatableStringHelper = self::getContainer()->get(TranslatableStringHelper::class); - $renderer = new AddressRender($engine, $translatableStringHelper); - - $this->assertEquals($expectedString, $renderer->renderString($addr, [])); - } - /** * @dataProvider addressDataProviderBEWithBuilding */ @@ -285,6 +205,26 @@ final class AddressRenderTest extends KernelTestCase $this->assertEquals($expectedString, $renderer->renderString($addr, [])); } + public static function addressDataProviderBEWithBuilding(): \Iterator + { + $addr = new Address(); + $country = (new Country()) + ->setName(['fr' => 'Belgium']) + ->setCountryCode('BE'); + $postCode = new PostalCode(); + $postCode->setName('Locality') + ->setCode('012345') + ->setCountry($country); + + $addr->setStreet('Rue ABC') + ->setStreetNumber('5') + ->setPostcode($postCode); + + $addr->setBuildingName('Résidence "Les Bleuets"'); + + yield [$addr, 'Résidence "Les Bleuets" — Rue ABC, 5 — 012345 Locality — Belgium']; + } + /** * @dataProvider addressDataProviderFRWithBuilding */ @@ -297,6 +237,26 @@ final class AddressRenderTest extends KernelTestCase $this->assertEquals($expectedString, $renderer->renderString($addr, [])); } + public static function addressDataProviderFRWithBuilding(): \Iterator + { + $addr = new Address(); + $country = (new Country()) + ->setName(['fr' => 'France']) + ->setCountryCode('FR'); + $postCode = new PostalCode(); + $postCode->setName('Locality') + ->setCode('012345') + ->setCountry($country); + + $addr->setStreet('Rue ABC') + ->setStreetNumber('5') + ->setPostcode($postCode); + + $addr->setBuildingName('Résidence "Les Bleuets"'); + + yield [$addr, 'Résidence "Les Bleuets" — 5, Rue ABC — 012345 Locality — France']; + } + /** * @dataProvider addressDataProviderBEWithSteps */ @@ -309,6 +269,26 @@ final class AddressRenderTest extends KernelTestCase $this->assertEquals($expectedString, $renderer->renderString($addr, [])); } + public static function addressDataProviderBEWithSteps(): \Iterator + { + $addr = new Address(); + $country = (new Country()) + ->setName(['fr' => 'Belgium']) + ->setCountryCode('BE'); + $postCode = new PostalCode(); + $postCode->setName('Locality') + ->setCode('012345') + ->setCountry($country); + + $addr->setStreet('Rue ABC') + ->setStreetNumber('5') + ->setPostcode($postCode); + + $addr->setSteps('4'); + + yield [$addr, 'esc 4 — Rue ABC, 5 — 012345 Locality — Belgium']; + } + /** * @dataProvider addressDataProviderFRWithSteps */ @@ -320,4 +300,24 @@ final class AddressRenderTest extends KernelTestCase $this->assertEquals($expectedString, $renderer->renderString($addr, [])); } + + public static function addressDataProviderFRWithSteps(): \Iterator + { + $addr = new Address(); + $country = (new Country()) + ->setName(['fr' => 'France']) + ->setCountryCode('FR'); + $postCode = new PostalCode(); + $postCode->setName('Locality') + ->setCode('012345') + ->setCountry($country); + + $addr->setStreet('Rue ABC') + ->setStreetNumber('5') + ->setPostcode($postCode); + + $addr->setSteps('4'); + + yield [$addr, 'esc 4 — 5, Rue ABC — 012345 Locality — France']; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowGuardTransitionTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowGuardTransitionTest.php index eaef9b0c2..b41dda808 100644 --- a/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowGuardTransitionTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowGuardTransitionTest.php @@ -116,6 +116,20 @@ class EntityWorkflowGuardTransitionTest extends TestCase } } + public static function provideBlockingTransition(): iterable + { + yield [self::buildEntityWorkflow([new User()]), 'transition1', new User(), false, 'f3eeb57c-7532-11ec-9495-e7942a2ac7bc']; + yield [self::buildEntityWorkflow([]), 'transition1', null, false, 'd9e39a18-704c-11ef-b235-8fe0619caee7']; + yield [self::buildEntityWorkflow([new User()]), 'transition1', null, false, 'd9e39a18-704c-11ef-b235-8fe0619caee7']; + yield [self::buildEntityWorkflow([$user = new User()]), 'transition3', $user, false, '5b6b95e0-704d-11ef-a5a9-4b6fc11a8eeb']; + yield [self::buildEntityWorkflow([$user = new User()]), 'transition3', $user, true, '5b6b95e0-704d-11ef-a5a9-4b6fc11a8eeb']; + + $userGroup = new UserGroup(); + $userGroup->addUser(new User()); + + yield [self::buildEntityWorkflow([$userGroup]), 'transition1', new User(), false, 'f3eeb57c-7532-11ec-9495-e7942a2ac7bc']; + } + /** * @dataProvider provideValidTransition */ @@ -139,20 +153,6 @@ class EntityWorkflowGuardTransitionTest extends TestCase self::assertEquals($newStep, $entityWorkflow->getStep()); } - public static function provideBlockingTransition(): iterable - { - yield [self::buildEntityWorkflow([new User()]), 'transition1', new User(), false, 'f3eeb57c-7532-11ec-9495-e7942a2ac7bc']; - yield [self::buildEntityWorkflow([]), 'transition1', null, false, 'd9e39a18-704c-11ef-b235-8fe0619caee7']; - yield [self::buildEntityWorkflow([new User()]), 'transition1', null, false, 'd9e39a18-704c-11ef-b235-8fe0619caee7']; - yield [self::buildEntityWorkflow([$user = new User()]), 'transition3', $user, false, '5b6b95e0-704d-11ef-a5a9-4b6fc11a8eeb']; - yield [self::buildEntityWorkflow([$user = new User()]), 'transition3', $user, true, '5b6b95e0-704d-11ef-a5a9-4b6fc11a8eeb']; - - $userGroup = new UserGroup(); - $userGroup->addUser(new User()); - - yield [self::buildEntityWorkflow([$userGroup]), 'transition1', new User(), false, 'f3eeb57c-7532-11ec-9495-e7942a2ac7bc']; - } - public static function provideValidTransition(): iterable { yield [self::buildEntityWorkflow([$u = new User()]), 'transition1', $u, false, 'step1']; diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowGuardUnsignedTransitionTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowGuardUnsignedTransitionTest.php index 6a27e723a..e2beb01e1 100644 --- a/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowGuardUnsignedTransitionTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/EventSubscriber/EntityWorkflowGuardUnsignedTransitionTest.php @@ -75,45 +75,6 @@ class EntityWorkflowGuardUnsignedTransitionTest extends TestCase } } - /** - * @dataProvider guardWaitingForSignatureWithPermissionToApplyAllTransitionsProvider - */ - public function testGuardWaitingForSignatureWithPermissionToApplyAllTransitions(EntityWorkflow $entityWorkflow, string $transition, bool $expectIsGranted, string $message) - { - $chillEntityRender = $this->prophesize(ChillEntityRenderManagerInterface::class); - $chillEntityRender->renderString(Argument::type('object'), Argument::type('array'))->will(fn ($args) => spl_object_hash($args[0])); - - $security = $this->prophesize(Security::class); - $isGranted = $security->isGranted(EntityWorkflowTransitionVoter::APPLY_ALL_TRANSITIONS, Argument::type(EntityWorkflow::class)); - if ($expectIsGranted) { - $isGranted->shouldBeCalled(); - } - $isGranted->willReturn(true); - - $registry = self::buildRegistry($chillEntityRender->reveal(), $security->reveal()); - - $workflow = $registry->get($entityWorkflow, 'dummy'); - - $actual = $workflow->buildTransitionBlockerList($entityWorkflow, $transition); - - self::assertCount(0, $actual, $message); - } - - public static function guardWaitingForSignatureWithPermissionToApplyAllTransitionsProvider(): iterable - { - $registry = self::buildRegistry(); - $entityWorkflow = new EntityWorkflow(); - $dto = new WorkflowTransitionContextDTO($entityWorkflow); - $dto->futureDestUsers = [$user = new User()]; - $dto->futureUserSignature = $user; - - $workflow = $registry->get($entityWorkflow, 'dummy'); - $workflow->apply($entityWorkflow, 'to_signature', ['context' => $dto, 'transitionAt' => new \DateTimeImmutable(), 'byUser' => new User(), 'transition' => 'to_signature']); - - yield [$entityWorkflow, 'to_post-signature', true, 'A transition forward is allowed, even if a signature is pending, because the user has permission to apply all transition']; - yield [$entityWorkflow, 'to_cancel', false, 'A transition backward is allowed, even if a signature is pending']; - } - public static function guardWaitingForSignatureWithoutPermissionToApplyAllTransitionsProvider(): iterable { $registry = self::buildRegistry(); @@ -156,6 +117,45 @@ class EntityWorkflowGuardUnsignedTransitionTest extends TestCase yield [$entityWorkflow, 'to_cancel', [], [], 'A transition backward is allowed, even if a signature is pending']; } + /** + * @dataProvider guardWaitingForSignatureWithPermissionToApplyAllTransitionsProvider + */ + public function testGuardWaitingForSignatureWithPermissionToApplyAllTransitions(EntityWorkflow $entityWorkflow, string $transition, bool $expectIsGranted, string $message) + { + $chillEntityRender = $this->prophesize(ChillEntityRenderManagerInterface::class); + $chillEntityRender->renderString(Argument::type('object'), Argument::type('array'))->will(fn ($args) => spl_object_hash($args[0])); + + $security = $this->prophesize(Security::class); + $isGranted = $security->isGranted(EntityWorkflowTransitionVoter::APPLY_ALL_TRANSITIONS, Argument::type(EntityWorkflow::class)); + if ($expectIsGranted) { + $isGranted->shouldBeCalled(); + } + $isGranted->willReturn(true); + + $registry = self::buildRegistry($chillEntityRender->reveal(), $security->reveal()); + + $workflow = $registry->get($entityWorkflow, 'dummy'); + + $actual = $workflow->buildTransitionBlockerList($entityWorkflow, $transition); + + self::assertCount(0, $actual, $message); + } + + public static function guardWaitingForSignatureWithPermissionToApplyAllTransitionsProvider(): iterable + { + $registry = self::buildRegistry(); + $entityWorkflow = new EntityWorkflow(); + $dto = new WorkflowTransitionContextDTO($entityWorkflow); + $dto->futureDestUsers = [$user = new User()]; + $dto->futureUserSignature = $user; + + $workflow = $registry->get($entityWorkflow, 'dummy'); + $workflow->apply($entityWorkflow, 'to_signature', ['context' => $dto, 'transitionAt' => new \DateTimeImmutable(), 'byUser' => new User(), 'transition' => 'to_signature']); + + yield [$entityWorkflow, 'to_post-signature', true, 'A transition forward is allowed, even if a signature is pending, because the user has permission to apply all transition']; + yield [$entityWorkflow, 'to_cancel', false, 'A transition backward is allowed, even if a signature is pending']; + } + private static function buildRegistry(?ChillEntityRenderManagerInterface $chillEntityRender = null, ?Security $security = null): Registry { $builder = new DefinitionBuilder(); diff --git a/src/Bundle/ChillMainBundle/Tests/Workflow/Helper/WorkflowRelatedEntityPermissionHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Workflow/Helper/WorkflowRelatedEntityPermissionHelperTest.php index fc06a533f..06e4304f3 100644 --- a/src/Bundle/ChillMainBundle/Tests/Workflow/Helper/WorkflowRelatedEntityPermissionHelperTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Workflow/Helper/WorkflowRelatedEntityPermissionHelperTest.php @@ -61,49 +61,6 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase self::assertEquals($expected, $helper->isAllowedByWorkflowForReadOperation(new \stdClass()), $message); } - /** - * @dataProvider provideDataAllowedByWorkflowWriteOperation - * - * @param list $entityWorkflows - */ - public function testAllowedByWorkflowWrite( - array $entityWorkflows, - User $user, - string $expected, - ?\DateTimeImmutable $atDate, - string $message, - ): void { - // all entities must have this workflow name, so we are ok to set it here - foreach ($entityWorkflows as $entityWorkflow) { - $entityWorkflow->setWorkflowName('dummy'); - } - $helper = $this->buildHelper($entityWorkflows, $user, $atDate); - - self::assertEquals($expected, $helper->isAllowedByWorkflowForWriteOperation(new \stdClass()), $message); - } - - public function testNoWorkflow(): void - { - $helper = $this->buildHelper([], new User(), null); - - self::assertEquals(WorkflowRelatedEntityPermissionHelper::ABSTAIN, $helper->isAllowedByWorkflowForWriteOperation(new \stdClass())); - self::assertEquals(WorkflowRelatedEntityPermissionHelper::ABSTAIN, $helper->isAllowedByWorkflowForReadOperation(new \stdClass())); - } - - /** - * @param list $entityWorkflows - */ - private function buildHelper(array $entityWorkflows, User $user, ?\DateTimeImmutable $atDateTime): WorkflowRelatedEntityPermissionHelper - { - $security = $this->prophesize(Security::class); - $security->getUser()->willReturn($user); - - $entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class); - $entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->willReturn($entityWorkflows); - - return new WorkflowRelatedEntityPermissionHelper($security->reveal(), $entityWorkflowManager->reveal(), $this->buildRegistry(), new MockClock($atDateTime ?? new \DateTimeImmutable())); - } - public static function provideDataAllowedByWorkflowReadOperation(): iterable { $entityWorkflow = new EntityWorkflow(); @@ -164,6 +121,27 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase 'force grant because there is a signature for person, already signed, a short time ago']; } + /** + * @dataProvider provideDataAllowedByWorkflowWriteOperation + * + * @param list $entityWorkflows + */ + public function testAllowedByWorkflowWrite( + array $entityWorkflows, + User $user, + string $expected, + ?\DateTimeImmutable $atDate, + string $message, + ): void { + // all entities must have this workflow name, so we are ok to set it here + foreach ($entityWorkflows as $entityWorkflow) { + $entityWorkflow->setWorkflowName('dummy'); + } + $helper = $this->buildHelper($entityWorkflows, $user, $atDate); + + self::assertEquals($expected, $helper->isAllowedByWorkflowForWriteOperation(new \stdClass()), $message); + } + public static function provideDataAllowedByWorkflowWriteOperation(): iterable { $entityWorkflow = new EntityWorkflow(); @@ -256,6 +234,28 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase 'abstain: there is a signature on a canceled workflow']; } + public function testNoWorkflow(): void + { + $helper = $this->buildHelper([], new User(), null); + + self::assertEquals(WorkflowRelatedEntityPermissionHelper::ABSTAIN, $helper->isAllowedByWorkflowForWriteOperation(new \stdClass())); + self::assertEquals(WorkflowRelatedEntityPermissionHelper::ABSTAIN, $helper->isAllowedByWorkflowForReadOperation(new \stdClass())); + } + + /** + * @param list $entityWorkflows + */ + private function buildHelper(array $entityWorkflows, User $user, ?\DateTimeImmutable $atDateTime): WorkflowRelatedEntityPermissionHelper + { + $security = $this->prophesize(Security::class); + $security->getUser()->willReturn($user); + + $entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class); + $entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->willReturn($entityWorkflows); + + return new WorkflowRelatedEntityPermissionHelper($security->reveal(), $entityWorkflowManager->reveal(), $this->buildRegistry(), new MockClock($atDateTime ?? new \DateTimeImmutable())); + } + private static function buildRegistry(): Registry { $builder = new DefinitionBuilder(); diff --git a/src/Bundle/ChillPersonBundle/Tests/Action/Remove/PersonMoveTest.php b/src/Bundle/ChillPersonBundle/Tests/Action/Remove/PersonMoveTest.php index b64b2cdeb..dfff116b2 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Action/Remove/PersonMoveTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Action/Remove/PersonMoveTest.php @@ -99,81 +99,6 @@ class PersonMoveTest extends KernelTestCase self::assertNotNull($personB?->getId(), $message); } - public function testMovePersonCenterHistory(): void - { - $personA = new Person(); - $personB = new Person(); - [$centerA, $centerB] = $this->centerRepository->findAll(); - - $this->em->persist($personA); - $this->em->persist($personB); - - $personCenterHistoryAFirst = (new Person\PersonCenterHistory())->setCenter($centerA) - ->setStartDate(new \DateTimeImmutable('2023-01-01')) - ->setEndDate(new \DateTimeImmutable('2023-06-30')); - $personCenterHistoryASecond = (new Person\PersonCenterHistory())->setCenter($centerB) - ->setStartDate(new \DateTimeImmutable('2023-06-30')) - ->setEndDate(new \DateTimeImmutable('2023-09-30')); - $personCenterHistoryBFirst = (new Person\PersonCenterHistory())->setCenter($centerA) - ->setStartDate(new \DateTimeImmutable('2023-03-01')) - ->setEndDate(new \DateTimeImmutable('2023-07-15')); - $personCenterHistoryBSecond = (new Person\PersonCenterHistory())->setCenter($centerB) - ->setStartDate(new \DateTimeImmutable('2023-07-15')) - ->setEndDate(new \DateTimeImmutable('2023-09-30')); - - $this->em->persist($personCenterHistoryAFirst); - $this->em->persist($personCenterHistoryASecond); - $this->em->persist($personCenterHistoryBFirst); - $this->em->persist($personCenterHistoryBSecond); - - $personA->addCenterHistory($personCenterHistoryAFirst); - $personA->addCenterHistory($personCenterHistoryASecond); - $personB->addCenterHistory($personCenterHistoryBFirst); - $personB->addCenterHistory($personCenterHistoryBSecond); - - $this->em->flush(); - $this->em->clear(); - - $move = new PersonMove($this->em, $this->personMoveManager, $this->eventDispatcher); - $sqls = $move->getSQL($personA, $personB); - $this->em->getConnection()->transactional(function (Connection $conn) use ($sqls) { - foreach ($sqls as $sql) { - $conn->executeStatement($sql); - } - }); - - $personsByIdOfA = $this->em->createQuery('SELECT p FROM '.Person::class.' p WHERE p.id = :id') - ->setParameter('id', $personA->getId()) - ->getResult(); - /** @var Person $personB */ - $personB = $this->em->find(Person::class, $personB->getId()); - $message = 'Move persons with overlapping center histories'; - - self::assertCount(0, $personsByIdOfA); - self::assertNotNull($personB?->getId(), $message); - - $centersHistories = $this->personCenterHistory->findBy(['person' => $personB]); - - // compute the oldest center history - $oldestCenterHistory = null; - foreach ($centersHistories as $centerHistory) { - $this->em->refresh($centerHistory); - if (null === $oldestCenterHistory || ($oldestCenterHistory instanceof Person\PersonCenterHistory && $oldestCenterHistory->getStartDate() >= $centerHistory->getStartDate())) { - $oldestCenterHistory = $centerHistory; - } - } - - self::assertCount(2, $centersHistories); - self::assertEquals('2023-01-01', $oldestCenterHistory?->getStartDate()->format('Y-m-d')); - - self::$entitiesToDelete[] = [Person::class, $personA]; - self::$entitiesToDelete[] = [Person::class, $personB]; - self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryAFirst]; - self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryASecond]; - self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryBFirst]; - self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryBSecond]; - } - public static function dataProviderMovePerson(): iterable { self::bootKernel(); @@ -280,4 +205,79 @@ class PersonMoveTest extends KernelTestCase $em->flush(); $em->clear(); } + + public function testMovePersonCenterHistory(): void + { + $personA = new Person(); + $personB = new Person(); + [$centerA, $centerB] = $this->centerRepository->findAll(); + + $this->em->persist($personA); + $this->em->persist($personB); + + $personCenterHistoryAFirst = (new Person\PersonCenterHistory())->setCenter($centerA) + ->setStartDate(new \DateTimeImmutable('2023-01-01')) + ->setEndDate(new \DateTimeImmutable('2023-06-30')); + $personCenterHistoryASecond = (new Person\PersonCenterHistory())->setCenter($centerB) + ->setStartDate(new \DateTimeImmutable('2023-06-30')) + ->setEndDate(new \DateTimeImmutable('2023-09-30')); + $personCenterHistoryBFirst = (new Person\PersonCenterHistory())->setCenter($centerA) + ->setStartDate(new \DateTimeImmutable('2023-03-01')) + ->setEndDate(new \DateTimeImmutable('2023-07-15')); + $personCenterHistoryBSecond = (new Person\PersonCenterHistory())->setCenter($centerB) + ->setStartDate(new \DateTimeImmutable('2023-07-15')) + ->setEndDate(new \DateTimeImmutable('2023-09-30')); + + $this->em->persist($personCenterHistoryAFirst); + $this->em->persist($personCenterHistoryASecond); + $this->em->persist($personCenterHistoryBFirst); + $this->em->persist($personCenterHistoryBSecond); + + $personA->addCenterHistory($personCenterHistoryAFirst); + $personA->addCenterHistory($personCenterHistoryASecond); + $personB->addCenterHistory($personCenterHistoryBFirst); + $personB->addCenterHistory($personCenterHistoryBSecond); + + $this->em->flush(); + $this->em->clear(); + + $move = new PersonMove($this->em, $this->personMoveManager, $this->eventDispatcher); + $sqls = $move->getSQL($personA, $personB); + $this->em->getConnection()->transactional(function (Connection $conn) use ($sqls) { + foreach ($sqls as $sql) { + $conn->executeStatement($sql); + } + }); + + $personsByIdOfA = $this->em->createQuery('SELECT p FROM '.Person::class.' p WHERE p.id = :id') + ->setParameter('id', $personA->getId()) + ->getResult(); + /** @var Person $personB */ + $personB = $this->em->find(Person::class, $personB->getId()); + $message = 'Move persons with overlapping center histories'; + + self::assertCount(0, $personsByIdOfA); + self::assertNotNull($personB?->getId(), $message); + + $centersHistories = $this->personCenterHistory->findBy(['person' => $personB]); + + // compute the oldest center history + $oldestCenterHistory = null; + foreach ($centersHistories as $centerHistory) { + $this->em->refresh($centerHistory); + if (null === $oldestCenterHistory || ($oldestCenterHistory instanceof Person\PersonCenterHistory && $oldestCenterHistory->getStartDate() >= $centerHistory->getStartDate())) { + $oldestCenterHistory = $centerHistory; + } + } + + self::assertCount(2, $centersHistories); + self::assertEquals('2023-01-01', $oldestCenterHistory?->getStartDate()->format('Y-m-d')); + + self::$entitiesToDelete[] = [Person::class, $personA]; + self::$entitiesToDelete[] = [Person::class, $personB]; + self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryAFirst]; + self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryASecond]; + self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryBFirst]; + self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryBSecond]; + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php index 301d90056..3d7156d84 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseApiControllerTest.php @@ -48,251 +48,6 @@ final class AccompanyingCourseApiControllerTest extends WebTestCase self::ensureKernelShutdown(); } - public static function dataGenerateNewAccompanyingCourse() - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - - $period = new AccompanyingPeriod(new \DateTime('1 week ago')); - $user = $em->getRepository(User::class) - ->findOneByUsernameCanonical('center a_social'); - $period->setCreatedBy($user); - // $period->setCreatedAt(new \DateTime('yesterday')); - - $center = $em->getRepository(Center::class) - ->findOneBy(['name' => 'Center A']); - - $personIds = $em->createQuery('SELECT p.id FROM '. - Person::class.' p JOIN p.centerCurrent cc'. - ' WHERE cc.center = :center') - ->setParameter('center', $center) - ->setMaxResults(100) - ->getScalarResult(); - - // create a random order - shuffle($personIds); - - for ($i = 0; 2 > $i; ++$i) { - $person = $em->getRepository(Person::class)->find(\array_pop($personIds)); - $period->addPerson($person); - } - - $em->persist($period); - $em->flush(); - - yield [$period]; - - self::ensureKernelShutdown(); - } - - public static function dataGenerateRandomAccompanyingCourse() - { - // note about max result for person query, and maxGenerated: - // - // in the final loop, an id is popped out of the personIds array twice: - // - // * one for getting the person, which will in turn provide his accompanying period; - // * one for getting the personId to populate to the data manager - // - // Ensure to keep always $maxGenerated to the double of $maxResults. x8 is a good compromize :) - $maxGenerated = 3; - $maxResults = $maxGenerated * 8; - - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - $center = $em->getRepository(Center::class) - ->findOneBy(['name' => 'Center A']); - - $qb = $em->createQueryBuilder(); - $personIds = $qb - ->select('p.id') - ->distinct(true) - ->from(Person::class, 'p') - ->join('p.accompanyingPeriodParticipations', 'participation') - ->join('participation.accompanyingPeriod', 'ap') - ->join('p.centerCurrent', 'cc') - ->where( - $qb->expr()->eq( - 'cc.center', - ':center' - ) - ) - ->andWhere( - $qb->expr()->gt( - 'SIZE(p.accompanyingPeriodParticipations)', - 0 - ) - ) - ->andWhere( - $qb->expr()->eq('ap.step', ':step') - ) - ->setParameter('center', $center) - ->setParameter('step', AccompanyingPeriod::STEP_CONFIRMED) - ->setMaxResults($maxResults) - ->getQuery() - ->getScalarResult(); - - // create a random order - shuffle($personIds); - - $nbGenerated = 0; - - while ($nbGenerated < $maxGenerated) { - $id = \array_pop($personIds)['id']; - - $person = $em->getRepository(Person::class) - ->find($id); - $periods = $person->getAccompanyingPeriods(); - - yield [\array_pop($personIds)['id'], $periods[\array_rand($periods)]->getId()]; - - ++$nbGenerated; - } - - self::ensureKernelShutdown(); - } - - public static function dataGenerateRandomAccompanyingCourseWithSocialIssue() - { - // note about max result for person query, and maxGenerated: - // - // in the final loop, an id is popped out of the personIds array twice: - // - // * one for getting the person, which will in turn provide his accompanying period; - // * one for getting the personId to populate to the data manager - // - // Ensure to keep always $maxGenerated to the double of $maxResults. x8 is a good compromize :) - $maxGenerated = 3; - $maxResults = $maxGenerated * 8; - - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - $center = $em->getRepository(Center::class) - ->findOneBy(['name' => 'Center A']); - $qb = $em->createQueryBuilder(); - - $personIds = $qb - ->select('p.id') - ->distinct(true) - ->from(Person::class, 'p') - ->join('p.accompanyingPeriodParticipations', 'participation') - ->join('participation.accompanyingPeriod', 'ap') - ->join('p.centerCurrent', 'cc') - ->where( - $qb->expr()->eq( - 'cc.center', - ':center' - ) - ) - ->andWhere( - $qb->expr()->gt( - 'SIZE(p.accompanyingPeriodParticipations)', - 0 - ) - ) - ->andWhere( - $qb->expr()->eq('ap.step', ':step') - ) - ->setParameter('center', $center) - ->setParameter('step', AccompanyingPeriod::STEP_CONFIRMED) - ->setMaxResults($maxResults) - ->getQuery() - ->getScalarResult(); - - // create a random order - shuffle($personIds); - - $socialIssues = $em->createQuery('SELECT s FROM '. - SocialIssue::class.' s ') - ->setMaxResults(10) - ->getResult(); - - $nbGenerated = 0; - - while ($nbGenerated < $maxGenerated) { - $id = \array_pop($personIds)['id']; - - $person = $em->getRepository(Person::class) - ->find($id); - $periods = $person->getAccompanyingPeriods(); - - yield [$periods[\array_rand($periods)], $socialIssues[\array_rand($socialIssues)]]; - - ++$nbGenerated; - } - - self::ensureKernelShutdown(); - } - - public static function dataGenerateRandomRequestorValidData(): \Iterator - { - $dataLength = 2; - $maxResults = 100; - - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - $center = $em->getRepository(Center::class) - ->findOneBy(['name' => 'Center A']); - $qb = $em->createQueryBuilder(); - - $personIds = $qb - ->select('p.id') - ->distinct(true) - ->from(Person::class, 'p') - ->join('p.accompanyingPeriodParticipations', 'participation') - ->join('participation.accompanyingPeriod', 'ap') - ->join('p.centerCurrent', 'cc') - ->where( - $qb->expr()->eq( - 'cc.center', - ':center' - ) - ) - ->andWhere( - $qb->expr()->gt( - 'SIZE(p.accompanyingPeriodParticipations)', - 0 - ) - ) - ->andWhere( - $qb->expr()->eq('ap.step', ':step') - ) - ->setParameter('center', $center) - ->setParameter('step', AccompanyingPeriod::STEP_CONFIRMED) - ->setMaxResults($maxResults) - ->getQuery() - ->getScalarResult(); - - // create a random order - shuffle($personIds); - - $thirdPartyIds = $em->createQuery('SELECT t.id FROM '. - ThirdParty::class.' t ') - ->setMaxResults($maxResults) - ->getScalarResult(); - - // create a random order - shuffle($thirdPartyIds); - - $i = 0; - - while ($i <= $dataLength) { - $person = $em->getRepository(Person::class) - ->find(\array_pop($personIds)['id']); - - if (0 === \count($person->getAccompanyingPeriods())) { - continue; - } - - $period = $person->getAccompanyingPeriods()[0]; - - yield [$period, \array_pop($personIds)['id'], \array_pop($thirdPartyIds)['id']]; - ++$i; - } - - self::ensureKernelShutdown(); - } - /** * @dataProvider dataGenerateRandomAccompanyingCourse */ @@ -403,6 +158,78 @@ final class AccompanyingCourseApiControllerTest extends WebTestCase $this->assertTrue(\in_array($this->client->getResponse()->getStatusCode(), [200, 422], true)); } + public static function dataGenerateRandomAccompanyingCourseWithSocialIssue() + { + // note about max result for person query, and maxGenerated: + // + // in the final loop, an id is popped out of the personIds array twice: + // + // * one for getting the person, which will in turn provide his accompanying period; + // * one for getting the personId to populate to the data manager + // + // Ensure to keep always $maxGenerated to the double of $maxResults. x8 is a good compromize :) + $maxGenerated = 3; + $maxResults = $maxGenerated * 8; + + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $center = $em->getRepository(Center::class) + ->findOneBy(['name' => 'Center A']); + $qb = $em->createQueryBuilder(); + + $personIds = $qb + ->select('p.id') + ->distinct(true) + ->from(Person::class, 'p') + ->join('p.accompanyingPeriodParticipations', 'participation') + ->join('participation.accompanyingPeriod', 'ap') + ->join('p.centerCurrent', 'cc') + ->where( + $qb->expr()->eq( + 'cc.center', + ':center' + ) + ) + ->andWhere( + $qb->expr()->gt( + 'SIZE(p.accompanyingPeriodParticipations)', + 0 + ) + ) + ->andWhere( + $qb->expr()->eq('ap.step', ':step') + ) + ->setParameter('center', $center) + ->setParameter('step', AccompanyingPeriod::STEP_CONFIRMED) + ->setMaxResults($maxResults) + ->getQuery() + ->getScalarResult(); + + // create a random order + shuffle($personIds); + + $socialIssues = $em->createQuery('SELECT s FROM '. + SocialIssue::class.' s ') + ->setMaxResults(10) + ->getResult(); + + $nbGenerated = 0; + + while ($nbGenerated < $maxGenerated) { + $id = \array_pop($personIds)['id']; + + $person = $em->getRepository(Person::class) + ->find($id); + $periods = $person->getAccompanyingPeriods(); + + yield [$periods[\array_rand($periods)], $socialIssues[\array_rand($socialIssues)]]; + + ++$nbGenerated; + } + + self::ensureKernelShutdown(); + } + /** * @dataProvider dataGenerateRandomAccompanyingCourse */ @@ -525,6 +352,43 @@ final class AccompanyingCourseApiControllerTest extends WebTestCase $this->period = $period; } + public static function dataGenerateNewAccompanyingCourse() + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + + $period = new AccompanyingPeriod(new \DateTime('1 week ago')); + $user = $em->getRepository(User::class) + ->findOneByUsernameCanonical('center a_social'); + $period->setCreatedBy($user); + // $period->setCreatedAt(new \DateTime('yesterday')); + + $center = $em->getRepository(Center::class) + ->findOneBy(['name' => 'Center A']); + + $personIds = $em->createQuery('SELECT p.id FROM '. + Person::class.' p JOIN p.centerCurrent cc'. + ' WHERE cc.center = :center') + ->setParameter('center', $center) + ->setMaxResults(100) + ->getScalarResult(); + + // create a random order + shuffle($personIds); + + for ($i = 0; 2 > $i; ++$i) { + $person = $em->getRepository(Person::class)->find(\array_pop($personIds)); + $period->addPerson($person); + } + + $em->persist($period); + $em->flush(); + + yield [$period]; + + self::ensureKernelShutdown(); + } + /** * @dataProvider dataGenerateRandomAccompanyingCourse */ @@ -539,6 +403,73 @@ final class AccompanyingCourseApiControllerTest extends WebTestCase $this->assertTrue(\in_array($client->getResponse()->getStatusCode(), [200, 422], true)); } + public static function dataGenerateRandomAccompanyingCourse() + { + // note about max result for person query, and maxGenerated: + // + // in the final loop, an id is popped out of the personIds array twice: + // + // * one for getting the person, which will in turn provide his accompanying period; + // * one for getting the personId to populate to the data manager + // + // Ensure to keep always $maxGenerated to the double of $maxResults. x8 is a good compromize :) + $maxGenerated = 3; + $maxResults = $maxGenerated * 8; + + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $center = $em->getRepository(Center::class) + ->findOneBy(['name' => 'Center A']); + + $qb = $em->createQueryBuilder(); + $personIds = $qb + ->select('p.id') + ->distinct(true) + ->from(Person::class, 'p') + ->join('p.accompanyingPeriodParticipations', 'participation') + ->join('participation.accompanyingPeriod', 'ap') + ->join('p.centerCurrent', 'cc') + ->where( + $qb->expr()->eq( + 'cc.center', + ':center' + ) + ) + ->andWhere( + $qb->expr()->gt( + 'SIZE(p.accompanyingPeriodParticipations)', + 0 + ) + ) + ->andWhere( + $qb->expr()->eq('ap.step', ':step') + ) + ->setParameter('center', $center) + ->setParameter('step', AccompanyingPeriod::STEP_CONFIRMED) + ->setMaxResults($maxResults) + ->getQuery() + ->getScalarResult(); + + // create a random order + shuffle($personIds); + + $nbGenerated = 0; + + while ($nbGenerated < $maxGenerated) { + $id = \array_pop($personIds)['id']; + + $person = $em->getRepository(Person::class) + ->find($id); + $periods = $person->getAccompanyingPeriods(); + + yield [\array_pop($personIds)['id'], $periods[\array_rand($periods)]->getId()]; + + ++$nbGenerated; + } + + self::ensureKernelShutdown(); + } + /** * @dataProvider dataGenerateRandomRequestorValidData */ @@ -713,6 +644,75 @@ final class AccompanyingCourseApiControllerTest extends WebTestCase $this->assertTrue(\in_array($response->getStatusCode(), [200, 422], true)); } + public static function dataGenerateRandomRequestorValidData(): \Iterator + { + $dataLength = 2; + $maxResults = 100; + + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $center = $em->getRepository(Center::class) + ->findOneBy(['name' => 'Center A']); + $qb = $em->createQueryBuilder(); + + $personIds = $qb + ->select('p.id') + ->distinct(true) + ->from(Person::class, 'p') + ->join('p.accompanyingPeriodParticipations', 'participation') + ->join('participation.accompanyingPeriod', 'ap') + ->join('p.centerCurrent', 'cc') + ->where( + $qb->expr()->eq( + 'cc.center', + ':center' + ) + ) + ->andWhere( + $qb->expr()->gt( + 'SIZE(p.accompanyingPeriodParticipations)', + 0 + ) + ) + ->andWhere( + $qb->expr()->eq('ap.step', ':step') + ) + ->setParameter('center', $center) + ->setParameter('step', AccompanyingPeriod::STEP_CONFIRMED) + ->setMaxResults($maxResults) + ->getQuery() + ->getScalarResult(); + + // create a random order + shuffle($personIds); + + $thirdPartyIds = $em->createQuery('SELECT t.id FROM '. + ThirdParty::class.' t ') + ->setMaxResults($maxResults) + ->getScalarResult(); + + // create a random order + shuffle($thirdPartyIds); + + $i = 0; + + while ($i <= $dataLength) { + $person = $em->getRepository(Person::class) + ->find(\array_pop($personIds)['id']); + + if (0 === \count($person->getAccompanyingPeriods())) { + continue; + } + + $period = $person->getAccompanyingPeriods()[0]; + + yield [$period, \array_pop($personIds)['id'], \array_pop($thirdPartyIds)['id']]; + ++$i; + } + + self::ensureKernelShutdown(); + } + public function testShow404() { $client = $this->getClientAuthenticated(); diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php index 240c3ab98..a2c221e8b 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingCourseControllerTest.php @@ -41,32 +41,6 @@ final class AccompanyingCourseControllerTest extends WebTestCase self::ensureKernelShutdown(); } - public static function dataGenerateRandomUsers(): \Iterator - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - - $period = new AccompanyingPeriod(new \DateTime('1 week ago')); - $user = $em->getRepository(User::class) - ->findOneByUsernameCanonical('center a_social'); - $period->setCreatedBy($user); - // $period->setCreatedAt(new \DateTime('yesterday')); - - $center = $em->getRepository(Center::class) - ->findOneBy(['name' => 'Center A']); - - $personIds = $em->createQuery('SELECT p.id FROM '. - Person::class.' p JOIN p.centerCurrent cc '. - ' WHERE cc.center = :center') - ->setParameter('center', $center) - ->setMaxResults(100) - ->getScalarResult(); - - yield [\array_pop($personIds), \array_pop($personIds)]; - - self::ensureKernelShutdown(); - } - public function testNewWithoutUsers() { $this->client->request('GET', '/fr/person/parcours/new'); @@ -104,4 +78,30 @@ final class AccompanyingCourseControllerTest extends WebTestCase $this->assertEquals(2, \count($period->getParticipations())); } + + public static function dataGenerateRandomUsers(): \Iterator + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + + $period = new AccompanyingPeriod(new \DateTime('1 week ago')); + $user = $em->getRepository(User::class) + ->findOneByUsernameCanonical('center a_social'); + $period->setCreatedBy($user); + // $period->setCreatedAt(new \DateTime('yesterday')); + + $center = $em->getRepository(Center::class) + ->findOneBy(['name' => 'Center A']); + + $personIds = $em->createQuery('SELECT p.id FROM '. + Person::class.' p JOIN p.centerCurrent cc '. + ' WHERE cc.center = :center') + ->setParameter('center', $center) + ->setMaxResults(100) + ->getScalarResult(); + + yield [\array_pop($personIds), \array_pop($personIds)]; + + self::ensureKernelShutdown(); + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php index 2b5c13a09..de7369469 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdApiControllerTest.php @@ -37,6 +37,28 @@ final class HouseholdApiControllerTest extends WebTestCase private static array $toDelete = []; + /** + * @dataProvider generateHouseholdAssociatedWithAddressReference + */ + public function testFindHouseholdByAddressReference(int $addressReferenceId, int $expectedHouseholdId) + { + $client = $this->getClientAuthenticated(); + + $client->request( + Request::METHOD_GET, + "/api/1.0/person/household/by-address-reference/{$addressReferenceId}.json" + ); + + $this->assertResponseIsSuccessful(); + $data = json_decode($client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR); + $this->assertArrayHasKey('count', $data); + $this->assertArrayHasKey('results', $data); + + $householdIds = \array_map(static fn ($r) => $r['id'], $data['results']); + + $this->assertContains($expectedHouseholdId, $householdIds); + } + public static function generateHouseholdAssociatedWithAddressReference() { self::bootKernel(); @@ -86,6 +108,21 @@ final class HouseholdApiControllerTest extends WebTestCase self::ensureKernelShutdown(); } + /** + * @dataProvider generateHouseholdId + */ + public function testSuggestAddressByHousehold(int $householdId) + { + $client = $this->getClientAuthenticated(); + + $client->request( + Request::METHOD_GET, + "/api/1.0/person/address/suggest/by-household/{$householdId}.json" + ); + + $this->assertResponseIsSuccessful(); + } + public static function generateHouseholdId() { self::bootKernel(); @@ -118,6 +155,21 @@ final class HouseholdApiControllerTest extends WebTestCase self::ensureKernelShutdown(); } + /** + * @dataProvider generatePersonId + */ + public function testSuggestByAccompanyingPeriodParticipation(int $personId) + { + $client = $this->getClientAuthenticated(); + + $client->request( + Request::METHOD_GET, + "/api/1.0/person/household/suggest/by-person/{$personId}/through-accompanying-period-participation.json" + ); + + $this->assertResponseIsSuccessful(); + } + public static function generatePersonId() { self::bootKernel(); @@ -139,56 +191,4 @@ final class HouseholdApiControllerTest extends WebTestCase self::ensureKernelShutdown(); } - - /** - * @dataProvider generateHouseholdAssociatedWithAddressReference - */ - public function testFindHouseholdByAddressReference(int $addressReferenceId, int $expectedHouseholdId) - { - $client = $this->getClientAuthenticated(); - - $client->request( - Request::METHOD_GET, - "/api/1.0/person/household/by-address-reference/{$addressReferenceId}.json" - ); - - $this->assertResponseIsSuccessful(); - $data = json_decode($client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR); - $this->assertArrayHasKey('count', $data); - $this->assertArrayHasKey('results', $data); - - $householdIds = \array_map(static fn ($r) => $r['id'], $data['results']); - - $this->assertContains($expectedHouseholdId, $householdIds); - } - - /** - * @dataProvider generateHouseholdId - */ - public function testSuggestAddressByHousehold(int $householdId) - { - $client = $this->getClientAuthenticated(); - - $client->request( - Request::METHOD_GET, - "/api/1.0/person/address/suggest/by-household/{$householdId}.json" - ); - - $this->assertResponseIsSuccessful(); - } - - /** - * @dataProvider generatePersonId - */ - public function testSuggestByAccompanyingPeriodParticipation(int $personId) - { - $client = $this->getClientAuthenticated(); - - $client->request( - Request::METHOD_GET, - "/api/1.0/person/household/suggest/by-person/{$personId}/through-accompanying-period-participation.json" - ); - - $this->assertResponseIsSuccessful(); - } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdControllerTest.php index 1012480b2..55976e270 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdControllerTest.php @@ -39,33 +39,6 @@ final class HouseholdControllerTest extends WebTestCase self::ensureKernelShutdown(); } - public static function generateValidHouseholdIds() - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - - $ids = $em->createQuery( - sprintf('SELECT DISTINCT h.id FROM %s h JOIN h.members m JOIN m.person p JOIN p.centerHistory ch JOIN ch.center c WHERE c.name = :center AND ch.endDate IS NULL', Household::class) - ) - ->setParameter('center', 'Center A') - ->setMaxResults(100) - ->getScalarResult(); - - if ([] === $ids) { - throw new \RuntimeException('no household ids with center "Center A"'); - } - - \shuffle($ids); - - yield [\array_pop($ids)['id']]; - - yield [\array_pop($ids)['id']]; - - yield [\array_pop($ids)['id']]; - - self::ensureKernelShutdown(); - } - /** * @dataProvider generateValidHouseholdIds */ @@ -136,4 +109,31 @@ final class HouseholdControllerTest extends WebTestCase $this->assertResponseIsSuccessful(); } + + public static function generateValidHouseholdIds() + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + + $ids = $em->createQuery( + sprintf('SELECT DISTINCT h.id FROM %s h JOIN h.members m JOIN m.person p JOIN p.centerHistory ch JOIN ch.center c WHERE c.name = :center AND ch.endDate IS NULL', Household::class) + ) + ->setParameter('center', 'Center A') + ->setMaxResults(100) + ->getScalarResult(); + + if ([] === $ids) { + throw new \RuntimeException('no household ids with center "Center A"'); + } + + \shuffle($ids); + + yield [\array_pop($ids)['id']]; + + yield [\array_pop($ids)['id']]; + + yield [\array_pop($ids)['id']]; + + self::ensureKernelShutdown(); + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php index 66b754eb3..245323ad4 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/HouseholdMemberControllerTest.php @@ -30,78 +30,6 @@ final class HouseholdMemberControllerTest extends WebTestCase { use PrepareClientTrait; - public static function provideValidDataEditMember(): \Iterator - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - - $membershipIds = $em->createQuery(sprintf('SELECT m.id FROM %s m JOIN m.person p JOIN p.centerHistory ch JOIN ch.center c WHERE c.name = :center AND m.endDate IS NULL AND ch.endDate IS NULL', HouseholdMember::class)) - ->setParameter('center', 'Center A') - ->getScalarResult(); - - if ([] === $membershipIds) { - throw new \RuntimeException("no memberships for person associated to 'Center A'"); - } - - \shuffle($membershipIds); - - yield [\array_pop($membershipIds)['id']]; - } - - public static function provideValidDataMove(): \Iterator - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - $yesterday = new \DateTimeImmutable('yesterday'); - - $personIds = $em->createQuery( - sprintf('SELECT p.id FROM %s p JOIN p.centerHistory ch JOIN ch.center c WHERE c.name = :center AND ch.endDate IS NULL', Person::class) - ) - ->setParameter('center', 'Center A') - ->setMaxResults(100) - ->getScalarResult(); - - if ([] === $personIds) { - throw new \RuntimeException('no person associated with "Center A"'); - } - - \shuffle($personIds); - - $household = new Household(); - $em->persist($household); - $em->flush(); - - $positions = $em->createQuery('SELECT pos.id FROM '.Position::class.' pos '. - 'WHERE pos.shareHouseHold = TRUE') - ->getResult(); - - $i = 0; - - do { - $id = \array_pop($personIds)['id']; - $person = self::getContainer()->get(EntityManagerInterface::class) - ->getRepository(Person::class) - ->find($id); - - $participation = $person->getCurrentHouseholdParticipationShareHousehold(); - - if (null === $participation - || ( - null === $participation->getEndDate() - && $participation->getStartDate() <= $yesterday - )) { - ++$i; - - yield [ - $id, - $household->getId(), - $positions[\random_int(0, \count($positions) - 1)]['id'], - new \DateTimeImmutable('tomorrow'), - ]; - } - } while (1 >= $i); - } - /** * @dataProvider provideValidDataEditMember */ @@ -124,6 +52,24 @@ final class HouseholdMemberControllerTest extends WebTestCase $this->assertEquals(302, $client->getResponse()->getStatusCode()); } + public static function provideValidDataEditMember(): \Iterator + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + + $membershipIds = $em->createQuery(sprintf('SELECT m.id FROM %s m JOIN m.person p JOIN p.centerHistory ch JOIN ch.center c WHERE c.name = :center AND m.endDate IS NULL AND ch.endDate IS NULL', HouseholdMember::class)) + ->setParameter('center', 'Center A') + ->getScalarResult(); + + if ([] === $membershipIds) { + throw new \RuntimeException("no memberships for person associated to 'Center A'"); + } + + \shuffle($membershipIds); + + yield [\array_pop($membershipIds)['id']]; + } + /** * @dataProvider provideValidDataMove */ @@ -278,4 +224,58 @@ final class HouseholdMemberControllerTest extends WebTestCase $this->assertArrayHasKey('id', $data['members'][0]['person']); $this->assertEquals($personId, $data['members'][0]['person']['id']); } + + public static function provideValidDataMove(): \Iterator + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $yesterday = new \DateTimeImmutable('yesterday'); + + $personIds = $em->createQuery( + sprintf('SELECT p.id FROM %s p JOIN p.centerHistory ch JOIN ch.center c WHERE c.name = :center AND ch.endDate IS NULL', Person::class) + ) + ->setParameter('center', 'Center A') + ->setMaxResults(100) + ->getScalarResult(); + + if ([] === $personIds) { + throw new \RuntimeException('no person associated with "Center A"'); + } + + \shuffle($personIds); + + $household = new Household(); + $em->persist($household); + $em->flush(); + + $positions = $em->createQuery('SELECT pos.id FROM '.Position::class.' pos '. + 'WHERE pos.shareHouseHold = TRUE') + ->getResult(); + + $i = 0; + + do { + $id = \array_pop($personIds)['id']; + $person = self::getContainer()->get(EntityManagerInterface::class) + ->getRepository(Person::class) + ->find($id); + + $participation = $person->getCurrentHouseholdParticipationShareHousehold(); + + if (null === $participation + || ( + null === $participation->getEndDate() + && $participation->getStartDate() <= $yesterday + )) { + ++$i; + + yield [ + $id, + $household->getId(), + $positions[\random_int(0, \count($positions) - 1)]['id'], + new \DateTimeImmutable('tomorrow'), + ]; + } + } while (1 >= $i); + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonApiControllerTest.php index 397dfdc78..bf23939cf 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonApiControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonApiControllerTest.php @@ -26,47 +26,6 @@ final class PersonApiControllerTest extends WebTestCase { use PrepareClientTrait; - public static function dataGetPersonFromCenterA(): \Iterator - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - $personIds = $em->createQuery(sprintf( - 'SELECT p.id FROM %s p JOIN p.centerCurrent pc JOIN pc.center c WHERE c.name = :center', - Person::class - )) - ->setParameter('center', 'Center A') - ->setMaxResults(100) - ->getScalarResult(); - - \shuffle($personIds); - - yield \array_pop($personIds); - - yield \array_pop($personIds); - - yield \array_pop($personIds); - - yield \array_pop($personIds); - } - - public static function dataGetPersonFromCenterB(): \Iterator - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - $personIds = $em->createQuery( - sprintf('SELECT p.id FROM %s p JOIN p.centerCurrent pc JOIN pc.center c WHERE c.name = :center', Person::class) - ) - ->setParameter('center', 'Center B') - ->setMaxResults(100) - ->getScalarResult(); - - \shuffle($personIds); - - yield \array_pop($personIds); - - yield \array_pop($personIds); - } - /** * @dataProvider dataGetPersonFromCenterA */ @@ -112,6 +71,29 @@ final class PersonApiControllerTest extends WebTestCase $this->assertEquals($personId, $data['id']); } + public static function dataGetPersonFromCenterA(): \Iterator + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $personIds = $em->createQuery(sprintf( + 'SELECT p.id FROM %s p JOIN p.centerCurrent pc JOIN pc.center c WHERE c.name = :center', + Person::class + )) + ->setParameter('center', 'Center A') + ->setMaxResults(100) + ->getScalarResult(); + + \shuffle($personIds); + + yield \array_pop($personIds); + + yield \array_pop($personIds); + + yield \array_pop($personIds); + + yield \array_pop($personIds); + } + /** * @dataProvider dataGetPersonFromCenterB */ @@ -124,4 +106,22 @@ final class PersonApiControllerTest extends WebTestCase $this->assertEquals(403, $response->getStatusCode()); } + + public static function dataGetPersonFromCenterB(): \Iterator + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $personIds = $em->createQuery( + sprintf('SELECT p.id FROM %s p JOIN p.centerCurrent pc JOIN pc.center c WHERE c.name = :center', Person::class) + ) + ->setParameter('center', 'Center B') + ->setMaxResults(100) + ->getScalarResult(); + + \shuffle($personIds); + + yield \array_pop($personIds); + + yield \array_pop($personIds); + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php index a290e56e8..9ca4be382 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php @@ -164,24 +164,6 @@ final class PersonControllerUpdateWithHiddenFieldsTest extends WebTestCase } } - /** - * Test the configurable fields are absent. - * - * @group configurable_fields - */ - public function testHiddenFielsAreAbsent() - { - $crawler = $this->client->request('GET', $this->editUrl); - - $configurables = ['placeOfBirth', 'phonenumber', 'email', - 'countryOfBirth', 'nationality', 'spokenLanguages', 'maritalStatus', ]; - $form = $crawler->selectButton('Submit')->form(); // ; - - foreach ($configurables as $key) { - $this->assertFalse($form->has('chill_personbundle_person['.$key.']')); - } - } - /** * provide valid values to test, with field name and * a function to find the value back from person entity. @@ -200,6 +182,24 @@ final class PersonControllerUpdateWithHiddenFieldsTest extends WebTestCase ]; } + /** + * Test the configurable fields are absent. + * + * @group configurable_fields + */ + public function testHiddenFielsAreAbsent() + { + $crawler = $this->client->request('GET', $this->editUrl); + + $configurables = ['placeOfBirth', 'phonenumber', 'email', + 'countryOfBirth', 'nationality', 'spokenLanguages', 'maritalStatus', ]; + $form = $crawler->selectButton('Submit')->form(); // ; + + foreach ($configurables as $key) { + $this->assertFalse($form->has('chill_personbundle_person['.$key.']')); + } + } + /** * Reload the person from the db. */ diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php index e64711e43..98c2c02aa 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php @@ -23,6 +23,38 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; */ final class PersonDuplicateControllerViewTest extends WebTestCase { + /** + * @dataProvider providePersonData + */ + public function testViewDuplicatePerson(int $personId, int $person2Id): void + { + $client = self::createClient([], [ + 'PHP_AUTH_USER' => 'center a_social', + 'PHP_AUTH_PW' => 'password', + ]); + + $crawler = $client->request('GET', '/en/person/'.$personId.'/duplicate/view'); + $response = $client->getResponse(); + $this->assertTrue($response->isSuccessful()); + + $this->assertGreaterThan(0, $crawler->filter('html:contains("Find duplicate")')->count()); + $this->assertGreaterThan(0, $crawler->filter('html:contains("Réginal")')->count()); + $this->assertGreaterThan(0, $crawler->filter('html:contains("Réginald")')->count()); + + $crawler = $client->request('GET', '/en/person/'.$personId.'/duplicate/'.$person2Id.'/confirm'); + $response = $client->getResponse(); + $this->assertTrue($response->isSuccessful()); + + $this->assertGreaterThan(0, $crawler->filter('html:contains("Old person")')->count()); + $this->assertGreaterThan(0, $crawler->filter('html:contains("New person")')->count()); + + $crawler = $client->request('POST', '/en/person/'.$personId.'/duplicate/'.$person2Id.'/confirm', [ + 'chill_personbundle_person_confirm_duplicate[confirm]' => 1, + ]); + $response = $client->getResponse(); + $this->assertTrue($response->isSuccessful()); + } + public static function providePersonData(): iterable { self::bootKernel(); @@ -60,36 +92,4 @@ final class PersonDuplicateControllerViewTest extends WebTestCase yield [$person->getId(), $person2->getId()]; } - - /** - * @dataProvider providePersonData - */ - public function testViewDuplicatePerson(int $personId, int $person2Id): void - { - $client = self::createClient([], [ - 'PHP_AUTH_USER' => 'center a_social', - 'PHP_AUTH_PW' => 'password', - ]); - - $crawler = $client->request('GET', '/en/person/'.$personId.'/duplicate/view'); - $response = $client->getResponse(); - $this->assertTrue($response->isSuccessful()); - - $this->assertGreaterThan(0, $crawler->filter('html:contains("Find duplicate")')->count()); - $this->assertGreaterThan(0, $crawler->filter('html:contains("Réginal")')->count()); - $this->assertGreaterThan(0, $crawler->filter('html:contains("Réginald")')->count()); - - $crawler = $client->request('GET', '/en/person/'.$personId.'/duplicate/'.$person2Id.'/confirm'); - $response = $client->getResponse(); - $this->assertTrue($response->isSuccessful()); - - $this->assertGreaterThan(0, $crawler->filter('html:contains("Old person")')->count()); - $this->assertGreaterThan(0, $crawler->filter('html:contains("New person")')->count()); - - $crawler = $client->request('POST', '/en/person/'.$personId.'/duplicate/'.$person2Id.'/confirm', [ - 'chill_personbundle_person_confirm_duplicate[confirm]' => 1, - ]); - $response = $client->getResponse(); - $this->assertTrue($response->isSuccessful()); - } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/RelationshipApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/RelationshipApiControllerTest.php index 206314939..65f2e60a6 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/RelationshipApiControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/RelationshipApiControllerTest.php @@ -40,6 +40,19 @@ final class RelationshipApiControllerTest extends WebTestCase self::ensureKernelShutdown(); } + /** + * @dataProvider personProvider + */ + public function testGetRelationshipByPerson(int $personId) + { + self::ensureKernelShutdown(); + $client = $this->getClientAuthenticated(); + $client->request(Request::METHOD_GET, sprintf('/api/1.0/relations/relationship/by-person/%d.json', $personId)); + + $response = $client->getResponse(); + $this->assertEquals(200, $response->getStatusCode(), 'Test to see that API response returns a status code 200'); + } + public static function personProvider(): array { self::bootKernel(); @@ -75,42 +88,6 @@ final class RelationshipApiControllerTest extends WebTestCase ]; } - public static function relationProvider(): array - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - $personIdWithoutRelations = $em->createQueryBuilder() - ->select('p.id') - ->from(Person::class, 'p') - ->join('p.centerCurrent', 'center_current') - ->join('center_current.center', 'c') - ->where('c.name LIKE :name') - ->andWhere('NOT EXISTS (SELECT 1 FROM '.Relationship::class.' r WHERE r.fromPerson = p OR r.toPerson = p)') - ->setParameter('name', 'Center A') - ->getQuery() - ->setMaxResults(2) - ->getResult(); - - self::ensureKernelShutdown(); - - return [ - [$personIdWithoutRelations[0]['id'], $personIdWithoutRelations[1]['id'], self::getRandomRelation($em)->getId(), true], - ]; - } - - /** - * @dataProvider personProvider - */ - public function testGetRelationshipByPerson(int $personId) - { - self::ensureKernelShutdown(); - $client = $this->getClientAuthenticated(); - $client->request(Request::METHOD_GET, sprintf('/api/1.0/relations/relationship/by-person/%d.json', $personId)); - - $response = $client->getResponse(); - $this->assertEquals(200, $response->getStatusCode(), 'Test to see that API response returns a status code 200'); - } - /** * @dataProvider relationProvider */ @@ -137,6 +114,29 @@ final class RelationshipApiControllerTest extends WebTestCase $this->assertEquals(200, $response->getStatusCode()); } + public static function relationProvider(): array + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $personIdWithoutRelations = $em->createQueryBuilder() + ->select('p.id') + ->from(Person::class, 'p') + ->join('p.centerCurrent', 'center_current') + ->join('center_current.center', 'c') + ->where('c.name LIKE :name') + ->andWhere('NOT EXISTS (SELECT 1 FROM '.Relationship::class.' r WHERE r.fromPerson = p OR r.toPerson = p)') + ->setParameter('name', 'Center A') + ->getQuery() + ->setMaxResults(2) + ->getResult(); + + self::ensureKernelShutdown(); + + return [ + [$personIdWithoutRelations[0]['id'], $personIdWithoutRelations[1]['id'], self::getRandomRelation($em)->getId(), true], + ]; + } + private static function getRandomRelation(EntityManagerInterface $em): Relation { if (null === self::$relations) { diff --git a/src/Bundle/ChillPersonBundle/Tests/Controller/SocialWorkEvaluationApiControllerTest.php b/src/Bundle/ChillPersonBundle/Tests/Controller/SocialWorkEvaluationApiControllerTest.php index ad41af989..c0ababe93 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Controller/SocialWorkEvaluationApiControllerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Controller/SocialWorkEvaluationApiControllerTest.php @@ -45,6 +45,24 @@ final class SocialWorkEvaluationApiControllerTest extends WebTestCase $em->flush(); } + /** + * @dataProvider dataGenerateSocialActionWithEvaluations + */ + public function testListEvaluationBySocialAction(SocialAction $action, Evaluation $inactiveEvaluation): void + { + $client = $this->getClientAuthenticated(); + + $client->request('GET', sprintf('/api/1.0/person/social-work/evaluation/by-social-action/%d.json', $action->getId())); + + $this->assertResponseIsSuccessful(); + + $content = json_decode($client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR); + + $ids = array_map(static fn (array $item) => $item['id'], $content['results']); + + $this->assertNotContains($inactiveEvaluation->getId(), $ids); + } + public static function dataGenerateSocialActionWithEvaluations(): iterable { self::bootKernel(); @@ -67,22 +85,4 @@ final class SocialWorkEvaluationApiControllerTest extends WebTestCase yield [$socialAction, self::$evaluationToReset]; } - - /** - * @dataProvider dataGenerateSocialActionWithEvaluations - */ - public function testListEvaluationBySocialAction(SocialAction $action, Evaluation $inactiveEvaluation): void - { - $client = $this->getClientAuthenticated(); - - $client->request('GET', sprintf('/api/1.0/person/social-work/evaluation/by-social-action/%d.json', $action->getId())); - - $this->assertResponseIsSuccessful(); - - $content = json_decode($client->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR); - - $ids = array_map(static fn (array $item) => $item['id'], $content['results']); - - $this->assertNotContains($inactiveEvaluation->getId(), $ids); - } } diff --git a/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php b/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php index 417cc6d01..87afef46f 100644 --- a/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/EventListener/PersonCreateEventTest.php @@ -21,30 +21,6 @@ use PHPUnit\Framework\TestCase; */ final class PersonCreateEventTest extends TestCase { - public static function generateAltNames(): iterator - { - yield ['vinCENT', 'VINCENT']; - - yield ['jean-marie', 'JEAN-MARIE']; - - yield ['fastré', 'FASTRÉ']; - - yield ['émile', 'ÉMILE']; - } - - public static function generateNames(): iterator - { - yield ['émelie-marie', 'Émelie-Marie', 'lenaerts', 'LENAERTS']; - - yield ['jean-marie', 'Jean-Marie', 'lenaerts', 'LENAERTS']; - - yield ['vinCENT', 'Vincent', 'fastré', 'FASTRÉ']; - - yield ['Vincent', 'Vincent', 'van Gogh', 'VAN GOGH']; - - yield ['André marie', 'André Marie', 'Bah', 'BAH']; - } - /** * @dataProvider generateAltNames */ @@ -61,6 +37,17 @@ final class PersonCreateEventTest extends TestCase $this->assertEquals($altnameExpected, $personAltname->getLabel()); } + public static function generateAltNames(): iterator + { + yield ['vinCENT', 'VINCENT']; + + yield ['jean-marie', 'JEAN-MARIE']; + + yield ['fastré', 'FASTRÉ']; + + yield ['émile', 'ÉMILE']; + } + /** * @dataProvider generateNames */ @@ -78,4 +65,17 @@ final class PersonCreateEventTest extends TestCase $this->assertEquals($firstnameExpected, $person->getFirstName()); $this->assertEquals($lastnameExpected, $person->getLastName()); } + + public static function generateNames(): iterator + { + yield ['émelie-marie', 'Émelie-Marie', 'lenaerts', 'LENAERTS']; + + yield ['jean-marie', 'Jean-Marie', 'lenaerts', 'LENAERTS']; + + yield ['vinCENT', 'Vincent', 'fastré', 'FASTRÉ']; + + yield ['Vincent', 'Vincent', 'van Gogh', 'VAN GOGH']; + + yield ['André marie', 'André Marie', 'Bah', 'BAH']; + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php index fb2e54fe9..1927df553 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Serializer/Normalizer/PersonDocGenNormalizerTest.php @@ -77,31 +77,6 @@ final class PersonDocGenNormalizerTest extends KernelTestCase $this->normalizer = self::getContainer()->get(NormalizerInterface::class); } - public static function dataGeneratorNormalizationNullOrNotNullHaveSameKeys(): iterable - { - yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read']]]; - - yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-household' => true]]; - - yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-relations' => true]]; - - yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-budget' => true]]; - } - - public static function generateData() - { - $person = new Person(); - $person - ->setFirstName('Renaud') - ->setLastName('Mégane'); - - $expected = [...self::BLANK, 'firstName' => 'Renaud', 'lastName' => 'Mégane', 'text' => 'Renaud Mégane']; - - yield [$person, $expected, 'partial normalization for a person']; - - yield [null, self::BLANK, 'normalization for a null person']; - } - /** * @dataProvider dataGeneratorNormalizationNullOrNotNullHaveSameKeys */ @@ -118,6 +93,17 @@ final class PersonDocGenNormalizerTest extends KernelTestCase ); } + public static function dataGeneratorNormalizationNullOrNotNullHaveSameKeys(): iterable + { + yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read']]]; + + yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-household' => true]]; + + yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-relations' => true]]; + + yield [['docgen:expects' => Person::class, 'groups' => ['docgen:read'], 'docgen:person:with-budget' => true]]; + } + /** * @dataProvider generateData */ @@ -236,6 +222,20 @@ final class PersonDocGenNormalizerTest extends KernelTestCase ]), $msg); } + public static function generateData() + { + $person = new Person(); + $person + ->setFirstName('Renaud') + ->setLastName('Mégane'); + + $expected = [...self::BLANK, 'firstName' => 'Renaud', 'lastName' => 'Mégane', 'text' => 'Renaud Mégane']; + + yield [$person, $expected, 'partial normalization for a person']; + + yield [null, self::BLANK, 'normalization for a null person']; + } + private function buildNormalizer( ?PersonRender $personRender = null, ?RelationshipRepository $relationshipRepository = null, diff --git a/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php index 06d4b37f9..e1d8a3b70 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProviderTest.php @@ -62,6 +62,24 @@ class AccompanyingPeriodCalendarGenericDocProviderTest extends KernelTestCase self::assertIsInt($nb); } + public static function provideDataForAccompanyingPeriod(): iterable + { + self::bootKernel(); + $entityManager = self::getContainer()->get(EntityManagerInterface::class); + + if (null === $period = $entityManager->createQuery('SELECT p FROM '.AccompanyingPeriod::class.' p ') + ->setMaxResults(1)->getSingleResult()) { + throw new \RuntimeException('There is no accompanying period'); + } + + yield [$period, null, null, null]; + yield [$period, new \DateTimeImmutable('1 year ago'), null, null]; + yield [$period, new \DateTimeImmutable('1 year ago'), new \DateTimeImmutable('6 month ago'), null]; + yield [$period, new \DateTimeImmutable('1 year ago'), new \DateTimeImmutable('6 month ago'), 'text']; + yield [$period, null, null, 'text']; + yield [$period, null, new \DateTimeImmutable('6 month ago'), null]; + } + /** * @dataProvider provideDataForPerson */ @@ -120,22 +138,4 @@ class AccompanyingPeriodCalendarGenericDocProviderTest extends KernelTestCase yield [$person, null, null, 'text']; yield [$person, null, new \DateTimeImmutable('6 month ago'), null]; } - - public static function provideDataForAccompanyingPeriod(): iterable - { - self::bootKernel(); - $entityManager = self::getContainer()->get(EntityManagerInterface::class); - - if (null === $period = $entityManager->createQuery('SELECT p FROM '.AccompanyingPeriod::class.' p ') - ->setMaxResults(1)->getSingleResult()) { - throw new \RuntimeException('There is no accompanying period'); - } - - yield [$period, null, null, null]; - yield [$period, new \DateTimeImmutable('1 year ago'), null, null]; - yield [$period, new \DateTimeImmutable('1 year ago'), new \DateTimeImmutable('6 month ago'), null]; - yield [$period, new \DateTimeImmutable('1 year ago'), new \DateTimeImmutable('6 month ago'), 'text']; - yield [$period, null, null, 'text']; - yield [$period, null, new \DateTimeImmutable('6 month ago'), null]; - } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Timeline/TimelineAccompanyingPeriodTest.php b/src/Bundle/ChillPersonBundle/Tests/Timeline/TimelineAccompanyingPeriodTest.php index 2a81a57d8..6cc82b422 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Timeline/TimelineAccompanyingPeriodTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Timeline/TimelineAccompanyingPeriodTest.php @@ -28,6 +28,33 @@ final class TimelineAccompanyingPeriodTest extends WebTestCase { use PrepareClientTrait; + /** + * @dataProvider provideDataPersonWithAccompanyingPeriod + */ + public function testEntriesAreShown(mixed $personId): never + { + $this->markTestSkipped('page does not work'); + + $client = $this->getClientAuthenticated(); + + $crawler = $client->request('GET', "/en/person/{$personId}/timeline"); + + $this->assertTrue( + $client->getResponse()->isSuccessful(), + 'the timeline page loads sucessfully' + ); + $this->assertGreaterThan( + 0, + $crawler->filter('.timeline div')->count(), + 'the timeline page contains multiple div inside a .timeline element' + ); + $this->assertStringContainsString( + 'est ouvert', + $crawler->filter('.timeline')->text(), + "the text 'est ouvert' is present" + ); + } + public static function provideDataPersonWithAccompanyingPeriod() { self::bootKernel(); @@ -55,31 +82,4 @@ final class TimelineAccompanyingPeriodTest extends WebTestCase yield [\array_pop($personIds)['id']]; } - - /** - * @dataProvider provideDataPersonWithAccompanyingPeriod - */ - public function testEntriesAreShown(mixed $personId): never - { - $this->markTestSkipped('page does not work'); - - $client = $this->getClientAuthenticated(); - - $crawler = $client->request('GET', "/en/person/{$personId}/timeline"); - - $this->assertTrue( - $client->getResponse()->isSuccessful(), - 'the timeline page loads sucessfully' - ); - $this->assertGreaterThan( - 0, - $crawler->filter('.timeline div')->count(), - 'the timeline page contains multiple div inside a .timeline element' - ); - $this->assertStringContainsString( - 'est ouvert', - $crawler->filter('.timeline')->text(), - "the text 'est ouvert' is present" - ); - } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Validator/Household/MaxHolderValidatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Validator/Household/MaxHolderValidatorTest.php index 2880afb35..0e3a41388 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Validator/Household/MaxHolderValidatorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Validator/Household/MaxHolderValidatorTest.php @@ -25,6 +25,20 @@ use Symfony\Component\Validator\Test\ConstraintValidatorTestCase; */ final class MaxHolderValidatorTest extends ConstraintValidatorTestCase { + /** + * @dataProvider provideInvalidHousehold + */ + public function testHouseholdInvalid(Household $household, mixed $parameters) + { + $constraint = $this->getConstraint(); + + $this->validator->validate($household, $constraint); + + $this->buildViolation('msg') + ->setParameters($parameters) + ->assertRaised(); + } + public static function provideInvalidHousehold() { $household = new Household(); @@ -59,20 +73,6 @@ final class MaxHolderValidatorTest extends ConstraintValidatorTestCase ]; } - /** - * @dataProvider provideInvalidHousehold - */ - public function testHouseholdInvalid(Household $household, mixed $parameters) - { - $constraint = $this->getConstraint(); - - $this->validator->validate($household, $constraint); - - $this->buildViolation('msg') - ->setParameters($parameters) - ->assertRaised(); - } - protected function createValidator() { return new MaxHolderValidator(); diff --git a/src/Bundle/ChillReportBundle/Tests/Security/Authorization/ReportVoterTest.php b/src/Bundle/ChillReportBundle/Tests/Security/Authorization/ReportVoterTest.php index c71051821..2cf75e86d 100644 --- a/src/Bundle/ChillReportBundle/Tests/Security/Authorization/ReportVoterTest.php +++ b/src/Bundle/ChillReportBundle/Tests/Security/Authorization/ReportVoterTest.php @@ -54,6 +54,25 @@ final class ReportVoterTest extends KernelTestCase $this->prophet = new \Prophecy\Prophet(); } + /** + * @dataProvider dataProvider + * + * @param type $expectedResult + * @param type $action + * @param type $message + */ + public function testAccess( + $expectedResult, + Report $report, + $action, + $message, + ?User $user = null, + ) { + $token = $this->prepareToken($user); + $result = $this->voter->vote($token, $report, [$action]); + $this->assertEquals($expectedResult, $result, $message); + } + public function dataProvider() { $centerA = $this->prepareCenter(1, 'center A'); @@ -120,25 +139,6 @@ final class ReportVoterTest extends KernelTestCase ]; } - /** - * @dataProvider dataProvider - * - * @param type $expectedResult - * @param type $action - * @param type $message - */ - public function testAccess( - $expectedResult, - Report $report, - $action, - $message, - ?User $user = null, - ) { - $token = $this->prepareToken($user); - $result = $this->voter->vote($token, $report, [$action]); - $this->assertEquals($expectedResult, $result, $message); - } - /** * prepare a person. * From 456f00566d93d21075f2942c1602c082a9255fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 26 Jun 2025 12:19:49 +0200 Subject: [PATCH 51/83] update juni guidelines --- .junie/guidelines.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 8ef0b7c8d..eace2f4fa 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -22,7 +22,7 @@ Chill is a comprehensive web application built as a set of Symfony bundles. It i - **Backend**: PHP 8.3+, Symfony 5.4 - **Frontend**: JavaScript/TypeScript, Vue.js 3, Bootstrap 5 - **Build Tools**: Webpack Encore, Yarn -- **Database**: PostgreSQL with materialized views +- **Database**: PostgreSQL with materialized views. We do not support other databases. - **Other Services**: Redis, AMQP (RabbitMQ), SMTP ## Project Structure @@ -149,6 +149,42 @@ Key configuration files: - `package.json`: JavaScript dependencies and scripts - `.env`: Default environment variables. Must usually not be updated: use `.env.local` instead. +### Database migrations + +Each time a doctrine entity is created, we generate migration to adapt the database. + +The migration are created using the command `symfony console doctrine:migrations:diff --no-interaction --namespace `, where the namespace is the relevant namespace for migration. As this is a bash script, do not forget to quote the `\` (`\` must become `\\` in your command). + +Each bundle has his own namespace for migration (always ask me to confirm that command, with a list of updated / created entities so that I can confirm you that it is ok): + +- `Chill\Bundle\ActivityBundle` writes migrations to `Chill\Migrations\Activity`; +- `Chill\Bundle\BudgetBundle` writes migrations to `Chill\Migrations\Budget`; +- `Chill\Bundle\CustomFieldsBundle` writes migrations to `Chill\Migrations\CustomFields`; +- `Chill\Bundle\DocGeneratorBundle` writes migrations to `Chill\Migrations\DocGenerator`; +- `Chill\Bundle\DocStoreBundle` writes migrations to `Chill\Migrations\DocStore`; +- `Chill\Bundle\EventBundle` writes migrations to `Chill\Migrations\Event`; +- `Chill\Bundle\CalendarBundle` writes migrations to `Chill\Migrations\Calendar`; +- `Chill\Bundle\FamilyMembersBundle` writes migrations to `Chill\Migrations\FamilyMembers`; +- `Chill\Bundle\FranceTravailApiBundle` writes migrations to `Chill\Migrations\FranceTravailApi`; +- `Chill\Bundle\JobBundle` writes migrations to `Chill\Migrations\Job`; +- `Chill\Bundle\MainBundle` writes migrations to `Chill\Migrations\Main`; +- `Chill\Bundle\PersonBundle` writes migrations to `Chill\Migrations\Person`; +- `Chill\Bundle\ReportBundle` writes migrations to `Chill\Migrations\Report`; +- `Chill\Bundle\TaskBundle` writes migrations to `Chill\Migrations\Task`; +- `Chill\Bundle\ThirdPartyBundle` writes migrations to `Chill\Migrations\ThirdParty`; +- `Chill\Bundle\TicketBundle` writes migrations to `Chill\Migrations\Ticket`; +- `Chill\Bundle\WopiBundle` writes migrations to `Chill\Migrations\Wopi`; + +Once created the, comment's classes should be removed and a description of the changes made to the entities should be added to the migrations, using the `getDescription` method. The migration should not be cleaned by any artificial intelligence, as modifying this migration is error prone. + +### Guidelines related to code structure and requirements + +#### Usage of clock + +When we need to use a DateTime or DateTimeImmutable that need to express "now", we prefer the usage of +`Symfony\Component\Clock\ClockInterface`, where possible. This is usually not possible in doctrine entities, +where injection does not work when restoring an entity from database, but usually possible in services. + ### Testing Information The project uses PHPUnit for testing. Each bundle has its own test suite, and there's also a global test suite at the root level. @@ -218,7 +254,7 @@ class TicketTest extends TestCase #### Test Database -For tests that require a database, the project uses an in-memory SQLite database by default. You can configure a different database for testing in the `.env.test` file. +For tests that require a database, the project uses postgresql database filled by fixtures (usage of doctrine-fixtures). You can configure a different database for testing in the `.env.test` file. ### Code Quality Tools From 786c60a50dbcdff46fe3759766efbb2f65357aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 26 Jun 2025 12:21:19 +0200 Subject: [PATCH 52/83] Send data dumps as email attachments instead of links, update translations, and add unit tests for the handler. --- .../unreleased/Fixed-20250626-122030.yaml | 6 + .../Email/send_data_dump_to_admin.txt.twig | 6 +- .../Messenger/RequestGenerationHandler.php | 33 ++--- .../RequestGenerationHandlerTest.php | 132 ++++++++++++++++++ .../translations/messages+intl-icu.fr.yml | 4 +- .../translations/messages.fr.yml | 6 +- 6 files changed, 162 insertions(+), 25 deletions(-) create mode 100644 .changes/unreleased/Fixed-20250626-122030.yaml create mode 100644 src/Bundle/ChillDocGeneratorBundle/Tests/Service/Messenger/RequestGenerationHandlerTest.php diff --git a/.changes/unreleased/Fixed-20250626-122030.yaml b/.changes/unreleased/Fixed-20250626-122030.yaml new file mode 100644 index 000000000..72ad87552 --- /dev/null +++ b/.changes/unreleased/Fixed-20250626-122030.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: 'Doc Generation: the "dump only" method send the document as an email attachment.' +time: 2025-06-26T12:20:30.083601058+02:00 +custom: + Issue: "393" + SchemaChange: No schema change diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/send_data_dump_to_admin.txt.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/send_data_dump_to_admin.txt.twig index 566bfbfa3..6e4228041 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/send_data_dump_to_admin.txt.twig +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Email/send_data_dump_to_admin.txt.twig @@ -1,7 +1,5 @@ {{ 'docgen.data_dump_email.Dear'|trans }} -{{ 'docgen.data_dump_email.data_dump_ready_and_link'|trans }} +{{ 'docgen.data_dump_email.data_dump_ready_and_attached'|trans }} -{{ link }} - -{{ 'docgen.data_dump_email.link_valid_until'|trans({validity: validity}) }} +{{ 'docgen.data_dump_email.filename'|trans({filename: filename}) }} diff --git a/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php index dbd45fde0..9dd20af91 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php +++ b/src/Bundle/ChillDocGeneratorBundle/Service/Messenger/RequestGenerationHandler.php @@ -11,13 +11,13 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\Service\Messenger; -use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; -use Chill\DocGeneratorBundle\Service\Generator\Generator; +use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface; use Chill\DocGeneratorBundle\Service\Generator\GeneratorException; -use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface; +use Chill\DocGeneratorBundle\Service\Generator\GeneratorInterface; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Exception\StoredObjectManagerException; -use Chill\DocStoreBundle\Repository\StoredObjectRepository; +use Chill\DocStoreBundle\Repository\StoredObjectRepositoryInterface; +use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; use Chill\MainBundle\Repository\UserRepositoryInterface; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -37,15 +37,15 @@ class RequestGenerationHandler implements MessageHandlerInterface private const LOG_PREFIX = '[docgen message handler] '; public function __construct( - private readonly DocGeneratorTemplateRepository $docGeneratorTemplateRepository, + private readonly DocGeneratorTemplateRepositoryInterface $docGeneratorTemplateRepository, private readonly EntityManagerInterface $entityManager, - private readonly Generator $generator, + private readonly GeneratorInterface $generator, private readonly LoggerInterface $logger, - private readonly StoredObjectRepository $storedObjectRepository, + private readonly StoredObjectRepositoryInterface $storedObjectRepository, private readonly UserRepositoryInterface $userRepository, private readonly MailerInterface $mailer, - private readonly TempUrlGeneratorInterface $tempUrlGenerator, private readonly TranslatorInterface $translator, + private readonly StoredObjectManagerInterface $storedObjectManager, ) {} public function __invoke(RequestGenerationMessage $message) @@ -90,7 +90,7 @@ class RequestGenerationHandler implements MessageHandlerInterface $this->sendDataDump($destinationStoredObject, $message); } else { - $destinationStoredObject = $this->generator->generateDocFromTemplate( + $this->generator->generateDocFromTemplate( $template, $message->getEntityId(), $message->getContextGenerationData(), @@ -122,19 +122,20 @@ class RequestGenerationHandler implements MessageHandlerInterface private function sendDataDump(StoredObject $destinationStoredObject, RequestGenerationMessage $message): void { - $url = $this->tempUrlGenerator->generate('GET', $destinationStoredObject->getFilename(), 3600); - $parts = []; - parse_str(parse_url($url->url)['query'], $parts); - $validity = \DateTimeImmutable::createFromFormat('U', $parts['temp_url_expires']); + // Get the content of the document + $content = $this->storedObjectManager->read($destinationStoredObject); + $filename = $destinationStoredObject->getFilename(); + $contentType = $destinationStoredObject->getType(); + // Create the email with the document as an attachment $email = (new TemplatedEmail()) ->to($message->getSendResultToEmail()) ->textTemplate('@ChillDocGenerator/Email/send_data_dump_to_admin.txt.twig') ->context([ - 'link' => $url->url, - 'validity' => $validity, + 'filename' => $filename, ]) - ->subject($this->translator->trans('docgen.data_dump_email.subject')); + ->subject($this->translator->trans('docgen.data_dump_email.subject')) + ->attach($content, $filename, $contentType); $this->mailer->send($email); } diff --git a/src/Bundle/ChillDocGeneratorBundle/Tests/Service/Messenger/RequestGenerationHandlerTest.php b/src/Bundle/ChillDocGeneratorBundle/Tests/Service/Messenger/RequestGenerationHandlerTest.php new file mode 100644 index 000000000..a0af351a4 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Tests/Service/Messenger/RequestGenerationHandlerTest.php @@ -0,0 +1,132 @@ +setPrivateProperty($template, 'id', 1); + + $storedObject = new StoredObject(); + $this->setPrivateProperty($storedObject, 'id', 2); + + $creator = new User(); + $creator->setEmail('test@example.com'); + $this->setPrivateProperty($creator, 'id', 3); + + $docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class); + $docGeneratorTemplateRepository->find(1)->willReturn($template); + + $storedObjectRepository = $this->prophesize(StoredObjectRepositoryInterface::class); + $storedObjectRepository->find(2)->willReturn($storedObject); + + $userRepository = $this->prophesize(UserRepositoryInterface::class); + $userRepository->find(3)->willReturn($creator); + + // Create a mock for the Query object + $query = $this->prophesize(Query::class); + $query->setParameter('id', 2)->willReturn($query->reveal()); + $query->execute()->shouldBeCalled(); + + // Create a mock for the EntityManager + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->createQuery(Argument::containingString('UPDATE'))->willReturn($query->reveal()); + $entityManager->flush()->shouldBeCalled(); + + $generator = $this->prophesize(GeneratorInterface::class); + $generator->generateDocFromTemplate( + $template, + 123, // entityId + ['key' => 'value'], // contextGenerationData + $storedObject, + $creator + ) + ->willReturn($storedObject)->shouldBeCalled(); + + $logger = new NullLogger(); + + $mailer = $this->prophesize(MailerInterface::class); + + $translator = $this->prophesize(TranslatorInterface::class); + + $storedObjectManager = $this->prophesize(StoredObjectManagerInterface::class); + + // Create handler + $handler = new RequestGenerationHandler( + $docGeneratorTemplateRepository->reveal(), + $entityManager->reveal(), + $generator->reveal(), + $logger, + $storedObjectRepository->reveal(), + $userRepository->reveal(), + $mailer->reveal(), + $translator->reveal(), + $storedObjectManager->reveal() + ); + + // Create message + $message = new RequestGenerationMessage( + $creator, + $template, + 123, // entityId + $storedObject, + ['key' => 'value'], // contextGenerationData + false, // isTest + null, // sendResultToEmail + false // dumpOnly + ); + + // Invoke handler + $handler->__invoke($message); + + // Assertions + // The assertions are handled by the shouldBeCalled() expectations on the mocks + $this->assertTrue(true); // Just to have an assertion in the test + } + + private function setPrivateProperty(object $object, string $propertyName, $value): void + { + $reflection = new \ReflectionClass($object); + $property = $reflection->getProperty($propertyName); + $property->setAccessible(true); + $property->setValue($object, $value); + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/translations/messages+intl-icu.fr.yml b/src/Bundle/ChillDocGeneratorBundle/translations/messages+intl-icu.fr.yml index df20907c9..6bbaa5f7d 100644 --- a/src/Bundle/ChillDocGeneratorBundle/translations/messages+intl-icu.fr.yml +++ b/src/Bundle/ChillDocGeneratorBundle/translations/messages+intl-icu.fr.yml @@ -1,4 +1,2 @@ docgen: - data_dump_email: - link_valid_until: >- - Ce lien est valide jusqu'au {validity, date, full}, {validity, time, medium} + # No ICU messages needed for data_dump_email anymore diff --git a/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml b/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml index 1fdf14c02..c1601ea6e 100644 --- a/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillDocGeneratorBundle/translations/messages.fr.yml @@ -34,8 +34,10 @@ docgen: data_dump_email: subject: Contenu des données de génération de document disponible Dear: Cher - data_dump_ready_and_link: >- - Le contenu des données est disponible. Vous pouvez le télécharger à l'aide du lien suivant: + data_dump_ready_and_attached: >- + Le contenu des données est disponible. Vous le trouverez en pièce jointe à cet email. + filename: >- + Nom du fichier: %filename% From 0b580658def7ba12b0b29b7b8fcd6f2c46d27162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Thu, 26 Jun 2025 14:38:12 +0200 Subject: [PATCH 53/83] Remove unnecessary workflow deletion logic when in the initial position --- .changes/unreleased/Feature-20250626-143735.yaml | 6 ++++++ .../Service/Workflow/CancelStaleWorkflowHandler.php | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 .changes/unreleased/Feature-20250626-143735.yaml diff --git a/.changes/unreleased/Feature-20250626-143735.yaml b/.changes/unreleased/Feature-20250626-143735.yaml new file mode 100644 index 000000000..b1a447016 --- /dev/null +++ b/.changes/unreleased/Feature-20250626-143735.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Do not remove workflow which are automatically canceled after staling for more than 30 days +time: 2025-06-26T14:37:35.267672192+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php index d392df1ca..4dec39572 100644 --- a/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php +++ b/src/Bundle/ChillMainBundle/Service/Workflow/CancelStaleWorkflowHandler.php @@ -58,7 +58,6 @@ final readonly class CancelStaleWorkflowHandler $transitions = $workflowComponent->getEnabledTransitions($workflow); $transitionApplied = false; - $wasInInitialPosition = 'initial' === $workflow->getStep(); foreach ($transitions as $transition) { if ($this->willTransitionLeadToFinalNegative($transition, $metadataStore)) { @@ -80,10 +79,6 @@ final readonly class CancelStaleWorkflowHandler throw new UnrecoverableMessageHandlingException(sprintf('No valid transition found for EntityWorkflow %d.', $workflowId)); } - if ($wasInInitialPosition) { - $this->em->remove($workflow); - } - $this->em->flush(); } From 298044bc82ba16e3e3205466098ac0921d6bc305 Mon Sep 17 00:00:00 2001 From: LenaertsJ Date: Mon, 30 Jun 2025 08:44:24 +0000 Subject: [PATCH 54/83] Improve admin templates for event admin entities + activity reason (category)... --- .../unreleased/Fixed-20250611-164623.yaml | 6 ++ .changes/unreleased/UX-20250617-192650.yaml | 6 ++ .../ActivityReasonCategoryController.php | 47 +------------ .../Controller/ActivityReasonController.php | 45 +----------- .../views/ActivityReason/index.html.twig | 7 +- .../ActivityReasonCategory/index.html.twig | 7 +- .../Controller/EventTypeController.php | 68 +------------------ .../Controller/RoleController.php | 66 +----------------- .../Controller/StatusController.php | 65 +----------------- .../Resources/views/EventType/edit.html.twig | 2 +- .../Resources/views/EventType/index.html.twig | 7 +- .../Resources/views/EventType/new.html.twig | 2 +- .../Resources/views/EventType/show.html.twig | 7 +- .../Resources/views/Role/edit.html.twig | 4 +- .../Resources/views/Role/index.html.twig | 7 +- .../Resources/views/Role/new.html.twig | 2 +- .../Resources/views/Role/show.html.twig | 7 +- .../Resources/views/Status/edit.html.twig | 2 +- .../Resources/views/Status/index.html.twig | 7 +- .../Resources/views/Status/new.html.twig | 2 +- .../Resources/views/Status/show.html.twig | 7 +- .../Form/PersonResourceType.php | 10 ++- .../views/PersonResource/form.html.twig | 12 ++-- .../translations/messages.fr.yml | 4 ++ 24 files changed, 54 insertions(+), 345 deletions(-) create mode 100644 .changes/unreleased/Fixed-20250611-164623.yaml create mode 100644 .changes/unreleased/UX-20250617-192650.yaml diff --git a/.changes/unreleased/Fixed-20250611-164623.yaml b/.changes/unreleased/Fixed-20250611-164623.yaml new file mode 100644 index 000000000..8bb956c34 --- /dev/null +++ b/.changes/unreleased/Fixed-20250611-164623.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Fix admin entity edit actions for event admin entities and activity reason (category) entities +time: 2025-06-11T16:46:23.113506434+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/.changes/unreleased/UX-20250617-192650.yaml b/.changes/unreleased/UX-20250617-192650.yaml new file mode 100644 index 000000000..810758b10 --- /dev/null +++ b/.changes/unreleased/UX-20250617-192650.yaml @@ -0,0 +1,6 @@ +kind: UX +body: Improve labeling of fields in person resource creation form +time: 2025-06-17T19:26:50.599703116+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php index 0d337416b..6ccddcc97 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonCategoryController.php @@ -48,28 +48,6 @@ class ActivityReasonCategoryController extends AbstractController ]); } - /** - * Displays a form to edit an existing ActivityReasonCategory entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/edit', name: 'chill_activity_activityreasoncategory_edit')] - public function editAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(ActivityReasonCategory::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.'); - } - - $editForm = $this->createEditForm($entity); - - return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [ - 'entity' => $entity, - 'edit_form' => $editForm->createView(), - ]); - } - /** * Lists all ActivityReasonCategory entities. */ @@ -100,29 +78,10 @@ class ActivityReasonCategoryController extends AbstractController ]); } - /** - * Finds and displays a ActivityReasonCategory entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/show', name: 'chill_activity_activityreasoncategory_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(ActivityReasonCategory::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.'); - } - - return $this->render('@ChillActivity/ActivityReasonCategory/show.html.twig', [ - 'entity' => $entity, - ]); - } - /** * Edits an existing ActivityReasonCategory entity. */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/update', name: 'chill_activity_activityreasoncategory_update', methods: ['POST', 'PUT'])] + #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreasoncategory/{id}/update', name: 'chill_activity_activityreasoncategory_update')] public function updateAction(Request $request, mixed $id) { $em = $this->managerRegistry->getManager(); @@ -139,7 +98,7 @@ class ActivityReasonCategoryController extends AbstractController if ($editForm->isSubmitted() && $editForm->isValid()) { $em->flush(); - return $this->redirectToRoute('chill_activity_activityreasoncategory_edit', ['id' => $id]); + return $this->redirectToRoute('chill_activity_activityreasoncategory', ['id' => $id]); } return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [ @@ -178,7 +137,7 @@ class ActivityReasonCategoryController extends AbstractController { $form = $this->createForm(ActivityReasonCategoryType::class, $entity, [ 'action' => $this->generateUrl('chill_activity_activityreasoncategory_update', ['id' => $entity->getId()]), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); diff --git a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php index 37d04d367..0167273c5 100644 --- a/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php +++ b/src/Bundle/ChillActivityBundle/Controller/ActivityReasonController.php @@ -50,28 +50,6 @@ class ActivityReasonController extends AbstractController ]); } - /** - * Displays a form to edit an existing ActivityReason entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/edit', name: 'chill_activity_activityreason_edit')] - public function editAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(ActivityReason::class)->find($id); - - if (null === $entity) { - throw new NotFoundHttpException('Unable to find ActivityReason entity.'); - } - - $editForm = $this->createEditForm($entity); - - return $this->render('@ChillActivity/ActivityReason/edit.html.twig', [ - 'entity' => $entity, - 'edit_form' => $editForm->createView(), - ]); - } - /** * Lists all ActivityReason entities. */ @@ -102,29 +80,10 @@ class ActivityReasonController extends AbstractController ]); } - /** - * Finds and displays a ActivityReason entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/show', name: 'chill_activity_activityreason_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(ActivityReason::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find ActivityReason entity.'); - } - - return $this->render('@ChillActivity/ActivityReason/show.html.twig', [ - 'entity' => $entity, - ]); - } - /** * Edits an existing ActivityReason entity. */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/update', name: 'chill_activity_activityreason_update', methods: ['POST', 'PUT'])] + #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/activityreason/{id}/update', name: 'chill_activity_activityreason_update')] public function updateAction(Request $request, mixed $id) { $em = $this->managerRegistry->getManager(); @@ -180,7 +139,7 @@ class ActivityReasonController extends AbstractController { $form = $this->createForm(ActivityReasonType::class, $entity, [ 'action' => $this->generateUrl('chill_activity_activityreason_update', ['id' => $entity->getId()]), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); diff --git a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig index 855c9386d..ee36958c3 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReason/index.html.twig @@ -3,7 +3,7 @@ {% block admin_content %}

    {{ 'ActivityReason list'|trans }}

    - +
    @@ -29,10 +29,7 @@ diff --git a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig index 5f48180b3..f64ce359d 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/ActivityReasonCategory/index.html.twig @@ -3,7 +3,7 @@ {% block admin_content %}

    {{ 'ActivityReasonCategory list'|trans }}

    -
    {{ 'Name'|trans }}
    • - -
    • -
    • - +
    +
    @@ -23,10 +23,7 @@ diff --git a/src/Bundle/ChillEventBundle/Controller/EventTypeController.php b/src/Bundle/ChillEventBundle/Controller/EventTypeController.php index f7b1b205c..969e60cb7 100644 --- a/src/Bundle/ChillEventBundle/Controller/EventTypeController.php +++ b/src/Bundle/ChillEventBundle/Controller/EventTypeController.php @@ -48,30 +48,6 @@ class EventTypeController extends AbstractController ]); } - /** - * Deletes a EventType entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/event_type/{id}/delete', name: 'chill_eventtype_admin_delete', methods: ['POST', 'DELETE'])] - public function deleteAction(Request $request, mixed $id) - { - $form = $this->createDeleteForm($id); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $em = $this->managerRegistry->getManager(); - $entity = $em->getRepository(EventType::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find EventType entity.'); - } - - $em->remove($entity); - $em->flush(); - } - - return $this->redirectToRoute('chill_eventtype_admin'); - } - /** * Displays a form to edit an existing EventType entity. */ @@ -87,12 +63,10 @@ class EventTypeController extends AbstractController } $editForm = $this->createEditForm($entity); - $deleteForm = $this->createDeleteForm($id); return $this->render('@ChillEvent/EventType/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -126,28 +100,6 @@ class EventTypeController extends AbstractController ]); } - /** - * Finds and displays a EventType entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/event_type/{id}/show', name: 'chill_eventtype_admin_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(EventType::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find EventType entity.'); - } - - $deleteForm = $this->createDeleteForm($id); - - return $this->render('@ChillEvent/EventType/show.html.twig', [ - 'entity' => $entity, - 'delete_form' => $deleteForm->createView(), - ]); - } - /** * Edits an existing EventType entity. */ @@ -162,7 +114,6 @@ class EventTypeController extends AbstractController throw $this->createNotFoundException('Unable to find EventType entity.'); } - $deleteForm = $this->createDeleteForm($id); $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); @@ -175,7 +126,6 @@ class EventTypeController extends AbstractController return $this->render('@ChillEvent/EventType/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -198,22 +148,6 @@ class EventTypeController extends AbstractController return $form; } - /** - * Creates a form to delete a EventType entity by id. - * - * @return \Symfony\Component\Form\FormInterface The form - */ - private function createDeleteForm(mixed $id) - { - return $this->createFormBuilder() - ->setAction($this->generateUrl( - 'chill_eventtype_admin_delete', - ['id' => $id] - )) - ->add('submit', SubmitType::class, ['label' => 'Delete']) - ->getForm(); - } - /** * Creates a form to edit a EventType entity. * @@ -228,7 +162,7 @@ class EventTypeController extends AbstractController 'chill_eventtype_admin_update', ['id' => $entity->getId()] ), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); diff --git a/src/Bundle/ChillEventBundle/Controller/RoleController.php b/src/Bundle/ChillEventBundle/Controller/RoleController.php index 1ff205dfc..9d0c38c92 100644 --- a/src/Bundle/ChillEventBundle/Controller/RoleController.php +++ b/src/Bundle/ChillEventBundle/Controller/RoleController.php @@ -48,30 +48,6 @@ class RoleController extends AbstractController ]); } - /** - * Deletes a Role entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/role/{id}/delete', name: 'chill_event_admin_role_delete', methods: ['POST', 'DELETE'])] - public function deleteAction(Request $request, mixed $id) - { - $form = $this->createDeleteForm($id); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $em = $this->managerRegistry->getManager(); - $entity = $em->getRepository(Role::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find Role entity.'); - } - - $em->remove($entity); - $em->flush(); - } - - return $this->redirectToRoute('chill_event_admin_role'); - } - /** * Displays a form to edit an existing Role entity. */ @@ -87,12 +63,10 @@ class RoleController extends AbstractController } $editForm = $this->createEditForm($entity); - $deleteForm = $this->createDeleteForm($id); return $this->render('@ChillEvent/Role/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -126,28 +100,6 @@ class RoleController extends AbstractController ]); } - /** - * Finds and displays a Role entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/role/{id}/show', name: 'chill_event_admin_role_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(Role::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find Role entity.'); - } - - $deleteForm = $this->createDeleteForm($id); - - return $this->render('@ChillEvent/Role/show.html.twig', [ - 'entity' => $entity, - 'delete_form' => $deleteForm->createView(), - ]); - } - /** * Edits an existing Role entity. */ @@ -162,7 +114,6 @@ class RoleController extends AbstractController throw $this->createNotFoundException('Unable to find Role entity.'); } - $deleteForm = $this->createDeleteForm($id); $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); @@ -175,7 +126,6 @@ class RoleController extends AbstractController return $this->render('@ChillEvent/Role/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -198,20 +148,6 @@ class RoleController extends AbstractController return $form; } - /** - * Creates a form to delete a Role entity by id. - * - * @return \Symfony\Component\Form\FormInterface The form - */ - private function createDeleteForm(mixed $id) - { - return $this->createFormBuilder() - ->setAction($this->generateUrl('chill_event_admin_role_delete', ['id' => $id])) - ->setMethod('DELETE') - ->add('submit', SubmitType::class, ['label' => 'Delete']) - ->getForm(); - } - /** * Creates a form to edit a Role entity. * @@ -226,7 +162,7 @@ class RoleController extends AbstractController 'chill_event_admin_role_update', ['id' => $entity->getId()] ), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); diff --git a/src/Bundle/ChillEventBundle/Controller/StatusController.php b/src/Bundle/ChillEventBundle/Controller/StatusController.php index 3b302500d..1001fe269 100644 --- a/src/Bundle/ChillEventBundle/Controller/StatusController.php +++ b/src/Bundle/ChillEventBundle/Controller/StatusController.php @@ -48,30 +48,6 @@ class StatusController extends AbstractController ]); } - /** - * Deletes a Status entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/status/{id}/delete', name: 'chill_event_admin_status_delete', methods: ['POST', 'DELETE'])] - public function deleteAction(Request $request, mixed $id) - { - $form = $this->createDeleteForm($id); - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $em = $this->managerRegistry->getManager(); - $entity = $em->getRepository(Status::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find Status entity.'); - } - - $em->remove($entity); - $em->flush(); - } - - return $this->redirectToRoute('chill_event_admin_status'); - } - /** * Displays a form to edit an existing Status entity. */ @@ -87,12 +63,10 @@ class StatusController extends AbstractController } $editForm = $this->createEditForm($entity); - $deleteForm = $this->createDeleteForm($id); return $this->render('@ChillEvent/Status/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -126,28 +100,6 @@ class StatusController extends AbstractController ]); } - /** - * Finds and displays a Status entity. - */ - #[\Symfony\Component\Routing\Annotation\Route(path: '/{_locale}/admin/event/status/{id}/show', name: 'chill_event_admin_status_show')] - public function showAction(mixed $id) - { - $em = $this->managerRegistry->getManager(); - - $entity = $em->getRepository(Status::class)->find($id); - - if (!$entity) { - throw $this->createNotFoundException('Unable to find Status entity.'); - } - - $deleteForm = $this->createDeleteForm($id); - - return $this->render('@ChillEvent/Status/show.html.twig', [ - 'entity' => $entity, - 'delete_form' => $deleteForm->createView(), - ]); - } - /** * Edits an existing Status entity. */ @@ -162,7 +114,6 @@ class StatusController extends AbstractController throw $this->createNotFoundException('Unable to find Status entity.'); } - $deleteForm = $this->createDeleteForm($id); $editForm = $this->createEditForm($entity); $editForm->handleRequest($request); @@ -175,7 +126,6 @@ class StatusController extends AbstractController return $this->render('@ChillEvent/Status/edit.html.twig', [ 'entity' => $entity, 'edit_form' => $editForm->createView(), - 'delete_form' => $deleteForm->createView(), ]); } @@ -198,19 +148,6 @@ class StatusController extends AbstractController return $form; } - /** - * Creates a form to delete a Status entity by id. - * - * @return \Symfony\Component\Form\FormInterface The form - */ - private function createDeleteForm(mixed $id) - { - return $this->createFormBuilder() - ->setAction($this->generateUrl('chill_event_admin_status_delete', ['id' => $id])) - ->add('submit', SubmitType::class, ['label' => 'Delete']) - ->getForm(); - } - /** * Creates a form to edit a Status entity. * @@ -222,7 +159,7 @@ class StatusController extends AbstractController { $form = $this->createForm(StatusType::class, $entity, [ 'action' => $this->generateUrl('chill_event_admin_status_update', ['id' => $entity->getId()]), - 'method' => 'PUT', + 'method' => 'POST', ]); $form->add('submit', SubmitType::class, ['label' => 'Update']); diff --git a/src/Bundle/ChillEventBundle/Resources/views/EventType/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/EventType/edit.html.twig index 151a9ec0c..28a5b22e8 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/EventType/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/EventType/edit.html.twig @@ -8,7 +8,7 @@ {{ form_row(edit_form.name) }} {{ form_row(edit_form.active) }} -
      +
      • {{ 'Back to the list'|trans }}
      • diff --git a/src/Bundle/ChillEventBundle/Resources/views/EventType/index.html.twig b/src/Bundle/ChillEventBundle/Resources/views/EventType/index.html.twig index 0a0154b3a..e55122d96 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/EventType/index.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/EventType/index.html.twig @@ -16,14 +16,11 @@
    {% for entity in entities %} - + - +
    {{ 'Name'|trans }}
    • - -
    • -
    • - +
    {{ entity.id }}{{ entity.id }} {{ entity.name|localize_translatable_string }}{{ entity.active }}
      -
    • - -
    • diff --git a/src/Bundle/ChillEventBundle/Resources/views/EventType/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/EventType/new.html.twig index 3474ddd01..778f2cf73 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/EventType/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/EventType/new.html.twig @@ -8,7 +8,7 @@ {{ form_row(form.name) }} {{ form_row(form.active) }} -
        +
        • {{ 'Back to the list'|trans }}
        • diff --git a/src/Bundle/ChillEventBundle/Resources/views/EventType/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/EventType/show.html.twig index d50a97f15..ce879a719 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/EventType/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/EventType/show.html.twig @@ -21,17 +21,12 @@
    -
      + {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Role/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Role/edit.html.twig index 41ad81b90..04dab6188 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Role/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Role/edit.html.twig @@ -8,12 +8,12 @@ {{ form_row(edit_form.type) }} {{ form_row(edit_form.active) }} -
        +
        • {{ 'Back to the list'|trans }}
        • - {{ form_row(edit_form.submit, { 'attr': { 'class' : 'btn btn-edit' }}) }} + {{ form_row(edit_form.submit, { 'attr': { 'class' : 'btn btn-update' }}) }}
        diff --git a/src/Bundle/ChillEventBundle/Resources/views/Role/index.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Role/index.html.twig index e7e9afe1b..67654bd34 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Role/index.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Role/index.html.twig @@ -17,15 +17,12 @@ {% for entity in entities %} - {{ entity.id }} + {{ entity.id }} {{ entity.name|localize_translatable_string }} {{ entity.type.name|localize_translatable_string }} - {{ entity.active }} +
          -
        • - -
        • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Role/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Role/new.html.twig index 15363c02c..df9cf1dcf 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Role/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Role/new.html.twig @@ -9,7 +9,7 @@ {{ form_row(form.type) }} {{ form_row(form.active) }} -
            +
            • {{ 'Back to the list'|trans }}
            • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Role/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Role/show.html.twig index 9fe7e3adf..4452b96e4 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Role/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Role/show.html.twig @@ -25,17 +25,12 @@ -
                + {% endblock %} diff --git a/src/Bundle/ChillEventBundle/Resources/views/Status/edit.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Status/edit.html.twig index 00ee2d25c..408af3b79 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Status/edit.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Status/edit.html.twig @@ -9,7 +9,7 @@ {{ form_row(edit_form.type) }} {{ form_row(edit_form.active) }} -
                  +
                  • {{ 'Back to the list'|trans }}
                  • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Status/index.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Status/index.html.twig index 64336b0d1..2f71e0d2e 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Status/index.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Status/index.html.twig @@ -17,15 +17,12 @@ {% for entity in entities %} - {{ entity.id }} + {{ entity.id }} {{ entity.name|localize_translatable_string }} {{ entity.type.name|localize_translatable_string }} - {{ entity.active }} +
                      -
                    • - -
                    • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Status/new.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Status/new.html.twig index 0e0b788ad..162de1d48 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Status/new.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Status/new.html.twig @@ -9,7 +9,7 @@ {{ form_row(form.type) }} {{ form_row(form.active) }} -
                        +
                        • {{ 'Back to the list'|trans }}
                        • diff --git a/src/Bundle/ChillEventBundle/Resources/views/Status/show.html.twig b/src/Bundle/ChillEventBundle/Resources/views/Status/show.html.twig index ce0aaae98..ca5371100 100644 --- a/src/Bundle/ChillEventBundle/Resources/views/Status/show.html.twig +++ b/src/Bundle/ChillEventBundle/Resources/views/Status/show.html.twig @@ -25,17 +25,12 @@ -
                            + {% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php index 2063b0e21..1412733bc 100644 --- a/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php +++ b/src/Bundle/ChillPersonBundle/Form/PersonResourceType.php @@ -16,10 +16,8 @@ use Chill\MainBundle\Form\Type\CommentType; use Chill\PersonBundle\Entity\Person\PersonResource; use Chill\PersonBundle\Entity\Person\PersonResourceKind; use Chill\PersonBundle\Form\Type\PickPersonDynamicType; -use Chill\PersonBundle\Templating\Entity\PersonRenderInterface; use Chill\PersonBundle\Templating\Entity\ResourceKindRender; use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType; -use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Doctrine\ORM\EntityRepository; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; @@ -29,7 +27,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; final class PersonResourceType extends AbstractType { - public function __construct(private readonly ResourceKindRender $resourceKindRender, private readonly PersonRenderInterface $personRender, private readonly ThirdPartyRender $thirdPartyRender, private readonly TranslatorInterface $translator) {} + public function __construct(private readonly ResourceKindRender $resourceKindRender, private readonly TranslatorInterface $translator) {} public function buildForm(FormBuilderInterface $builder, array $options) { @@ -52,13 +50,13 @@ final class PersonResourceType extends AbstractType }, ]) ->add('person', PickPersonDynamicType::class, [ - 'label' => 'Usager', + 'label' => $this->translator->trans('person_resource.person_non_prof'), ]) ->add('thirdparty', PickThirdpartyDynamicType::class, [ - 'label' => 'Tiers', + 'label' => $this->translator->trans('person_resource.thirdparty_prof'), ]) ->add('freetext', ChillTextareaType::class, [ - 'label' => 'Description libre', + 'label' => $this->translator->trans('person_resource.freetext'), 'required' => false, ]) ->add('comment', CommentType::class, [ diff --git a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/form.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/form.html.twig index 9bda2856b..5542074fa 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/form.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/PersonResource/form.html.twig @@ -10,28 +10,28 @@
                            {% if resource is defined and resource.person is not null %} - + {% else %} - + {% endif %}
                            {% if resource is defined and resource.thirdparty is not null %} - + {% else %} - + {% endif %}
                            {% if resource is defined and resource.freeText is not null %} - + {% else %} - + {% endif %}
    diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 9bce912a0..94e6083ca 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -265,6 +265,10 @@ no comment found: "Aucun commentaire" Select a type: "Choisissez un type" Select a person: "Choisissez un usager" Kind: "Type" +person_resource: + person_non_prof: "Usager/ Tiers non-professionnel" + thirdparty_prof: "Tiers professionnel" + freetext: "Description libre" # pickAPersonType From 27d344c97dafeaf495fe5bc9109e0793ba75a5ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 30 Jun 2025 11:00:42 +0200 Subject: [PATCH 55/83] Release v3.12.0 --- .changes/unreleased/DX-20250430-144550.yaml | 6 --- .changes/unreleased/DX-20250528-165813.yaml | 6 --- .../unreleased/Feature-20250424-142211.yaml | 7 --- .../unreleased/Feature-20250520-095628.yaml | 6 --- .../unreleased/Feature-20250523-133341.yaml | 6 --- .../unreleased/Feature-20250523-133434.yaml | 6 --- .../unreleased/Feature-20250626-143735.yaml | 6 --- .../unreleased/Fixed-20250424-133943.yaml | 7 --- .../unreleased/Fixed-20250424-163746.yaml | 7 --- .../unreleased/Fixed-20250505-102715.yaml | 6 --- .../unreleased/Fixed-20250514-145339.yaml | 6 --- .../unreleased/Fixed-20250611-164623.yaml | 6 --- .../unreleased/Fixed-20250619-170142.yaml | 7 --- .../unreleased/Fixed-20250626-122030.yaml | 6 --- .changes/unreleased/UX-20250423-172624.yaml | 6 --- .changes/unreleased/UX-20250617-192650.yaml | 6 --- .changes/v3.12.0.md | 22 ++++++++++ CHANGELOG.md | 43 +++++++++++++++++++ 18 files changed, 65 insertions(+), 100 deletions(-) delete mode 100644 .changes/unreleased/DX-20250430-144550.yaml delete mode 100644 .changes/unreleased/DX-20250528-165813.yaml delete mode 100644 .changes/unreleased/Feature-20250424-142211.yaml delete mode 100644 .changes/unreleased/Feature-20250520-095628.yaml delete mode 100644 .changes/unreleased/Feature-20250523-133341.yaml delete mode 100644 .changes/unreleased/Feature-20250523-133434.yaml delete mode 100644 .changes/unreleased/Feature-20250626-143735.yaml delete mode 100644 .changes/unreleased/Fixed-20250424-133943.yaml delete mode 100644 .changes/unreleased/Fixed-20250424-163746.yaml delete mode 100644 .changes/unreleased/Fixed-20250505-102715.yaml delete mode 100644 .changes/unreleased/Fixed-20250514-145339.yaml delete mode 100644 .changes/unreleased/Fixed-20250611-164623.yaml delete mode 100644 .changes/unreleased/Fixed-20250619-170142.yaml delete mode 100644 .changes/unreleased/Fixed-20250626-122030.yaml delete mode 100644 .changes/unreleased/UX-20250423-172624.yaml delete mode 100644 .changes/unreleased/UX-20250617-192650.yaml create mode 100644 .changes/v3.12.0.md diff --git a/.changes/unreleased/DX-20250430-144550.yaml b/.changes/unreleased/DX-20250430-144550.yaml deleted file mode 100644 index 7d9f0c32b..000000000 --- a/.changes/unreleased/DX-20250430-144550.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: DX -body: Remove dead code for wopi-link module -time: 2025-04-30T14:45:50.406111606+02:00 -custom: - Issue: "352" - SchemaChange: No schema change diff --git a/.changes/unreleased/DX-20250528-165813.yaml b/.changes/unreleased/DX-20250528-165813.yaml deleted file mode 100644 index f13945e76..000000000 --- a/.changes/unreleased/DX-20250528-165813.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: DX -body: Replace library node-sass by sass, and upgrade bootstrap to version 5.3 (yarn upgrade / install is required) -time: 2025-05-28T16:58:13.226870341+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/Feature-20250424-142211.yaml b/.changes/unreleased/Feature-20250424-142211.yaml deleted file mode 100644 index e1f5297c3..000000000 --- a/.changes/unreleased/Feature-20250424-142211.yaml +++ /dev/null @@ -1,7 +0,0 @@ -kind: Feature -body: Add the document file name to the document title when a user upload a document, - unless there is already a document title. -time: 2025-04-24T14:22:11.800975422+02:00 -custom: - Issue: "377" - SchemaChange: No schema change diff --git a/.changes/unreleased/Feature-20250520-095628.yaml b/.changes/unreleased/Feature-20250520-095628.yaml deleted file mode 100644 index 4b1fba30c..000000000 --- a/.changes/unreleased/Feature-20250520-095628.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Feature -body: Add desactivation date for social action and issue csv export -time: 2025-05-20T09:56:28.108941934+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/Feature-20250523-133341.yaml b/.changes/unreleased/Feature-20250523-133341.yaml deleted file mode 100644 index 1a5513a0d..000000000 --- a/.changes/unreleased/Feature-20250523-133341.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Feature -body: Add Emoji and Fullscreen feature to ckeditor configuration -time: 2025-05-23T13:33:41.645095128+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/Feature-20250523-133434.yaml b/.changes/unreleased/Feature-20250523-133434.yaml deleted file mode 100644 index 31dced03a..000000000 --- a/.changes/unreleased/Feature-20250523-133434.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Feature -body: Create editor which allow us to toggle between rich and simple text editor -time: 2025-05-23T13:34:34.56795603+02:00 -custom: - Issue: "321" - SchemaChange: No schema change diff --git a/.changes/unreleased/Feature-20250626-143735.yaml b/.changes/unreleased/Feature-20250626-143735.yaml deleted file mode 100644 index b1a447016..000000000 --- a/.changes/unreleased/Feature-20250626-143735.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Feature -body: Do not remove workflow which are automatically canceled after staling for more than 30 days -time: 2025-06-26T14:37:35.267672192+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250424-133943.yaml b/.changes/unreleased/Fixed-20250424-133943.yaml deleted file mode 100644 index c11c42ac0..000000000 --- a/.changes/unreleased/Fixed-20250424-133943.yaml +++ /dev/null @@ -1,7 +0,0 @@ -kind: Fixed -body: trying to prevent bug of typeerror in doc-history + improved display of document - history -time: 2025-04-24T13:39:43.878468232+02:00 -custom: - Issue: "376" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250424-163746.yaml b/.changes/unreleased/Fixed-20250424-163746.yaml deleted file mode 100644 index 79c07b080..000000000 --- a/.changes/unreleased/Fixed-20250424-163746.yaml +++ /dev/null @@ -1,7 +0,0 @@ -kind: Fixed -body: Display previous participation in acc course work even if the person has left - the acc course -time: 2025-04-24T16:37:46.970203594+02:00 -custom: - Issue: "381" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250505-102715.yaml b/.changes/unreleased/Fixed-20250505-102715.yaml deleted file mode 100644 index 5d47c25af..000000000 --- a/.changes/unreleased/Fixed-20250505-102715.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Fixed -body: Fix display of text in calendar events -time: 2025-05-05T10:27:15.461493066+02:00 -custom: - Issue: "372" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250514-145339.yaml b/.changes/unreleased/Fixed-20250514-145339.yaml deleted file mode 100644 index 862ea764b..000000000 --- a/.changes/unreleased/Fixed-20250514-145339.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Fixed -body: Add missing translation for user_group.no_user_groups -time: 2025-05-14T14:53:39.53927329+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250611-164623.yaml b/.changes/unreleased/Fixed-20250611-164623.yaml deleted file mode 100644 index 8bb956c34..000000000 --- a/.changes/unreleased/Fixed-20250611-164623.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Fixed -body: Fix admin entity edit actions for event admin entities and activity reason (category) entities -time: 2025-06-11T16:46:23.113506434+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250619-170142.yaml b/.changes/unreleased/Fixed-20250619-170142.yaml deleted file mode 100644 index d9ee78e1b..000000000 --- a/.changes/unreleased/Fixed-20250619-170142.yaml +++ /dev/null @@ -1,7 +0,0 @@ -kind: Fixed -body: | - Allow null and cast as string to setContent method for NewsItem -time: 2025-06-19T17:01:42.125730402+02:00 -custom: - Issue: "392" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250626-122030.yaml b/.changes/unreleased/Fixed-20250626-122030.yaml deleted file mode 100644 index 72ad87552..000000000 --- a/.changes/unreleased/Fixed-20250626-122030.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Fixed -body: 'Doc Generation: the "dump only" method send the document as an email attachment.' -time: 2025-06-26T12:20:30.083601058+02:00 -custom: - Issue: "393" - SchemaChange: No schema change diff --git a/.changes/unreleased/UX-20250423-172624.yaml b/.changes/unreleased/UX-20250423-172624.yaml deleted file mode 100644 index 2a17e4195..000000000 --- a/.changes/unreleased/UX-20250423-172624.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: UX -body: Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page -time: 2025-04-23T17:26:24.45777387+02:00 -custom: - Issue: "374" - SchemaChange: No schema change diff --git a/.changes/unreleased/UX-20250617-192650.yaml b/.changes/unreleased/UX-20250617-192650.yaml deleted file mode 100644 index 810758b10..000000000 --- a/.changes/unreleased/UX-20250617-192650.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: UX -body: Improve labeling of fields in person resource creation form -time: 2025-06-17T19:26:50.599703116+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/v3.12.0.md b/.changes/v3.12.0.md new file mode 100644 index 000000000..cfed2e013 --- /dev/null +++ b/.changes/v3.12.0.md @@ -0,0 +1,22 @@ +## v3.12.0 - 2025-06-30 +### Feature +* ([#377](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/377)) Add the document file name to the document title when a user upload a document, unless there is already a document title. +* Add desactivation date for social action and issue csv export +* Add Emoji and Fullscreen feature to ckeditor configuration +* ([#321](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/321)) Create editor which allow us to toggle between rich and simple text editor +* Do not remove workflow which are automatically canceled after staling for more than 30 days +### Fixed +* ([#376](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/376)) trying to prevent bug of typeerror in doc-history + improved display of document history +* ([#381](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/381)) Display previous participation in acc course work even if the person has left the acc course +* ([#372](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/372)) Fix display of text in calendar events +* Add missing translation for user_group.no_user_groups +* Fix admin entity edit actions for event admin entities and activity reason (category) entities +* ([#392](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/392)) Allow null and cast as string to setContent method for NewsItem + +* ([#393](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/393)) Doc Generation: the "dump only" method send the document as an email attachment. +### DX +* ([#352](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/352)) Remove dead code for wopi-link module +* Replace library node-sass by sass, and upgrade bootstrap to version 5.3 (yarn upgrade / install is required) +### UX +* ([#374](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/374)) Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page +* Improve labeling of fields in person resource creation form diff --git a/CHANGELOG.md b/CHANGELOG.md index b1b9aa894..f28c3cefd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,49 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## v3.12.0 - 2025-06-30 +### Feature +* ([#377](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/377)) Add the document file name to the document title when a user upload a document, unless there is already a document title. +* Add desactivation date for social action and issue csv export +* Add Emoji and Fullscreen feature to ckeditor configuration +* ([#321](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/321)) Create editor which allow us to toggle between rich and simple text editor +* Do not remove workflow which are automatically canceled after staling for more than 30 days +### Fixed +* ([#376](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/376)) trying to prevent bug of typeerror in doc-history + improved display of document history +* ([#381](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/381)) Display previous participation in acc course work even if the person has left the acc course +* ([#372](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/372)) Fix display of text in calendar events +* Add missing translation for user_group.no_user_groups +* Fix admin entity edit actions for event admin entities and activity reason (category) entities +* ([#392](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/392)) Allow null and cast as string to setContent method for NewsItem + +* ([#393](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/393)) Doc Generation: the "dump only" method send the document as an email attachment. +### DX +* ([#352](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/352)) Remove dead code for wopi-link module +* Replace library node-sass by sass, and upgrade bootstrap to version 5.3 (yarn upgrade / install is required) +### UX +* ([#374](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/374)) Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page +* Improve labeling of fields in person resource creation form + +## v3.11.0 - 2025-04-17 +### Feature +* ([#365](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/365)) Add counters of actions and activities, with 2 boxes to (1) show the number of active actions on total actions and (2) show the number of activities in a accompanying period, and pills in menus for showing the number of active actions and the number of activities. +* ([#364](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/364)) Added a second phone number "telephone2" to the thirdParty entity. Adapted twig templates and vuejs apps to handle this phone number + + **Schema Change**: Add columns or tables +* Signature: add a button to go directly to the signature zone, even if there is only one +### Fixed +* Fixed wrong translations in the on-the-fly for creation of thirdParty +* Fixed update of phone number in on-the-fly edition of thirdParty +* Fixed closing of modal when editing thirdParty in accompanying course works +* Shorten the delay between two execution of AccompanyingPeriodStepChangeCronjob, to ensure at least one execution in a day +* ([#102](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/102)) Fix display of title in document list +* When cleaning the old stored object versions, do not throw an error if the stored object is not found on disk +* Add consistent log prefix and key to logs when stale workflows are automatically canceled +* ([#380](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/380)) Remove the "not null" validation constraint on recently added properties on HouseholdComposition + +### DX +* Add new chill-col style for displaying title and aside in a flex table + ## v3.10.3 - 2025-03-18 ### DX * Eslint fixes From 2d8cda30b90a88e47be8504b286589ff57369007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 30 Jun 2025 20:32:17 +0200 Subject: [PATCH 56/83] Add `localizeString` method to `PickTemplate` component for string localization --- .../Resources/public/vuejs/_components/PickTemplate.vue | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue b/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue index 6639b0477..1bcc95fd0 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue @@ -58,6 +58,7 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue new file mode 100644 index 000000000..c3615c959 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue new file mode 100644 index 000000000..ad6c4c82f --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue @@ -0,0 +1,101 @@ + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig index 438a6fb7f..176861d75 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig @@ -68,6 +68,14 @@ {% endif %}

    + {% if work.accompanyingPeriod.getWorks|length > 1 %} +
  • + + + {{ 'Merge'|trans }} + +
  • + {% endif %} {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', work) %}
  • + +
    +

    {{ 'acpw_duplicate.to keep'|trans ~ ':' }}

    +
    +
    + {{ details.details(acpw, accompanyingCourse) }} +
    +
    +
    + +

    {{ 'acpw_duplicate.Assign duplicate'|trans }}

    + {{ form_start(form) }} + {%- if form.acpw is defined -%} + {{ form_row(form.acpw) }} +
    + {% endif %} + {{ form_rest(form) }} + +
    + + {{ form_end(form) }} + +
  • +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_duplicate_selector') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_duplicate_selector') }} +{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriodWorkDuplicate/confirm.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriodWorkDuplicate/confirm.html.twig new file mode 100644 index 000000000..92d65b7c3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingPeriodWorkDuplicate/confirm.html.twig @@ -0,0 +1,72 @@ +{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %} + +{% import '@ChillPerson/AccompanyingPeriodWorkDuplicate/_details.html.twig' as details %} + +{% block title %}{{ 'acpw_duplicate.title'|trans }}{% endblock %} + +{% block content %} +
    + +

    {{ 'acpw_duplicate.title'|trans }}

    +
    +

    {{ 'acpw_duplicate.description'|trans }}

    +
    + +
    +
    +

    {{ 'acpw_duplicate.to delete'|trans ~ ':' }}

    +
    +
    + {{ details.details(acpw2, accompanyingCourse) }} +
    +
    +
    +
    + +
    +
    +

    {{ 'acpw_duplicate.to keep'|trans ~ ':' }}

    +
    +
    + {{ details.details(acpw, accompanyingCourse) }} +
    +
    +
    +
    + + {{ form_start(form) }} + +
    + +
    +
    + {{ form_widget(form.confirm) }} +
    +
    + {{ form_label(form.confirm) }} +
    +
    +
    + + + + {{ form_end(form) }} + +
    +{% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php b/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php new file mode 100644 index 000000000..ba0f93ce3 --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php @@ -0,0 +1,112 @@ +em->wrapInTransaction(function (EntityManagerInterface $entityManager) use ($toKeep, $toDelete) { + $this->alterStartDate($toKeep, $toDelete); + $this->alterEndDate($toKeep, $toDelete); + $this->concatenateComments($toKeep, $toDelete); + $this->transferWorkflowsSQL($toKeep, $toDelete); + $this->updateReferencesSQL($toKeep, $toDelete); + $entityManager->remove($toDelete); + }); + + return $toKeep; + } + + private function transferWorkflowsSQL(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void + { + $this->em->getConnection()->executeQuery( + "UPDATE chill_main_workflow_entity w + SET relatedentityid = :toKeepId + WHERE w.relatedentityid = :toDeleteId + AND w.relatedentityclass = 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork'", + ['toKeepId' => $toKeep->getId(), 'toDeleteId' => $toDelete->getId()] + ); + } + + private function alterStartDate(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void + { + $startDate = min($toKeep->getStartDate(), $toDelete->getStartDate()); + $toKeep->setStartDate($startDate); + } + + private function alterEndDate(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void + { + if (null === $toKeep->getEndDate() || null === $toDelete->getEndDate()) { + $toKeep->setEndDate(null); + + return; + } + + $endDate = max($toKeep->getEndDate(), $toDelete->getEndDate()); + $toKeep->setEndDate($endDate); + } + + private function concatenateComments(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void + { + $toKeep->setNote($toKeep->getNote()."\n\n-----------------\n\n".$toDelete->getNote()); + $toKeep->getPrivateComment()->concatenateComments($toDelete->getPrivateComment()); + } + + private function updateReferencesSQL(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void + { + foreach ($toDelete->getAccompanyingPeriodWorkEvaluations() as $evaluation) { + $toKeep->addAccompanyingPeriodWorkEvaluation($evaluation); + } + + foreach ($toDelete->getReferrers() as $referrer) { + // we only keep the current referrer + $toKeep->addReferrer($referrer); + } + + foreach ($toDelete->getPersons() as $person) { + $toKeep->addPerson($person); + } + + if (null === $toKeep->getHandlingThierParty()) { + $toKeep->setHandlingThierParty($toDelete->getHandlingThierParty()); + } + + foreach ($toDelete->getThirdParties() as $thirdParty) { + $toKeep->addThirdParty($thirdParty); + } + + foreach ($toDelete->getGoals() as $goal) { + $toKeep->addGoal($goal); + } + + foreach ($toDelete->getResults() as $result) { + $toKeep->addResult($result); + } + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeServiceTest.php b/src/Bundle/ChillPersonBundle/Tests/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeServiceTest.php new file mode 100644 index 000000000..e69c8beee --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeServiceTest.php @@ -0,0 +1,205 @@ +prophesize(EntityManagerInterface::class); + $entityManager->wrapInTransaction(Argument::type('callable'))->will(function ($args) use ($entityManager) { + call_user_func_array($args[0], [$entityManager->reveal()]); + })->shouldBeCalled(); + $entityManager->remove($toRemove)->shouldBeCalled(); + + $connection = $this->prophesize(Connection::class); + $connection->executeQuery(Argument::type('string'), Argument::type('array'))->shouldBeCalled(); + + $entityManager->getConnection()->willReturn($connection->reveal()); + + return new AccompanyingPeriodWorkMergeService($entityManager->reveal()); + } + + /** + * @dataProvider provideStartDateMoveData + */ + public function testStartDateMove(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete, ?\DateTime $expected): void + { + $service = $this->buildMergeService($toDelete); + $return = $service->merge($toKeep, $toDelete); + + self::assertEquals($expected, $return->getStartDate()); + } + + public static function provideStartDateMoveData(): array + { + return [ + 'Earliest date kept when toKeep is earlier' => [ + (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-01-01')), + (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-06-01')), + new \DateTime('2023-01-01'), + ], + 'Earliest date kept when toDelete is earlier' => [ + (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-06-01')), + (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-01-01')), + new \DateTime('2023-01-01'), + ], + 'Same start dates remain unchanged' => [ + (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-01-01')), + (new AccompanyingPeriodWork())->setStartDate(new \DateTime('2023-01-01')), + new \DateTime('2023-01-01'), + ], + ]; + } + + /** + * @dataProvider provideEndDateMoveData + */ + public function testEndDateMove(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete, ?\DateTimeImmutable $expected): void + { + $service = $this->buildMergeService($toDelete); + $return = $service->merge($toKeep, $toDelete); + + self::assertEquals($expected, $return->getEndDate()); + } + + public static function provideEndDateMoveData(): array + { + return [ + 'Oldest date kept when toKeep is older' => [ + (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2022-01-01'))->setStartDate(new \DateTime('2021-01-01')), + (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-06-01'))->setStartDate(new \DateTime('2021-01-01')), + new \DateTimeImmutable('2023-06-01'), + ], + 'Oldest date kept when toDelete is older' => [ + (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-06-01'))->setStartDate(new \DateTime('2021-01-01')), + (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2022-01-01'))->setStartDate(new \DateTime('2021-01-01')), + new \DateTimeImmutable('2023-06-01'), + ], + 'Same end dates remain unchanged' => [ + (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-01-01'))->setStartDate(new \DateTime('2021-01-01')), + (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-01-01'))->setStartDate(new \DateTime('2021-01-01')), + new \DateTimeImmutable('2023-01-01'), + ], + 'End date is null if toKeep is null' => [ + (new AccompanyingPeriodWork())->setEndDate(null)->setStartDate(new \DateTime('2021-01-01')), + (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-01-01'))->setStartDate(new \DateTime('2021-01-01')), + null, + ], + 'End date is null if toDelete is null' => [ + (new AccompanyingPeriodWork())->setEndDate(new \DateTimeImmutable('2023-01-01'))->setStartDate(new \DateTime('2021-01-01')), + (new AccompanyingPeriodWork())->setEndDate(null)->setStartDate(new \DateTime('2021-01-01')), + null, + ], + 'End date is null if both are null' => [ + (new AccompanyingPeriodWork())->setEndDate(null)->setStartDate(new \DateTime('2021-01-01')), + (new AccompanyingPeriodWork())->setEndDate(null)->setStartDate(new \DateTime('2021-01-01')), + null, + ], + ]; + } + + /** + * @dataProvider provideMoveHandlingThirdPartyData + */ + public function testMoveHandlingThirdParty(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete, ?ThirdParty $expected): void + { + $service = $this->buildMergeService($toDelete); + $return = $service->merge($toKeep, $toDelete); + + self::assertSame($expected, $return->getHandlingThierParty()); + } + + public static function provideMoveHandlingThirdPartyData(): iterable + { + yield 'Third party not change when existing in kept' => [ + (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01'))->setHandlingThierParty($tpA = new ThirdParty()), + (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01'))->setHandlingThierParty(new ThirdParty()), + $tpA, + ]; + + yield 'Third party will change when not existing in kept' => [ + (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01')), + (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01'))->setHandlingThierParty($tpB = new ThirdParty()), + $tpB, + ]; + + yield 'Third party do not change when not existing in removed' => [ + (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01'))->setHandlingThierParty($tpC = new ThirdParty()), + (new AccompanyingPeriodWork())->setStartDate(new \DateTimeImmutable('2022-01-01')), + $tpC, + ]; + } + + public function testMerge(): void + { + $accompanyingPeriodWork = new AccompanyingPeriodWork(); + $accompanyingPeriodWork->setStartDate(new \DateTime('2022-01-01')); + $accompanyingPeriodWork->addReferrer($userA = new User()); + $accompanyingPeriodWork->addReferrer($userC = new User()); + $accompanyingPeriodWork->addAccompanyingPeriodWorkEvaluation($evaluationA = new AccompanyingPeriodWorkEvaluation()); + $accompanyingPeriodWork->setNote('blabla'); + $accompanyingPeriodWork->addThirdParty($thirdPartyA = new ThirdParty()); + + $toDelete = new AccompanyingPeriodWork(); + $toDelete->setStartDate(new \DateTime('2022-01-01')); + $toDelete->addReferrer($userB = new User()); + $toDelete->addReferrer($userC); + $toDelete->addAccompanyingPeriodWorkEvaluation($evaluationB = new AccompanyingPeriodWorkEvaluation()); + $toDelete->setNote('boum'); + $toDelete->addThirdParty($thirdPartyB = new ThirdParty()); + $toDelete->addGoal($goalA = new AccompanyingPeriodWorkGoal()); + $toDelete->addResult($resultA = new Result()); + + $service = $this->buildMergeService($toDelete); + $service->merge($accompanyingPeriodWork, $toDelete); + + self::assertTrue($accompanyingPeriodWork->getReferrers()->contains($userA)); + self::assertTrue($accompanyingPeriodWork->getReferrers()->contains($userB)); + self::assertTrue($accompanyingPeriodWork->getReferrers()->contains($userC)); + + self::assertTrue($accompanyingPeriodWork->getAccompanyingPeriodWorkEvaluations()->contains($evaluationA)); + self::assertTrue($accompanyingPeriodWork->getAccompanyingPeriodWorkEvaluations()->contains($evaluationB)); + foreach ($accompanyingPeriodWork->getAccompanyingPeriodWorkEvaluations() as $evaluation) { + self::assertSame($accompanyingPeriodWork, $evaluation->getAccompanyingPeriodWork()); + } + + self::assertStringContainsString('blabla', $accompanyingPeriodWork->getNote()); + self::assertStringContainsString('boum', $toDelete->getNote()); + + self::assertTrue($accompanyingPeriodWork->getThirdParties()->contains($thirdPartyA)); + self::assertTrue($accompanyingPeriodWork->getThirdParties()->contains($thirdPartyB)); + + self::assertTrue($accompanyingPeriodWork->getGoals()->contains($goalA)); + self::assertTrue($accompanyingPeriodWork->getResults()->contains($resultA)); + } +} diff --git a/src/Bundle/ChillPersonBundle/chill.webpack.config.js b/src/Bundle/ChillPersonBundle/chill.webpack.config.js index e4c6ed3af..effbec70f 100644 --- a/src/Bundle/ChillPersonBundle/chill.webpack.config.js +++ b/src/Bundle/ChillPersonBundle/chill.webpack.config.js @@ -6,7 +6,6 @@ module.exports = function (encore, entries) { encore.addAliases({ ChillPersonAssets: __dirname + "/Resources/public", }); - encore.addEntry( "vue_household_members_editor", __dirname + "/Resources/public/vuejs/HouseholdMembersEditor/index.js", @@ -31,7 +30,6 @@ module.exports = function (encore, entries) { "vue_export_action_goal_result", __dirname + "/Resources/public/vuejs/ExportFormActionGoalResult/index.js", ); - encore.addEntry( "mod_set_referrer", __dirname + "/Resources/public/mod/AccompanyingPeriod/setReferrer.js", @@ -66,4 +64,8 @@ module.exports = function (encore, entries) { "page_create_person", __dirname + "/Resources/public/page/person/create-person.js", ); + encore.addEntry( + "mod_duplicate_selector", + __dirname + "/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts", + ); }; diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 94e6083ca..17a00d525 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -1502,6 +1502,18 @@ entity_display_title: Work (n°%w%): "Action d'accompagnement (n°%w%)" Accompanying Course (n°%w%): "Parcours d'accompagnement (n°%w%)" +acpw_duplicate: + title: Fusionner les actions d'accompagnement + description: Cette fusion conservera la date de début la plus ancienne, la date de fin la plus récente, toutes les évaluations, documents et workflows. Les agents traitants seront additionnés ainsi que les tiers intervenants. Les commentaires seront mis l'un à la suite de l'autre. + Select accompanying period work: Selectionner un action d'accompagnement + Assign duplicate: Désigner un action d'accompagnement doublon + Accompanying period work to delete: Action d'accompagnement à supprimer + Accompanying period work to delete explanation: Cet action d'accompagnement sera supprimé. + Accompanying period work to keep: Action d'accompagnement à conserver + to keep: Action d'accompagnement à conserver + to delete: Action d'accompagnement à supprimer + Successfully merged: Action d'accompagnement fusionnée avec succès. + my_parcours_filters: referrer_parcours_and_acpw: Agent traitant ou réferent referrer_acpw: Agent traitant d'une action diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts b/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts new file mode 100644 index 000000000..20ae3309b --- /dev/null +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts @@ -0,0 +1,47 @@ +import { + Address, + Center, + Civility, + DateTime, + User, +} from "ChillMainAssets/types"; + +export interface Thirdparty { + acronym: string | null; + active: boolean; + address: Address | null; + canonicalized: string | null; + categories: ThirdpartyCategory[]; + centers: Center[]; + children: Thirdparty[]; + civility: Civility | null; + comment: string | null; + contactDataAnonymous: boolean; + createdAt: DateTime; + createdBy: User | null; + email: string | null; + firstname: string | null; + id: number | null; + kind: string; + name: string; + nameCompany: string | null; + parent: Thirdparty | null; + profession: string; + telephone: string | null; + thirdPartyTypes: ThirdpartyType[] | null; + updatedAt: DateTime | null; + updatedBy: User | null; +} + +interface ThirdpartyType { + key: string; + value: string; +} + +export interface ThirdpartyCategory { + id: number; + active: boolean; + name: { + fr: string; + }; +} diff --git a/symfony.lock b/symfony.lock index ee44b19d8..6409f0c1d 100644 --- a/symfony.lock +++ b/symfony.lock @@ -14,6 +14,15 @@ "config/routes/annotations.yaml" ] }, + "doctrine/deprecations": { + "version": "1.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "87424683adc81d7dc305eefec1fced883084aab9" + } + }, "doctrine/doctrine-bundle": { "version": "2.13", "recipe": { From 6f7015b1520df1060d41881a151f103cd3a1bedc Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 2 Jul 2025 13:56:41 +0200 Subject: [PATCH 61/83] Fix translations of form fields in admin for social actions --- .changes/unreleased/Fixed-20250702-135534.yaml | 6 ++++++ .../Form/SocialWork/SocialActionType.php | 14 +++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 .changes/unreleased/Fixed-20250702-135534.yaml diff --git a/.changes/unreleased/Fixed-20250702-135534.yaml b/.changes/unreleased/Fixed-20250702-135534.yaml new file mode 100644 index 000000000..75a911301 --- /dev/null +++ b/.changes/unreleased/Fixed-20250702-135534.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: 'Fix translations for social action fields in admin form: results, goals, evaluations' +time: 2025-07-02T13:55:34.599050626+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialActionType.php b/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialActionType.php index 0b6cbe73d..31fa6f432 100644 --- a/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialActionType.php +++ b/src/Bundle/ChillPersonBundle/Form/SocialWork/SocialActionType.php @@ -25,21 +25,14 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; /** * Class SocialActionType. */ class SocialActionType extends AbstractType { - /** - * @var TranslatableStringHelper - */ - protected $translatableStringHelper; - - public function __construct(TranslatableStringHelper $translatableStringHelper) - { - $this->translatableStringHelper = $translatableStringHelper; - } + public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly TranslatorInterface $translator) {} public function buildForm(FormBuilderInterface $builder, array $options) { @@ -64,6 +57,7 @@ class SocialActionType extends AbstractType ->add('results', EntityType::class, [ 'class' => Result::class, 'required' => false, + 'label' => $this->translator->trans('person_admin.social_result'), 'multiple' => true, 'attr' => ['class' => 'select2'], 'choice_label' => fn (Result $r) => $this->translatableStringHelper->localize($r->getTitle()), @@ -74,6 +68,7 @@ class SocialActionType extends AbstractType 'required' => false, 'multiple' => true, 'attr' => ['class' => 'select2'], + 'label' => $this->translator->trans('person_admin.social_goal'), 'choice_label' => fn (Goal $g) => $this->translatableStringHelper->localize($g->getTitle()), ]) @@ -82,6 +77,7 @@ class SocialActionType extends AbstractType 'required' => false, 'multiple' => true, 'attr' => ['class' => 'select2'], + 'label' => $this->translator->trans('person_admin.social_evaluation'), 'choice_label' => fn (Evaluation $e) => $this->translatableStringHelper->localize($e->getTitle()), ]) From e592b89c9474b331814e3c497b1f73b3f015ab70 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Mon, 7 Jul 2025 12:36:49 +0200 Subject: [PATCH 62/83] remove ux-translator from dependencies --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 73fa14d86..fc50ba29c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "@hotwired/stimulus": "^3.0.0", "@luminateone/eslint-baseline": "^1.0.9", "@symfony/stimulus-bridge": "^3.2.0", - "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/webpack-encore": "^4.1.0", "@tsconfig/node20": "^20.1.4", "@types/dompurify": "^3.0.5", From aed114c75c91a3ebf594db17141ded857a899dfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 8 Jul 2025 13:38:51 +0000 Subject: [PATCH 63/83] Fix Eslint issues --- .../Resources/public/vuejs/Activity/store.js | 4 +- .../Export/Formatter/CSVFormatter.php | 1 - .../Export/Formatter/CSVListFormatter.php | 1 - .../Formatter/SpreadsheetListFormatter.php | 1 - .../CancelStaleWorkflowHandlerTest.php | 2 +- .../AccompanyingPeriodWorkSelector.ts | 74 ++-- .../Resources/public/types.ts | 334 +++++++++--------- .../vuejs/ExportFormActionGoalResult/App.vue | 2 +- .../Resources/public/vuejs/VisGraph/App.vue | 5 + .../Resources/public/vuejs/VisGraph/store.js | 5 +- .../AccompanyingPeriodWorkItem.vue | 90 ++--- .../AccompanyingPeriodWorkList.vue | 42 +-- .../AccompanyingPeriodWorkSelectorModal.vue | 144 ++++---- .../public/vuejs/_components/AddPersons.vue | 2 +- .../Resources/public/module/pending/index.ts | 20 +- 15 files changed, 379 insertions(+), 348 deletions(-) diff --git a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js index 8d09d2dd3..acd616dc9 100644 --- a/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js +++ b/src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/store.js @@ -2,7 +2,7 @@ import "es6-promise/auto"; import { createStore } from "vuex"; import { postLocation } from "./api"; import prepareLocations from "./store.locations.js"; -import {fetchResults, makeFetch} from "ChillMainAssets/lib/api/apiMethods"; +import { fetchResults, makeFetch } from "ChillMainAssets/lib/api/apiMethods"; const debug = process.env.NODE_ENV !== "production"; //console.log('window.activity', window.activity); @@ -369,7 +369,7 @@ const store = createStore({ // console.log('works', works); commit("setAccompanyingPeriodWorks", works); } catch (error) { - console.error('Failed to fetch works:', error); + console.error("Failed to fetch works:", error); } }, getWhoAmI({ commit }) { diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php index f0c2b9cee..c7ee03fe8 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php @@ -18,7 +18,6 @@ use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\Translation\TranslatorInterface; -use function count; /** * Command to get the report with curl: diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php index fda49f202..1a54eee5f 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php @@ -17,7 +17,6 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\Translation\TranslatorInterface; -use function count; // command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php index 3c5e90381..0a35e087f 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php @@ -21,7 +21,6 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\Translation\TranslatorInterface; -use function count; // command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff diff --git a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php index 275adfe75..aefcf7948 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/Workflow/CancelStaleWorkflowHandlerTest.php @@ -98,7 +98,7 @@ class CancelStaleWorkflowHandlerTest extends TestCase $em = $this->prophesize(EntityManagerInterface::class); $em->flush()->shouldBeCalled(); - $em->remove($workflow)->shouldBeCalled(); + $em->remove($workflow)->shouldNotBeCalled(); $handler = $this->buildHandler($workflow, $em->reveal(), $clock); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts b/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts index 6ceb64b98..8f117d6ef 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts @@ -3,47 +3,49 @@ import AccompanyingPeriodWorkSelectorModal from "../../vuejs/_components/Accompa import { AccompanyingPeriodWork } from "../../types"; document.addEventListener("DOMContentLoaded", () => { - const elements = document.querySelectorAll( - 'div[data-pick-entities-type="acpw"]', - ); - elements.forEach((el) => { - const uniqid = el.dataset.inputUniqid; - - if (undefined === uniqid) { - throw "Uniqid not found on this element"; - } - - const input = document.querySelector( - `input[data-input-uniqid="${uniqid}"]`, + const elements = document.querySelectorAll( + 'div[data-pick-entities-type="acpw"]', ); + elements.forEach((el) => { + const uniqid = el.dataset.inputUniqid; - if (null === input) { - throw "Element with uniqid not found: " + uniqid; - } + if (undefined === uniqid) { + throw "Uniqid not found on this element"; + } - const accompanyingPeriodIdAsString = input.dataset.accompanyingPeriodId; + const input = document.querySelector( + `input[data-input-uniqid="${uniqid}"]`, + ); - if (undefined === accompanyingPeriodIdAsString) { - throw "accompanying period id not found"; - } + if (null === input) { + throw "Element with uniqid not found: " + uniqid; + } - const accompanyingPeriodId = Number.parseInt(accompanyingPeriodIdAsString); + const accompanyingPeriodIdAsString = input.dataset.accompanyingPeriodId; - const app = createApp({ - template: - '', - components: { AccompanyingPeriodWorkSelectorModal }, - data() { - return { accompanyingPeriodId }; - }, - methods: { - pickWork: function (payload: { work: AccompanyingPeriodWork }) { - console.log("payload", payload); - input.value = payload.work.id.toString(); - }, - }, + if (undefined === accompanyingPeriodIdAsString) { + throw "accompanying period id not found"; + } + + const accompanyingPeriodId = Number.parseInt( + accompanyingPeriodIdAsString, + ); + + const app = createApp({ + template: + '', + components: { AccompanyingPeriodWorkSelectorModal }, + data() { + return { accompanyingPeriodId }; + }, + methods: { + pickWork: function (payload: { work: AccompanyingPeriodWork }) { + console.log("payload", payload); + input.value = payload.work.id.toString(); + }, + }, + }); + + app.mount(el); }); - - app.mount(el); - }); }); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/types.ts b/src/Bundle/ChillPersonBundle/Resources/public/types.ts index 78e82e48e..7d641c859 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/types.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/types.ts @@ -1,13 +1,13 @@ import { Address, Scope, - Center, - Civility, - DateTime, - User, - WorkflowAvailable, - Job, - PrivateCommentEmbeddable, + Center, + Civility, + DateTime, + User, + WorkflowAvailable, + Job, + PrivateCommentEmbeddable, } from "ChillMainAssets/types"; import { StoredObject } from "ChillDocStoreAssets/types"; import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types"; @@ -35,39 +35,39 @@ export interface Person { } export interface AccompanyingPeriod { - id: number; - addressLocation?: Address | null; - administrativeLocation?: Location | null; - calendars: Calendar[]; - closingDate?: Date | null; - closingMotive?: ClosingMotive | null; - comments: Comment[]; - confidential: boolean; - createdAt?: Date | null; - createdBy?: User | null; - emergency: boolean; - intensity?: "occasional" | "regular"; - job?: Job | null; - locationHistories: AccompanyingPeriodLocationHistory[]; - openingDate?: Date | null; - origin?: Origin | null; - participations: AccompanyingPeriodParticipation[]; - personLocation?: Person | null; - pinnedComment?: Comment | null; - preventUserIsChangedNotification: boolean; - remark: string; - requestorAnonymous: boolean; - requestorPerson?: Person | null; - requestorThirdParty?: Thirdparty | null; - resources: AccompanyingPeriodResource[]; - scopes: Scope[]; - socialIssues: SocialIssue[]; - step?: - | "CLOSED" - | "CONFIRMED" - | "CONFIRMED_INACTIVE_SHORT" - | "CONFIRMED_INACTIVE_LONG" - | "DRAFT"; + id: number; + addressLocation?: Address | null; + administrativeLocation?: Location | null; + calendars: Calendar[]; + closingDate?: Date | null; + closingMotive?: ClosingMotive | null; + comments: Comment[]; + confidential: boolean; + createdAt?: Date | null; + createdBy?: User | null; + emergency: boolean; + intensity?: "occasional" | "regular"; + job?: Job | null; + locationHistories: AccompanyingPeriodLocationHistory[]; + openingDate?: Date | null; + origin?: Origin | null; + participations: AccompanyingPeriodParticipation[]; + personLocation?: Person | null; + pinnedComment?: Comment | null; + preventUserIsChangedNotification: boolean; + remark: string; + requestorAnonymous: boolean; + requestorPerson?: Person | null; + requestorThirdParty?: Thirdparty | null; + resources: AccompanyingPeriodResource[]; + scopes: Scope[]; + socialIssues: SocialIssue[]; + step?: + | "CLOSED" + | "CONFIRMED" + | "CONFIRMED_INACTIVE_SHORT" + | "CONFIRMED_INACTIVE_LONG" + | "DRAFT"; } export interface AccompanyingPeriodWorkEvaluationDocument { @@ -84,170 +84,170 @@ export interface AccompanyingPeriodWorkEvaluationDocument { } export interface AccompanyingPeriodWork { - id: number; - accompanyingPeriod?: AccompanyingPeriod; - accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[]; - createdAt?: string; - createdAutomatically: boolean; - createdAutomaticallyReason: string; - createdBy: User; - endDate?: string; - goals: AccompanyingPeriodWorkGoal[]; - handlingThierParty?: Thirdparty; - note: string; - persons: Person[]; - privateComment: PrivateCommentEmbeddable; - referrersHistory: AccompanyingPeriodWorkReferrerHistory[]; - results: Result[]; - socialAction?: SocialAction; - startDate?: string; - thirdParties: Thirdparty[]; - updatedAt?: string; - updatedBy: User; - version: number; + id: number; + accompanyingPeriod?: AccompanyingPeriod; + accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[]; + createdAt?: string; + createdAutomatically: boolean; + createdAutomaticallyReason: string; + createdBy: User; + endDate?: string; + goals: AccompanyingPeriodWorkGoal[]; + handlingThierParty?: Thirdparty; + note: string; + persons: Person[]; + privateComment: PrivateCommentEmbeddable; + referrersHistory: AccompanyingPeriodWorkReferrerHistory[]; + results: Result[]; + socialAction?: SocialAction; + startDate?: string; + thirdParties: Thirdparty[]; + updatedAt?: string; + updatedBy: User; + version: number; } interface SocialAction { - id: number; - parent?: SocialAction | null; - children: SocialAction[]; - issue?: SocialIssue | null; - ordering: number; - title: { - fr: string; - }; - defaultNotificationDelay?: string | null; - desactivationDate?: string | null; - evaluations: Evaluation[]; - goals: Goal[]; - results: Result[]; + id: number; + parent?: SocialAction | null; + children: SocialAction[]; + issue?: SocialIssue | null; + ordering: number; + title: { + fr: string; + }; + defaultNotificationDelay?: string | null; + desactivationDate?: string | null; + evaluations: Evaluation[]; + goals: Goal[]; + results: Result[]; } export interface AccompanyingPeriodResource { - id: number; - accompanyingPeriod: AccompanyingPeriod; - comment?: string | null; - person?: Person | null; - thirdParty?: Thirdparty | null; + id: number; + accompanyingPeriod: AccompanyingPeriod; + comment?: string | null; + person?: Person | null; + thirdParty?: Thirdparty | null; } export interface Origin { - id: number; - label: { - fr: string; - }; - noActiveAfter: DateTime; + id: number; + label: { + fr: string; + }; + noActiveAfter: DateTime; } export interface ClosingMotive { - id: number; - active: boolean; - name: { - fr: string; - }; - ordering: number; - isCanceledAccompanyingPeriod: boolean; - parent?: ClosingMotive | null; - children: ClosingMotive[]; + id: number; + active: boolean; + name: { + fr: string; + }; + ordering: number; + isCanceledAccompanyingPeriod: boolean; + parent?: ClosingMotive | null; + children: ClosingMotive[]; } export interface AccompanyingPeriodParticipation { - id: number; - startDate: DateTime; - endDate?: DateTime | null; - accompanyingPeriod: AccompanyingPeriod; - person: Person; + id: number; + startDate: DateTime; + endDate?: DateTime | null; + accompanyingPeriod: AccompanyingPeriod; + person: Person; } export interface AccompanyingPeriodLocationHistory { - id: number; - startDate: DateTime; - endDate?: DateTime | null; - addressLocation?: Address | null; - period: AccompanyingPeriod; - personLocation?: Person | null; + id: number; + startDate: DateTime; + endDate?: DateTime | null; + addressLocation?: Address | null; + period: AccompanyingPeriod; + personLocation?: Person | null; } export interface SocialIssue { - id: number; - parent?: SocialIssue | null; - children: SocialIssue[]; - socialActions?: SocialAction[] | null; - ordering: number; - title: { - fr: string; - }; - desactivationDate?: string | null; + id: number; + parent?: SocialIssue | null; + children: SocialIssue[]; + socialActions?: SocialAction[] | null; + ordering: number; + title: { + fr: string; + }; + desactivationDate?: string | null; } export interface Goal { - id: number; - results: Result[]; - socialActions?: SocialAction[] | null; - title: { - fr: string; - }; + id: number; + results: Result[]; + socialActions?: SocialAction[] | null; + title: { + fr: string; + }; } export interface Result { - id: number; - accompanyingPeriodWorks: AccompanyingPeriodWork[]; - accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[]; - goals: Goal[]; - socialActions: SocialAction[]; - title: { - fr: string; - }; - desactivationDate?: string | null; + id: number; + accompanyingPeriodWorks: AccompanyingPeriodWork[]; + accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[]; + goals: Goal[]; + socialActions: SocialAction[]; + title: { + fr: string; + }; + desactivationDate?: string | null; } export interface AccompanyingPeriodWorkGoal { - id: number; - accompanyingPeriodWork: AccompanyingPeriodWork; - goal: Goal; - note: string; - results: Result[]; + id: number; + accompanyingPeriodWork: AccompanyingPeriodWork; + goal: Goal; + note: string; + results: Result[]; } export interface AccompanyingPeriodWorkEvaluation { - accompanyingPeriodWork: AccompanyingPeriodWork | null; - comment: string; - createdAt: DateTime | null; - createdBy: User | null; - documents: AccompanyingPeriodWorkEvaluationDocument[]; - endDate: DateTime | null; - evaluation: Evaluation | null; - id: number | null; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - key: any; - maxDate: DateTime | null; - startDate: DateTime | null; - updatedAt: DateTime | null; - updatedBy: User | null; - warningInterval: string | null; - timeSpent: number | null; + accompanyingPeriodWork: AccompanyingPeriodWork | null; + comment: string; + createdAt: DateTime | null; + createdBy: User | null; + documents: AccompanyingPeriodWorkEvaluationDocument[]; + endDate: DateTime | null; + evaluation: Evaluation | null; + id: number | null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + key: any; + maxDate: DateTime | null; + startDate: DateTime | null; + updatedAt: DateTime | null; + updatedBy: User | null; + warningInterval: string | null; + timeSpent: number | null; } export interface Evaluation { - id: number; - url: string; - socialActions: SocialAction[]; - title: { - fr: string; - }; - active: boolean; - delay: string; - notificationDelay: string; + id: number; + url: string; + socialActions: SocialAction[]; + title: { + fr: string; + }; + active: boolean; + delay: string; + notificationDelay: string; } export interface AccompanyingPeriodWorkReferrerHistory { - id: number; - accompanyingPeriodWork: AccompanyingPeriodWork; - user: User; - startDate: DateTime; - endDate: DateTime | null; - createdAt: DateTime; - updatedAt: DateTime | null; - createdBy: User; - updatedBy: User | null; + id: number; + accompanyingPeriodWork: AccompanyingPeriodWork; + user: User; + startDate: DateTime; + endDate: DateTime | null; + createdAt: DateTime; + updatedAt: DateTime | null; + createdBy: User; + updatedBy: User | null; } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue index 25877aa7a..29b707842 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue @@ -279,7 +279,7 @@ export default { (results) => ([this.results.options, this.results.value] = this.removeElementInData("results", results)), - ).catch; + ); }, // selectResult(value) { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue index 8359ad90c..401f41be5 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue @@ -294,10 +294,13 @@ export default { refreshNetwork() { //console.log('--- refresh network') window.network.setData(this.visgraph_data); + + return 1; }, legendLayers() { //console.log('--- refresh legend and rebuild checked Layers') + // eslint-disable-next-line vue/no-side-effects-in-computed-properties this.checkedLayers = []; let layersDisplayed = [ ...this.nodes.filter((n) => n.id.startsWith("household")), @@ -309,6 +312,7 @@ export default { return [...this.households, ...this.courses]; }, + // eslint-disable-next-line vue/no-dupe-keys checkedLayers() { // required to refresh data checkedLayers //console.log('--- checkedLayers') @@ -386,6 +390,7 @@ export default { }, forceUpdateComponent() { //console.log('!! forceUpdateComponent !!') + // eslint-disable-next-line @typescript-eslint/no-unused-expressions this.refreshNetwork; this.$forceUpdate(); }, diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js index 4d5f13d2e..e7fc6186f 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/store.js @@ -164,7 +164,8 @@ const store = createStore({ return; } let age = getAge(person); - age = age === "" ? "" : " - " + age; + let separator = person.gender === null ? "" : " - "; + age = age === "" ? "" : separator + age; let debug = ""; /// Debug mode: uncomment to display person_id on visgraph @@ -173,7 +174,7 @@ const store = createStore({ person.group = person.type; person._id = person.id; person.id = `person_${person.id}`; - person.label = `*${person.text}${person.deathdate ? " (‡)" : ""}*\n_${person.gender.label}${age}_${debug}`; + person.label = `*${person.text}${person.deathdate ? " (‡)" : ""}*\n_${person.gender === null ? "" : person.gender?.label}${age}_${debug}`; person.folded = false; // folded is used for missing persons if (options.folded) { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkItem.vue index df5d2ffdd..330423a20 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkItem.vue @@ -1,43 +1,51 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue index c3615c959..b2ebc5f1c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue @@ -1,24 +1,24 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue index ad6c4c82f..4b4f3ddba 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue @@ -1,62 +1,78 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue index 6f55b6636..5e81b8241 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue @@ -377,7 +377,7 @@ export default { "/api/1.0/person/household/members/move.json", member, ) - .then((_response) => { + .then(() => { makeFetch( "POST", `/api/1.0/person/household/${responseHousehold.id}/address.json`, diff --git a/src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts b/src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts index faeb00540..584fac047 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts +++ b/src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts @@ -1,7 +1,11 @@ -import { is_object_ready } from "../../../../../../ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers"; -import { - StoredObject, -} from "../../../../../../ChillDocStoreBundle/Resources/public/types"; +import { is_object_ready } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpers"; +import { StoredObject } from "ChillDocStoreAssets/types"; + +declare global { + interface Window { + stored_object: string | undefined; + } +} async function reload_if_needed( stored_object: StoredObject, @@ -25,14 +29,12 @@ function wait_before_reload(stored_object: StoredObject, i: number): void { setTimeout(reload_if_needed, timeout, stored_object, i); } -window.addEventListener("DOMContentLoaded", async function (e) { - if (undefined === (window as any).stored_object) { +window.addEventListener("DOMContentLoaded", async function () { + if (undefined === window.stored_object) { console.error("window.stored_object is undefined"); throw Error("window.stored_object is undefined"); } - const stored_object = JSON.parse( - (window as any).stored_object, - ) as StoredObject; + const stored_object = JSON.parse(window.stored_object) as StoredObject; reload_if_needed(stored_object, 0); }); From 8bc16dadb02cc821802a20a2fbd7177babe9ba34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 8 Jul 2025 13:53:25 +0000 Subject: [PATCH 64/83] =?UTF-8?q?Partage=20d'export=20enregistr=C3=A9=20et?= =?UTF-8?q?=20g=C3=A9n=C3=A9ration=20asynchrone=20des=20exports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changes/unreleased/DX-20250401-144728.yaml | 6 + .changes/unreleased/DX-20250407-121010.yaml | 71 +++ config/packages/chill.yaml | 1 + config/packages/messenger.yaml | 7 +- .../_static/code/exports/BirthdateFilter.php | 26 +- .../_static/code/exports/CountPerson.php | 22 +- docs/source/development/export-sequence.puml | 84 +++ docs/source/development/messages-to-users.rst | 21 +- package.json | 1 + phpstan-baseline.neon | 5 - phpstan.dist.neon | 2 +- phpunit.rector.xml | 33 +- rector.php | 19 +- .../ByActivityNumberAggregator.php | 19 +- .../ByActivityTypeAggregator.php | 27 +- .../BySocialActionAggregator.php | 21 +- .../BySocialIssueAggregator.php | 21 +- .../Aggregator/ActivityLocationAggregator.php | 23 +- .../Aggregator/ActivityPresenceAggregator.php | 25 +- .../Aggregator/ActivityReasonAggregator.php | 27 +- .../Aggregator/ActivityTypeAggregator.php | 23 +- .../Aggregator/ActivityUserAggregator.php | 23 +- .../Aggregator/ActivityUsersAggregator.php | 25 +- .../Aggregator/ActivityUsersJobAggregator.php | 21 +- .../ActivityUsersScopeAggregator.php | 21 +- .../Export/Aggregator/ByCreatorAggregator.php | 21 +- .../Aggregator/ByThirdpartyAggregator.php | 21 +- .../Aggregator/CreatorJobAggregator.php | 21 +- .../Aggregator/CreatorScopeAggregator.php | 21 +- .../Export/Aggregator/DateAggregator.php | 21 +- .../Aggregator/LocationTypeAggregator.php | 21 +- .../PersonAggregators/HouseholdAggregator.php | 27 +- .../PersonAggregators/PersonAggregator.php | 27 +- .../Export/Aggregator/PersonsAggregator.php | 27 +- .../Aggregator/SentReceivedAggregator.php | 17 +- .../LinkedToACP/AvgActivityDuration.php | 20 +- .../LinkedToACP/AvgActivityVisitDuration.php | 20 +- .../Export/LinkedToACP/CountActivity.php | 19 +- .../LinkedToACP/CountHouseholdOnActivity.php | 19 +- .../LinkedToACP/CountPersonsOnActivity.php | 19 +- .../Export/LinkedToACP/ListActivity.php | 30 +- .../LinkedToACP/SumActivityDuration.php | 20 +- .../LinkedToACP/SumActivityVisitDuration.php | 20 +- .../Export/LinkedToPerson/CountActivity.php | 23 +- .../CountHouseholdOnActivity.php | 23 +- .../Export/LinkedToPerson/ListActivity.php | 23 +- .../LinkedToPerson/StatActivityDuration.php | 23 +- .../Filter/ACPFilters/ActivityTypeFilter.php | 25 +- .../ACPFilters/BySocialActionFilter.php | 34 +- .../Filter/ACPFilters/BySocialIssueFilter.php | 28 +- .../Filter/ACPFilters/HasNoActivityFilter.php | 22 +- ...PeriodHavingActivityBetweenDatesFilter.php | 26 +- .../Export/Filter/ActivityDateFilter.php | 24 +- .../Export/Filter/ActivityPresenceFilter.php | 30 +- .../Export/Filter/ActivityTypeFilter.php | 28 +- .../Export/Filter/ActivityUsersFilter.php | 39 +- .../Export/Filter/ByCreatorFilter.php | 35 +- .../Export/Filter/CreatorJobFilter.php | 23 +- .../Export/Filter/CreatorScopeFilter.php | 23 +- .../Export/Filter/EmergencyFilter.php | 22 +- .../Export/Filter/LocationFilter.php | 31 +- .../Export/Filter/LocationTypeFilter.php | 32 +- .../PersonFilters/ActivityReasonFilter.php | 28 +- .../PersonHavingActivityBetweenDateFilter.php | 24 +- .../Export/Filter/PersonsFilter.php | 24 +- .../Export/Filter/SentReceivedFilter.php | 22 +- .../Export/Filter/UserFilter.php | 37 +- .../Export/Filter/UsersJobFilter.php | 27 +- .../Export/Filter/UsersScopeFilter.php | 23 +- .../ActivityPresenceRepositoryInterface.php | 3 +- .../Export/Filter/ByCreatorFilterTest.php | 6 + .../Tests/Export/Filter/UserFilterTest.php | 4 + .../Constraints/ActivityValidity.php | 2 +- .../Aggregator/ByActivityTypeAggregator.php | 21 +- .../Aggregator/ByLocationAggregator.php | 19 +- .../Export/Aggregator/ByUserJobAggregator.php | 21 +- .../Aggregator/ByUserScopeAggregator.php | 21 +- .../Export/AvgAsideActivityDuration.php | 19 +- .../src/Export/Export/CountAsideActivity.php | 19 +- .../src/Export/Export/ListAsideActivity.php | 23 +- .../Export/SumAsideActivityDuration.php | 19 +- .../Export/Filter/ByActivityTypeFilter.php | 25 +- .../src/Export/Filter/ByDateFilter.php | 22 +- .../src/Export/Filter/ByLocationFilter.php | 24 +- .../src/Export/Filter/ByUserFilter.php | 37 +- .../src/Export/Filter/ByUserJobFilter.php | 23 +- .../src/Export/Filter/ByUserScopeFilter.php | 23 +- .../Export/Export/ListAsideActivityTest.php | 4 +- .../Export/Aggregator/AgentAggregator.php | 21 +- .../Aggregator/CancelReasonAggregator.php | 21 +- .../Export/Aggregator/JobAggregator.php | 21 +- .../Export/Aggregator/LocationAggregator.php | 21 +- .../Aggregator/LocationTypeAggregator.php | 21 +- .../Export/Aggregator/MonthYearAggregator.php | 21 +- .../Export/Aggregator/ScopeAggregator.php | 21 +- .../Export/Aggregator/UrgencyAggregator.php | 21 +- .../Export/Export/CountCalendars.php | 19 +- .../Export/Export/StatCalendarAvgDuration.php | 19 +- .../Export/Export/StatCalendarSumDuration.php | 19 +- .../Export/Filter/AgentFilter.php | 29 +- .../Export/Filter/BetweenDatesFilter.php | 22 +- .../Export/Filter/CalendarRangeFilter.php | 22 +- .../Export/Filter/JobFilter.php | 23 +- .../Export/Filter/ScopeFilter.php | 23 +- ...ssociatedEntityToStoredObjectInterface.php | 6 + .../Export/Aggregator/EventDateAggregator.php | 21 +- .../Export/Aggregator/EventTypeAggregator.php | 23 +- .../Export/Aggregator/RoleAggregator.php | 23 +- .../Export/CountEventParticipations.php | 23 +- .../Export/Export/CountEvents.php | 23 +- .../Export/Filter/EventDateFilter.php | 24 +- .../Export/Filter/EventTypeFilter.php | 28 +- .../Export/Filter/RoleFilter.php | 28 +- .../ChillJobBundle/src/Export/ListCV.php | 47 +- .../ChillJobBundle/src/Export/ListFrein.php | 47 +- .../src/Export/ListProjetProfessionnel.php | 47 +- .../ChillMainBundle/ChillMainBundle.php | 2 - .../Controller/ExportController.php | 329 +++------- .../Controller/ExportGenerationController.php | 64 ++ ...erationCreateFromSavedExportController.php | 62 ++ .../Controller/ExportIndexController.php | 62 ++ .../Controller/SavedExportController.php | 186 ++++-- .../Controller/SavedExportIndexController.php | 104 ++++ .../DataFixtures/ORM/LoadUsers.php | 21 +- .../ChillMainExtension.php | 4 + .../CompilerPass/ExportsCompilerPass.php | 102 --- .../ChillMainBundle/Doctrine/DQL/Unaccent.php | 4 +- .../Entity/ExportGeneration.php | 150 +++++ .../SimpleGeographicalUnitDTO.php | 5 + .../ChillMainBundle/Entity/Regroupment.php | 18 + .../ChillMainBundle/Entity/SavedExport.php | 86 +++ .../ChillMainBundle/Entity/UserGroup.php | 35 ++ .../Export/AggregatorInterface.php | 29 +- .../RemoveExpiredExportGenerationCronJob.php | 52 ++ .../Export/DirectExportInterface.php | 10 +- .../Exception/ExportGenerationException.php | 14 + .../Export/Exception/ExportLogicException.php | 14 + .../Exception/ExportRuntimeException.php | 14 + .../UnauthorizedGenerationException.php | 20 + .../Export/ExportConfigNormalizer.php | 128 ++++ .../Export/ExportConfigProcessor.php | 49 ++ .../Export/ExportDataNormalizerTrait.php | 189 ++++++ .../Export/ExportDescriptionHelper.php | 74 +++ .../Export/ExportElementInterface.php | 6 +- .../ExportElementValidatedInterface.php | 2 +- .../ExportElementsProviderInterface.php | 4 +- .../Export/ExportFormHelper.php | 107 ++-- .../Export/ExportGenerationContext.php | 21 + .../Export/ExportGenerator.php | 273 ++++++++ .../Export/ExportInterface.php | 40 +- .../ChillMainBundle/Export/ExportManager.php | 367 ++--------- .../Export/ExportManagerAwareInterface.php | 20 + .../Export/FilterInterface.php | 28 +- .../Export/FormattedExportGeneration.php | 20 + .../Export/Formatter/CSVFormatter.php | 440 ------------- .../Export/Formatter/CSVListFormatter.php | 223 ------- .../Formatter/CSVPivotedListFormatter.php | 217 ------- .../Export/Formatter/SpreadSheetFormatter.php | 375 +++++------ .../Formatter/SpreadsheetListFormatter.php | 193 +++--- .../Export/FormatterInterface.php | 29 +- .../Export/Helper/ExportManagerAwareTrait.php | 34 + .../ChillMainBundle/Export/ListInterface.php | 8 + .../ExportRequestGenerationMessage.php | 31 + .../ExportRequestGenerationMessageHandler.php | 77 +++ .../Messenger/OnExportGenerationFails.php | 74 +++ .../RemoveExportGenerationMessage.php | 24 + .../RemoveExportGenerationMessageHandler.php | 49 ++ .../Migrator/SavedExportOptionsMigrator.php | 124 ++++ .../Export/ModifierInterface.php | 4 +- .../Export/SortExportElement.php | 14 +- .../DataMapper/ExportPickCenterDataMapper.php | 56 -- .../ChillMainBundle/Form/SavedExportType.php | 15 + .../EntityToJsonTransformer.php | 6 +- .../Form/Type/Export/AggregatorType.php | 6 +- .../Form/Type/Export/ExportType.php | 40 +- .../Form/Type/Export/FilterType.php | 2 + .../Form/Type/Export/PickCenterType.php | 25 +- .../Form/Type/PickUserOrMeDynamicType.php | 82 +++ .../ChillMainBundle/Form/UserGroupType.php | 39 +- .../Repository/ExportGenerationRepository.php | 85 +++ .../Repository/GeographicalUnitRepository.php | 20 +- .../GeographicalUnitRepositoryInterface.php | 2 + .../Repository/RegroupmentRepository.php | 3 +- .../RegroupmentRepositoryInterface.php | 34 + ...avedExportOrExportGenerationRepository.php | 32 + .../Repository/SavedExportRepository.php | 48 ++ .../SavedExportRepositoryInterface.php | 12 + .../Repository/UserJobRepository.php | 24 +- .../Repository/UserJobRepositoryInterface.php | 18 +- .../Resources/public/lib/api/export.ts | 18 + .../Resources/public/lib/api/return_path.ts | 3 + .../public/module/pick-entity/index.js | 23 +- .../public/page/export/download-export.js | 14 - .../ChillMainBundle/Resources/public/types.ts | 11 + .../public/vuejs/DownloadExport/App.vue | 141 +++++ .../public/vuejs/DownloadExport/index.ts | 15 + .../public/vuejs/PickEntity/PickEntity.vue | 202 +++--- .../public/vuejs/SavedExportButtons/App.vue | 18 + .../Component/GenerateButton.vue | 186 ++++++ .../public/vuejs/SavedExportButtons/index.ts | 13 + .../Resources/views/Dev/dev.assets.html.twig | 22 +- .../Resources/views/Export/_navbar.html.twig | 16 +- .../Resources/views/Export/layout.html.twig | 85 ++- .../views/Export/new_centers_step.html.twig | 6 +- .../views/ExportGeneration/wait.html.twig | 61 ++ .../views/SavedExport/edit.html.twig | 18 +- .../views/SavedExport/index.html.twig | 143 +++-- .../Resources/views/SavedExport/new.html.twig | 23 +- .../views/layoutWithVerticalMenu.html.twig | 6 +- .../MenuBuilder/SectionMenuBuilder.php | 4 +- .../Authorization/ChillExportVoter.php | 32 +- .../Authorization/ExportGenerationVoter.php | 32 + .../Authorization/SavedExportVoter.php | 45 +- .../ExportGenerationStoredObjectVoter.php | 43 ++ .../CenterRegroupementResolver.php | 44 ++ .../Regroupement/RegroupementFiltering.php | 37 ++ .../Service/RollingDate/RollingDate.php | 35 +- .../RollingDate/RollingDateConverter.php | 36 +- .../UserGroupRelatedToUserJobSync.php | 129 ++++ .../UserGroupRelatedToUserJobSyncCronJob.php | 42 ++ ...UserGroupRelatedToUserJobSyncInterface.php | 20 + .../Test/Export/AbstractAggregatorTest.php | 61 +- .../Test/Export/AbstractExportTest.php | 72 ++- .../Test/Export/AbstractFilterTest.php | 85 ++- ...ionCreateFromSavedExportControllerTest.php | 81 +++ .../ExportGenerationControllerTest.php | 56 ++ ...Test.php => ExportIndexControllerTest.php} | 4 +- .../Tests/Entity/Workflow/SavedExportTest.php | 52 ++ ...moveExpiredExportGenerationCronJobTest.php | 117 ++++ .../Export/ExportConfigNormalizerTest.php | 333 ++++++++++ .../Export/ExportDataNormalizerTraitTest.php | 121 ++++ .../Export/ExportDescriptionHelperTest.php | 174 ++++++ .../Tests/Export/ExportGeneratorTest.php | 413 ++++++++++++ .../Tests/Export/ExportManagerTest.php | 232 ++----- .../Formatter/SpreadsheetFormatterTest.php | 138 +++++ .../Messenger/OnExportGenerationFailsTest.php | 62 ++ ...moveExportGenerationMessageHandlerTest.php | 76 +++ .../SavedExportOptionsMigratorTest.php | 586 ++++++++++++++++++ .../Tests/Export/SortExportElementTest.php | 62 +- .../Authorization/SavedExportVoterTest.php | 145 +++++ .../CenterRegroupementResolverTest.php | 78 +++ .../RegroupementFilteringTest.php | 96 +++ .../RollingDate/RollingDateConverterTest.php | 27 +- .../Services/RollingDate/RollingDateTest.php | 45 ++ ...erGroupRelatedToUserJobSyncCronJobTest.php | 58 ++ .../ChillMainBundle/chill.api.specs.yaml | 44 ++ .../ChillMainBundle/chill.webpack.config.js | 7 +- .../ChillMainBundle/config/services.yaml | 5 +- .../config/services/export.yaml | 39 +- .../ChillMainBundle/config/services/form.yaml | 2 + .../migrations/Version20250219130532.php | 40 ++ .../migrations/Version20250313165611.php | 38 ++ .../migrations/Version20250404123326.php | 49 ++ .../migrations/Version20250410145342.php | 49 ++ .../migrations/Version20250417135712.php | 67 ++ .../migrations/Version20250425093948.php | 49 ++ .../migrations/Version20250617141354.php | 43 ++ .../translations/messages+intl-icu.fr.yaml | 1 + .../translations/messages.fr.yml | 28 +- .../Events/UserRefEventSubscriber.php | 2 +- .../ChillPersonExtension.php | 3 - .../AdministrativeLocationAggregator.php | 23 +- .../ByActionNumberAggregator.php | 19 +- .../ClosingDateAggregator.php | 25 +- .../ClosingMotiveAggregator.php | 23 +- .../ConfidentialAggregator.php | 21 +- .../CreatorJobAggregator.php | 21 +- .../DurationAggregator.php | 21 +- .../EmergencyAggregator.php | 21 +- .../EvaluationAggregator.php | 21 +- .../GeographicalUnitStatAggregator.php | 29 +- .../IntensityAggregator.php | 21 +- .../JobWorkingOnCourseAggregator.php | 21 +- .../OpeningDateAggregator.php | 25 +- .../OriginAggregator.php | 21 +- .../PersonParticipatingAggregator.php | 27 +- .../ReferrerAggregator.php | 26 +- .../ReferrerScopeAggregator.php | 26 +- .../RequestorAggregator.php | 21 +- .../ScopeAggregator.php | 21 +- .../ScopeWorkingOnCourseAggregator.php | 21 +- .../SocialActionAggregator.php | 21 +- .../SocialIssueAggregator.php | 21 +- .../StepAggregator.php | 21 +- .../UserJobAggregator.php | 21 +- .../UserWorkingOnCourseAggregator.php | 27 +- .../ByClosingMotiveAggregator.php | 27 +- .../ByDateAggregator.php | 27 +- .../ByStepAggregator.php | 27 +- .../ByEndDateAggregator.php | 21 +- .../ByMaxDateAggregator.php | 21 +- .../ByStartDateAggregator.php | 21 +- .../EvaluationTypeAggregator.php | 21 +- .../HavingEndDateAggregator.php | 21 +- .../ChildrenNumberAggregator.php | 21 +- .../CompositionAggregator.php | 21 +- .../AdministrativeStatusAggregator.php | 27 +- .../PersonAggregators/AgeAggregator.php | 29 +- .../ByHouseholdCompositionAggregator.php | 27 +- .../PersonAggregators/CenterAggregator.php | 27 +- .../CountryOfBirthAggregator.php | 29 +- .../EmploymentStatusAggregator.php | 27 +- .../PersonAggregators/GenderAggregator.php | 27 +- .../GeographicalUnitAggregator.php | 37 +- .../HouseholdPositionAggregator.php | 29 +- .../MaritalStatusAggregator.php | 27 +- .../NationalityAggregator.php | 29 +- .../PostalCodeAggregator.php | 27 +- .../ActionTypeAggregator.php | 27 +- .../CreatorAggregator.php | 21 +- .../CreatorJobAggregator.php | 21 +- .../CreatorScopeAggregator.php | 21 +- .../CurrentActionAggregator.php | 19 +- .../SocialWorkAggregators/GoalAggregator.php | 21 +- .../GoalResultAggregator.php | 21 +- .../HandlingThirdPartyAggregator.php | 27 +- .../SocialWorkAggregators/JobAggregator.php | 21 +- .../ReferrerAggregator.php | 29 +- .../ResultAggregator.php | 21 +- .../SocialWorkAggregators/ScopeAggregator.php | 21 +- ...rkPersonAssociatedOnAccompanyingPeriod.php | 19 +- ...vgDurationAPWorkPersonAssociatedOnWork.php | 19 +- .../Export/Export/CountAccompanyingCourse.php | 25 +- .../CountAccompanyingCourseStepHistory.php | 19 +- ...orkAssociatePersonOnAccompanyingPeriod.php | 19 +- ...panyingPeriodWorkAssociatePersonOnWork.php | 19 +- .../Export/Export/CountEvaluation.php | 19 +- .../Export/Export/CountHouseholdInPeriod.php | 19 +- .../Export/Export/CountPerson.php | 27 +- ...panyingPeriodWorkAssociatePersonOnWork.php | 19 +- .../CountPersonWithAccompanyingCourse.php | 19 +- .../Export/Export/ListAccompanyingPeriod.php | 28 +- ...orkAssociatePersonOnAccompanyingPeriod.php | 24 +- ...panyingPeriodWorkAssociatePersonOnWork.php | 24 +- .../Export/Export/ListEvaluation.php | 24 +- .../Export/Export/ListHouseholdInPeriod.php | 19 +- .../Export/Export/ListPerson.php | 26 +- .../Export/Export/ListPersonDuplicate.php | 32 +- .../ListPersonHavingAccompanyingPeriod.php | 25 +- ...istPersonWithAccompanyingPeriodDetails.php | 28 +- .../Export/StatAccompanyingCourseDuration.php | 19 +- .../ActiveOnDateFilter.php | 22 +- .../ActiveOneDayBetweenDatesFilter.php | 22 +- .../AdministrativeLocationFilter.php | 29 +- .../ClosingMotiveFilter.php | 29 +- .../ConfidentialFilter.php | 22 +- .../CreatorFilter.php | 37 +- .../CreatorJobFilter.php | 40 +- .../EmergencyFilter.php | 22 +- .../EvaluationFilter.php | 28 +- .../GeographicalUnitStatFilter.php | 33 +- .../HandlingThirdPartyFilter.php | 29 +- .../HasNoActionFilter.php | 20 +- .../HasNoReferrerFilter.php | 22 +- .../HasTemporaryLocationFilter.php | 22 +- ...ccompanyingPeriodInfoWithinDatesFilter.php | 20 +- .../IntensityFilter.php | 22 +- .../JobWorkingOnCourseFilter.php | 21 +- ...tAssociatedWithAReferenceAddressFilter.php | 26 +- .../OpenBetweenDatesFilter.php | 22 +- .../OriginFilter.php | 29 +- .../ReferrerFilter.php | 42 +- .../ReferrerFilterBetweenDates.php | 35 +- .../RequestorFilter.php | 22 +- .../ScopeWorkingOnCourseFilter.php | 21 +- .../SocialActionFilter.php | 40 +- .../SocialIssueFilter.php | 38 +- .../StepFilterBetweenDates.php | 26 +- .../StepFilterOnDate.php | 35 +- .../UserJobFilter.php | 37 +- .../UserScopeFilter.php | 29 +- .../UserWorkingOnCourseFilter.php | 33 +- .../ByDateFilter.php | 26 +- .../ByStepFilter.php | 26 +- .../EvaluationFilters/ByEndDateFilter.php | 20 +- .../EvaluationFilters/ByStartDateFilter.php | 20 +- .../CurrentEvaluationsFilter.php | 20 +- .../EvaluationTypeFilter.php | 24 +- .../EvaluationFilters/MaxDateFilter.php | 22 +- .../HouseholdFilters/CompositionFilter.php | 24 +- .../PersonFilters/AddressRefStatusFilter.php | 24 +- .../Export/Filter/PersonFilters/AgeFilter.php | 28 +- .../Filter/PersonFilters/BirthdateFilter.php | 28 +- .../ByHouseholdCompositionFilter.php | 28 +- .../PersonFilters/DeadOrAliveFilter.php | 26 +- .../Filter/PersonFilters/DeathdateFilter.php | 28 +- .../Filter/PersonFilters/GenderFilter.php | 69 ++- .../PersonFilters/GeographicalUnitFilter.php | 44 +- .../PersonFilters/MaritalStatusFilter.php | 33 +- .../PersonFilters/NationalityFilter.php | 35 +- .../ResidentialAddressAtThirdpartyFilter.php | 37 +- .../ResidentialAddressAtUserFilter.php | 26 +- .../WithParticipationBetweenDatesFilter.php | 24 +- .../WithoutHouseholdComposition.php | 26 +- ...WithoutParticipationBetweenDatesFilter.php | 24 +- ...yingPeriodWorkEndDateBetweenDateFilter.php | 26 +- ...ngPeriodWorkStartDateBetweenDateFilter.php | 26 +- ...odWorkWithEvaluationBetweenDatesFilter.php | 20 +- .../SocialWorkFilters/CreatorFilter.php | 36 +- .../SocialWorkFilters/CreatorJobFilter.php | 29 +- .../SocialWorkFilters/CreatorScopeFilter.php | 23 +- .../SocialWorkFilters/CurrentActionFilter.php | 20 +- .../Filter/SocialWorkFilters/JobFilter.php | 33 +- .../SocialWorkFilters/ReferrerFilter.php | 40 +- .../Filter/SocialWorkFilters/ScopeFilter.php | 33 +- .../SocialWorkTypeFilter.php | 48 +- .../FilterListAccompanyingPeriodHelper.php | 25 +- ...rListAccompanyingPeriodHelperInterface.php | 3 +- .../Form/Type/PickSocialActionType.php | 14 +- .../AccompanyingPeriod/OriginRepository.php | 15 +- .../MaritalStatusRepositoryInterface.php | 3 +- .../Authorization/AccompanyingPeriodVoter.php | 2 +- .../Templating/Entity/SocialActionRender.php | 33 +- .../ReferrerScopeAggregatorTest.php | 3 +- .../ReferrerAggregatorTest.php | 25 + .../Export/ListAccompanyingPeriodTest.php | 20 +- .../ReferrerFilterBetweenDatesTest.php | 11 +- .../ReferrerFilterTest.php | 2 + ...ilterTest.php => StepFilterOnDateTest.php} | 2 +- .../Filter/PersonFilters/GenderFilterTest.php | 41 +- .../SocialWorkFilters/CreatorFilterTest.php | 3 + .../HandlingThirdPartyFilterTest.php | 6 +- .../SocialWorkFilters/ReferrerFilterTest.php | 48 ++ ...FilterListAccompanyingPeriodHelperTest.php | 13 +- .../AccompanyingPeriodValidity.php | 2 +- .../ConfidentialCourseMustHaveReferrer.php | 5 +- .../AccompanyingPeriod/LocationValidity.php | 2 +- .../Constraints/Household/MaxHolder.php | 2 +- .../Export/Export/ReportList.php | 33 +- .../Export/Export/ReportListProvider.php | 2 +- .../Export/Filter/ReportDateFilter.php | 26 +- ...eAddNormalizationMethodsOnExportRector.php | 359 +++++++++++ ...th-no-method-get-form-default-data.php.inc | 4 +- ...-default-data-with-chained-builder.php.inc | 6 +- ...le-reuse-data-on-form-default-data.php.inc | 6 +- .../Fixture/filter-no-data-on-builder.php.inc | 6 +- ...er-reuse-data-on-form-default-data.php.inc | 6 +- ...th-no-method-get-form-default-data.php.inc | 6 +- ...th-no-method-get-form-default-data.php.inc | 4 +- ...sting-get-form-default-data-method.php.inc | 3 +- ...NormalizationMethodsOnExportRectorTest.php | 40 ++ .../Fixture/add-method-chained-field.php.inc | 120 ++++ .../Fixture/add-method-simple-field.php.inc | 126 ++++ .../add-method-with-already-trait.php.inc | 109 ++++ .../Fixture/skip-existing-method.php.inc | 55 ++ .../Fixture/skip-no-filter-class.php.inc | 9 + .../config/config.php | 14 + 447 files changed, 14134 insertions(+), 3854 deletions(-) create mode 100644 .changes/unreleased/DX-20250401-144728.yaml create mode 100644 .changes/unreleased/DX-20250407-121010.yaml create mode 100644 docs/source/development/export-sequence.puml create mode 100644 src/Bundle/ChillMainBundle/Controller/ExportGenerationController.php create mode 100644 src/Bundle/ChillMainBundle/Controller/ExportGenerationCreateFromSavedExportController.php create mode 100644 src/Bundle/ChillMainBundle/Controller/ExportIndexController.php create mode 100644 src/Bundle/ChillMainBundle/Controller/SavedExportIndexController.php delete mode 100644 src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php create mode 100644 src/Bundle/ChillMainBundle/Entity/ExportGeneration.php create mode 100644 src/Bundle/ChillMainBundle/Export/Cronjob/RemoveExpiredExportGenerationCronJob.php create mode 100644 src/Bundle/ChillMainBundle/Export/Exception/ExportGenerationException.php create mode 100644 src/Bundle/ChillMainBundle/Export/Exception/ExportLogicException.php create mode 100644 src/Bundle/ChillMainBundle/Export/Exception/ExportRuntimeException.php create mode 100644 src/Bundle/ChillMainBundle/Export/Exception/UnauthorizedGenerationException.php create mode 100644 src/Bundle/ChillMainBundle/Export/ExportConfigNormalizer.php create mode 100644 src/Bundle/ChillMainBundle/Export/ExportConfigProcessor.php create mode 100644 src/Bundle/ChillMainBundle/Export/ExportDataNormalizerTrait.php create mode 100644 src/Bundle/ChillMainBundle/Export/ExportDescriptionHelper.php create mode 100644 src/Bundle/ChillMainBundle/Export/ExportGenerationContext.php create mode 100644 src/Bundle/ChillMainBundle/Export/ExportGenerator.php create mode 100644 src/Bundle/ChillMainBundle/Export/ExportManagerAwareInterface.php create mode 100644 src/Bundle/ChillMainBundle/Export/FormattedExportGeneration.php delete mode 100644 src/Bundle/ChillMainBundle/Export/Formatter/CSVFormatter.php delete mode 100644 src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php delete mode 100644 src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php create mode 100644 src/Bundle/ChillMainBundle/Export/Helper/ExportManagerAwareTrait.php create mode 100644 src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessage.php create mode 100644 src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessageHandler.php create mode 100644 src/Bundle/ChillMainBundle/Export/Messenger/OnExportGenerationFails.php create mode 100644 src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessage.php create mode 100644 src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessageHandler.php create mode 100644 src/Bundle/ChillMainBundle/Export/Migrator/SavedExportOptionsMigrator.php delete mode 100644 src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php create mode 100644 src/Bundle/ChillMainBundle/Form/Type/PickUserOrMeDynamicType.php create mode 100644 src/Bundle/ChillMainBundle/Repository/ExportGenerationRepository.php create mode 100644 src/Bundle/ChillMainBundle/Repository/RegroupmentRepositoryInterface.php create mode 100644 src/Bundle/ChillMainBundle/Repository/SavedExportOrExportGenerationRepository.php create mode 100644 src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts create mode 100644 src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts delete mode 100644 src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/App.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/Component/GenerateButton.vue create mode 100644 src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/index.ts create mode 100644 src/Bundle/ChillMainBundle/Resources/views/ExportGeneration/wait.html.twig create mode 100644 src/Bundle/ChillMainBundle/Security/Authorization/ExportGenerationVoter.php create mode 100644 src/Bundle/ChillMainBundle/Security/Authorization/StoredObject/ExportGenerationStoredObjectVoter.php create mode 100644 src/Bundle/ChillMainBundle/Service/Regroupement/CenterRegroupementResolver.php create mode 100644 src/Bundle/ChillMainBundle/Service/Regroupement/RegroupementFiltering.php create mode 100644 src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSync.php create mode 100644 src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncCronJob.php create mode 100644 src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncInterface.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Authorization/ExportGenerationCreateFromSavedExportControllerTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Controller/ExportGenerationControllerTest.php rename src/Bundle/ChillMainBundle/Tests/Controller/{ExportControllerTest.php => ExportIndexControllerTest.php} (84%) create mode 100644 src/Bundle/ChillMainBundle/Tests/Entity/Workflow/SavedExportTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/Cronjob/RemoveExpiredExportGenerationCronJobTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/ExportDataNormalizerTraitTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/ExportDescriptionHelperTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/Formatter/SpreadsheetFormatterTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/Messenger/OnExportGenerationFailsTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/Messenger/RemoveExportGenerationMessageHandlerTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Export/Migrator/SavedExportOptionsMigratorTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Security/Authorization/SavedExportVoterTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Services/Regroupement/CenterRegroupementResolverTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Services/Regroupement/RegroupementFilteringTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateTest.php create mode 100644 src/Bundle/ChillMainBundle/Tests/Services/UserGroup/UserGroupRelatedToUserJobSyncCronJobTest.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20250219130532.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20250313165611.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20250404123326.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20250410145342.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20250417135712.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20250425093948.php create mode 100644 src/Bundle/ChillMainBundle/migrations/Version20250617141354.php rename src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/{StepFilterTest.php => StepFilterOnDateTest.php} (96%) create mode 100644 utils/rector/src/Rector/ChillBundleAddNormalizationMethodsOnExportRector.php create mode 100644 utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/ChillBundleAddNormalizationMethodsOnExportRectorTest.php create mode 100644 utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-chained-field.php.inc create mode 100644 utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-simple-field.php.inc create mode 100644 utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-with-already-trait.php.inc create mode 100644 utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/skip-existing-method.php.inc create mode 100644 utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/skip-no-filter-class.php.inc create mode 100644 utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/config/config.php diff --git a/.changes/unreleased/DX-20250401-144728.yaml b/.changes/unreleased/DX-20250401-144728.yaml new file mode 100644 index 000000000..a08ba1760 --- /dev/null +++ b/.changes/unreleased/DX-20250401-144728.yaml @@ -0,0 +1,6 @@ +kind: DX +body: Allow TranslatableMessage in flash messages +time: 2025-04-01T14:47:28.814268801+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/.changes/unreleased/DX-20250407-121010.yaml b/.changes/unreleased/DX-20250407-121010.yaml new file mode 100644 index 000000000..1f8a0496a --- /dev/null +++ b/.changes/unreleased/DX-20250407-121010.yaml @@ -0,0 +1,71 @@ +kind: DX +body: | + Rewrite exports to run them asynchronously + +changelog: | + - Add new methods to serialize data using the rector rule + - Remove all references to the Request in filters, aggregators, filters. Actually, the most frequent occurence is `$security->getUser()`. + - Refactor manually the initializeQuery method + - Remove the injection of ExportManager into the constructor of each export element: + + ```diff + + - class MyFormatter implements FormatterInterface + + class MyFormatter implements FormatterInterface, \Chill\MainBundle\Export\ExportManagerAwareInterface + { + + use \Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; + + - public function __construct(private ExportManager $exportmanager) {} + + public function MyMethod(): void + { + - $this->exportManager->getFilter('alias'); + + $this->getExportManager()->getFilter('alias'); + } + } + ``` + - configure messenger to handle export in a queue: + + ```diff + # config/packages/messenger.yaml + framework: + messenger: + routing: + + 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority + ``` + + - add missing methods to exports, aggregators, filters, formatter: + + ```php + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + ``` + + There are rector rules to generate those methods: + + - `Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector` + + See: + + ```php + // upgrade chill exports + $rectorConfig->rules([\Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class]); + ``` + + This rule will create most of the work necessary, but some manuals changes are still necessary: + + - we must set manually the correct repository for method `denormalizeDoctrineEntity`; + - when the form data contains some entities, and the form type is not one of EntityType::class, PickUserDynamicType::class, PickUserLocationType::class, PickThirdpartyDynamicType::class, Select2CountryType::class, then we must handle the normalization manually (using the `\Chill\MainBundle\Export\ExportDataNormalizerTrait`) + + + + + + + + + time: 2025-04-07T12:10:10.682561327+02:00 +custom: + Issue: "" + SchemaChange: Add columns or tables diff --git a/config/packages/chill.yaml b/config/packages/chill.yaml index 76578a2c7..6ed6b6984 100644 --- a/config/packages/chill.yaml +++ b/config/packages/chill.yaml @@ -17,6 +17,7 @@ chill_main: acl: form_show_scopes: true form_show_centers: true + filter_stats_by_center: true access_global_history: false access_user_change_password: true access_permissions_group_list: true diff --git a/config/packages/messenger.yaml b/config/packages/messenger.yaml index b460fd60c..39eab3875 100644 --- a/config/packages/messenger.yaml +++ b/config/packages/messenger.yaml @@ -5,7 +5,6 @@ framework: # Uncomment this (and the failed transport below) to send failed messages to this transport for later handling. failure_transport: failed - transports: # those transports are added by chill-bundles recipes sync: sync:// @@ -19,7 +18,9 @@ framework: async: ~ auto_setup: true - priority: '%env(MESSENGER_TRANSPORT_DSN)%/priority' + priority: + dsn: '%env(MESSENGER_TRANSPORT_DSN)%/priority' + # end of transports added by chill-bundles recipes # https://symfony.com/doc/current/messenger.html#transport-configuration failed: 'doctrine://default?queue_name=failed' @@ -61,6 +62,8 @@ framework: 'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority 'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async 'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async + 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority + 'Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessage': async # end of routes added by chill-bundles recipes # Route your messages to the transports # 'App\Message\YourMessage': async diff --git a/docs/source/_static/code/exports/BirthdateFilter.php b/docs/source/_static/code/exports/BirthdateFilter.php index fca768ab3..872d48001 100644 --- a/docs/source/_static/code/exports/BirthdateFilter.php +++ b/docs/source/_static/code/exports/BirthdateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use DateTime; use Doctrine\ORM\Query\Expr; @@ -20,6 +21,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class BirthdateFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; // add specific role for this filter public function addRole(): ?string { @@ -28,7 +30,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac } // here, we alter the query created by Export - public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data) + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); // we create the clause here @@ -52,13 +54,13 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac } // we give information on which type of export this filter applies - public function applyOn() + public function applyOn(): string { return 'person'; } // we build a form to collect some parameters from the users - public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) + public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder): void { $builder->add('date_from', DateType::class, [ 'label' => 'Born after this date', @@ -74,6 +76,18 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac 'format' => 'dd-MM-yyyy', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + public function normalizeFormData(array $formData): array + { + return ['date_from' => $this->normalizeDate($formData['date_from']), 'date_to' => $this->normalizeDate($formData['date_to'])]; + } + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => $this->denormalizeDate($formData['date_from']), 'date_to' => $this->denormalizeDate($formData['date_to'])]; + } public function getFormDefaultData(): array { return ['date_from' => new DateTime(), 'date_to' => new DateTime()]; @@ -81,7 +95,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac // here, we create a simple string which will describe the action of // the filter in the Response - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s birtdate: ' . 'between %date_from% and %date_to%', [ @@ -90,7 +104,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s birthdate'; } @@ -99,7 +113,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac // is executed here. This function is added by the interface // `ExportElementValidatedInterface`, and can be ignore if there is // no need for a validation - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { $date_from = $data['date_from']; $date_to = $data['date_to']; diff --git a/docs/source/_static/code/exports/CountPerson.php b/docs/source/_static/code/exports/CountPerson.php index a0f6931ac..ce4fb8017 100644 --- a/docs/source/_static/code/exports/CountPerson.php +++ b/docs/source/_static/code/exports/CountPerson.php @@ -36,6 +36,18 @@ class CountPerson implements ExportInterface { // this export does not add any form } + 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 []; @@ -60,29 +72,29 @@ class CountPerson implements ExportInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { // this array match the result keys in the query. We have only // one column. return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count peoples'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // we gather all center the user choose. $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/docs/source/development/export-sequence.puml b/docs/source/development/export-sequence.puml new file mode 100644 index 000000000..0d0c77c14 --- /dev/null +++ b/docs/source/development/export-sequence.puml @@ -0,0 +1,84 @@ +@startuml +'https://plantuml.com/sequence-diagram + +autonumber + +User -> ExportController: configure export using form +activate ExportController +ExportController -> ExportForm: build form +activate ExportForm + +loop for every ExportElement (Filter, Aggregator) + ExportForm -> ExportElement: `buildForm` + activate ExportElement + ExportElement -> ExportForm: add form to builders + deactivate ExportElement +end + +ExportForm -> ExportController +deactivate ExportForm + +ExportController -> User: show form +deactivate ExportController + +note left of User: Configure the export:\ncheck filters, aggregators, … + +User -> ExportController: post configuration of the export +activate ExportController + +ExportController -> ExportForm: `getData` +activate ExportForm +ExportForm -> ExportController: return data: list of entities, etc. +deactivate ExportForm + +loop for every ExportElement (Filter, Aggregator) + ExportController -> ExportElement: serializeData (data) + activate ExportElement + ExportElement -> ExportController: return serializedData (simple array with string, int, …) + deactivate ExportElement +end + +ExportController -> Database: `INSERT INTO RequestGeneration_table` (insert new entity) +ExportController -> MessageQueue: warn about a new request +activate MessageQueue +ExportController -> User: "ok, generation is in process" +deactivate ExportController + +note left of User: The user see a waiting screen + +MessageQueue -> MessengerConsumer: forward the message to the MessengerConsumer +deactivate MessageQueue +activate MessengerConsumer +MessengerConsumer -> Database: `SELECT * FROM RequestGeneration_table WHERE id = %s` +activate Database +Database -> MessengerConsumer: return RequestGeneration with serializedData +deactivate Database + +loop for every ExportElement (Filter, Aggregator) + MessengerConsumer -> ExportElement: deserializeData + activate ExportElement + ExportElement -> MessengerConsumer: return data (list of entities, etc.) from the serialized array + deactivate ExportElement + MessengerConsumer -> ExportElement: alter the sql query (`ExportElement::alterQuery`) + activate ExportElement + ExportElement -> MessengerConsumer: return the query with WHERE and GROUP BY clauses + deactivate ExportElement +end + +MessengerConsumer -> MessengerConsumer: prepare the export +MessengerConsumer -> MessengerConsumer: save the export as a stored object +MessengerConsumer -> Database: `UPDATE RequestGeneration_table SET ready = true` +deactivate MessengerConsumer + +User -> ExportController: pull every 5s to know if the export is generated +activate ExportController +ExportController -> User: warn the export is generated +deactivate ExportController + +User -> ExportController: download the export from object storage + + + + + +@enduml diff --git a/docs/source/development/messages-to-users.rst b/docs/source/development/messages-to-users.rst index a935ee855..244638d0b 100644 --- a/docs/source/development/messages-to-users.rst +++ b/docs/source/development/messages-to-users.rst @@ -15,24 +15,31 @@ Messages to users, flashbags and buttons Flashbags ========== -The four following levels are defined : +The four following levels are defined : +-----------+----------------------------------------------------------------------------------------------+ |Key |Intent | +===========+==============================================================================================+ -|alert |A message not linked with the user action, but which should require an action or a | -| |correction. | -+-----------+----------------------------------------------------------------------------------------------+ |success |The user action succeeds. | +-----------+----------------------------------------------------------------------------------------------+ |notice |A simple message to give information to the user. The message may be linked or not linked with| | |the user action. | +-----------+----------------------------------------------------------------------------------------------+ -|warning |A message linked with an action, the user should correct. | -+-----------+----------------------------------------------------------------------------------------------+ |error |The user's action failed: he must correct something to process the action. | +-----------+----------------------------------------------------------------------------------------------+ +We can use :code:`TranslatableMessage` (and other :code:`TranslatableMessageInterface` instances) into the controller: + +.. code-block:: php + + // in a controller action: + if (($session = $request->getSession()) instanceof Session) { + $session->getFlashBag()->add( + 'success', + new TranslatableMessage('saved_export.Saved export is saved!') + ); + } + .. seealso:: `Flash Messages on Symfony documentation `_ @@ -66,7 +73,7 @@ To add the action on button, use them as class along with ``sc-button`` : | | | - Submitting this form will remove the entity | +-----------+----------------+------------------------------------------------------------------------------+ | Edit | ``bt-edit`` or | Link to a form to edit an entity | -| | ``bt-update`` | | +| | ``bt-update`` | | +-----------+----------------+------------------------------------------------------------------------------+ | Save | ``bt-save`` | Submitting this form will save change on the entity | +-----------+----------------+------------------------------------------------------------------------------+ diff --git a/package.json b/package.json index fc50ba29c..73fa14d86 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@hotwired/stimulus": "^3.0.0", "@luminateone/eslint-baseline": "^1.0.9", "@symfony/stimulus-bridge": "^3.2.0", + "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/webpack-encore": "^4.1.0", "@tsconfig/node20": "^20.1.4", "@types/dompurify": "^3.0.5", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7c969f941..1a66e5be2 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -2154,11 +2154,6 @@ parameters: count: 1 path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php - - - message: "#^Instanceof between string and DateTimeInterface will always evaluate to false\\.$#" - count: 1 - path: src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php - - message: "#^PHPDoc tag @var for property Chill\\\\MainBundle\\\\Export\\\\Helper\\\\ExportAddressHelper\\:\\:\\$unitNamesKeysCache contains unresolvable type\\.$#" count: 1 diff --git a/phpstan.dist.neon b/phpstan.dist.neon index feb453c81..60c36771e 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -3,7 +3,7 @@ parameters: paths: - src/ - utils/ - tmpDir: .cache/ + tmpDir: var/cache/phpstan reportUnmatchedIgnoredErrors: false excludePaths: - .php_cs* diff --git a/phpunit.rector.xml b/phpunit.rector.xml index 25e0d25aa..73fbee8e5 100644 --- a/phpunit.rector.xml +++ b/phpunit.rector.xml @@ -1,13 +1,24 @@ - - - - utils/rector/tests - - - - - utils/rector/src - - + + + + utils/rector/tests + + + + + utils/rector/src + + diff --git a/rector.php b/rector.php index 3923d37e4..2b8180208 100644 --- a/rector.php +++ b/rector.php @@ -18,14 +18,15 @@ return static function (RectorConfig $rectorConfig): void { $rectorConfig->paths([ __DIR__ . '/docs', __DIR__ . '/src', + __DIR__ . '/rector.php', ]); $rectorConfig->skip([ \Rector\Php55\Rector\String_\StringClassNameToClassConstantRector::class => __DIR__ . 'src/Bundle/ChillMainBundle/Service/Notifier/LegacyOvhCloudFactory.php' ]); - $rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/test/App_KernelTestDebugContainer.xml '); - $rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php'); + //$rectorConfig->symfonyContainerXml(__DIR__ . '/var/cache/dev/test/App_KernelTestDebugContainer.xml '); + //$rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php'); //$rectorConfig->cacheClass(\Rector\Caching\ValueObject\Storage\FileCacheStorage::class); //$rectorConfig->cacheDirectory(__DIR__ . '/.cache/rector'); @@ -39,16 +40,11 @@ return static function (RectorConfig $rectorConfig): void { // part of the symfony 54 rules $rectorConfig->rule(\Rector\Symfony\Symfony53\Rector\StaticPropertyFetch\KernelTestCaseContainerPropertyDeprecationRector::class); $rectorConfig->rule(\Rector\Symfony\Symfony60\Rector\MethodCall\GetHelperControllerToServiceRector::class); - $rectorConfig->disableParallel(); + //$rectorConfig->disableParallel(); //define sets of rules $rectorConfig->sets([ LevelSetList::UP_TO_PHP_82, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_40, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_41, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_42, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_43, - \Rector\Symfony\Set\SymfonySetList::SYMFONY_44, \Rector\Doctrine\Set\DoctrineSetList::DOCTRINE_CODE_QUALITY, \Rector\PHPUnit\Set\PHPUnitSetList::PHPUNIT_90, ]); @@ -66,11 +62,6 @@ return static function (RectorConfig $rectorConfig): void { // some routes are added twice if it remains activated // $rectorConfig->rule(\Rector\Symfony\Configs\Rector\ClassMethod\AddRouteAnnotationRector::class); - // chill rules - $rectorConfig->rules([ - \Chill\Utils\Rector\Rector\ChillBundleMakeDataProviderStaticForAbstractExportTestRector::class, - ]); - // skip some path... $rectorConfig->skip([ // waiting for fixing this bug: https://github.com/rectorphp/rector-doctrine/issues/342 @@ -94,4 +85,6 @@ return static function (RectorConfig $rectorConfig): void { new \Rector\Php80\ValueObject\AnnotationToAttribute('Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency'), new \Rector\Php80\ValueObject\AnnotationToAttribute('Chill\MainBundle\Workflow\Validator\EntityWorkflowCreation'), ]); + + }; diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php index e9e8fb474..e304a9d7f 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php @@ -24,7 +24,7 @@ class ByActivityNumberAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('(SELECT COUNT(activity.id) FROM '.Activity::class.' activity WHERE activity.accompanyingPeriod = acp) AS activity_by_number_aggregator') @@ -41,12 +41,27 @@ class ByActivityNumberAggregator implements AggregatorInterface // 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) + public function getLabels($key, array $values, $data): callable { return static function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php index 0097854d3..79af0d8fe 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityTypeAggregator.php @@ -33,7 +33,7 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface private TranslatableStringHelperInterface $translatableStringHelper, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('after_date', PickRollingDateType::class, [ @@ -46,6 +46,21 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['after_date' => $formData['after_date']->normalize(), 'before_date' => $formData['before_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['after_date' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['after_date']), 'before_date' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['before_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -54,7 +69,7 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -69,12 +84,12 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_actype_id']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.acp.by_activity_type.title'; } @@ -84,7 +99,7 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -115,7 +130,7 @@ final readonly class ByActivityTypeAggregator implements AggregatorInterface ->addGroupBy("{$p}_actype_id"); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php index 9282f92e4..8585f79e5 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php @@ -27,7 +27,7 @@ class BySocialActionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actsocialaction', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.socialActions', 'actsocialaction'); @@ -42,17 +42,32 @@ class BySocialActionAggregator implements AggregatorInterface return Declarations::ACTIVITY_ACP; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php index bbdadf4d6..8932abaf0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php @@ -27,7 +27,7 @@ class BySocialIssueAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actsocialissue', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.socialIssues', 'actsocialissue'); @@ -42,17 +42,32 @@ class BySocialIssueAggregator implements AggregatorInterface return Declarations::ACTIVITY_ACP; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php index ab07afca7..007b42fdb 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityLocationAggregator.php @@ -25,7 +25,7 @@ final readonly class ActivityLocationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actloc', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.location', 'actloc'); @@ -39,17 +39,32 @@ final readonly class ActivityLocationAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form required for this aggregator } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -69,7 +84,7 @@ final readonly class ActivityLocationAggregator implements AggregatorInterface return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.activity.by_location.Title'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php index 56a6d0249..1a161840a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityPresenceAggregator.php @@ -22,14 +22,29 @@ final readonly class ActivityPresenceAggregator implements AggregatorInterface { public function __construct(private ActivityPresenceRepositoryInterface $activityPresenceRepository, private TranslatableStringHelperInterface $translatableStringHelper) {} - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -44,7 +59,7 @@ final readonly class ActivityPresenceAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['activity_presence_aggregator_attendee']; } @@ -59,13 +74,13 @@ final readonly class ActivityPresenceAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(activity.attendee) AS activity_presence_aggregator_attendee'); $qb->addGroupBy('activity_presence_aggregator_attendee'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php index ad3937a6e..58b8788cc 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityReasonAggregator.php @@ -36,7 +36,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // add select element if ('reasons' === $data['level']) { @@ -72,7 +72,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add( 'level', @@ -89,6 +89,21 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali ); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['level' => $formData['level']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['level' => $formData['level']]; + } + public function getFormDefaultData(): array { return [ @@ -96,7 +111,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali ]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) use ($data) { if ('_header' === $value) { @@ -125,7 +140,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { // add select element if ('reasons' === $data['level']) { @@ -139,12 +154,12 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali throw new \RuntimeException('The data provided are not recognised.'); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by activity reason'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['level']) { $context diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php index dbdc982b6..b7cb73021 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php @@ -29,7 +29,7 @@ class ActivityTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acttype', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.activityType', 'acttype'); @@ -44,17 +44,32 @@ class ActivityTypeAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form required for this aggregator } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -74,7 +89,7 @@ class ActivityTypeAggregator implements AggregatorInterface return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by activity type'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php index 61452af22..6a8f1875b 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php @@ -29,7 +29,7 @@ class ActivityUserAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // add select element $qb->addSelect(sprintf('IDENTITY(activity.user) AS %s', self::KEY)); @@ -43,17 +43,32 @@ class ActivityUserAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add } + 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, $values, $data): \Closure + public function getLabels($key, $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -70,7 +85,7 @@ class ActivityUserAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::KEY]; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php index cc4ab9d14..62594b0c0 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php @@ -27,7 +27,7 @@ class ActivityUsersAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actusers', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.users', 'actusers'); @@ -43,17 +43,32 @@ class ActivityUsersAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add on the form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -70,12 +85,12 @@ class ActivityUsersAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['activity_users_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by activity users'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php index b90c2b0cc..14ffff521 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php @@ -34,7 +34,7 @@ class ActivityUsersJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class ActivityUsersJobAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php index a93a2a875..260f4cd10 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php @@ -34,7 +34,7 @@ class ActivityUsersScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class ActivityUsersScopeAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php index 09bdab89e..8da8019e9 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByCreatorAggregator.php @@ -27,7 +27,7 @@ class ByCreatorAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(activity.createdBy) AS creator_aggregator'); $qb->addGroupBy('creator_aggregator'); @@ -38,17 +38,32 @@ class ByCreatorAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php index 13224bade..ed4cc699e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ByThirdpartyAggregator.php @@ -27,7 +27,7 @@ class ByThirdpartyAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acttparty', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.thirdParties', 'acttparty'); @@ -42,17 +42,32 @@ class ByThirdpartyAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php index ef8dd8c50..d42fd48b4 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorJobAggregator.php @@ -34,7 +34,7 @@ class CreatorJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class CreatorJobAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php index ddf90dfbc..b50457a02 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/CreatorScopeAggregator.php @@ -34,7 +34,7 @@ class CreatorScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class CreatorScopeAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php index 9d9e5ed61..58321383e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/DateAggregator.php @@ -32,7 +32,7 @@ class DateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $order = null; @@ -67,7 +67,7 @@ class DateAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -76,12 +76,27 @@ class DateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value) use ($data): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php index da2d74f64..3497313df 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/LocationTypeAggregator.php @@ -27,7 +27,7 @@ class LocationTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actloc', $qb->getAllAliases(), true)) { $qb->leftJoin('activity.location', 'actloc'); @@ -42,17 +42,32 @@ class LocationTypeAggregator implements AggregatorInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/HouseholdAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/HouseholdAggregator.php index ab32c278e..8eb3054c7 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/HouseholdAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/HouseholdAggregator.php @@ -24,17 +24,32 @@ final readonly class HouseholdAggregator implements AggregatorInterface { public function __construct(private HouseholdRepository $householdRepository) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string|int { if ('_header' === $value) { @@ -49,12 +64,12 @@ final readonly class HouseholdAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['activity_household_agg']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_household.title'; } @@ -64,7 +79,7 @@ final readonly class HouseholdAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->join( HouseholdMember::class, @@ -92,7 +107,7 @@ final readonly class HouseholdAggregator implements AggregatorInterface ->addGroupBy('activity_household_agg'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY_PERSON; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php index c7a3bd6b5..cff7e26d2 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/PersonAggregator.php @@ -21,27 +21,42 @@ final readonly class PersonAggregator implements AggregatorInterface { public function __construct(private LabelPersonHelper $labelPersonHelper) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.person.by_person.person'); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['activity_by_person_agg']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_person.title'; } @@ -51,14 +66,14 @@ final readonly class PersonAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('IDENTITY(activity.person) AS activity_by_person_agg') ->addGroupBy('activity_by_person_agg'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY_PERSON; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php index 8459741c5..b9189001c 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonsAggregator.php @@ -27,17 +27,32 @@ final readonly class PersonsAggregator implements AggregatorInterface public function __construct(private LabelPersonHelper $labelPersonHelper) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { if ($key !== self::PREFIX.'_pid') { throw new \UnexpectedValueException('this key should not be handled: '.$key); @@ -46,12 +61,12 @@ final readonly class PersonsAggregator implements AggregatorInterface return $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.activity.by_persons.Persons'); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_pid']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.activity.by_persons.Group activity by persons'; } @@ -61,7 +76,7 @@ final readonly class PersonsAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -71,7 +86,7 @@ final readonly class PersonsAggregator implements AggregatorInterface ->addGroupBy("{$p}_pid"); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php index 774968544..5b17e52ee 100644 --- a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php +++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php @@ -26,7 +26,7 @@ class SentReceivedAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('activity.sentReceived AS activity_sentreceived_aggregator') ->addGroupBy('activity_sentreceived_aggregator'); @@ -42,6 +42,21 @@ class SentReceivedAggregator implements AggregatorInterface // 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 []; diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php index d8b204e8c..756f81d57 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Repository\ActivityRepository; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -38,6 +39,21 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -72,7 +88,7 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface return ['export_avg_activity_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -87,7 +103,7 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php index acfb073f5..5240dec3a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -46,6 +47,21 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac // TODO: Implement buildForm() method. } + 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 []; @@ -80,7 +96,7 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac return ['export_avg_activity_visit_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -95,7 +111,7 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php index dfd5d966f..a1f0a30d8 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php @@ -43,6 +43,21 @@ class CountActivity implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -77,7 +92,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -92,7 +107,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php index 41c05494c..fa3598258 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountHouseholdOnActivity.php @@ -44,6 +44,21 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -78,7 +93,7 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php index c49b9d4ad..0ce5f722e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountPersonsOnActivity.php @@ -43,6 +43,21 @@ class CountPersonsOnActivity implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -77,7 +92,7 @@ class CountPersonsOnActivity implements ExportInterface, GroupedExportInterface return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -92,7 +107,7 @@ class CountPersonsOnActivity implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php index aab2d7db8..fa09380ff 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php @@ -17,6 +17,7 @@ use Chill\ActivityBundle\Export\Export\ListActivityHelper; use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\Helper\TranslatableStringExportLabelHelper; use Chill\MainBundle\Export\ListInterface; @@ -38,6 +39,21 @@ final readonly class ListActivity implements ListInterface, GroupedExportInterfa $this->helper->buildForm($builder); } + 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 []; @@ -73,7 +89,7 @@ final readonly class ListActivity implements ListInterface, GroupedExportInterfa }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return array_merge( @@ -85,25 +101,23 @@ final readonly class ListActivity implements ListInterface, GroupedExportInterfa ); } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $this->helper->getResult($query, $data); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return ListActivityHelper::MSG_KEY.'List activity linked to a course'; } - public function getType() + public function getType(): string { return $this->helper->getType(); } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { - $centers = array_map(static fn ($el) => $el['center'], $acl); - $qb = $this->entityManager->createQueryBuilder(); $qb @@ -114,7 +128,7 @@ final readonly class ListActivity implements ListInterface, GroupedExportInterfa ->leftJoin('acppart.person', 'person') ->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL'); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); $qb // some grouping are necessary diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php index 159fcf78d..151018907 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php @@ -40,9 +40,21 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getNormalizationVersion(): int { - // TODO: Implement buildForm() method. + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; } public function getFormDefaultData(): array @@ -79,7 +91,7 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface return ['export_sum_activity_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -94,7 +106,7 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php index 27502e2b0..735d55182 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php @@ -40,9 +40,21 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder) {} + + public function getNormalizationVersion(): int { - // TODO: Implement buildForm() method. + return 1; + } + + public function normalizeFormData(array $formData): array + { + return []; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return []; } public function getFormDefaultData(): array @@ -79,7 +91,7 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac return ['export_sum_activity_visit_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -94,7 +106,7 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php index e0a95b1f5..0239aa481 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php @@ -35,6 +35,21 @@ class CountActivity implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -64,17 +79,17 @@ class CountActivity implements ExportInterface, GroupedExportInterface return static fn ($value) => '_header' === $value ? 'Number of activities linked to a person' : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count activities linked to a person'; } @@ -84,7 +99,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php index 31111a083..95a5aff2d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountHouseholdOnActivity.php @@ -36,6 +36,21 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -65,17 +80,17 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe return static fn ($value) => '_header' === $value ? 'export.export.count_household_on_activity_person.header' : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_count_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.export.count_household_on_activity_person.title'; } @@ -85,7 +100,7 @@ final readonly class CountHouseholdOnActivity implements ExportInterface, Groupe return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php index de1713542..295b8e3d3 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php @@ -78,6 +78,21 @@ class ListActivity implements ListInterface, GroupedExportInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields']]; + } + public function getFormDefaultData(): array { return []; @@ -167,17 +182,17 @@ class ListActivity implements ListInterface, GroupedExportInterface } } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return $data['fields']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'List activity linked to a person'; } @@ -187,7 +202,7 @@ class ListActivity implements ListInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php index 547dbf6bc..e971fe129 100644 --- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php +++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php @@ -49,6 +49,21 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -84,17 +99,17 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface return static fn (string $value) => '_header' === $value ? $header : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_stat_activity']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { if (self::SUM === $this->action) { return 'Sum activity linked to a person duration'; @@ -108,7 +123,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface return Declarations::ACTIVITY; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map( static fn (array $el): Center => $el['center'], diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php index 7c6eb0bb2..5953f95d8 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class ActivityTypeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const BASE_EXISTS = 'SELECT 1 FROM '.Activity::class.' act_type_filter_activity WHERE act_type_filter_activity.accompanyingPeriod = acp'; public function __construct( @@ -38,7 +40,7 @@ final readonly class ActivityTypeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $exists = self::BASE_EXISTS; @@ -62,12 +64,12 @@ final readonly class ActivityTypeFilter implements FilterInterface } } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_activitytypes', EntityType::class, [ 'class' => ActivityType::class, @@ -92,6 +94,21 @@ final readonly class ActivityTypeFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_activitytypes' => $this->normalizeDoctrineEntity($formData['accepted_activitytypes']), 'date_after' => $formData['date_after']?->normalize(), 'date_before' => $formData['date_before']?->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_activitytypes' => $this->denormalizeDoctrineEntity($formData['accepted_activitytypes'], $this->activityTypeRepository), 'date_after' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['date_after']), 'date_before' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['date_before'])]; + } + public function getFormDefaultData(): array { return [ @@ -101,7 +118,7 @@ final readonly class ActivityTypeFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $types = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php index 13349baa5..37011fb25 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php @@ -12,23 +12,28 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\SocialWork\SocialAction; use Chill\PersonBundle\Form\Type\PickSocialActionType; +use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class BySocialActionFilter implements FilterInterface { - public function __construct(private readonly SocialActionRender $actionRender) {} + use ExportDataNormalizerTrait; + + public function __construct(private readonly SocialActionRender $actionRender, private readonly SocialActionRepository $socialActionRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actsocialaction', $qb->getAllAliases(), true)) { $qb->join('activity.socialActions', 'actsocialaction'); @@ -48,19 +53,36 @@ class BySocialActionFilter implements FilterInterface return Declarations::ACTIVITY_ACP; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_socialactions', PickSocialActionType::class, [ 'multiple' => true, ]); } - public function getFormDefaultData(): array + public function getNormalizationVersion(): int { - return []; + return 1; } - public function describeAction($data, $format = 'string'): array + public function normalizeFormData(array $formData): array + { + return ['accepted_socialactions' => $this->normalizeDoctrineEntity($formData['accepted_socialactions'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_socialactions' => $this->denormalizeDoctrineEntity($formData['accepted_socialactions'], $this->socialActionRepository)]; + } + + public function getFormDefaultData(): array + { + return [ + 'accepted_socialactions' => [], + ]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $actions = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php index bef40290e..15a3e7e2e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php @@ -12,23 +12,28 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Form\Type\PickSocialIssueType; +use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class BySocialIssueFilter implements FilterInterface { - public function __construct(private readonly SocialIssueRender $issueRender) {} + use ExportDataNormalizerTrait; + + public function __construct(private readonly SocialIssueRender $issueRender, private readonly SocialIssueRepository $issueRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actsocialissue', $qb->getAllAliases(), true)) { $qb->join('activity.socialIssues', 'actsocialissue'); @@ -48,19 +53,34 @@ class BySocialIssueFilter implements FilterInterface return Declarations::ACTIVITY_ACP; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_socialissues', PickSocialIssueType::class, [ 'multiple' => true, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_socialissues' => $this->normalizeDoctrineEntity($formData['accepted_socialissues'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_socialissues' => $this->denormalizeDoctrineEntity($formData['accepted_socialissues'], $this->issueRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $issues = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php index afd708d33..15c517e51 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Entity\Activity; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; @@ -27,7 +28,7 @@ class HasNoActivityFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere(' @@ -43,17 +44,32 @@ class HasNoActivityFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + 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 describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered acp which has no activities', []]; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php index 2d3282ad1..da2dfb3c3 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/PeriodHavingActivityBetweenDatesFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter\ACPFilters; use Chill\ActivityBundle\Entity\Activity; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -25,12 +26,12 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt private RollingDateConverterInterface $rollingDateConverter, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.activity.course_having_activity_between_date.Title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -41,6 +42,21 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -49,7 +65,7 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.activity.course_having_activity_between_date.Only course having an activity between from and to', @@ -65,7 +81,7 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $alias = 'act_period_having_act_betw_date_alias'; $from = 'act_period_having_act_betw_date_start'; @@ -82,7 +98,7 @@ final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInt ->setParameter($to, $this->rollingDateConverter->convert($data['end_date'])); } - public function applyOn() + public function applyOn(): string { return \Chill\PersonBundle\Export\Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php index e3d7796d3..bc254abae 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -30,7 +31,7 @@ class ActivityDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -61,7 +62,7 @@ class ActivityDateFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, [ @@ -72,12 +73,27 @@ class ActivityDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'Filtered by date of activity: only between %date_from% and %date_to%', @@ -88,7 +104,7 @@ class ActivityDateFilter implements FilterInterface ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filtered by date activity'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php index c9eab9066..d431fa260 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityPresenceFilter.php @@ -13,6 +13,8 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Entity\ActivityPresence; use Chill\ActivityBundle\Export\Declarations; +use Chill\ActivityBundle\Repository\ActivityPresenceRepositoryInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\Common\Collections\Collection; @@ -23,17 +25,20 @@ use Symfony\Contracts\Translation\TranslatorInterface; final readonly class ActivityPresenceFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private TranslatableStringHelperInterface $translatableStringHelper, private TranslatorInterface $translator, + private ActivityPresenceRepositoryInterface $activityPresenceRepository, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.activity.by_presence.Filter activity by activity presence'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('presences', EntityType::class, [ 'class' => ActivityPresence::class, @@ -45,12 +50,27 @@ final readonly class ActivityPresenceFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['presences' => $this->normalizeDoctrineEntity($formData['presences'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['presences' => $this->denormalizeDoctrineEntity($formData['presences'], $this->activityPresenceRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $presences = array_map( fn (ActivityPresence $presence) => $this->translatableStringHelper->localize($presence->getName()), @@ -68,14 +88,14 @@ final readonly class ActivityPresenceFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere('activity.attendee IN (:activity_presence_filter_presences)') ->setParameter('activity_presence_filter_presences', $data['presences']); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php index 96e42c3e6..99d10cf4d 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\ActivityType; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\QueryBuilder; @@ -24,6 +25,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( protected TranslatableStringHelperInterface $translatableStringHelper, protected ActivityTypeRepositoryInterface $activityTypeRepository, @@ -34,7 +37,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('activity.activityType', ':selected_activity_types'); @@ -47,7 +50,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('types', EntityType::class, [ 'choices' => $this->activityTypeRepository->findAllActive(), @@ -70,12 +73,27 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['types' => $this->normalizeDoctrineEntity($formData['types'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['types' => $this->denormalizeDoctrineEntity($formData['types'], $this->activityTypeRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { // collect all the reasons'name used in this filter in one array $reasonsNames = array_map( @@ -88,12 +106,12 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by activity type'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['types'] || 0 === \count($data['types'])) { $context diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php index 56285c026..a3c70f9fe 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php @@ -12,26 +12,30 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class ActivityUsersFilter implements FilterInterface +final readonly class ActivityUsersFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private UserRender $userRender, private UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $orX = $qb->expr()->orX(); - foreach ($data['accepted_users'] as $key => $user) { + foreach ($this->userOrMe($data['accepted_users'], $exportGenerationContext) as $key => $user) { $orX->add($qb->expr()->isMemberOf(':activity_users_filter_u'.$key, 'activity.users')); $qb->setParameter('activity_users_filter_u'.$key, $user); } @@ -39,29 +43,44 @@ class ActivityUsersFilter implements FilterInterface $qb->andWhere($orX); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { - $builder->add('accepted_users', PickUserDynamicType::class, [ + $builder->add('accepted_users', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'Users', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_users' => $this->normalizeUserOrMe($formData['accepted_users'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_users' => $this->denormalizeUserOrMe($formData['accepted_users'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $users = []; - foreach ($data['accepted_users'] as $u) { + foreach ($this->userOrMe($data['accepted_users'], $context) as $u) { $users[] = $this->userRender->renderString($u, []); } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php index f75c5a817..bdad045c8 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/ByCreatorFilter.php @@ -12,28 +12,32 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class ByCreatorFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private readonly UserRender $userRender, private readonly UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere( $qb->expr()->in('activity.createdBy', ':users') ) - ->setParameter('users', $data['accepted_users']); + ->setParameter('users', $this->userOrMe($data['accepted_users'], $exportGenerationContext)); } public function applyOn(): string @@ -41,23 +45,38 @@ class ByCreatorFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { - $builder->add('accepted_users', PickUserDynamicType::class, [ + $builder->add('accepted_users', PickUserOrMeDynamicType::class, [ 'multiple' => true, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_users' => $this->normalizeUserOrMe($formData['accepted_users'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_users' => $this->denormalizeUserOrMe($formData['accepted_users'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_users'] as $u) { + foreach ($this->userOrMe($data['accepted_users'], $context) as $u) { $users[] = $this->userRender->renderString($u, []); } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php index 3b805d4ff..dd87200c3 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorJobFilter.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -26,6 +27,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; final readonly class CreatorJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_act_filter_creator_job'; public function __construct( @@ -39,7 +41,7 @@ final readonly class CreatorJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -75,7 +77,7 @@ final readonly class CreatorJobFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -90,7 +92,22 @@ final readonly class CreatorJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $jobs = array_map( fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php index 36b827e9b..bb695d68c 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/CreatorScopeFilter.php @@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -24,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface; class CreatorScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_act_filter_creator_scope'; public function __construct( @@ -36,7 +38,7 @@ class CreatorScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -72,7 +74,7 @@ class CreatorScopeFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -86,7 +88,22 @@ class CreatorScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $scopes = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php index b74be2552..60923ad41 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/EmergencyFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; @@ -35,7 +36,7 @@ class EmergencyFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -56,7 +57,7 @@ class EmergencyFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_emergency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -66,12 +67,27 @@ class EmergencyFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_emergency' => $formData['accepted_emergency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_emergency' => $formData['accepted_emergency']]; + } + public function getFormDefaultData(): array { return ['accepted_emergency' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by emergency: only %emergency%', [ diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php index 77b4ce20d..cb1f18bf8 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/LocationFilter.php @@ -12,19 +12,27 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickUserLocationType; +use Chill\MainBundle\Repository\LocationRepository; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class LocationFilter implements FilterInterface +final readonly class LocationFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct( + private LocationRepository $locationRepository, + ) {} + public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere( $qb->expr()->in('activity.location', ':location') @@ -38,7 +46,7 @@ class LocationFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_location', PickUserLocationType::class, [ 'multiple' => true, @@ -46,12 +54,27 @@ class LocationFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_location' => $this->normalizeDoctrineEntity($formData['accepted_location'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_location' => $this->denormalizeDoctrineEntity($formData['accepted_location'], $this->locationRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $locations = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php index 771dfca30..3a9fff0bc 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/LocationTypeFilter.php @@ -12,8 +12,11 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickLocationTypeType; +use Chill\MainBundle\Repository\LocationTypeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; @@ -21,14 +24,16 @@ use Symfony\Component\Form\FormBuilderInterface; class LocationTypeFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use ExportDataNormalizerTrait; + + public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private LocationTypeRepository $locationTypeRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('actloc', $qb->getAllAliases(), true)) { $qb->join('activity.location', 'actloc'); @@ -52,7 +57,7 @@ class LocationTypeFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_locationtype', PickLocationTypeType::class, [ 'multiple' => true, @@ -60,12 +65,27 @@ class LocationTypeFilter implements FilterInterface ]); } - public function getFormDefaultData(): array + public function getNormalizationVersion(): int { - return []; + return 1; } - public function describeAction($data, $format = 'string'): array + public function normalizeFormData(array $formData): array + { + return ['accepted_locationtype' => $this->normalizeDoctrineEntity($formData['accepted_locationtype'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_locationtype' => $this->denormalizeDoctrineEntity($formData['accepted_locationtype'], $this->locationTypeRepository)]; + } + + public function getFormDefaultData(): array + { + return ['accepted_locationtype' => []]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $types = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php index b8ce3259f..4802fc7ac 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\ActivityReason; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Repository\ActivityReasonRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Doctrine\Common\Collections\Collection; @@ -26,6 +27,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct(protected TranslatableStringHelper $translatableStringHelper, protected ActivityReasonRepository $activityReasonRepository) {} public function addRole(): ?string @@ -33,7 +36,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $join = $qb->getDQLPart('join'); @@ -58,7 +61,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt return Declarations::ACTIVITY_PERSON; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('reasons', EntityType::class, [ 'class' => ActivityReason::class, @@ -70,12 +73,27 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['reasons' => $this->normalizeDoctrineEntity($formData['reasons'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['reasons' => $this->denormalizeDoctrineEntity($formData['reasons'], $this->activityReasonRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { // collect all the reasons'name used in this filter in one array $reasonsNames = array_map( @@ -91,12 +109,12 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by reason'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['reasons'] || 0 === \count($data['reasons'])) { $context diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php index 2fd628edb..673aac25e 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Entity\ActivityReason; use Chill\ActivityBundle\Repository\ActivityReasonRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -39,7 +40,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { // create a subquery for activity $sqb = $qb->getEntityManager()->createQueryBuilder(); @@ -92,7 +93,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_from_rolling', PickRollingDateType::class, [ 'label' => 'export.filter.activity.person_between_dates.Implied in an activity after this date', @@ -116,6 +117,21 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem } } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from_rolling' => $formData['date_from_rolling']->normalize(), 'date_to_rolling' => $formData['date_to_rolling']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']), 'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling'])]; + } + public function getFormDefaultData(): array { return [ @@ -125,7 +141,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ [] === $data['reasons'] ? @@ -150,7 +166,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem return 'export.filter.activity.person_between_dates.title'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if ($this->rollingDateConverter->convert($data['date_from_rolling']) >= $this->rollingDateConverter->convert($data['date_to_rolling'])) { diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php index 8bdefeb24..820830e23 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonsFilter.php @@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; use Chill\ActivityBundle\Tests\Export\Filter\PersonsFilterTest; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Form\Type\PickPersonDynamicType; use Chill\PersonBundle\Templating\Entity\PersonRenderInterface; @@ -33,7 +34,7 @@ final readonly class PersonsFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -47,12 +48,12 @@ final readonly class PersonsFilter implements FilterInterface $qb->andWhere($orX); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_persons', PickPersonDynamicType::class, [ 'multiple' => true, @@ -60,6 +61,21 @@ final readonly class PersonsFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_persons' => $formData['accepted_persons']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_persons' => $formData['accepted_persons']]; + } + public function getFormDefaultData(): array { return [ @@ -67,7 +83,7 @@ final readonly class PersonsFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $users = []; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php index 3011627e8..ba85ff731 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/SentReceivedFilter.php @@ -13,6 +13,7 @@ namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; @@ -36,7 +37,7 @@ class SentReceivedFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -57,7 +58,7 @@ class SentReceivedFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_sentreceived', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -68,12 +69,27 @@ class SentReceivedFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_sentreceived' => $formData['accepted_sentreceived']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_sentreceived' => $formData['accepted_sentreceived']]; + } + public function getFormDefaultData(): array { return ['accepted_sentreceived' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $sentreceived = array_flip(self::CHOICES)[$data['accepted_sentreceived']]; diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php index 6e6b745b9..2a63be54a 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UserFilter.php @@ -12,23 +12,27 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Export\Filter; use Chill\ActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class UserFilter implements FilterInterface +final readonly class UserFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private UserRender $userRender, private UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -41,7 +45,7 @@ class UserFilter implements FilterInterface } $qb->add('where', $where); - $qb->setParameter('users', $data['accepted_users']); + $qb->setParameter('users', $this->userOrMe($data['accepted_users'], $exportGenerationContext)); } public function applyOn(): string @@ -49,24 +53,39 @@ class UserFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { - $builder->add('accepted_users', PickUserDynamicType::class, [ + $builder->add('accepted_users', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'Creators', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_users' => $this->normalizeUserOrMe($formData['accepted_users'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_users' => $this->denormalizeUserOrMe($formData['accepted_users'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_users'] as $u) { + foreach ($this->userOrMe($data['accepted_users'], $context) as $u) { $users[] = $this->userRender->renderString($u, []); } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php index cf2787359..f5bf9f2ae 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; class UsersJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'act_filter_user_job'; public function __construct( @@ -37,7 +39,7 @@ class UsersJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -60,12 +62,12 @@ class UsersJobFilter implements FilterInterface ); } - public function applyOn() + public function applyOn(): string { return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -77,7 +79,22 @@ class UsersJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string') + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['export.filter.activity.by_users_job.Filtered activity by users job: only %jobs%', [ '%jobs%' => implode( @@ -97,7 +114,7 @@ class UsersJobFilter implements FilterInterface ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.activity.by_users_job.Filter by users job'; } diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php index bbf1630c4..83af6701c 100644 --- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php +++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php @@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity; use Chill\ActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; class UsersScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'act_filter_user_scope'; public function __construct( @@ -37,7 +39,7 @@ class UsersScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class UsersScopeFilter implements FilterInterface return Declarations::ACTIVITY; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -77,7 +79,22 @@ class UsersScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return ['export.filter.activity.by_users_scope.Filtered activity by users scope: only %scopes%', [ '%scopes%' => implode( diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php index 228d70856..0efc8d056 100644 --- a/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php +++ b/src/Bundle/ChillActivityBundle/Repository/ActivityPresenceRepositoryInterface.php @@ -12,8 +12,9 @@ declare(strict_types=1); namespace Chill\ActivityBundle\Repository; use Chill\ActivityBundle\Entity\ActivityPresence; +use Doctrine\Persistence\ObjectRepository; -interface ActivityPresenceRepositoryInterface +interface ActivityPresenceRepositoryInterface extends ObjectRepository { public function find($id): ?ActivityPresence; diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php index cbd168ce8..670492695 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/ByCreatorFilterTest.php @@ -45,14 +45,20 @@ final class ByCreatorFilterTest extends AbstractFilterTest ->from(User::class, 'u') ->select('u') ->getQuery() + ->setMaxResults(1) ->getResult(); $data = []; + foreach ($array as $a) { $data[] = [ 'accepted_users' => $a, ]; } + $data[] = [ + 'accepted_users' => 'me', + ]; + return $data; } diff --git a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php index 569fb5403..976ab97e6 100644 --- a/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php +++ b/src/Bundle/ChillActivityBundle/Tests/Export/Filter/UserFilterTest.php @@ -55,6 +55,10 @@ final class UserFilterTest extends AbstractFilterTest ]; } + $data[] = [ + 'accepted_users' => 'me', + ]; + return $data; } diff --git a/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php b/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php index bea38c768..ca85f2687 100644 --- a/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php +++ b/src/Bundle/ChillActivityBundle/Validator/Constraints/ActivityValidity.php @@ -30,7 +30,7 @@ class ActivityValidity extends Constraint public $socialIssuesMessage = 'For this type of activity, you must add at least one social issue'; - public function getTargets() + public function getTargets(): string { return self::CLASS_CONSTRAINT; } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php index cc311d470..fff522ef1 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php @@ -30,7 +30,7 @@ class ByActivityTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(aside.type) AS by_aside_activity_type_aggregator') ->addGroupBy('by_aside_activity_type_aggregator'); @@ -41,17 +41,32 @@ class ByActivityTypeAggregator implements AggregatorInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php index b5ca1022b..1c9ae467e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByLocationAggregator.php @@ -26,12 +26,27 @@ class ByLocationAggregator implements AggregatorInterface // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -60,7 +75,7 @@ class ByLocationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(aside.location) AS by_aside_activity_location_aggregator') ->addGroupBy('by_aside_activity_location_aggregator'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php index 58d5584b4..6a34acf81 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php @@ -34,7 +34,7 @@ class ByUserJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ class ByUserJobAggregator implements AggregatorInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php index a0277a37b..f5d74490a 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php @@ -34,7 +34,7 @@ class ByUserScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -64,14 +64,29 @@ class ByUserScopeAggregator implements AggregatorInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php index 70922b6ae..6cf542d2d 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php @@ -26,6 +26,21 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -60,7 +75,7 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac return ['export_avg_aside_activity_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -75,7 +90,7 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac return Declarations::ASIDE_ACTIVITY_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->repository->createQueryBuilder('aside'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php index 6d1eed5fe..4bd7659ee 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php @@ -26,6 +26,21 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -63,7 +78,7 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -78,7 +93,7 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->repository->createQueryBuilder('aside'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php index 816af5771..0f6802104 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php @@ -46,6 +46,21 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -142,7 +157,7 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ 'id', @@ -160,12 +175,12 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn ]; } - public function getResult($query, $data): array + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aside_activity.List of aside activities'; } @@ -175,7 +190,7 @@ final readonly class ListAsideActivity implements ListInterface, GroupedExportIn return Declarations::ASIDE_ACTIVITY_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->em->createQueryBuilder() ->from(AsideActivity::class, 'aside') diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php index 0fd318902..18f78f3f8 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php @@ -26,6 +26,21 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -60,7 +75,7 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac return ['export_sum_aside_activity_duration']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -75,7 +90,7 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac return Declarations::ASIDE_ACTIVITY_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->repository ->createQueryBuilder('aside'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php index bfb8fe0b1..d96f74b7f 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php @@ -15,6 +15,7 @@ use Chill\AsideActivityBundle\Entity\AsideActivityCategory; use Chill\AsideActivityBundle\Export\Declarations; use Chill\AsideActivityBundle\Repository\AsideActivityCategoryRepository; use Chill\AsideActivityBundle\Templating\Entity\CategoryRender; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\Common\Collections\Collection; @@ -24,10 +25,13 @@ use Symfony\Component\Form\FormBuilderInterface; class ByActivityTypeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private readonly CategoryRender $categoryRender, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly AsideActivityCategoryRepository $asideActivityTypeRepository, + private readonly AsideActivityCategoryRepository $asideActivityCategoryRepository, ) {} public function addRole(): ?string @@ -35,7 +39,7 @@ class ByActivityTypeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('aside.type', ':types'); @@ -48,7 +52,7 @@ class ByActivityTypeFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('types', EntityType::class, [ @@ -68,12 +72,27 @@ class ByActivityTypeFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['types' => $this->normalizeDoctrineEntity($formData['types'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['types' => $this->denormalizeDoctrineEntity($formData['types'], $this->asideActivityCategoryRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $types = array_map( fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getTitle()), diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php index b8d77d942..4521d7a94 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Export\Filter; use Chill\AsideActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class ByDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->between( 'aside.date', @@ -53,7 +54,7 @@ class ByDateFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, [ @@ -64,6 +65,21 @@ class ByDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return [ @@ -72,7 +88,7 @@ class ByDateFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['export.filter.Filtered by aside activities between %dateFrom% and %dateTo%', [ '%dateFrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php index 23c141a06..9c231782e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByLocationFilter.php @@ -14,8 +14,10 @@ namespace Chill\AsideActivityBundle\Export\Filter; use Chill\AsideActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Location; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickUserLocationType; +use Chill\MainBundle\Repository\LocationRepository; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; @@ -23,8 +25,11 @@ use Symfony\Component\Security\Core\Security; final readonly class ByLocationFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private Security $security, + private LocationRepository $locationRepository, ) {} public function getTitle(): string @@ -38,6 +43,21 @@ final readonly class ByLocationFilter implements FilterInterface ->add('locations', PickUserLocationType::class); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['locations' => $this->normalizeDoctrineEntity($formData['locations'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['locations' => $this->denormalizeDoctrineEntity($formData['locations'], $this->locationRepository)]; + } + public function getFormDefaultData(): array { $user = $this->security->getUser(); @@ -53,7 +73,7 @@ final readonly class ByLocationFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $extractFunction = fn (Location $l): string => $l->getName(); if ($data['locations'] instanceof Collection) { @@ -72,7 +92,7 @@ final readonly class ByLocationFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('aside.location', ':locations'); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php index 8dd1a8eac..04e02d3ff 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php @@ -12,28 +12,32 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Export\Filter; use Chill\AsideActivityBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class ByUserFilter implements FilterInterface +final readonly class ByUserFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private UserRender $userRender, private UserRepository $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('aside.agent', ':users'); $qb ->andWhere($clause) - ->setParameter('users', $data['accepted_users']); + ->setParameter('users', $this->userOrMe($data['accepted_users'], $exportGenerationContext)); } public function applyOn(): string @@ -41,24 +45,39 @@ class ByUserFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { - $builder->add('accepted_users', PickUserDynamicType::class, [ + $builder->add('accepted_users', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'Creators', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_users' => $this->normalizeUserOrMe($formData['accepted_users'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_users' => $this->denormalizeUserOrMe($formData['accepted_users'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_users'] as $u) { + foreach ($this->userOrMe($data['accepted_users'], $context) as $u) { $users[] = $this->userRender->renderString($u, []); } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php index 551e91cd8..3cf8d8322 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php @@ -15,6 +15,7 @@ use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; class ByUserJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'aside_act_filter_user_job'; public function __construct( @@ -37,7 +39,7 @@ class ByUserJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class ByUserJobFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -77,7 +79,22 @@ class ByUserJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return ['export.filter.by_user_job.Filtered aside activities by user jobs: only %jobs%', [ '%jobs%' => implode( diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php index 5257495aa..387cc11da 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php @@ -15,6 +15,7 @@ use Chill\AsideActivityBundle\Entity\AsideActivity; use Chill\AsideActivityBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -25,6 +26,7 @@ use Symfony\Component\Form\FormBuilderInterface; class ByUserScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'aside_act_filter_user_scope'; public function __construct( @@ -37,7 +39,7 @@ class ByUserScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class ByUserScopeFilter implements FilterInterface return Declarations::ASIDE_ACTIVITY_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -77,7 +79,22 @@ class ByUserScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string') + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['export.filter.by_user_scope.Filtered aside activities by user scope: only %scopes%', [ '%scopes%' => implode( diff --git a/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php index aae93c17b..25f93bc3e 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Tests/Export/Export/ListAsideActivityTest.php @@ -12,6 +12,8 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Tests\Export\Export; use Chill\AsideActivityBundle\Export\Export\ListAsideActivity; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Doctrine\ORM\AbstractQuery; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -33,7 +35,7 @@ class ListAsideActivityTest extends KernelTestCase public function testExecuteQuery(): void { - $qb = $this->listAsideActivity->initiateQuery([], [], []) + $qb = $this->listAsideActivity->initiateQuery([], [], [], new ExportGenerationContext(new User())) ->setMaxResults(1); $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php index 5e2091fac..40be6542d 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/AgentAggregator.php @@ -27,7 +27,7 @@ final readonly class AgentAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('caluser', $qb->getAllAliases(), true)) { $qb->join('cal.mainUser', 'caluser'); @@ -42,17 +42,32 @@ final readonly class AgentAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php index 7c84653d2..718fcd8bd 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/CancelReasonAggregator.php @@ -27,7 +27,7 @@ class CancelReasonAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // TODO: still needs to take into account calendars without a cancel reason somehow if (!\in_array('calcancel', $qb->getAllAliases(), true)) { @@ -43,17 +43,32 @@ class CancelReasonAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php index 1182ea374..420ff8d2f 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/JobAggregator.php @@ -34,7 +34,7 @@ final readonly class JobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ final readonly class JobAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php index 6481f95b4..94bc0fdea 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationAggregator.php @@ -26,7 +26,7 @@ final readonly class LocationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('calloc', $qb->getAllAliases(), true)) { $qb->join('cal.location', 'calloc'); @@ -40,17 +40,32 @@ final readonly class LocationAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php index be9406cfa..f84c6b3d7 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/LocationTypeAggregator.php @@ -27,7 +27,7 @@ final readonly class LocationTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('calloc', $qb->getAllAliases(), true)) { $qb->join('cal.location', 'calloc'); @@ -42,17 +42,32 @@ final readonly class LocationTypeAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php index 6bf65b8ef..ea899ef54 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/MonthYearAggregator.php @@ -23,7 +23,7 @@ class MonthYearAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect("to_char(cal.startDate, 'MM-YYYY') AS month_year_aggregator"); // $qb->addSelect("extract(month from age(cal.startDate, cal.endDate)) AS month_aggregator"); @@ -35,17 +35,32 @@ class MonthYearAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + 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): \Closure + public function getLabels($key, array $values, $data): callable { return static function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php index ff24baa8c..85dac1f3e 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/ScopeAggregator.php @@ -34,7 +34,7 @@ final readonly class ScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,14 +65,29 @@ final readonly class ScopeAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php b/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php index e9213d3cb..cc5114a4f 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php +++ b/src/Bundle/ChillCalendarBundle/Export/Aggregator/UrgencyAggregator.php @@ -33,7 +33,7 @@ class UrgencyAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('cal.urgent AS urgency_aggregator'); $qb->addGroupBy('urgency_aggregator'); @@ -44,17 +44,32 @@ class UrgencyAggregator implements AggregatorInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php b/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php index f643eaa68..8b14a1628 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/CountCalendars.php @@ -34,6 +34,21 @@ class CountCalendars implements ExportInterface, GroupedExportInterface // No form necessary } + 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 []; @@ -71,7 +86,7 @@ class CountCalendars implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -89,7 +104,7 @@ class CountCalendars implements ExportInterface, GroupedExportInterface /** * Initiate the query. */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php index b69185a17..afda99ee9 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarAvgDuration.php @@ -31,6 +31,21 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface // 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 []; @@ -68,7 +83,7 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -83,7 +98,7 @@ class StatCalendarAvgDuration implements ExportInterface, GroupedExportInterface return Declarations::CALENDAR_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $qb = $this->calendarRepository->createQueryBuilder('cal'); diff --git a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php index 8ea23014c..80369e354 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php +++ b/src/Bundle/ChillCalendarBundle/Export/Export/StatCalendarSumDuration.php @@ -31,6 +31,21 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface // 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 []; @@ -68,7 +83,7 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -83,7 +98,7 @@ class StatCalendarSumDuration implements ExportInterface, GroupedExportInterface return Declarations::CALENDAR_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $qb = $this->calendarRepository->createQueryBuilder('cal'); diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php index c16c148fc..b19ce3596 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/AgentFilter.php @@ -13,23 +13,27 @@ namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class AgentFilter implements FilterInterface +final readonly class AgentFilter implements FilterInterface { - public function __construct(private readonly UserRender $userRender) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private UserRender $userRender, private UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->in('cal.mainUser', ':agents'); @@ -49,7 +53,7 @@ class AgentFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_agents', EntityType::class, [ 'class' => User::class, @@ -59,12 +63,27 @@ class AgentFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_agents' => $this->normalizeDoctrineEntity($formData['accepted_agents'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_agents' => $this->denormalizeDoctrineEntity($formData['accepted_agents'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php index 90a004388..dfb450ea9 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,7 +29,7 @@ class BetweenDatesFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->andX( $qb->expr()->gte('cal.startDate', ':dateFrom'), @@ -52,19 +53,34 @@ class BetweenDatesFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, []) ->add('date_to', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by calendars between %dateFrom% and %dateTo%', [ '%dateFrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php index 63149509f..dbdc297a8 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php @@ -19,6 +19,7 @@ declare(strict_types=1); namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; @@ -41,7 +42,7 @@ class CalendarRangeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (null !== $data['hasCalendarRange']) { $qb->andWhere($qb->expr()->isNotNull('cal.calendarRange')); @@ -55,7 +56,7 @@ class CalendarRangeFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('hasCalendarRange', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -66,12 +67,27 @@ class CalendarRangeFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['hasCalendarRange' => $formData['hasCalendarRange']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['hasCalendarRange' => $formData['hasCalendarRange']]; + } + public function getFormDefaultData(): array { return ['hasCalendarRange' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $choice = ''; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php index 3c87397e4..1885d6dcb 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php @@ -14,6 +14,7 @@ namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -24,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class JobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'cal_filter_job'; public function __construct( @@ -36,7 +38,7 @@ final readonly class JobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -70,7 +72,7 @@ final readonly class JobFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('job', EntityType::class, [ @@ -84,7 +86,22 @@ final readonly class JobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['job' => $this->normalizeDoctrineEntity($formData['job'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['job' => $this->denormalizeDoctrineEntity($formData['job'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $userJobs = []; diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php index 3a1c5a35a..447d3750e 100644 --- a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php +++ b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php @@ -14,6 +14,7 @@ namespace Chill\CalendarBundle\Export\Filter; use Chill\CalendarBundle\Export\Declarations; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -25,6 +26,7 @@ use Symfony\Contracts\Translation\TranslatorInterface; class ScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'cal_filter_scope'; public function __construct( @@ -38,7 +40,7 @@ class ScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -72,7 +74,7 @@ class ScopeFilter implements FilterInterface return Declarations::CALENDAR_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scope', EntityType::class, [ @@ -86,7 +88,22 @@ class ScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scope' => $this->normalizeDoctrineEntity($formData['scope'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scope' => $this->denormalizeDoctrineEntity($formData['scope'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $scopes = []; diff --git a/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php b/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php index 81d230e67..e90164a2a 100644 --- a/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php +++ b/src/Bundle/ChillDocStoreBundle/Repository/AssociatedEntityToStoredObjectInterface.php @@ -13,7 +13,13 @@ namespace Chill\DocStoreBundle\Repository; use Chill\DocStoreBundle\Entity\StoredObject; +/** + * @template T of object + */ interface AssociatedEntityToStoredObjectInterface { + /** + * @return T|null + */ public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?object; } diff --git a/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php b/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php index 1e519997a..bdb28f9c3 100644 --- a/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php +++ b/src/Bundle/ChillEventBundle/Export/Aggregator/EventDateAggregator.php @@ -32,7 +32,7 @@ class EventDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $order = null; @@ -67,7 +67,7 @@ class EventDateAggregator implements AggregatorInterface return Declarations::EVENT; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -76,12 +76,27 @@ class EventDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value) use ($data): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php b/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php index db757cac9..7aafb3791 100644 --- a/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php +++ b/src/Bundle/ChillEventBundle/Export/Aggregator/EventTypeAggregator.php @@ -29,7 +29,7 @@ class EventTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('eventtype', $qb->getAllAliases(), true)) { $qb->leftJoin('event.type', 'eventtype'); @@ -44,17 +44,32 @@ class EventTypeAggregator implements AggregatorInterface return Declarations::EVENT; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form required for this aggregator } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -74,7 +89,7 @@ class EventTypeAggregator implements AggregatorInterface return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group by event type'; } diff --git a/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php b/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php index c02483db2..20f663d4e 100644 --- a/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php +++ b/src/Bundle/ChillEventBundle/Export/Aggregator/RoleAggregator.php @@ -29,7 +29,7 @@ class RoleAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('event_part', $qb->getAllAliases(), true)) { $qb->leftJoin('event_part.role', 'role'); @@ -44,17 +44,32 @@ class RoleAggregator implements AggregatorInterface return Declarations::EVENT_PARTICIPANTS; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form required for this aggregator } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -74,7 +89,7 @@ class RoleAggregator implements AggregatorInterface return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group by participant role'; } diff --git a/src/Bundle/ChillEventBundle/Export/Export/CountEventParticipations.php b/src/Bundle/ChillEventBundle/Export/Export/CountEventParticipations.php index 5c5d5bf05..d95fb4ecb 100644 --- a/src/Bundle/ChillEventBundle/Export/Export/CountEventParticipations.php +++ b/src/Bundle/ChillEventBundle/Export/Export/CountEventParticipations.php @@ -36,6 +36,21 @@ readonly class CountEventParticipations implements ExportInterface, GroupedExpor public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -65,17 +80,17 @@ readonly class CountEventParticipations implements ExportInterface, GroupedExpor return static fn ($value) => '_header' === $value ? 'Count event participants' : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_count_event_participants']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count event participants'; } @@ -85,7 +100,7 @@ readonly class CountEventParticipations implements ExportInterface, GroupedExpor return Declarations::EVENT_PARTICIPANTS; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php b/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php index 7cb98b5a6..fe51fdfd8 100644 --- a/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php +++ b/src/Bundle/ChillEventBundle/Export/Export/CountEvents.php @@ -36,6 +36,21 @@ readonly class CountEvents implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -65,17 +80,17 @@ readonly class CountEvents implements ExportInterface, GroupedExportInterface return static fn ($value) => '_header' === $value ? 'Number of events' : $value; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_count_event']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count events'; } @@ -85,7 +100,7 @@ readonly class CountEvents implements ExportInterface, GroupedExportInterface return Declarations::EVENT; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php b/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php index 10f1dbd81..e7b245fbc 100644 --- a/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php +++ b/src/Bundle/ChillEventBundle/Export/Filter/EventDateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\EventBundle\Export\Filter; use Chill\EventBundle\Export\Declarations; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -30,7 +31,7 @@ class EventDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -61,7 +62,7 @@ class EventDateFilter implements FilterInterface return Declarations::EVENT; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, [ @@ -72,12 +73,27 @@ class EventDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'Filtered by date of event: only between %date_from% and %date_to%', @@ -88,7 +104,7 @@ class EventDateFilter implements FilterInterface ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filtered by event date'; } diff --git a/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php b/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php index 4b64022ea..5314a0bc6 100644 --- a/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php +++ b/src/Bundle/ChillEventBundle/Export/Filter/EventTypeFilter.php @@ -15,6 +15,7 @@ use Chill\EventBundle\Entity\EventType; use Chill\EventBundle\Export\Declarations; use Chill\EventBundle\Repository\EventTypeRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\QueryBuilder; @@ -24,6 +25,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class EventTypeFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( protected TranslatableStringHelperInterface $translatableStringHelper, protected EventTypeRepository $eventTypeRepository, @@ -34,7 +37,7 @@ class EventTypeFilter implements ExportElementValidatedInterface, FilterInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('event.type', ':selected_event_types'); @@ -47,7 +50,7 @@ class EventTypeFilter implements ExportElementValidatedInterface, FilterInterfac return Declarations::EVENT; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('types', EntityType::class, [ 'choices' => $this->eventTypeRepository->findAllActive(), @@ -61,12 +64,27 @@ class EventTypeFilter implements ExportElementValidatedInterface, FilterInterfac ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['types' => $this->normalizeDoctrineEntity($formData['types'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['types' => $this->denormalizeDoctrineEntity($formData['types'], $this->eventTypeRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $typeNames = array_map( fn (EventType $t): string => $this->translatableStringHelper->localize($t->getName()), @@ -78,12 +96,12 @@ class EventTypeFilter implements ExportElementValidatedInterface, FilterInterfac ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filtered by event type'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['types'] || 0 === \count($data['types'])) { $context diff --git a/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php b/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php index 612cea84e..4ef2ec2aa 100644 --- a/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php +++ b/src/Bundle/ChillEventBundle/Export/Filter/RoleFilter.php @@ -15,6 +15,7 @@ use Chill\EventBundle\Entity\Role; use Chill\EventBundle\Export\Declarations; use Chill\EventBundle\Repository\RoleRepository; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\QueryBuilder; @@ -24,6 +25,8 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; class RoleFilter implements ExportElementValidatedInterface, FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( protected TranslatableStringHelperInterface $translatableStringHelper, protected RoleRepository $roleRepository, @@ -34,7 +37,7 @@ class RoleFilter implements ExportElementValidatedInterface, FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('event_part.role', ':selected_part_roles'); @@ -47,7 +50,7 @@ class RoleFilter implements ExportElementValidatedInterface, FilterInterface return Declarations::EVENT_PARTICIPANTS; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('part_roles', EntityType::class, [ 'choices' => $this->roleRepository->findAllActive(), @@ -61,12 +64,27 @@ class RoleFilter implements ExportElementValidatedInterface, FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['part_roles' => $this->normalizeDoctrineEntity($formData['part_roles'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['part_roles' => $this->denormalizeDoctrineEntity($formData['part_roles'], $this->roleRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $roleNames = array_map( fn (Role $r): string => $this->translatableStringHelper->localize($r->getName()), @@ -78,12 +96,12 @@ class RoleFilter implements ExportElementValidatedInterface, FilterInterface ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by participant roles'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['part_roles'] || 0 === \count($data['part_roles'])) { $context diff --git a/src/Bundle/ChillJobBundle/src/Export/ListCV.php b/src/Bundle/ChillJobBundle/src/Export/ListCV.php index 896c7db01..7fd23abf0 100644 --- a/src/Bundle/ChillJobBundle/src/Export/ListCV.php +++ b/src/Bundle/ChillJobBundle/src/Export/ListCV.php @@ -33,6 +33,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ class ListCV implements ListInterface, ExportElementValidatedInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; /** * @var array */ @@ -71,14 +72,14 @@ class ListCV implements ListInterface, ExportElementValidatedInterface * * @param mixed $data the data, as returned by the user */ - public function validateForm($data, ExecutionContextInterface $context) {} + public function validateForm($data, ExecutionContextInterface $context): void {} /** * get a title, which will be used in UI (and translated). * * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Liste des CVs par personne'; } @@ -132,15 +133,28 @@ class ListCV implements ListInterface, ExportElementValidatedInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->normalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->normalizeDate($formData['reportdate_max'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->denormalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->denormalizeDate($formData['reportdate_max'])]; + } + /** * Return the Export's type. This will inform _on what_ export will apply. * Most of the type, it will be a string which references an entity. * * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE - * - * @return string */ - public function getType() + public function getType(): string { return Person::class; } @@ -156,22 +170,7 @@ class ListCV implements ListInterface, ExportElementValidatedInterface return 'Crée une liste des CVs en fonction de différents paramètres.'; } - /** - * The initial query, which will be modified by ModifiersInterface - * (i.e. AggregatorInterface, FilterInterface). - * - * This query should take into account the `$acl` and restrict result only to - * what the user is allowed to see. (Do not show personal data the user - * is not allowed to see). - * - * The returned object should be an instance of QueryBuilder or NativeQuery. - * - * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` - * @param array $data the data from the form, if any - * - * @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute - */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { return $this->entityManager->createQueryBuilder() ->from('ChillPersonBundle:Person', 'person'); @@ -215,10 +214,8 @@ class ListCV implements ListInterface, ExportElementValidatedInterface * this function will return `array('count_id')`. * * @param mixed[] $data the data from the export's form (added by self::buildForm) - * - * @return array */ - public function getQueryKeys($data) + public function getQueryKeys($data): array { return array_keys($data['fields']); } @@ -239,7 +236,7 @@ class ListCV implements ListInterface, ExportElementValidatedInterface * @param QueryBuilder|\Doctrine\ORM\NativeQuery $qb * @param mixed[] $data the data from the export's form (added by self::buildForm) */ - public function getResult($qb, $data) + public function getResult($qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { $qb->select('person.id'); diff --git a/src/Bundle/ChillJobBundle/src/Export/ListFrein.php b/src/Bundle/ChillJobBundle/src/Export/ListFrein.php index 8e3a64294..9517bbdfb 100644 --- a/src/Bundle/ChillJobBundle/src/Export/ListFrein.php +++ b/src/Bundle/ChillJobBundle/src/Export/ListFrein.php @@ -34,6 +34,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ class ListFrein implements ListInterface, ExportElementValidatedInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; /** * @var array */ @@ -84,14 +85,14 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface * * @param mixed $data the data, as returned by the user */ - public function validateForm($data, ExecutionContextInterface $context) {} + public function validateForm($data, ExecutionContextInterface $context): void {} /** * get a title, which will be used in UI (and translated). * * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Liste des freins identifiés par personne'; } @@ -145,15 +146,28 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->normalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->normalizeDate($formData['reportdate_max'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->denormalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->denormalizeDate($formData['reportdate_max'])]; + } + /** * Return the Export's type. This will inform _on what_ export will apply. * Most of the type, it will be a string which references an entity. * * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE - * - * @return string */ - public function getType() + public function getType(): string { return Person::class; } @@ -169,22 +183,7 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface return 'Crée une liste des personnes et de leurs freins identifiés en fonction de différents paramètres.'; } - /** - * The initial query, which will be modified by ModifiersInterface - * (i.e. AggregatorInterface, FilterInterface). - * - * This query should take into account the `$acl` and restrict result only to - * what the user is allowed to see. (Do not show personal data the user - * is not allowed to see). - * - * The returned object should be an instance of QueryBuilder or NativeQuery. - * - * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` - * @param array $data the data from the form, if any - * - * @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute - */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { return $this->entityManager->createQueryBuilder() ->from(Person::class, 'person'); @@ -238,10 +237,8 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface * this function will return `array('count_id')`. * * @param mixed[] $data the data from the export's form (added by self::buildForm) - * - * @return array */ - public function getQueryKeys($data) + public function getQueryKeys($data): array { $freins = self::FREINS; $fields = []; @@ -332,7 +329,7 @@ class ListFrein implements ListInterface, ExportElementValidatedInterface * @param QueryBuilder|\Doctrine\ORM\NativeQuery $qb * @param mixed[] $data the data from the export's form (added by self::buildForm) */ - public function getResult($qb, $data) + public function getResult($qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { $qb->select('person.id'); diff --git a/src/Bundle/ChillJobBundle/src/Export/ListProjetProfessionnel.php b/src/Bundle/ChillJobBundle/src/Export/ListProjetProfessionnel.php index 0a1eeb753..c4a525c7a 100644 --- a/src/Bundle/ChillJobBundle/src/Export/ListProjetProfessionnel.php +++ b/src/Bundle/ChillJobBundle/src/Export/ListProjetProfessionnel.php @@ -34,6 +34,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; */ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; /** * @var array */ @@ -89,14 +90,14 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn * * @param mixed $data the data, as returned by the user */ - public function validateForm($data, ExecutionContextInterface $context) {} + public function validateForm($data, ExecutionContextInterface $context): void {} /** * get a title, which will be used in UI (and translated). * * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Liste des projets professionnels par personne'; } @@ -151,15 +152,28 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->normalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->normalizeDate($formData['reportdate_max'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields'], 'reportdate_min' => $this->denormalizeDate($formData['reportdate_min']), 'reportdate_max' => $this->denormalizeDate($formData['reportdate_max'])]; + } + /** * Return the Export's type. This will inform _on what_ export will apply. * Most of the type, it will be a string which references an entity. * * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE - * - * @return string */ - public function getType() + public function getType(): string { return Person::class; } @@ -175,22 +189,7 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn return 'Crée une liste des personnes et de leur projet professionnel en fonction de différents paramètres.'; } - /** - * The initial query, which will be modified by ModifiersInterface - * (i.e. AggregatorInterface, FilterInterface). - * - * This query should take into account the `$acl` and restrict result only to - * what the user is allowed to see. (Do not show personal data the user - * is not allowed to see). - * - * The returned object should be an instance of QueryBuilder or NativeQuery. - * - * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` - * @param array $data the data from the form, if any - * - * @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute - */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { return $this->entityManager->createQueryBuilder() ->from('ChillPersonBundle:Person', 'person'); @@ -244,10 +243,8 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn * this function will return `array('count_id')`. * * @param mixed[] $data the data from the export's form (added by self::buildForm) - * - * @return array */ - public function getQueryKeys($data) + public function getQueryKeys($data): array { $projet_professionnel = self::PPROF; @@ -370,7 +367,7 @@ class ListProjetProfessionnel implements ListInterface, ExportElementValidatedIn * @param QueryBuilder|\Doctrine\ORM\NativeQuery $qb * @param mixed[] $data the data from the export's form (added by self::buildForm) */ - public function getResult($qb, $data) + public function getResult($qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { $qb->select('person.id'); diff --git a/src/Bundle/ChillMainBundle/ChillMainBundle.php b/src/Bundle/ChillMainBundle/ChillMainBundle.php index bca92b85c..07d794ee5 100644 --- a/src/Bundle/ChillMainBundle/ChillMainBundle.php +++ b/src/Bundle/ChillMainBundle/ChillMainBundle.php @@ -14,7 +14,6 @@ namespace Chill\MainBundle; use Chill\MainBundle\Cron\CronJobInterface; use Chill\MainBundle\CRUD\CompilerPass\CRUDControllerCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass; -use Chill\MainBundle\DependencyInjection\CompilerPass\ExportsCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompilerPass; use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass; @@ -66,7 +65,6 @@ class ChillMainBundle extends Bundle $container->addCompilerPass(new SearchableServicesCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new ConfigConsistencyCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new TimelineCompilerClass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); - $container->addCompilerPass(new ExportsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new WidgetsCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new NotificationCounterCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); $container->addCompilerPass(new MenuCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0); diff --git a/src/Bundle/ChillMainBundle/Controller/ExportController.php b/src/Bundle/ChillMainBundle/Controller/ExportController.php index 08b89e4bb..693c0e55e 100644 --- a/src/Bundle/ChillMainBundle/Controller/ExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/ExportController.php @@ -11,35 +11,39 @@ declare(strict_types=1); namespace Chill\MainBundle\Controller; +use Chill\MainBundle\Entity\ExportGeneration; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\DirectExportInterface; +use Chill\MainBundle\Export\ExportConfigNormalizer; +use Chill\MainBundle\Export\ExportConfigProcessor; use Chill\MainBundle\Export\ExportFormHelper; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\ExportManager; -use Chill\MainBundle\Form\SavedExportType; +use Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage; use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\FormatterType; use Chill\MainBundle\Form\Type\Export\PickCenterType; -use Chill\MainBundle\Redis\ChillRedis; -use Chill\MainBundle\Repository\SavedExportRepositoryInterface; +use Chill\MainBundle\Repository\SavedExportOrExportGenerationRepository; +use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Chill\MainBundle\Security\Authorization\SavedExportVoter; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\Clock\ClockInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; -use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Security; -use Symfony\Contracts\Translation\TranslatorInterface; /** * Class ExportController @@ -50,117 +54,23 @@ class ExportController extends AbstractController private readonly bool $filterStatsByCenters; public function __construct( - private readonly ChillRedis $redis, private readonly ExportManager $exportManager, private readonly FormFactoryInterface $formFactory, private readonly LoggerInterface $logger, private readonly SessionInterface $session, - private readonly TranslatorInterface $translator, private readonly EntityManagerInterface $entityManager, private readonly ExportFormHelper $exportFormHelper, - private readonly SavedExportRepositoryInterface $savedExportRepository, private readonly Security $security, ParameterBagInterface $parameterBag, + private readonly MessageBusInterface $messageBus, + private readonly ClockInterface $clock, + private readonly ExportConfigNormalizer $exportConfigNormalizer, + private readonly SavedExportOrExportGenerationRepository $savedExportOrExportGenerationRepository, + private readonly ExportConfigProcessor $exportConfigProcessor, ) { $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } - #[Route(path: '/{_locale}/exports/download/{alias}', name: 'chill_main_export_download', methods: ['GET'])] - public function downloadResultAction(Request $request, mixed $alias) - { - /** @var ExportManager $exportManager */ - $exportManager = $this->exportManager; - $export = $exportManager->getExport($alias); - $key = $request->query->get('key', null); - $savedExport = $this->getSavedExportFromRequest($request); - - [$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key, $savedExport); - - $formatterAlias = $exportManager->getFormatterAlias($dataExport['export']); - - if (null !== $formatterAlias) { - $formater = $exportManager->getFormatter($formatterAlias); - } else { - $formater = null; - } - - $viewVariables = [ - 'alias' => $alias, - 'export' => $export, - 'export_group' => $this->getExportGroup($export), - 'saved_export' => $savedExport, - ]; - - if ($formater instanceof \Chill\MainBundle\Export\Formatter\CSVListFormatter) { - // due to a bug in php, we add the mime type in the download view - $viewVariables['mime_type'] = 'text/csv'; - } - - return $this->render('@ChillMain/Export/download.html.twig', $viewVariables); - } - - /** - * Generate a report. - * - * This action must work with GET queries. - * - * @param string $alias - */ - #[Route(path: '/{_locale}/exports/generate/{alias}', name: 'chill_main_export_generate', methods: ['GET'])] - public function generateAction(Request $request, $alias): Response - { - /** @var ExportManager $exportManager */ - $exportManager = $this->exportManager; - $key = $request->query->get('key', null); - $savedExport = $this->getSavedExportFromRequest($request); - - [$dataCenters, $dataExport, $dataFormatter] = $this->rebuildData($key, $savedExport); - - return $exportManager->generate( - $alias, - $dataCenters['centers'], - $dataExport['export'], - null !== $dataFormatter ? $dataFormatter['formatter'] : [] - ); - } - - /** - * @throws \RedisException - */ - #[Route(path: '/{_locale}/exports/generate-from-saved/{id}', name: 'chill_main_export_generate_from_saved')] - public function generateFromSavedExport(SavedExport $savedExport): RedirectResponse - { - $this->denyAccessUnlessGranted(SavedExportVoter::GENERATE, $savedExport); - - $key = md5(uniqid((string) random_int(0, mt_getrandmax()), false)); - - $this->redis->setEx($key, 3600, \serialize($savedExport->getOptions())); - - return $this->redirectToRoute( - 'chill_main_export_download', - [ - 'alias' => $savedExport->getExportAlias(), - 'key' => $key, 'prevent_save' => true, - 'returnPath' => $this->generateUrl('chill_main_export_saved_list_my'), - ] - ); - } - - /** - * Render the list of available exports. - */ - #[Route(path: '/{_locale}/exports/', name: 'chill_main_export_index')] - public function indexAction(): Response - { - $exportManager = $this->exportManager; - - $exports = $exportManager->getExportsGrouped(true); - - return $this->render('@ChillMain/Export/layout.html.twig', [ - 'grouped_exports' => $exports, - ]); - } - /** * handle the step to build a query for an export. * @@ -197,64 +107,6 @@ class ExportController extends AbstractController }; } - #[Route(path: '/{_locale}/export/saved/update-from-key/{id}/{key}', name: 'chill_main_export_saved_edit_options_from_key')] - public function editSavedExportOptionsFromKey(SavedExport $savedExport, string $key): Response - { - $this->denyAccessUnlessGranted('ROLE_USER'); - $user = $this->getUser(); - - if (!$user instanceof User) { - throw new AccessDeniedHttpException(); - } - - $data = $this->rebuildRawData($key); - - $savedExport - ->setOptions($data); - - $this->entityManager->flush(); - - return $this->redirectToRoute('chill_main_export_saved_edit', ['id' => $savedExport->getId()]); - } - - #[Route(path: '/{_locale}/export/save-from-key/{alias}/{key}', name: 'chill_main_export_save_from_key')] - public function saveFromKey(string $alias, string $key, Request $request): Response - { - $this->denyAccessUnlessGranted('ROLE_USER'); - $user = $this->getUser(); - - if (!$user instanceof User) { - throw new AccessDeniedHttpException(); - } - - $data = $this->rebuildRawData($key); - - $savedExport = new SavedExport(); - $savedExport - ->setOptions($data) - ->setExportAlias($alias) - ->setUser($user); - - $form = $this->createForm(SavedExportType::class, $savedExport); - - $form->handleRequest($request); - - if ($form->isSubmitted() && $form->isValid()) { - $this->entityManager->persist($savedExport); - $this->entityManager->flush(); - - return $this->redirectToRoute('chill_main_export_index'); - } - - return $this->render( - '@ChillMain/SavedExport/new.html.twig', - [ - 'form' => $form->createView(), - 'saved_export' => $savedExport, - ] - ); - } - /** * create a form to show on different steps. * @@ -262,19 +114,26 @@ class ExportController extends AbstractController */ protected function createCreateFormExport(string $alias, string $step, array $data, ?SavedExport $savedExport): FormInterface { - /** @var ExportManager $exportManager */ $exportManager = $this->exportManager; $isGenerate = str_starts_with($step, 'generate_'); + $canEditFull = $this->security->isGranted(ChillExportVoter::COMPOSE_EXPORT); + + if (!$canEditFull && null === $savedExport) { + throw new AccessDeniedHttpException('The user is not allowed to edit all filter, it should edit only SavedExport'); + } $options = match ($step) { 'export', 'generate_export' => [ 'export_alias' => $alias, - 'picked_centers' => $exportManager->getPickedCenters($data['centers'] ?? []), + 'picked_centers' => $this->filterStatsByCenters ? $this->exportFormHelper->getPickedCenters($data) : [], + 'can_edit_full' => $canEditFull, + 'allowed_filters' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedFilters($savedExport->getOptions()['filters']), + 'allowed_aggregators' => $canEditFull ? null : $this->exportConfigProcessor->retrieveUsedAggregators($savedExport->getOptions()['aggregators']), ], 'formatter', 'generate_formatter' => [ 'export_alias' => $alias, 'formatter_alias' => $exportManager->getFormatterAlias($data['export']), - 'aggregator_aliases' => $exportManager->getUsedAggregatorsAliases($data['export']), + 'aggregator_aliases' => $exportManager->getUsedAggregatorsAliases($data['export']['aggregators']), ], default => [ 'export_alias' => $alias, @@ -283,14 +142,14 @@ class ExportController extends AbstractController $defaultFormData = match ($savedExport) { null => $this->exportFormHelper->getDefaultData($step, $exportManager->getExport($alias), $options), - default => $this->exportFormHelper->savedExportDataToFormData($savedExport, $step, $options), + default => $this->exportFormHelper->savedExportDataToFormData($savedExport, $step), }; $builder = $this->formFactory ->createNamedBuilder( '', FormType::class, - $defaultFormData, + 'centers' === $step ? ['centers' => $defaultFormData] : $defaultFormData, [ 'method' => $isGenerate ? Request::METHOD_GET : Request::METHOD_POST, 'csrf_protection' => !$isGenerate, @@ -429,13 +288,14 @@ class ExportController extends AbstractController * and redirect to the `generate` action. * * The data from previous steps is removed from session. - * - * @param string $alias - * - * @return RedirectResponse */ - private function forwardToGenerate(Request $request, DirectExportInterface|ExportInterface $export, $alias, ?SavedExport $savedExport) + private function forwardToGenerate(Request $request, DirectExportInterface|ExportInterface $export, $alias, ?SavedExport $savedExport): Response { + $user = $this->getUser(); + + if (!$user instanceof User) { + throw new AccessDeniedHttpException('only regular users can generate export'); + } $dataCenters = $this->session->get('centers_step_raw', null); $dataFormatter = $this->session->get('formatter_step_raw', null); $dataExport = $this->session->get('export_step_raw', null); @@ -448,60 +308,82 @@ class ExportController extends AbstractController ]); } - $parameters = [ - 'formatter' => $dataFormatter ?? [], - 'export' => $dataExport ?? [], - 'centers' => $dataCenters ?? [], - 'alias' => $alias, - ]; - unset($parameters['_token']); - $key = md5(uniqid((string) random_int(0, mt_getrandmax()), false)); + $dataToNormalize = $this->buildExportDataForNormalization( + $alias, + $dataCenters, + $dataExport, + $dataFormatter, + $savedExport, + ); - $this->redis->setEx($key, 3600, \serialize($parameters)); + $deleteAt = $this->clock->now()->add(new \DateInterval('P6M')); + $options = $this->exportConfigNormalizer->normalizeConfig($alias, $dataToNormalize); + $exportGeneration = match (null === $savedExport) { + true => new ExportGeneration($alias, $options, $deleteAt), + false => ExportGeneration::fromSavedExport($savedExport, $deleteAt, $options), + }; + + $this->entityManager->persist($exportGeneration); + $this->entityManager->flush(); + $this->messageBus->dispatch(new ExportRequestGenerationMessage($exportGeneration, $user)); // remove data from session + $this->session->remove('centers_step_raw'); $this->session->remove('export_step_raw'); $this->session->remove('export_step'); $this->session->remove('formatter_step_raw'); $this->session->remove('formatter_step'); - return $this->redirectToRoute('chill_main_export_download', [ - 'key' => $key, - 'alias' => $alias, - 'from_saved' => $savedExport?->getId(), - ]); + return $this->redirectToRoute('chill_main_export-generation_wait', ['id' => $exportGeneration->getId()]); } - private function rebuildData($key, ?SavedExport $savedExport) + /** + * Build the export form data into a way suitable for normalization. + * + * @param string $alias the export alias + * @param array $dataCenters Raw data from center step + * @param array $dataExport Raw data from export step + * @param array $dataFormatter Raw data from formatter step + */ + private function buildExportDataForNormalization(string $alias, ?array $dataCenters, array $dataExport, array $dataFormatter, ?SavedExport $savedExport): array { - $rawData = $this->rebuildRawData($key); - - $alias = $rawData['alias']; - if ($this->filterStatsByCenters) { - $formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], $savedExport); - $formCenters->submit($rawData['centers']); - $dataCenters = $formCenters->getData(); + $formCenters = $this->createCreateFormExport($alias, 'generate_centers', [], null); + $formCenters->submit($dataCenters); + $dataAsCollection = $formCenters->getData()['centers']; + $centers = $dataAsCollection['centers']; + $regroupments = $dataAsCollection['regroupments'] ?? []; + $dataCenters = [ + 'centers' => $centers instanceof Collection ? $centers->toArray() : $centers, + 'regroupments' => $regroupments instanceof Collection ? $regroupments->toArray() : $regroupments, + ]; } else { - $dataCenters = ['centers' => []]; + $dataCenters = ['centers' => [], 'regroupments' => []]; } $formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters, $savedExport); - $formExport->submit($rawData['export']); + $formExport->submit($dataExport); $dataExport = $formExport->getData(); - if (\count($rawData['formatter']) > 0) { + if (\count($dataFormatter) > 0) { $formFormatter = $this->createCreateFormExport( $alias, 'generate_formatter', $dataExport, $savedExport ); - $formFormatter->submit($rawData['formatter']); + $formFormatter->submit($dataFormatter); $dataFormatter = $formFormatter->getData(); } - return [$dataCenters, $dataExport, $dataFormatter ?? null]; + return [ + 'centers' => ['centers' => $dataCenters['centers'], 'regroupments' => $dataCenters['regroupments']], + 'export' => $dataExport['export']['export'] ?? [], + 'filters' => $dataExport['export']['filters'] ?? [], + 'aggregators' => $dataExport['export']['aggregators'] ?? [], + 'pick_formatter' => $dataExport['export']['pick_formatter']['alias'], + 'formatter' => $dataFormatter['formatter'] ?? [], + ]; } /** @@ -509,7 +391,7 @@ class ExportController extends AbstractController * * @return Response */ - private function selectCentersStep(Request $request, DirectExportInterface|ExportInterface $export, $alias, ?SavedExport $savedExport = null) + private function selectCentersStep(Request $request, DirectExportInterface|ExportInterface $export, $alias, ExportGeneration|SavedExport|null $savedExport = null) { if (!$this->filterStatsByCenters) { return $this->redirectToRoute('chill_main_export_new', [ @@ -522,7 +404,12 @@ class ExportController extends AbstractController /** @var ExportManager $exportManager */ $exportManager = $this->exportManager; - $form = $this->createCreateFormExport($alias, 'centers', [], $savedExport); + $form = $this->createCreateFormExport( + $alias, + 'centers', + $this->exportFormHelper->getDefaultData('centers', $export, []), + $savedExport + ); if (Request::METHOD_POST === $request->getMethod()) { $form->handleRequest($request); @@ -538,7 +425,7 @@ class ExportController extends AbstractController false === $exportManager->isGrantedForElement( $export, null, - $exportManager->getPickedCenters($data['centers']) + $this->exportFormHelper->getPickedCenters($data['centers']), ) ) { throw $this->createAccessDeniedException('you do not have access to this export for those centers'); @@ -548,7 +435,7 @@ class ExportController extends AbstractController 'centers_step_raw', $request->request->all() ); - $this->session->set('centers_step', $data); + $this->session->set('centers_step', $data['centers']); return $this->redirectToRoute('chill_main_export_new', [ 'step' => $this->getNextStep('centers', $export), @@ -632,43 +519,15 @@ class ExportController extends AbstractController } } - private function rebuildRawData(?string $key): array - { - if (null === $key) { - throw $this->createNotFoundException('key does not exists'); - } - - if (1 !== $this->redis->exists($key)) { - $this->addFlash('error', $this->translator->trans('This report is not available any more')); - - throw $this->createNotFoundException('key does not exists'); - } - - $serialized = $this->redis->get($key); - - if (false === $serialized) { - throw new \LogicException('the key could not be reached from redis'); - } - - $rawData = \unserialize($serialized); - - $this->logger->notice('[export] choices for an export unserialized', [ - 'key' => $key, - 'rawData' => json_encode($rawData, JSON_THROW_ON_ERROR), - ]); - - return $rawData; - } - - private function getSavedExportFromRequest(Request $request): ?SavedExport + private function getSavedExportFromRequest(Request $request): SavedExport|ExportGeneration|null { $savedExport = match ($savedExportId = $request->query->get('from_saved', '')) { '' => null, - default => $this->savedExportRepository->find($savedExportId), + default => $this->savedExportOrExportGenerationRepository->findById($savedExportId), }; - if (null !== $savedExport && !$this->security->isGranted(SavedExportVoter::EDIT, $savedExport)) { - throw new AccessDeniedHttpException('saved export edition not allowed'); + if (null !== $savedExport && !$this->security->isGranted(SavedExportVoter::GENERATE, $savedExport)) { + throw new AccessDeniedHttpException('saved export generation not allowed'); } return $savedExport; diff --git a/src/Bundle/ChillMainBundle/Controller/ExportGenerationController.php b/src/Bundle/ChillMainBundle/Controller/ExportGenerationController.php new file mode 100644 index 000000000..0e1b3b8c4 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/ExportGenerationController.php @@ -0,0 +1,64 @@ +security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException('Only users can download an export'); + } + + $export = $this->exportManager->getExport($exportGeneration->getExportAlias()); + + return new Response( + $this->twig->render('@ChillMain/ExportGeneration/wait.html.twig', ['exportGeneration' => $exportGeneration, 'export' => $export]), + ); + } + + #[Route('/api/1.0/main/export-generation/{id}/object', methods: ['GET'])] + public function objectStatus(ExportGeneration $exportGeneration): JsonResponse + { + if (!$this->security->isGranted('ROLE_USER')) { + throw new AccessDeniedHttpException('Only users can download an export'); + } + + return new JsonResponse( + $this->serializer->serialize( + $exportGeneration, + 'json', + [AbstractNormalizer::GROUPS => ['read']], + ), + json: true, + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/ExportGenerationCreateFromSavedExportController.php b/src/Bundle/ChillMainBundle/Controller/ExportGenerationCreateFromSavedExportController.php new file mode 100644 index 000000000..199b9c92d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/ExportGenerationCreateFromSavedExportController.php @@ -0,0 +1,62 @@ +security->isGranted(SavedExportVoter::GENERATE, $export)) { + throw new AccessDeniedHttpException('Not allowed to generate an export from this saved export'); + } + $user = $this->security->getUser(); + + if (!$user instanceof User) { + throw new AccessDeniedHttpException('Only users can create exports'); + } + + $exportGeneration = ExportGeneration::fromSavedExport($export, $this->clock->now()->add(new \DateInterval('P6M'))); + + $this->entityManager->persist($exportGeneration); + $this->entityManager->flush(); + + $this->messageBus->dispatch(new ExportRequestGenerationMessage($exportGeneration, $user)); + + return new JsonResponse( + $this->serializer->serialize($exportGeneration, 'json', ['groups' => ['read']]), + json: true, + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php b/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php new file mode 100644 index 000000000..4f3de7aef --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/ExportIndexController.php @@ -0,0 +1,62 @@ +security->getUser(); + if (!$user instanceof User) { + throw new AccessDeniedHttpException('Only regular user can see this page'); + } + + if (!$this->security->isGranted(ChillExportVoter::COMPOSE_EXPORT)) { + throw new AccessDeniedHttpException(sprintf('Require the %s role', ChillExportVoter::COMPOSE_EXPORT)); + } + + $exports = $this->exportManager->getExportsGrouped(true); + + $lastExecutions = []; + foreach ($this->exportManager->getExports() as $alias => $export) { + $lastExecutions[$alias] = $this->exportGenerationRepository->findExportGenerationByAliasAndUser($alias, $user, 5); + } + + return new Response( + $this->twig->render('@ChillMain/Export/layout.html.twig', [ + 'grouped_exports' => $exports, + 'last_executions' => $lastExecutions, + ]), + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php index 82d41a9e1..20b9bf1c5 100644 --- a/src/Bundle/ChillMainBundle/Controller/SavedExportController.php +++ b/src/Bundle/ChillMainBundle/Controller/SavedExportController.php @@ -11,13 +11,13 @@ declare(strict_types=1); namespace Chill\MainBundle\Controller; +use Chill\MainBundle\Entity\ExportGeneration; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Entity\User; -use Chill\MainBundle\Export\ExportInterface; +use Chill\MainBundle\Export\ExportDescriptionHelper; use Chill\MainBundle\Export\ExportManager; -use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Form\SavedExportType; -use Chill\MainBundle\Repository\SavedExportRepositoryInterface; +use Chill\MainBundle\Security\Authorization\ExportGenerationVoter; use Chill\MainBundle\Security\Authorization\SavedExportVoter; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -25,16 +25,28 @@ use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Security; +use Symfony\Component\Translation\TranslatableMessage; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; -class SavedExportController +final readonly class SavedExportController { - public function __construct(private readonly \Twig\Environment $templating, private readonly EntityManagerInterface $entityManager, private readonly ExportManager $exportManager, private readonly FormFactoryInterface $formFactory, private readonly SavedExportRepositoryInterface $savedExportRepository, private readonly Security $security, private readonly SessionInterface $session, private readonly TranslatorInterface $translator, private readonly UrlGeneratorInterface $urlGenerator) {} + public function __construct( + private \Twig\Environment $templating, + private EntityManagerInterface $entityManager, + private ExportManager $exportManager, + private FormFactoryInterface $formFactory, + private Security $security, + private TranslatorInterface $translator, + private UrlGeneratorInterface $urlGenerator, + private ExportDescriptionHelper $exportDescriptionHelper, + ) {} #[Route(path: '/{_locale}/exports/saved/{id}/delete', name: 'chill_main_export_saved_delete')] public function delete(SavedExport $savedExport, Request $request): Response @@ -51,7 +63,10 @@ class SavedExportController $this->entityManager->remove($savedExport); $this->entityManager->flush(); - $this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Export is deleted')); + $session = $request->getSession(); + if ($session instanceof Session) { + $session->getFlashBag()->add('success', new TranslatableMessage('saved_export.Export is deleted')); + } return new RedirectResponse( $this->urlGenerator->generate('chill_main_export_saved_list_my') @@ -69,6 +84,105 @@ class SavedExportController ); } + #[Route(path: '/exports/saved/create-from-export-generation/{id}/new', name: 'chill_main_export_saved_create_from_export_generation')] + public function createFromExportGeneration(ExportGeneration $exportGeneration, Request $request): Response + { + if (!$this->security->isGranted(ExportGenerationVoter::VIEW, $exportGeneration)) { + throw new AccessDeniedHttpException(); + } + + $user = $this->security->getUser(); + if (!$user instanceof User) { + throw new AccessDeniedHttpException('only regular user can create a saved export'); + } + + $export = $this->exportManager->getExport($exportGeneration->getExportAlias()); + $title = $export->getTitle() instanceof TranslatableInterface ? $export->getTitle()->trans($this->translator) : + $this->translator->trans($export->getTitle()); + + $savedExport = new SavedExport(); + $savedExport + ->setExportAlias($exportGeneration->getExportAlias()) + ->setUser($user) + ->setOptions($exportGeneration->getOptions()) + ->setTitle( + $request->query->has('title') ? $request->query->get('title') : $title + ); + + if ($exportGeneration->isLinkedToSavedExport()) { + $savedExport->setDescription($exportGeneration->getSavedExport()->getDescription()); + } else { + $savedExport->setDescription( + implode( + "\n", + array_map( + fn (string $item) => '- '.$item."\n", + $this->exportDescriptionHelper->describe($savedExport->getExportAlias(), $savedExport->getOptions(), includeExportTitle: false) + ) + ) + ); + } + + return $this->handleEdit($savedExport, $request, true); + } + + #[Route(path: '/exports/saved/duplicate-from-saved-export/{id}/new', name: 'chill_main_export_saved_duplicate')] + public function duplicate(SavedExport $previousSavedExport, Request $request): Response + { + $user = $this->security->getUser(); + if (!$user instanceof User) { + throw new AccessDeniedHttpException('only regular user can create a saved export'); + } + + if (!$this->security->isGranted(SavedExportVoter::EDIT, $previousSavedExport)) { + throw new AccessDeniedHttpException('Not allowed to edit this saved export'); + } + + $savedExport = new SavedExport(); + $savedExport + ->setExportAlias($previousSavedExport->getExportAlias()) + ->setUser($user) + ->setOptions($previousSavedExport->getOptions()) + ->setDescription($previousSavedExport->getDescription()) + ->setTitle( + $request->query->has('title') ? + $request->query->get('title') : + $previousSavedExport->getTitle().' ('.$this->translator->trans('saved_export.Duplicated').' '.(new \DateTimeImmutable('now'))->format('d-m-Y H:i:s').')' + ); + + return $this->handleEdit($savedExport, $request); + + } + + private function handleEdit(SavedExport $savedExport, Request $request, bool $showWarningAutoGeneratedDescription = false): Response + { + $form = $this->formFactory->create(SavedExportType::class, $savedExport); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->entityManager->persist($savedExport); + $this->entityManager->flush(); + + if (($session = $request->getSession()) instanceof Session) { + $session->getFlashBag()->add('success', new TranslatableMessage('saved_export.Saved export is saved!')); + } + + return new RedirectResponse( + $this->urlGenerator->generate('chill_main_export_saved_list_my'), + ); + } + + return new Response( + $this->templating->render( + '@ChillMain/SavedExport/new.html.twig', + [ + 'form' => $form->createView(), + 'showWarningAutoGeneratedDescription' => $showWarningAutoGeneratedDescription, + ], + ), + ); + } + #[Route(path: '/{_locale}/exports/saved/{id}/edit', name: 'chill_main_export_saved_edit')] public function edit(SavedExport $savedExport, Request $request): Response { @@ -83,10 +197,12 @@ class SavedExportController if ($form->isSubmitted() && $form->isValid()) { $this->entityManager->flush(); - $this->session->getFlashBag()->add('success', $this->translator->trans('saved_export.Saved export is saved!')); + if (($session = $request->getSession()) instanceof Session) { + $session->getFlashBag()->add('success', new TranslatableMessage('saved_export.Saved export is saved!')); + } return new RedirectResponse( - $this->urlGenerator->generate('chill_main_export_saved_list_my') + $this->urlGenerator->generate('chill_main_export_saved_list_my'), ); } @@ -95,45 +211,37 @@ class SavedExportController '@ChillMain/SavedExport/edit.html.twig', [ 'form' => $form->createView(), - ] - ) + ], + ), ); } - #[Route(path: '/{_locale}/exports/saved/my', name: 'chill_main_export_saved_list_my')] - public function list(): Response + #[Route(path: '/{_locale}/exports/saved/{savedExport}/edit-options/{exportGeneration}', name: 'chill_main_export_saved_options_edit')] + public function updateOptionsFromGeneration(SavedExport $savedExport, ExportGeneration $exportGeneration, Request $request): Response { - $user = $this->security->getUser(); - - if (!$this->security->isGranted('ROLE_USER') || !$user instanceof User) { - throw new AccessDeniedHttpException(); + if (!$this->security->isGranted(SavedExportVoter::DUPLICATE, $savedExport)) { + throw new AccessDeniedHttpException('You are not allowed to access this saved export'); } - $exports = $this->savedExportRepository->findByUser($user, ['title' => 'ASC']); - - // group by center - /** @var array $exportsGrouped */ - $exportsGrouped = []; - - foreach ($exports as $savedExport) { - $export = $this->exportManager->getExport($savedExport->getExportAlias()); - - $exportsGrouped[ - $export instanceof GroupedExportInterface - ? $this->translator->trans($export->getGroup()) : '_' - ][] = ['saved' => $savedExport, 'export' => $export]; + if (!$this->security->isGranted(ExportGenerationVoter::VIEW, $exportGeneration)) { + throw new AccessDeniedHttpException('You are not allowed to access this export generation'); } - ksort($exportsGrouped); + if ($savedExport->getExportAlias() !== $exportGeneration->getExportAlias()) { + throw new UnprocessableEntityHttpException('export alias does not match'); + } - return new Response( - $this->templating->render( - '@ChillMain/SavedExport/index.html.twig', - [ - 'grouped_exports' => $exportsGrouped, - 'total' => \count($exports), - ] - ) + $savedExport->setOptions($exportGeneration->getOptions()); + + $this->entityManager->flush(); + + $session = $request->getSession(); + if ($session instanceof Session) { + $session->getFlashBag()->add('success', new TranslatableMessage('saved_export.Options updated successfully')); + } + + return new RedirectResponse( + $this->urlGenerator->generate('chill_main_export_saved_edit', ['id' => $savedExport->getId()]), ); } } diff --git a/src/Bundle/ChillMainBundle/Controller/SavedExportIndexController.php b/src/Bundle/ChillMainBundle/Controller/SavedExportIndexController.php new file mode 100644 index 000000000..82e9913da --- /dev/null +++ b/src/Bundle/ChillMainBundle/Controller/SavedExportIndexController.php @@ -0,0 +1,104 @@ +security->getUser(); + + if (!$this->security->isGranted(ChillExportVoter::GENERATE_SAVED_EXPORT) || !$user instanceof User) { + throw new AccessDeniedHttpException(sprintf('Missing role: %s', ChillExportVoter::GENERATE_SAVED_EXPORT)); + } + + $filter = $this->buildFilter(); + + $filterParams = []; + if ('' !== $filter->getQueryString() && null !== $filter->getQueryString()) { + $filterParams[SavedExportRepositoryInterface::FILTER_DESCRIPTION | SavedExportRepositoryInterface::FILTER_TITLE] = $filter->getQueryString(); + } + + $exports = array_filter( + $this->savedExportRepository->findSharedWithUser($user, ['exportAlias' => 'ASC', 'title' => 'ASC'], filters: $filterParams), + fn (SavedExport $savedExport): bool => $this->security->isGranted(SavedExportVoter::GENERATE, $savedExport), + ); + + // group by center + /** @var array $exportsGrouped */ + $exportsGrouped = []; + + foreach ($exports as $savedExport) { + $export = $this->exportManager->getExport($savedExport->getExportAlias()); + + $exportsGrouped[$export instanceof GroupedExportInterface + ? $this->translator->trans($export->getGroup()) : '_'][] = ['saved' => $savedExport, 'export' => $export]; + } + + ksort($exportsGrouped); + + // get last executions + $lastExecutions = []; + foreach ($exports as $savedExport) { + $lastExecutions[$savedExport->getId()->toString()] = $this->exportGenerationRepository + ->findExportGenerationBySavedExportAndUser($savedExport, $user, 5); + } + + return new Response( + $this->templating->render( + '@ChillMain/SavedExport/index.html.twig', + [ + 'grouped_exports' => $exportsGrouped, + 'total' => \count($exports), + 'last_executions' => $lastExecutions, + 'filter' => $filter, + ], + ), + ); + } + + private function buildFilter(): FilterOrderHelper + { + $filter = $this->filterOrderHelperFactory->create('saved-export-index-filter'); + $filter->addSearchBox(); + + return $filter->build(); + } +} diff --git a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php index 3afdefbab..e71c662b3 100644 --- a/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php +++ b/src/Bundle/ChillMainBundle/DataFixtures/ORM/LoadUsers.php @@ -11,7 +11,10 @@ declare(strict_types=1); namespace Chill\MainBundle\DataFixtures\ORM; +use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\GroupCenter; +use Chill\MainBundle\Entity\PermissionsGroup; +use Chill\MainBundle\Entity\RoleScope; use Chill\MainBundle\Entity\User; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\OrderedFixtureInterface; @@ -62,6 +65,15 @@ class LoadUsers extends AbstractFixture implements ContainerAwareInterface, Orde public function load(ObjectManager $manager): void { + $roleScope = new RoleScope(); + $roleScope->setRole('CHILL_MAIN_COMPOSE_EXPORT'); + $permissionGroup = new PermissionsGroup(); + $permissionGroup->setName('export'); + $permissionGroup->addRoleScope($roleScope); + + $manager->persist($roleScope); + $manager->persist($permissionGroup); + foreach (self::$refs as $username => $params) { $user = new User(); @@ -81,7 +93,14 @@ class LoadUsers extends AbstractFixture implements ContainerAwareInterface, Orde ->setEmail(sprintf('%s@chill.social', \str_replace(' ', '', (string) $username))); foreach ($params['groupCenterRefs'] as $groupCenterRef) { - $user->addGroupCenter($this->getReference($groupCenterRef, GroupCenter::class)); + $user->addGroupCenter($gc = $this->getReference($groupCenterRef, GroupCenter::class)); + + $exportGroupCenter = new GroupCenter(); + $exportGroupCenter->setPermissionsGroup($permissionGroup); + $exportGroupCenter->setCenter($gc->getCenter()); + $manager->persist($exportGroupCenter); + + $user->addGroupCenter($exportGroupCenter); } echo 'Creating user '.$username."... \n"; diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php index 9fa3b0f42..6b33b998d 100644 --- a/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php +++ b/src/Bundle/ChillMainBundle/DependencyInjection/ChillMainExtension.php @@ -78,6 +78,7 @@ use Chill\MainBundle\Form\RegroupmentType; use Chill\MainBundle\Form\UserGroupType; use Chill\MainBundle\Form\UserJobType; use Chill\MainBundle\Form\UserType; +use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType; use Ramsey\Uuid\Doctrine\UuidType; use Symfony\Component\Config\FileLocator; @@ -332,6 +333,9 @@ class ChillMainExtension extends Extension implements 'strategy' => 'unanimous', 'allow_if_all_abstain' => false, ], + 'role_hierarchy' => [ + ChillExportVoter::COMPOSE_EXPORT => ChillExportVoter::GENERATE_SAVED_EXPORT, + ], ]); // add crud api diff --git a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php b/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php deleted file mode 100644 index c2126da1e..000000000 --- a/src/Bundle/ChillMainBundle/DependencyInjection/CompilerPass/ExportsCompilerPass.php +++ /dev/null @@ -1,102 +0,0 @@ -has(ExportManager::class)) { - throw new \LogicException('service '.ExportManager::class.' is not defined. It is required by ExportsCompilerPass'); - } - - $chillManagerDefinition = $container->findDefinition( - ExportManager::class - ); - - $this->compileFormatters($chillManagerDefinition, $container); - $this->compileExportElementsProvider($chillManagerDefinition, $container); - } - - private function compileExportElementsProvider( - Definition $chillManagerDefinition, - ContainerBuilder $container, - ) { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export_elements_provider' - ); - - $knownAliases = []; - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes['prefix'])) { - throw new \LogicException("the 'prefix' attribute is missing in your service '{$id}' definition"); - } - - if (array_search($attributes['prefix'], $knownAliases, true)) { - throw new \LogicException('There is already a chill.export_elements_provider service with prefix '.$attributes['prefix'].'. Choose another prefix.'); - } - $knownAliases[] = $attributes['prefix']; - - $chillManagerDefinition->addMethodCall( - 'addExportElementsProvider', - [new Reference($id), $attributes['prefix']] - ); - } - } - } - - private function compileFormatters( - Definition $chillManagerDefinition, - ContainerBuilder $container, - ) { - $taggedServices = $container->findTaggedServiceIds( - 'chill.export_formatter' - ); - - $knownAliases = []; - - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - if (!isset($attributes['alias'])) { - throw new \LogicException("the 'alias' attribute is missing in your service '{$id}' definition"); - } - - if (array_search($attributes['alias'], $knownAliases, true)) { - throw new \LogicException('There is already a chill.export_formatter service with alias '.$attributes['alias'].'. Choose another alias.'); - } - $knownAliases[] = $attributes['alias']; - - $chillManagerDefinition->addMethodCall( - 'addFormatter', - [new Reference($id), $attributes['alias']] - ); - } - } - } -} diff --git a/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php b/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php index 12a745a0c..3e1086409 100644 --- a/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php +++ b/src/Bundle/ChillMainBundle/Doctrine/DQL/Unaccent.php @@ -23,12 +23,12 @@ class Unaccent extends FunctionNode { private ?\Doctrine\ORM\Query\AST\Node $string = null; - public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker): string { return 'UNACCENT('.$this->string->dispatch($sqlWalker).')'; } - public function parse(\Doctrine\ORM\Query\Parser $parser) + public function parse(\Doctrine\ORM\Query\Parser $parser): void { $parser->match(\Doctrine\ORM\Query\TokenType::T_IDENTIFIER); $parser->match(\Doctrine\ORM\Query\TokenType::T_OPEN_PARENTHESIS); diff --git a/src/Bundle/ChillMainBundle/Entity/ExportGeneration.php b/src/Bundle/ChillMainBundle/Entity/ExportGeneration.php new file mode 100644 index 000000000..4cfbda0f6 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Entity/ExportGeneration.php @@ -0,0 +1,150 @@ + ExportGeneration::class])] +class ExportGeneration implements TrackCreationInterface +{ + use TrackCreationTrait; + + #[ORM\Id] + #[ORM\Column(type: 'uuid', unique: true)] + #[Serializer\Groups(['read'])] + private UuidInterface $id; + + + #[ORM\ManyToOne(targetEntity: StoredObject::class, cascade: ['persist', 'refresh'])] + #[ORM\JoinColumn(nullable: false)] + #[Serializer\Groups(['read'])] + private StoredObject $storedObject; + + public function __construct( + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::TEXT, nullable: false, options: ['default' => ''])] + #[Serializer\Groups(['read'])] + private string $exportAlias, + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::JSON, nullable: false, options: ['default' => '[]'])] + private array $options = [], + #[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)] + private ?\DateTimeImmutable $deleteAt = null, + + /** + * The related saved export. + * + * Note that, in some case, the options of this ExportGeneration are not equals to the options of the saved export. + * This happens when the options of the saved export are updated. + */ + #[ORM\ManyToOne(targetEntity: SavedExport::class)] + #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')] + private ?SavedExport $savedExport = null, + ) { + $this->id = Uuid::uuid4(); + $this->storedObject = new StoredObject(StoredObject::STATUS_PENDING); + } + + public function setDeleteAt(?\DateTimeImmutable $deleteAt): ExportGeneration + { + $this->deleteAt = $deleteAt; + + return $this; + } + + public function getDeleteAt(): ?\DateTimeImmutable + { + return $this->deleteAt; + } + + public function getId(): UuidInterface + { + return $this->id; + } + + public function getStoredObject(): StoredObject + { + return $this->storedObject; + } + + public function getExportAlias(): string + { + return $this->exportAlias; + } + + public function getOptions(): array + { + return $this->options; + } + + public function getSavedExport(): ?SavedExport + { + return $this->savedExport; + } + + #[Serializer\Groups(['read'])] + #[Serializer\SerializedName('status')] + public function getStatus(): string + { + return $this->getStoredObject()->getStatus(); + } + + public function setSavedExport(SavedExport $savedExport): self + { + $this->savedExport = $savedExport; + + return $this; + } + + public function isLinkedToSavedExport(): bool + { + return null !== $this->savedExport; + } + + /** + * Compares the options of the saved export and the current export generation. + * + * Return false if the current export generation's options are not equal to the one in the saved export. This may + * happens when we update the configuration of a saved export. + */ + public function isConfigurationDifferentFromSavedExport(): bool + { + if (!$this->isLinkedToSavedExport()) { + return false; + } + + return $this->savedExport->getOptions() !== $this->getOptions(); + } + + public static function fromSavedExport(SavedExport $savedExport, ?\DateTimeImmutable $deletedAt = null, ?array $overrideOptions = null): self + { + $generation = new self($savedExport->getExportAlias(), $overrideOptions ?? $savedExport->getOptions(), $deletedAt, $savedExport); + $generation->getStoredObject()->setTitle($savedExport->getTitle()); + + return $generation; + } +} diff --git a/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php index 481b530d9..2d7028d21 100644 --- a/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php +++ b/src/Bundle/ChillMainBundle/Entity/GeographicalUnit/SimpleGeographicalUnitDTO.php @@ -50,4 +50,9 @@ class SimpleGeographicalUnitDTO #[Serializer\Groups(['read'])] public int $layerId, ) {} + + public function getId(): int + { + return $this->id; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/Regroupment.php b/src/Bundle/ChillMainBundle/Entity/Regroupment.php index 389eceb31..d28784210 100644 --- a/src/Bundle/ChillMainBundle/Entity/Regroupment.php +++ b/src/Bundle/ChillMainBundle/Entity/Regroupment.php @@ -102,4 +102,22 @@ class Regroupment return $this; } + + /** + * Return true if the given center is contained into this regroupment. + */ + public function containsCenter(Center $center): bool + { + return $this->centers->contains($center); + } + + /** + * Return true if at least one of the given centers is contained into this regroupment. + * + * @param list
    $centers + */ + public function containsAtLeastOneCenter(array $centers): bool + { + return array_reduce($centers, fn (bool $carry, Center $center) => $carry || $this->containsCenter($center), false); + } } diff --git a/src/Bundle/ChillMainBundle/Entity/SavedExport.php b/src/Bundle/ChillMainBundle/Entity/SavedExport.php index eec6b83e4..7e0306a7e 100644 --- a/src/Bundle/ChillMainBundle/Entity/SavedExport.php +++ b/src/Bundle/ChillMainBundle/Entity/SavedExport.php @@ -15,6 +15,9 @@ use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; use Chill\MainBundle\Doctrine\Model\TrackCreationTrait; use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; use Chill\MainBundle\Doctrine\Model\TrackUpdateTrait; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\ReadableCollection; use Doctrine\ORM\Mapping as ORM; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\UuidInterface; @@ -50,9 +53,25 @@ class SavedExport implements TrackCreationInterface, TrackUpdateInterface #[ORM\ManyToOne(targetEntity: User::class)] private User $user; + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: User::class)] + #[ORM\JoinTable(name: 'chill_main_saved_export_users')] + private Collection $sharedWithUsers; + + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: UserGroup::class)] + #[ORM\JoinTable(name: 'chill_main_saved_export_usergroups')] + private Collection $sharedWithGroups; + public function __construct() { $this->id = Uuid::uuid4(); + $this->sharedWithUsers = new ArrayCollection(); + $this->sharedWithGroups = new ArrayCollection(); } public function getDescription(): string @@ -119,4 +138,71 @@ class SavedExport implements TrackCreationInterface, TrackUpdateInterface return $this; } + + public function addShare(User|UserGroup $shareUser): SavedExport + { + if ($shareUser instanceof User) { + if (!$this->sharedWithUsers->contains($shareUser)) { + $this->sharedWithUsers->add($shareUser); + } + } else { + if (!$this->sharedWithGroups->contains($shareUser)) { + $this->sharedWithGroups->add($shareUser); + } + } + + return $this; + } + + public function removeShare(User|UserGroup $shareUser): SavedExport + { + if ($shareUser instanceof User) { + $this->sharedWithUsers->removeElement($shareUser); + } else { + $this->sharedWithGroups->removeElement($shareUser); + } + + return $this; + } + + /** + * @return ReadableCollection + */ + public function getShare(): ReadableCollection + { + return new ArrayCollection([ + ...$this->sharedWithUsers->toArray(), + ...$this->sharedWithGroups->toArray(), + ]); + } + + /** + * Return true if shared with at least one user or one group. + */ + public function isShared(): bool + { + return $this->sharedWithUsers->count() > 0 || $this->sharedWithGroups->count() > 0; + } + + /** + * Determines if the user is shared with either directly or through a group. + * + * @param User $user the user to check + * + * @return bool returns true if the user is shared with directly or via group, otherwise false + */ + public function isSharedWithUser(User $user): bool + { + if ($this->sharedWithUsers->contains($user)) { + return true; + } + + foreach ($this->sharedWithGroups as $group) { + if ($group->contains($user)) { + return true; + } + } + + return false; + } } diff --git a/src/Bundle/ChillMainBundle/Entity/UserGroup.php b/src/Bundle/ChillMainBundle/Entity/UserGroup.php index d720d4ffc..39df04b31 100644 --- a/src/Bundle/ChillMainBundle/Entity/UserGroup.php +++ b/src/Bundle/ChillMainBundle/Entity/UserGroup.php @@ -21,6 +21,19 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; use Symfony\Component\Validator\Constraints as Assert; +/** + * Represents a user group entity in the system. + * + * This class is used for managing user groups, including their relationships + * with users, administrative users, and additional metadata such as colors and labels. + * + * Groups may be configured to have mutual exclusion properties based on an + * exclusion key. This ensures that groups sharing the same key cannot coexist + * in certain relationship contexts. + * + * Groups may be related to a UserJob. In that case, a cronjob task ensure that the members of the groups are + * automatically synced with this group. Such groups are also automatically created by the cronjob. + */ #[ORM\Entity] #[ORM\Table(name: 'chill_main_user_group')] // this discriminator key is required for automated denormalization @@ -71,6 +84,13 @@ class UserGroup #[Assert\Email] private string $email = ''; + /** + * UserJob to which the group is related. + */ + #[ORM\ManyToOne(targetEntity: UserJob::class)] + #[ORM\JoinColumn(nullable: true)] + private ?UserJob $userJob = null; + public function __construct() { $this->adminUsers = new ArrayCollection(); @@ -209,6 +229,21 @@ class UserGroup return '' !== $this->email; } + public function hasUserJob(): bool + { + return null !== $this->userJob; + } + + public function getUserJob(): ?UserJob + { + return $this->userJob; + } + + public function setUserJob(?UserJob $userJob): void + { + $this->userJob = $userJob; + } + /** * Checks if the current object is an instance of the UserGroup class. * diff --git a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php index e849dec07..27ce09746 100644 --- a/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php +++ b/src/Bundle/ChillMainBundle/Export/AggregatorInterface.php @@ -12,25 +12,42 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Contracts\Translation\TranslatableInterface; /** * Interface for Aggregators. * * Aggregators gather result of a query. Most of the time, it will add * a GROUP BY clause. + * + * @template D of array */ interface AggregatorInterface extends ModifierInterface { /** * Add a form to collect data from the user. */ - public function buildForm(FormBuilderInterface $builder); + public function buildForm(FormBuilderInterface $builder): void; /** * Get the default data, that can be use as "data" for the form. + * + * @return D */ public function getFormDefaultData(): array; + /** + * @param D $formData + */ + public function normalizeFormData(array $formData): array; + + /** + * @return D + */ + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getNormalizationVersion(): int; + /** * get a callable which will be able to transform the results into * viewable and understable string. @@ -74,9 +91,9 @@ interface AggregatorInterface extends ModifierInterface * @param string $key The column key, as added in the query * @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') * - * @return \Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` + * @return callable(mixed $value): (string|int|\DateTimeInterface|TranslatableInterface) where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` */ - public function getLabels($key, array $values, mixed $data); + public function getLabels(string $key, array $values, mixed $data): callable; /** * give the list of keys the current export added to the queryBuilder in @@ -85,7 +102,9 @@ interface AggregatorInterface extends ModifierInterface * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, * this function will return `array('count_id')`. * - * @param mixed[] $data the data from the export's form (added by self::buildForm) + * @param D $data the data from the export's form (added by self::buildForm) + * + * @return list */ - public function getQueryKeys($data); + public function getQueryKeys(array $data): array; } diff --git a/src/Bundle/ChillMainBundle/Export/Cronjob/RemoveExpiredExportGenerationCronJob.php b/src/Bundle/ChillMainBundle/Export/Cronjob/RemoveExpiredExportGenerationCronJob.php new file mode 100644 index 000000000..2f15bf095 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Cronjob/RemoveExpiredExportGenerationCronJob.php @@ -0,0 +1,52 @@ +getLastStart()->getTimestamp() < $this->clock->now()->sub(new \DateInterval('PT24H'))->getTimestamp(); + } + + public function getKey(): string + { + return self::KEY; + } + + public function run(array $lastExecutionData): ?array + { + $now = $this->clock->now(); + + foreach ($this->exportGenerationRepository->findExpiredExportGeneration($now) as $exportGeneration) { + $this->messageBus->dispatch(new Envelope(new RemoveExportGenerationMessage($exportGeneration))); + } + + return ['last-deletion' => $now->getTimestamp()]; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php b/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php index 0949b0be0..0ca0b4a43 100644 --- a/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/DirectExportInterface.php @@ -28,8 +28,16 @@ interface DirectExportInterface extends ExportElementInterface /** * Generate the export. + * + * @return FormattedExportGeneration */ - public function generate(array $acl, array $data = []): Response; + public function generate(array $acl, array $data, ExportGenerationContext $context): Response|FormattedExportGeneration; + + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getNormalizationVersion(): int; /** * get a description, which will be used in UI (and translated). diff --git a/src/Bundle/ChillMainBundle/Export/Exception/ExportGenerationException.php b/src/Bundle/ChillMainBundle/Export/Exception/ExportGenerationException.php new file mode 100644 index 000000000..b8f12c6bf --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Exception/ExportGenerationException.php @@ -0,0 +1,14 @@ +, regroupments: list}, export: array{form: array, version: int}, filters: array, version: int}>, aggregators: array, version: int}>, pick_formatter: string, formatter: array{form: array, version: int}} + */ +class ExportConfigNormalizer +{ + public function __construct( + private readonly ExportManager $exportManager, + private readonly CenterRepositoryInterface $centerRepository, + private readonly RegroupmentRepositoryInterface $regroupmentRepository, + ) {} + + /** + * @return NormalizedData + */ + public function normalizeConfig(string $exportAlias, array $formData): array + { + $exportData = $formData[ExportType::EXPORT_KEY]; + $export = $this->exportManager->getExport($exportAlias); + + $serialized = [ + 'export' => [ + 'form' => $export->normalizeFormData($exportData), + 'version' => $export->getNormalizationVersion(), + ], + ]; + + $serialized['centers'] = [ + 'centers' => array_values(array_map(static fn (Center $center) => $center->getId(), $formData['centers']['centers'] ?? [])), + 'regroupments' => array_values(array_map(static fn (Regroupment $group) => $group->getId(), $formData['centers']['regroupments'] ?? [])), + ]; + + $filtersSerialized = []; + foreach ($formData[ExportType::FILTER_KEY] as $alias => $filterData) { + $filter = $this->exportManager->getFilter($alias); + $filtersSerialized[$alias][FilterType::ENABLED_FIELD] = (bool) $filterData[FilterType::ENABLED_FIELD]; + if ($filterData[FilterType::ENABLED_FIELD]) { + $filtersSerialized[$alias]['form'] = $filter->normalizeFormData($filterData['form']); + $filtersSerialized[$alias]['version'] = $filter->getNormalizationVersion(); + } + } + $serialized['filters'] = $filtersSerialized; + + $aggregatorsSerialized = []; + foreach ($formData[ExportType::AGGREGATOR_KEY] as $alias => $aggregatorData) { + $aggregator = $this->exportManager->getAggregator($alias); + $aggregatorsSerialized[$alias][FilterType::ENABLED_FIELD] = (bool) $aggregatorData[AggregatorType::ENABLED_FIELD]; + if ($aggregatorData[AggregatorType::ENABLED_FIELD]) { + $aggregatorsSerialized[$alias]['form'] = $aggregator->normalizeFormData($aggregatorData['form']); + $aggregatorsSerialized[$alias]['version'] = $aggregator->getNormalizationVersion(); + } + } + $serialized['aggregators'] = $aggregatorsSerialized; + + $serialized['pick_formatter'] = $formData['pick_formatter']; + $formatter = $this->exportManager->getFormatter($formData['pick_formatter']); + $serialized['formatter']['form'] = $formatter->normalizeFormData($formData['formatter']); + $serialized['formatter']['version'] = $formatter->getNormalizationVersion(); + + return $serialized; + } + + /** + * @param NormalizedData $serializedData + * @param bool $replaceDisabledByDefaultData if true, when a filter is not enabled, the formDefaultData is set + */ + public function denormalizeConfig(string $exportAlias, array $serializedData, bool $replaceDisabledByDefaultData = false): array + { + $export = $this->exportManager->getExport($exportAlias); + $formater = $this->exportManager->getFormatter($serializedData['pick_formatter']); + + $filtersConfig = []; + foreach ($serializedData['filters'] as $alias => $filterData) { + $aggregator = $this->exportManager->getFilter($alias); + $filtersConfig[$alias]['enabled'] = $filterData['enabled']; + + if ($filterData['enabled']) { + $filtersConfig[$alias]['form'] = $aggregator->denormalizeFormData($filterData['form'], $filterData['version']); + } elseif ($replaceDisabledByDefaultData) { + $filtersConfig[$alias]['form'] = $aggregator->getFormDefaultData(); + } + } + + $aggregatorsConfig = []; + foreach ($serializedData['aggregators'] as $alias => $aggregatorData) { + $aggregator = $this->exportManager->getAggregator($alias); + $aggregatorsConfig[$alias]['enabled'] = $aggregatorData['enabled']; + + if ($aggregatorData['enabled']) { + $aggregatorsConfig[$alias]['form'] = $aggregator->denormalizeFormData($aggregatorData['form'], $aggregatorData['version']); + } elseif ($replaceDisabledByDefaultData) { + $aggregatorsConfig[$alias]['form'] = $aggregator->getFormDefaultData(); + } + } + + return [ + 'export' => $export->denormalizeFormData($serializedData['export']['form'], $serializedData['export']['version']), + 'filters' => $filtersConfig, + 'aggregators' => $aggregatorsConfig, + 'pick_formatter' => $serializedData['pick_formatter'], + 'formatter' => $formater->denormalizeFormData($serializedData['formatter']['form'], $serializedData['formatter']['version']), + 'centers' => [ + 'centers' => array_values(array_filter(array_map(fn (int $id) => $this->centerRepository->find($id), $serializedData['centers']['centers']), fn ($item) => null !== $item)), + 'regroupments' => array_values(array_filter(array_map(fn (int $id) => $this->regroupmentRepository->find($id), $serializedData['centers']['regroupments']), fn ($item) => null !== $item)), + ], + ]; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportConfigProcessor.php b/src/Bundle/ChillMainBundle/Export/ExportConfigProcessor.php new file mode 100644 index 000000000..d898ac1af --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportConfigProcessor.php @@ -0,0 +1,49 @@ + + */ + public function retrieveUsedAggregators(mixed $data): iterable + { + if (null === $data) { + return []; + } + + foreach ($data as $alias => $aggregatorData) { + if ($this->exportManager->hasAggregator($alias) && true === $aggregatorData['enabled']) { + yield $alias => $this->exportManager->getAggregator($alias); + } + } + } + + /** + * @return iterable + */ + public function retrieveUsedFilters(mixed $data): iterable + { + if (null === $data) { + return []; + } + + foreach ($data as $alias => $filterData) { + if ($this->exportManager->hasFilter($alias) && true === $filterData['enabled']) { + yield $alias => $this->exportManager->getFilter($alias); + } + } + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportDataNormalizerTrait.php b/src/Bundle/ChillMainBundle/Export/ExportDataNormalizerTrait.php new file mode 100644 index 000000000..147b5844f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportDataNormalizerTrait.php @@ -0,0 +1,189 @@ +|null $entity the entity or collection of entities to normalize + * + * @return array|int|string Returns the identifier(s) of the entity or entities. If an array of entities is provided, + * an array of their identifiers is returned. If a single entity is provided, its identifier + * is returned. If null, returns an empty value. + */ + private function normalizeDoctrineEntity(object|array|null $entity): array|int|string + { + if (is_array($entity)) { + return array_values(array_filter(array_map(static fn (object $entity) => $entity->getId(), $entity), fn ($value) => null !== $value)); + } + if ($entity instanceof Collection) { + return $this->normalizeDoctrineEntity($entity->toArray()); + } + + return $entity?->getId(); + } + + /** + * Denormalizes a Doctrine entity by fetching it from the provided repository based on the given ID(s). + * + * @param list|int|string $id the identifier(s) of the entity to find + * @param ObjectRepository $repository the Doctrine repository to query + * + * @return object|array the found entity or an array of entities if multiple IDs are provided + * + * @throws \UnexpectedValueException when the entity with the given ID does not exist + */ + private function denormalizeDoctrineEntity(array|int|string $id, ObjectRepository $repository): object|array + { + if (is_array($id)) { + if ([] === $id) { + return []; + } + + return $repository->findBy(['id' => $id]); + } + + if (null === $object = $repository->find($id)) { + throw new \UnexpectedValueException(sprintf('Object with id "%s" does not exist.', $id)); + } + + return $object; + } + + /** + * Normalizer the "user or me" values. + * + * @param 'me'|User|iterable<'me'|User> $user + * + * @return int|'me'|list<'me'|int> + */ + private function normalizeUserOrMe(string|User|iterable $user): int|string|array + { + if (is_iterable($user)) { + $users = []; + foreach ($user as $u) { + $users[] = $this->normalizeUserOrMe($u); + } + + return $users; + } + + if ('me' === $user) { + return $user; + } + + return $user->getId(); + } + + /** + * @param 'me'|int|iterable<'me'|int> $userId + * + * @return 'me'|User|array|null + */ + private function denormalizeUserOrMe(string|int|iterable $userId, UserRepositoryInterface $userRepository): string|User|array|null + { + if (is_iterable($userId)) { + $users = []; + foreach ($userId as $id) { + $users[] = $this->denormalizeUserOrMe($id, $userRepository); + } + + return $users; + } + + if ('me' === $userId) { + return 'me'; + } + + return $userRepository->find($userId); + } + + /** + * @param 'me'|User|iterable<'me'|User> $user + * + * @return User|list + */ + private function userOrMe(string|User|iterable $user, ExportGenerationContext $context): User|array + { + if (is_iterable($user)) { + $users = []; + foreach ($user as $u) { + $users[] = $this->userOrMe($u, $context); + } + + return array_values( + array_filter($users, static fn (?User $user) => null !== $user) + ); + } + + if ('me' === $user) { + return $context->byUser; + } + + return $user; + } + + /** + * Normalizes a provided date into a specific string format. + * + * @param \DateTimeImmutable|\DateTime $date the date instance to normalize + * + * @return string a formatted string containing the type and formatted date + */ + private function normalizeDate(\DateTimeImmutable|\DateTime $date): string + { + return sprintf( + '%s,%s', + $date instanceof \DateTimeImmutable ? 'imm1' : 'mut1', + $date->format('d-m-Y-H:i:s.u e'), + ); + } + + /** + * Denormalizes a string back into a DateTime instance. + * + * The string is expected to contain a kind selector (e.g., 'imm1' or 'mut1') + * to determine the type of DateTime object (immutable or mutable) followed by a date format. + * + * @param string $date the string to be denormalized, containing the kind selector and formatted date + * + * @return \DateTimeImmutable|\DateTime a DateTime instance created from the given string + * + * @throws \UnexpectedValueException if the kind selector or date format is invalid + */ + private function denormalizeDate(string $date): \DateTimeImmutable|\DateTime + { + $format = 'd-m-Y-H:i:s.u e'; + + $denormalized = match (substr($date, 0, 4)) { + 'imm1' => \DateTimeImmutable::createFromFormat($format, substr($date, 5)), + 'mut1' => \DateTime::createFromFormat($format, substr($date, 5)), + default => throw new \UnexpectedValueException(sprintf('Unexpected format for the kind selector: %s', substr($date, 0, 4))), + }; + + if (false === $denormalized) { + throw new \UnexpectedValueException(sprintf('Unexpected date format: %s', substr($date, 5))); + } + + return $denormalized; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportDescriptionHelper.php b/src/Bundle/ChillMainBundle/Export/ExportDescriptionHelper.php new file mode 100644 index 000000000..3016f2f79 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportDescriptionHelper.php @@ -0,0 +1,74 @@ + + */ + public function describe(string $exportAlias, array $exportOptions, bool $includeExportTitle = true): array + { + $output = []; + $denormalized = $this->exportConfigNormalizer->denormalizeConfig($exportAlias, $exportOptions); + $user = $this->security->getUser(); + + if ($includeExportTitle) { + $output[] = $this->trans($this->exportManager->getExport($exportAlias)->getTitle()); + } + + if (!$user instanceof User) { + return $output; + } + $context = new ExportGenerationContext($user); + + foreach ($this->exportConfigProcessor->retrieveUsedFilters($denormalized['filters']) as $name => $filter) { + $output[] = $this->trans($filter->describeAction($denormalized['filters'][$name]['form'], $context)); + } + + foreach ($this->exportConfigProcessor->retrieveUsedAggregators($denormalized['aggregators']) as $name => $aggregator) { + $output[] = $this->trans($aggregator->getTitle()); + } + + return $output; + } + + private function trans(string|TranslatableInterface|array $translatable): string + { + if (is_string($translatable)) { + return $this->translator->trans($translatable); + } + + if ($translatable instanceof TranslatableInterface) { + return $translatable->trans($this->translator); + } + + // array case + return $this->translator->trans($translatable[0], $translatable[1] ?? []); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportElementInterface.php b/src/Bundle/ChillMainBundle/Export/ExportElementInterface.php index 49b2500f0..e8018bb1a 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportElementInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportElementInterface.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Symfony\Contracts\Translation\TranslatableInterface; + /** * The common methods between different object used to build export (i.e. : ExportInterface, * FilterInterface, AggregatorInterface). @@ -19,8 +21,6 @@ interface ExportElementInterface { /** * get a title, which will be used in UI (and translated). - * - * @return string */ - public function getTitle(); + public function getTitle(): string|TranslatableInterface; } diff --git a/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php b/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php index d4f58a570..d5c5b435c 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportElementValidatedInterface.php @@ -31,5 +31,5 @@ interface ExportElementValidatedInterface * validate the form's data and, if required, build a contraint * violation on the data. */ - public function validateForm(mixed $data, ExecutionContextInterface $context); + public function validateForm(mixed $data, ExecutionContextInterface $context): void; } diff --git a/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php b/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php index 43e0e506a..f92e9bd50 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportElementsProviderInterface.php @@ -21,7 +21,7 @@ namespace Chill\MainBundle\Export; interface ExportElementsProviderInterface { /** - * @return ExportElementInterface[] + * @return iterable */ - public function getExportElements(); + public function getExportElements(): iterable; } diff --git a/src/Bundle/ChillMainBundle/Export/ExportFormHelper.php b/src/Bundle/ChillMainBundle/Export/ExportFormHelper.php index ce43c230a..7bc7e9fc5 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportFormHelper.php +++ b/src/Bundle/ChillMainBundle/Export/ExportFormHelper.php @@ -11,27 +11,28 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\ExportGeneration; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Form\Type\Export\FilterType; -use Chill\MainBundle\Form\Type\Export\FormatterType; -use Chill\MainBundle\Form\Type\Export\PickCenterType; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; -use Symfony\Component\Form\Extension\Core\Type\FormType; -use Symfony\Component\Form\FormFactoryInterface; +use Chill\MainBundle\Service\Regroupement\CenterRegroupementResolver; +use Doctrine\Common\Collections\Collection; final readonly class ExportFormHelper { public function __construct( private AuthorizationHelperForCurrentUserInterface $authorizationHelper, private ExportManager $exportManager, - private FormFactoryInterface $formFactory, + private ExportConfigNormalizer $configNormalizer, + private CenterRegroupementResolver $centerRegroupementResolver, ) {} public function getDefaultData(string $step, DirectExportInterface|ExportInterface $export, array $options = []): array { return match ($step) { - 'centers', 'generate_centers' => ['centers' => $this->authorizationHelper->getReachableCenters($export->requiredRole())], + 'centers', 'generate_centers' => ['centers' => $this->authorizationHelper->getReachableCenters($export->requiredRole()), 'regroupments' => []], 'export', 'generate_export' => ['export' => $this->getDefaultDataStepExport($export, $options)], 'formatter', 'generate_formatter' => ['formatter' => $this->getDefaultDataStepFormatter($options)], default => throw new \LogicException('step not allowed : '.$step), @@ -91,80 +92,68 @@ final readonly class ExportFormHelper } public function savedExportDataToFormData( - SavedExport $savedExport, + ExportGeneration|SavedExport $savedExport, string $step, - array $formOptions = [], ): array { return match ($step) { 'centers', 'generate_centers' => $this->savedExportDataToFormDataStepCenter($savedExport), - 'export', 'generate_export' => $this->savedExportDataToFormDataStepExport($savedExport, $formOptions), - 'formatter', 'generate_formatter' => $this->savedExportDataToFormDataStepFormatter($savedExport, $formOptions), + 'export', 'generate_export' => $this->savedExportDataToFormDataStepExport($savedExport), + 'formatter', 'generate_formatter' => $this->savedExportDataToFormDataStepFormatter($savedExport), default => throw new \LogicException('this step is not allowed: '.$step), }; } private function savedExportDataToFormDataStepCenter( - SavedExport $savedExport, + ExportGeneration|SavedExport $savedExport, ): array { - $builder = $this->formFactory - ->createBuilder( - FormType::class, - [], - [ - 'csrf_protection' => false, - ] - ); - $builder->add('centers', PickCenterType::class, [ - 'export_alias' => $savedExport->getExportAlias(), - ]); - $form = $builder->getForm(); - $form->submit($savedExport->getOptions()['centers']); - - return $form->getData(); + return [ + 'centers' => $this->configNormalizer->denormalizeConfig($savedExport->getExportAlias(), $savedExport->getOptions(), true)['centers'], + ]; } private function savedExportDataToFormDataStepExport( - SavedExport $savedExport, - array $formOptions, + ExportGeneration|SavedExport $savedExport, ): array { - $builder = $this->formFactory - ->createBuilder( - FormType::class, - [], - [ - 'csrf_protection' => false, - ] - ); + $data = $this->configNormalizer->denormalizeConfig($savedExport->getExportAlias(), $savedExport->getOptions(), true); - $builder->add('export', ExportType::class, [ - 'export_alias' => $savedExport->getExportAlias(), ...$formOptions, - ]); - $form = $builder->getForm(); - $form->submit($savedExport->getOptions()['export']); - - return $form->getData(); + return [ + 'export' => [ + 'export' => $data['export'], + 'filters' => $data['filters'], + 'pick_formatter' => ['alias' => $data['pick_formatter']], + 'aggregators' => $data['aggregators'], + ], + ]; } private function savedExportDataToFormDataStepFormatter( - SavedExport $savedExport, - array $formOptions, + ExportGeneration|SavedExport $savedExport, ): array { - $builder = $this->formFactory - ->createBuilder( - FormType::class, - [], - [ - 'csrf_protection' => false, - ] - ); + $data = $this->configNormalizer->denormalizeConfig($savedExport->getExportAlias(), $savedExport->getOptions(), true); - $builder->add('formatter', FormatterType::class, [ - 'export_alias' => $savedExport->getExportAlias(), ...$formOptions, - ]); - $form = $builder->getForm(); - $form->submit($savedExport->getOptions()['formatter']); + return [ + 'formatter' => $data['formatter'], + ]; + } - return $form->getData(); + /** + * Get the Center picked by the user for this export. The data are + * extracted from the PickCenterType data. + * + * @param array $data the data as given by the @see{Chill\MainBundle\Form\Type\Export\PickCenterType} + * + * @return list
    + */ + public function getPickedCenters(array $data): array + { + if (!array_key_exists('centers', $data)) { + throw new \RuntimeException('array has not the expected shape'); + } + + $centers = $data['centers'] instanceof Collection ? $data['centers']->toArray() : $data['centers']; + $regroupments = ($data['regroupments'] ?? []) instanceof Collection ? $data['regroupments']->toArray() : ($data['regroupments'] ?? []); + + return $this->centerRegroupementResolver->resolveCenters($regroupments, $centers); } } diff --git a/src/Bundle/ChillMainBundle/Export/ExportGenerationContext.php b/src/Bundle/ChillMainBundle/Export/ExportGenerationContext.php new file mode 100644 index 000000000..0ffb29c20 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportGenerationContext.php @@ -0,0 +1,21 @@ +filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; + } + + public function generate(string $exportAlias, array $configuration, ?User $byUser = null): FormattedExportGeneration + { + $data = $this->configNormalizer->denormalizeConfig($exportAlias, $configuration); + $export = $this->exportManager->getExport($exportAlias); + + $centers = $this->filterCenters($byUser, $data['centers']['centers'], $data['centers']['regroupments'], $export); + + $context = new ExportGenerationContext($byUser); + + if ($export instanceof DirectExportInterface) { + $generatedExport = $export->generate( + $this->buildCenterReachableScopes($centers), + $data['export'], + $context, + ); + + if ($generatedExport instanceof Response) { + trigger_deprecation('chill-project/chill-bundles', '3.10', 'DirectExportInterface should not return a %s instance, but a %s instance', Response::class, FormattedExportGeneration::class); + + return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('Content-Type')); + } + + return $generatedExport; + } + + $query = $export->initiateQuery( + $this->retrieveUsedModifiers($data), + $this->buildCenterReachableScopes($centers), + $data['export'], + $context, + ); + + if ($query instanceof \Doctrine\ORM\NativeQuery) { + // throw an error if the export require other modifier, which is + // not allowed when the export return a `NativeQuery` + if (\count($export->supportsModifiers()) > 0) { + throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\ORM\QueryBuilder`'); + } + } elseif ($query instanceof QueryBuilder) { + // handle filters + $this->handleFilters($query, $data[ExportType::FILTER_KEY], $context); + + // handle aggregators + $this->handleAggregators($query, $data[ExportType::AGGREGATOR_KEY], $context); + + $this->logger->notice('[export] will execute this qb in export', [ + 'dql' => $query->getDQL(), + ]); + $this->logger->debug('[export] will execute this sql qb in export', [ + 'sql' => $query->getQuery()->getSQL(), + ]); + } else { + throw new \UnexpectedValueException('The method `intiateQuery` should return a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` object.'); + } + + $result = $export->getResult($query, $data['export'], $context); + + $formatter = $this->exportManager->getFormatter($data['pick_formatter']); + $filtersData = []; + $aggregatorsData = []; + + if ($query instanceof QueryBuilder) { + foreach ($this->exportConfigProcessor->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]) as $alias => $aggregator) { + $aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form']; + } + foreach ($this->exportConfigProcessor->retrieveUsedFilters($data[ExportType::FILTER_KEY]) as $alias => $filter) { + $filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form']; + } + } + + /* @phpstan-ignore-next-line the method "generate" is not yet implemented on all formatters */ + if (method_exists($formatter, 'generate')) { + return $formatter->generate( + $result, + $data['formatter'], + $exportAlias, + $data['export'], + $filtersData, + $aggregatorsData, + $context, + ); + } + + trigger_deprecation('chill-project/chill-bundles', '3.10', '%s should implements the "generate" method', FormatterInterface::class); + + /* @phpstan-ignore-next-line this is a deprecated method that we must still call */ + $generatedExport = $formatter->getResponse( + $result, + $data['formatter'], + $exportAlias, + $data['export'], + $filtersData, + $aggregatorsData, + $context, + ); + + return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('content-type')); + } + + private function filterCenters(User $byUser, array $centers, array $regroupements, ExportInterface|DirectExportInterface $export): array + { + if (!$this->filterStatsByCenters) { + return $this->centerRepository->findActive(); + } + + $authorizedCenters = new ArrayCollection($this->authorizationHelper->getReachableCenters($byUser, $export->requiredRole())); + if ($authorizedCenters->isEmpty()) { + throw new UnauthorizedGenerationException('No authorized centers'); + } + + $wantedCenters = $this->centerRegroupementResolver->resolveCenters($regroupements, $centers); + + $resolvedCenters = []; + foreach ($wantedCenters as $wantedCenter) { + if ($authorizedCenters->contains($wantedCenter)) { + $resolvedCenters[] = $wantedCenter; + } + } + + if ([] == $resolvedCenters) { + throw new UnauthorizedGenerationException('No common centers between wanted centers and authorized centers'); + } + + return $resolvedCenters; + } + + /** + * parse the data to retrieve the used filters and aggregators. + * + * @return list + */ + private function retrieveUsedModifiers(mixed $data): array + { + if (null === $data) { + return []; + } + + $usedTypes = array_merge( + $this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]), + $this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY]), + ); + + return array_values(array_unique($usedTypes)); + } + + /** + * Retrieve the filter used in this export. + * + * @return list an array with types + */ + private function retrieveUsedFiltersType(mixed $data): array + { + if (null === $data) { + return []; + } + + $usedTypes = []; + + foreach ($this->exportConfigProcessor->retrieveUsedFilters($data) as $filter) { + if (!\in_array($filter->applyOn(), $usedTypes, true)) { + $usedTypes[] = $filter->applyOn(); + } + } + + return $usedTypes; + } + + /** + * @return string[] + */ + private function retrieveUsedAggregatorsType(mixed $data): array + { + if (null === $data) { + return []; + } + + $usedTypes = []; + + foreach ($this->exportConfigProcessor->retrieveUsedAggregators($data) as $alias => $aggregator) { + if (!\in_array($aggregator->applyOn(), $usedTypes, true)) { + $usedTypes[] = $aggregator->applyOn(); + } + } + + return $usedTypes; + } + + /** + * Alter the query with selected aggregators. + */ + private function handleAggregators( + QueryBuilder $qb, + array $data, + ExportGenerationContext $context, + ): void { + foreach ($this->exportConfigProcessor->retrieveUsedAggregators($data) as $alias => $aggregator) { + $formData = $data[$alias]; + $aggregator->alterQuery($qb, $formData['form'], $context); + } + } + + /** + * alter the query with selected filters. + */ + private function handleFilters( + QueryBuilder $qb, + mixed $data, + ExportGenerationContext $context, + ): void { + foreach ($this->exportConfigProcessor->retrieveUsedFilters($data) as $alias => $filter) { + $formData = $data[$alias]; + + $filter->alterQuery($qb, $formData['form'], $context); + } + } + + /** + * build the array required for defining centers and circles in the initiate + * queries of ExportElementsInterfaces. + * + * @param list
    $centers + */ + private function buildCenterReachableScopes(array $centers) + { + return array_map(static fn (Center $center) => ['center' => $center, 'circles' => []], $centers); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ExportInterface.php b/src/Bundle/ChillMainBundle/Export/ExportInterface.php index a9d3efd13..edb5a53d1 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportInterface.php @@ -14,6 +14,7 @@ namespace Chill\MainBundle\Export; use Doctrine\ORM\NativeQuery; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Contracts\Translation\TranslatableInterface; /** * Interface for Export. @@ -27,6 +28,7 @@ use Symfony\Component\Form\FormBuilderInterface; * @example Chill\PersonBundle\Export\CountPerson an example of implementation * * @template Q of QueryBuilder|NativeQuery + * @template D of array */ interface ExportInterface extends ExportElementInterface { @@ -94,12 +96,12 @@ interface ExportInterface extends ExportElementInterface * which do not need to be translated, or value already translated in * database. But the header must be, in every case, translated. * - * @param string $key The column key, as added in the query - * @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') + * @param string $key The column key, as added in the query + * @param list $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') * - * @return (callable(string|int|float|'_header'|null $value): string|int|\DateTimeInterface) where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` + * @return (callable(string|int|float|'_header'|null $value): string|int|\DateTimeInterface|TranslatableInterface) where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` */ - public function getLabels($key, array $values, mixed $data); + public function getLabels(string $key, array $values, mixed $data); /** * give the list of keys the current export added to the queryBuilder in @@ -108,29 +110,27 @@ interface ExportInterface extends ExportElementInterface * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, * this function will return `array('count_id')`. * - * @param mixed[] $data the data from the export's form (added by self::buildForm) + * @param D $data the data from the export's form (added by self::buildForm) */ - public function getQueryKeys($data); + public function getQueryKeys(array $data): array; /** * Return the results of the query builder. * - * @param Q $query - * @param mixed[] $data the data from the export's fomr (added by self::buildForm) + * @param Q $query + * @param D $data the data from the export's fomr (added by self::buildForm) * * @return mixed[] an array of results */ - public function getResult($query, $data); + public function getResult(QueryBuilder|NativeQuery $query, array $data, ExportGenerationContext $context): array; /** * Return the Export's type. This will inform _on what_ export will apply. * Most of the type, it will be a string which references an entity. * * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE - * - * @return string */ - public function getType(); + public function getType(): string; /** * The initial query, which will be modified by ModifiersInterface @@ -147,7 +147,21 @@ interface ExportInterface extends ExportElementInterface * * @return Q the query to execute */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []); + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder|NativeQuery; + + /** + * @param D $formData + */ + public function normalizeFormData(array $formData): array; + + /** + * @param array $formData the normalized data + * + * @return D + */ + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getNormalizationVersion(): int; /** * Return the required Role to execute the Export. diff --git a/src/Bundle/ChillMainBundle/Export/ExportManager.php b/src/Bundle/ChillMainBundle/Export/ExportManager.php index 949523eb0..f5e9bfc4d 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportManager.php +++ b/src/Bundle/ChillMainBundle/Export/ExportManager.php @@ -11,13 +11,9 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; -use Chill\MainBundle\Entity\User; use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; -use Doctrine\ORM\QueryBuilder; use Psr\Log\LoggerInterface; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; @@ -63,14 +59,13 @@ class ExportManager iterable $exports, iterable $aggregators, iterable $filters, - // iterable $formatters, + iterable $formatters, // iterable $exportElementProvider ) { $this->exports = iterator_to_array($exports); $this->aggregators = iterator_to_array($aggregators); $this->filters = iterator_to_array($filters); - // NOTE: PHP crashes on the next line (exit error code 11). This is desactivated until further investigation - // $this->formatters = iterator_to_array($formatters); + $this->formatters = iterator_to_array($formatters); // foreach ($exportElementProvider as $prefix => $provider) { // $this->addExportElementsProvider($provider, $prefix); @@ -102,7 +97,7 @@ class ExportManager \in_array($filter->applyOn(), $export->supportsModifiers(), true) && $this->isGrantedForElement($filter, $export, $centers) ) { - $filters[$alias] = $filter; + $filters[$alias] = $this->getFilter($alias); } } @@ -136,25 +131,6 @@ class ExportManager return $aggregators; } - public function addExportElementsProvider(ExportElementsProviderInterface $provider, string $prefix): void - { - foreach ($provider->getExportElements() as $suffix => $element) { - $alias = $prefix.'_'.$suffix; - - if ($element instanceof ExportInterface) { - $this->exports[$alias] = $element; - } elseif ($element instanceof FilterInterface) { - $this->filters[$alias] = $element; - } elseif ($element instanceof AggregatorInterface) { - $this->aggregators[$alias] = $element; - } elseif ($element instanceof FormatterInterface) { - $this->addFormatter($element, $alias); - } else { - throw new \LogicException('This element '.$element::class.' is not an instance of export element'); - } - } - } - /** * add a formatter. * @@ -165,96 +141,22 @@ class ExportManager $this->formatters[$alias] = $formatter; } - /** - * Generate a response which contains the requested data. - */ - public function generate(string $exportAlias, array $pickedCentersData, array $data, array $formatterData): Response - { - $export = $this->getExport($exportAlias); - $centers = $this->getPickedCenters($pickedCentersData); - - if ($export instanceof DirectExportInterface) { - return $export->generate( - $this->buildCenterReachableScopes($centers, $export), - $data[ExportType::EXPORT_KEY] - ); - } - - $query = $export->initiateQuery( - $this->retrieveUsedModifiers($data), - $this->buildCenterReachableScopes($centers, $export), - $data[ExportType::EXPORT_KEY] - ); - - if ($query instanceof \Doctrine\ORM\NativeQuery) { - // throw an error if the export require other modifier, which is - // not allowed when the export return a `NativeQuery` - if (\count($export->supportsModifiers()) > 0) { - throw new \LogicException("The export with alias `{$exportAlias}` return ".'a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not allowed. Either the method `supportsModifiers` should return an empty array, or return a `Doctrine\ORM\QueryBuilder`'); - } - } elseif ($query instanceof QueryBuilder) { - // handle filters - $this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers); - - // handle aggregators - $this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers); - - $this->logger->notice('[export] will execute this qb in export', [ - 'dql' => $query->getDQL(), - ]); - } else { - throw new \UnexpectedValueException('The method `intiateQuery` should return a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` object.'); - } - - $result = $export->getResult($query, $data[ExportType::EXPORT_KEY]); - - if (!is_iterable($result)) { - throw new \UnexpectedValueException(sprintf('The result of the export should be an iterable, %s given', \gettype($result))); - } - - /** @var FormatterInterface $formatter */ - $formatter = $this->getFormatter($this->getFormatterAlias($data)); - $filtersData = []; - $aggregatorsData = []; - - if ($query instanceof QueryBuilder) { - $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); - - foreach ($aggregators as $alias => $aggregator) { - $aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form']; - } - } - - $filters = $this->retrieveUsedFilters($data[ExportType::FILTER_KEY]); - - foreach ($filters as $alias => $filter) { - $filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form']; - } - - return $formatter->getResponse( - $result, - $formatterData, - $exportAlias, - $data[ExportType::EXPORT_KEY], - $filtersData, - $aggregatorsData - ); - } - /** * @param string $alias * - * @return AggregatorInterface - * * @throws \RuntimeException if the aggregator is not known */ - public function getAggregator($alias) + public function getAggregator($alias): AggregatorInterface { - if (!\array_key_exists($alias, $this->aggregators)) { + if (null === $aggregator = $this->aggregators[$alias] ?? null) { throw new \RuntimeException("The aggregator with alias {$alias} is not known."); } - return $this->aggregators[$alias]; + if ($aggregator instanceof ExportManagerAwareInterface) { + $aggregator->setExportManager($this); + } + + return $aggregator; } /** @@ -313,10 +215,10 @@ class ExportManager foreach ($this->exports as $alias => $export) { if ($whereUserIsGranted) { if ($this->isGrantedForElement($export, null, null)) { - yield $alias => $export; + yield $alias => $this->getExport($alias); } } else { - yield $alias => $export; + yield $alias => $this->getExport($alias); } } } @@ -332,9 +234,9 @@ class ExportManager foreach ($this->getExports($whereUserIsGranted) as $alias => $export) { if ($export instanceof GroupedExportInterface) { - $groups[$export->getGroup()][$alias] = $export; + $groups[$export->getGroup()][$alias] = $this->getExport($alias); } else { - $groups['_'][$alias] = $export; + $groups['_'][$alias] = $this->getExport($alias); } } @@ -346,11 +248,25 @@ class ExportManager */ public function getFilter(string $alias): FilterInterface { - if (!\array_key_exists($alias, $this->filters)) { + if (null === $filter = $this->filters[$alias] ?? null) { throw new \RuntimeException("The filter with alias {$alias} is not known."); } - return $this->filters[$alias]; + if ($filter instanceof ExportManagerAwareInterface) { + $filter->setExportManager($this); + } + + return $filter; + } + + public function hasFilter(string $alias): bool + { + return array_key_exists($alias, $this->filters); + } + + public function hasAggregator(string $alias): bool + { + return array_key_exists($alias, $this->aggregators); } public function getAllFilters(): array @@ -358,7 +274,7 @@ class ExportManager $filters = []; foreach ($this->filters as $alias => $filter) { - $filters[$alias] = $filter; + $filters[$alias] = $this->getFilter($alias); } return $filters; @@ -380,11 +296,15 @@ class ExportManager public function getFormatter(string $alias): FormatterInterface { - if (!\array_key_exists($alias, $this->formatters)) { + if (null === $formatter = $this->formatters[$alias] ?? null) { throw new \RuntimeException("The formatter with alias {$alias} is not known."); } - return $this->formatters[$alias]; + if ($formatter instanceof ExportManagerAwareInterface) { + $formatter->setExportManager($this); + } + + return $formatter; } /** @@ -412,26 +332,13 @@ class ExportManager { foreach ($this->formatters as $alias => $formatter) { if (\in_array($formatter->getType(), $types, true)) { - yield $alias => $formatter; + yield $alias => $this->getFormatter($alias); } } } /** - * Get the Center picked by the user for this export. The data are - * extracted from the PickCenterType data. - * - * @param array $data the data from a PickCenterType - * - * @return \Chill\MainBundle\Entity\Center[] the picked center - */ - public function getPickedCenters(array $data): array - { - return $data; - } - - /** - * get the aggregators typse used in the form export data. + * get the aggregators types used in the form export data. * * @param array $data the data from the export form * @@ -439,9 +346,15 @@ class ExportManager */ public function getUsedAggregatorsAliases(array $data): array { - $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); + $keys = []; - return array_keys(iterator_to_array($aggregators)); + foreach ($data as $alias => $aggregatorData) { + if (true === $aggregatorData['enabled']) { + $keys[] = $alias; + } + } + + return array_values(array_unique($keys)); } /** @@ -490,190 +403,4 @@ class ExportManager return true; } - - /** - * build the array required for defining centers and circles in the initiate - * queries of ExportElementsInterfaces. - * - * @param \Chill\MainBundle\Entity\Center[] $centers - */ - private function buildCenterReachableScopes(array $centers, ExportElementInterface $element) - { - $r = []; - - $user = $this->tokenStorage->getToken()->getUser(); - - if (!$user instanceof User) { - return []; - } - - foreach ($centers as $center) { - $r[] = [ - 'center' => $center, - 'circles' => $this->authorizationHelper->getReachableScopes( - $user, - $element->requiredRole(), - $center - ), - ]; - } - - return $r; - } - - /** - * Alter the query with selected aggregators. - * - * Check for acl. If an user is not authorized to see an aggregator, throw an - * UnauthorizedException. - * - * @throw UnauthorizedHttpException if the user is not authorized - */ - private function handleAggregators( - ExportInterface $export, - QueryBuilder $qb, - array $data, - array $center, - ) { - $aggregators = $this->retrieveUsedAggregators($data); - - foreach ($aggregators as $alias => $aggregator) { - if (false === $this->isGrantedForElement($aggregator, $export, $center)) { - throw new UnauthorizedHttpException('You are not authorized to use the aggregator'.$aggregator->getTitle()); - } - - $formData = $data[$alias]; - $aggregator->alterQuery($qb, $formData['form']); - } - } - - /** - * alter the query with selected filters. - * - * This function check the acl. - * - * @param \Chill\MainBundle\Entity\Center[] $centers the picked centers - * - * @throw UnauthorizedHttpException if the user is not authorized - */ - private function handleFilters( - ExportInterface $export, - QueryBuilder $qb, - mixed $data, - array $centers, - ) { - $filters = $this->retrieveUsedFilters($data); - - foreach ($filters as $alias => $filter) { - if (false === $this->isGrantedForElement($filter, $export, $centers)) { - throw new UnauthorizedHttpException('You are not authorized to use the filter '.$filter->getTitle()); - } - - $formData = $data[$alias]; - - $this->logger->debug('alter query by filter '.$alias, [ - 'class' => self::class, 'function' => __FUNCTION__, - ]); - $filter->alterQuery($qb, $formData['form']); - } - } - - /** - * @return iterable - */ - private function retrieveUsedAggregators(mixed $data): iterable - { - if (null === $data) { - return []; - } - - foreach ($data as $alias => $aggregatorData) { - if (true === $aggregatorData['enabled']) { - yield $alias => $this->getAggregator($alias); - } - } - } - - /** - * @return string[] - */ - private function retrieveUsedAggregatorsType(mixed $data) - { - if (null === $data) { - return []; - } - - $usedTypes = []; - - foreach ($this->retrieveUsedAggregators($data) as $alias => $aggregator) { - if (!\in_array($aggregator->applyOn(), $usedTypes, true)) { - $usedTypes[] = $aggregator->applyOn(); - } - } - - return $usedTypes; - } - - private function retrieveUsedFilters(mixed $data): iterable - { - if (null === $data) { - return []; - } - - foreach ($data as $alias => $filterData) { - if (true === $filterData['enabled']) { - yield $alias => $this->getFilter($alias); - } - } - } - - /** - * Retrieve the filter used in this export. - * - * @return array an array with types - */ - private function retrieveUsedFiltersType(mixed $data): iterable - { - if (null === $data) { - return []; - } - - $usedTypes = []; - - foreach ($data as $alias => $filterData) { - if (true === $filterData['enabled']) { - $filter = $this->getFilter($alias); - - if (!\in_array($filter->applyOn(), $usedTypes, true)) { - $usedTypes[] = $filter->applyOn(); - } - } - } - - return $usedTypes; - } - - /** - * parse the data to retrieve the used filters and aggregators. - * - * @return string[] - */ - private function retrieveUsedModifiers(mixed $data) - { - if (null === $data) { - return []; - } - - $usedTypes = array_merge( - $this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]), - $this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY]) - ); - - $this->logger->debug( - 'Required types are '.implode(', ', $usedTypes), - ['class' => self::class, 'function' => __FUNCTION__] - ); - - return array_unique($usedTypes); - } } diff --git a/src/Bundle/ChillMainBundle/Export/ExportManagerAwareInterface.php b/src/Bundle/ChillMainBundle/Export/ExportManagerAwareInterface.php new file mode 100644 index 000000000..f4e43d206 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/ExportManagerAwareInterface.php @@ -0,0 +1,20 @@ + 'good news'], 'mydomain', 'mylocale')` * - * @param array $data - * @param string $format the format + * @param D $data * - * @return array|string a string with the data or, if translatable, an array where first element is string, second elements is an array of arguments + * @return array|string|TranslatableInterface a string with the data or, if translatable, an array where first element is string, second elements is an array of arguments */ - public function describeAction($data, $format = 'string'); + public function describeAction(array $data, ExportGenerationContext $context): array|string|TranslatableInterface; } diff --git a/src/Bundle/ChillMainBundle/Export/FormattedExportGeneration.php b/src/Bundle/ChillMainBundle/Export/FormattedExportGeneration.php new file mode 100644 index 000000000..678bb8f31 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/FormattedExportGeneration.php @@ -0,0 +1,20 @@ +exportManager = $manager; - } - - /** - * @uses appendAggregatorForm - */ - public function buildForm(FormBuilderInterface $builder, $exportAlias, array $aggregatorAliases) - { - $aggregators = $this->exportManager->getAggregators($aggregatorAliases); - $nb = \count($aggregatorAliases); - - foreach ($aggregators as $alias => $aggregator) { - $builderAggregator = $builder->create($alias, FormType::class, [ - 'label' => $aggregator->getTitle(), - 'block_name' => '_aggregator_placement_csv_formatter', - ]); - $this->appendAggregatorForm($builderAggregator, $nb); - $builder->add($builderAggregator); - } - } - - public function getFormDefaultData(array $aggregatorAliases): array - { - return []; - } - - public function gatherFiltersDescriptions() - { - $descriptions = []; - - foreach ($this->filtersData as $key => $filterData) { - $statement = $this->exportManager - ->getFilter($key) - ->describeAction($filterData); - - if (null === $statement) { - continue; - } - - if (\is_array($statement)) { - $descriptions[] = $this->translator->trans( - $statement[0], - $statement[1], - $statement[2] ?? null, - $statement[3] ?? null - ); - } else { - $descriptions[] = $statement; - } - } - - return $descriptions; - } - - public function getName() - { - return 'Comma separated values (CSV)'; - } - - public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData, - ) { - $this->result = $result; - $this->orderingHeaders($formatterData); - $this->export = $this->exportManager->getExport($exportAlias); - $this->aggregators = iterator_to_array($this->exportManager - ->getAggregators(array_keys($aggregatorsData))); - $this->exportData = $exportData; - $this->aggregatorsData = $aggregatorsData; - $this->labels = $this->gatherLabels(); - $this->filtersData = $filtersData; - - $response = new Response(); - $response->setStatusCode(200); - $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); - // $response->headers->set('Content-Disposition','attachment; filename="export.csv"'); - - $response->setContent($this->generateContent()); - - return $response; - } - - public function getType() - { - return 'tabular'; - } - - protected function gatherLabels() - { - return array_merge( - $this->gatherLabelsFromAggregators(), - $this->gatherLabelsFromExport() - ); - } - - protected function gatherLabelsFromAggregators() - { - $labels = []; - /* @var $aggretator \Chill\MainBundle\Export\AggregatorInterface */ - foreach ($this->aggregators as $alias => $aggregator) { - $keys = $aggregator->getQueryKeys($this->aggregatorsData[$alias]); - - // gather data in an array - foreach ($keys as $key) { - $values = array_map(static function ($row) use ($key, $alias) { - if (!\array_key_exists($key, $row)) { - throw new \LogicException("the key '".$key."' is declared by the aggregator with alias '".$alias."' but is not ".'present in results'); - } - - return $row[$key]; - }, $this->result); - $labels[$key] = $aggregator->getLabels( - $key, - array_unique($values), - $this->aggregatorsData[$alias] - ); - } - } - - return $labels; - } - - protected function gatherLabelsFromExport() - { - $labels = []; - $export = $this->export; - $keys = $this->export->getQueryKeys($this->exportData); - - foreach ($keys as $key) { - $values = array_map(static function ($row) use ($key, $export) { - if (!\array_key_exists($key, $row)) { - throw new \LogicException("the key '".$key."' is declared by the export with title '".$export->getTitle()."' but is not ".'present in results'); - } - - return $row[$key]; - }, $this->result); - $labels[$key] = $this->export->getLabels( - $key, - array_unique($values), - $this->exportData - ); - } - - return $labels; - } - - protected function generateContent() - { - $line = null; - $rowKeysNb = \count($this->getRowHeaders()); - $columnKeysNb = \count($this->getColumnHeaders()); - $resultsKeysNb = \count($this->export->getQueryKeys($this->exportData)); - $results = $this->getOrderedResults(); - /** @var string[] $columnHeaders the column headers associations */ - $columnHeaders = []; - /** @var string[] $data the data of the csv file */ - $contentData = []; - $content = []; - - // create a file pointer connected to the output stream - $output = fopen('php://output', 'wb'); - - // title - fputcsv($output, [$this->translator->trans($this->export->getTitle())]); - // blank line - fputcsv($output, ['']); - - // add filtering description - foreach ($this->gatherFiltersDescriptions() as $desc) { - fputcsv($output, [$desc]); - } - // blank line - fputcsv($output, ['']); - - // iterate on result to : 1. populate row headers, 2. populate column headers, 3. add result - foreach ($results as $row) { - $rowHeaders = \array_slice($row, 0, $rowKeysNb); - - // first line : we create line and adding them row headers - if (!isset($line)) { - $line = \array_slice($row, 0, $rowKeysNb); - } - - // do we have to create a new line ? if the rows are equals, continue on the line, else create a next line - if (\array_slice($line, 0, $rowKeysNb) !== $rowHeaders) { - $contentData[] = $line; - $line = \array_slice($row, 0, $rowKeysNb); - } - - // add the column headers - /** @var string[] $columns the column for this row */ - $columns = \array_slice($row, $rowKeysNb, $columnKeysNb); - $columnPosition = $this->findColumnPosition($columnHeaders, $columns); - - // fill with blank at the position given by the columnPosition + nbRowHeaders - for ($i = 0; $i < $columnPosition; ++$i) { - if (!isset($line[$rowKeysNb + $i])) { - $line[$rowKeysNb + $i] = ''; - } - } - - $resultData = \array_slice($row, $resultsKeysNb * -1); - - foreach ($resultData as $data) { - $line[] = $data; - } - } - - // we add the last line - $contentData[] = $line; - - // column title headers - for ($i = 0; $i < $columnKeysNb; ++$i) { - $line = array_fill(0, $rowKeysNb, ''); - - foreach ($columnHeaders as $set) { - $line[] = $set[$i]; - } - - $content[] = $line; - } - - // row title headers - $headerLine = []; - - foreach ($this->getRowHeaders() as $headerKey) { - $headerLine[] = \array_key_exists('_header', $this->labels[$headerKey]) ? - $this->labels[$headerKey]['_header'] : ''; - } - - foreach ($this->export->getQueryKeys($this->exportData) as $key) { - $headerLine[] = \array_key_exists('_header', $this->labels[$key]) ? - $this->labels[$key]['_header'] : ''; - } - fputcsv($output, $headerLine); - unset($headerLine); // free memory - - // generate CSV - foreach ($content as $line) { - fputcsv($output, $line); - } - - foreach ($contentData as $line) { - fputcsv($output, $line); - } - - $text = stream_get_contents($output); - fclose($output); - - return $text; - } - - protected function getColumnHeaders() - { - return $this->getPositionnalHeaders('c'); - } - - protected function getRowHeaders() - { - return $this->getPositionnalHeaders('r'); - } - - /** - * ordering aggregators, preserving key association. - * - * This function do not mind about position. - * - * If two aggregators have the same order, the second given will be placed - * after. This is not significant for the first ordering. - */ - protected function orderingHeaders(array $formatterData) - { - $this->formatterData = $formatterData; - uasort( - $this->formatterData, - static fn (array $a, array $b): int => ($a['order'] <= $b['order'] ? -1 : 1) - ); - } - - /** - * append a form line by aggregator on the formatter form. - * - * This form allow to choose the aggregator position (row or column) and - * the ordering - * - * @param string $nbAggregators - */ - private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators) - { - $builder->add('order', ChoiceType::class, [ - 'choices' => array_combine( - range(1, $nbAggregators), - range(1, $nbAggregators) - ), - 'multiple' => false, - 'expanded' => false, - ]); - - $builder->add('position', ChoiceType::class, [ - 'choices' => [ - 'row' => 'r', - 'column' => 'c', - ], - 'multiple' => false, - 'expanded' => false, - ]); - } - - private function findColumnPosition(&$columnHeaders, $columnToFind): int - { - $i = 0; - - foreach ($columnHeaders as $set) { - if ($set === $columnToFind) { - return $i; - } - ++$i; - } - - // we didn't find it, adding the column - $columnHeaders[] = $columnToFind; - - return $i++; - } - - private function getOrderedResults() - { - $r = []; - $results = $this->result; - $labels = $this->labels; - $rowKeys = $this->getRowHeaders(); - $columnKeys = $this->getColumnHeaders(); - $resultsKeys = $this->export->getQueryKeys($this->exportData); - $headers = array_merge($rowKeys, $columnKeys); - - foreach ($results as $row) { - $line = []; - - foreach ($headers as $key) { - $line[] = \call_user_func($labels[$key], $row[$key]); - } - - // append result - foreach ($resultsKeys as $key) { - $line[] = \call_user_func($labels[$key], $row[$key]); - } - - $r[] = $line; - } - - array_multisort($r); - - return $r; - } - - /** - * @param string $position may be 'c' (column) or 'r' (row) - * - * @return string[] - * - * @throws \RuntimeException - */ - private function getPositionnalHeaders($position) - { - $headers = []; - - foreach ($this->formatterData as $alias => $data) { - if (!\array_key_exists($alias, $this->aggregatorsData)) { - throw new \RuntimeException('the formatter wants to use the '."aggregator with alias {$alias}, but the export do not ".'contains data about it'); - } - - $aggregator = $this->aggregators[$alias]; - - if ($data['position'] === $position) { - $headers = array_merge($headers, $aggregator->getQueryKeys($this->aggregatorsData[$alias])); - } - } - - return $headers; - } -} diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php deleted file mode 100644 index 1a54eee5f..000000000 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVListFormatter.php +++ /dev/null @@ -1,223 +0,0 @@ -translator = $translatorInterface; - $this->exportManager = $exportManager; - } - - /** - * build a form, which will be used to collect data required for the execution - * of this formatter. - * - * @uses appendAggregatorForm - * - * @param type $exportAlias - */ - public function buildForm( - FormBuilderInterface $builder, - $exportAlias, - array $aggregatorAliases, - ) { - $builder->add('numerotation', ChoiceType::class, [ - 'choices' => [ - 'yes' => true, - 'no' => false, - ], - 'expanded' => true, - 'multiple' => false, - 'label' => 'Add a number on first column', - ]); - } - - public function getFormDefaultData(array $aggregatorAliases): array - { - return ['numerotation' => true]; - } - - public function getName() - { - return 'CSV vertical list'; - } - - /** - * Generate a response from the data collected on differents ExportElementInterface. - * - * @param mixed[] $result The result, as given by the ExportInterface - * @param mixed[] $formatterData collected from the current form - * @param string $exportAlias the id of the current export - * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data - * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data - * - * @return Response The response to be shown - */ - public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData, - ) { - $this->result = $result; - $this->exportAlias = $exportAlias; - $this->exportData = $exportData; - $this->formatterData = $formatterData; - - $output = fopen('php://output', 'wb'); - - $this->prepareHeaders($output); - - $i = 1; - - foreach ($result as $row) { - $line = []; - - if (true === $this->formatterData['numerotation']) { - $line[] = $i; - } - - foreach ($row as $key => $value) { - $line[] = $this->getLabel($key, $value); - } - - fputcsv($output, $line); - - ++$i; - } - - $csvContent = stream_get_contents($output); - fclose($output); - - $response = new Response(); - $response->setStatusCode(200); - $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); - // $response->headers->set('Content-Disposition','attachment; filename="export.csv"'); - - $response->setContent($csvContent); - - return $response; - } - - public function getType() - { - return FormatterInterface::TYPE_LIST; - } - - /** - * Give the label corresponding to the given key and value. - * - * @param string $key - * @param string $value - * - * @return string - * - * @throws \LogicException if the label is not found - */ - protected function getLabel($key, $value) - { - if (null === $this->labelsCache) { - $this->prepareCacheLabels(); - } - - if (!\array_key_exists($key, $this->labelsCache)) { - throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($this->labelsCache)))); - } - - return $this->labelsCache[$key]($value); - } - - /** - * Prepare the label cache which will be used by getLabel. This function - * should be called only once in the generation lifecycle. - */ - protected function prepareCacheLabels() - { - $export = $this->exportManager->getExport($this->exportAlias); - $keys = $export->getQueryKeys($this->exportData); - - foreach ($keys as $key) { - // get an array with all values for this key if possible - $values = \array_map(static fn ($v) => $v[$key], $this->result); - // store the label in the labelsCache property - $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); - } - } - - /** - * add the headers to the csv file. - * - * @param resource $output - */ - protected function prepareHeaders($output) - { - $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); - // we want to keep the order of the first row. So we will iterate on the first row of the results - $first_row = \count($this->result) > 0 ? $this->result[0] : []; - $header_line = []; - - if (true === $this->formatterData['numerotation']) { - $header_line[] = $this->translator->trans('Number'); - } - - foreach ($first_row as $key => $value) { - $header_line[] = $this->translator->trans( - $this->getLabel($key, '_header') - ); - } - - if (\count($header_line) > 0) { - fputcsv($output, $header_line); - } - } -} diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php deleted file mode 100644 index 9385f1b61..000000000 --- a/src/Bundle/ChillMainBundle/Export/Formatter/CSVPivotedListFormatter.php +++ /dev/null @@ -1,217 +0,0 @@ -translator = $translatorInterface; - $this->exportManager = $exportManager; - } - - /** - * build a form, which will be used to collect data required for the execution - * of this formatter. - * - * @uses appendAggregatorForm - * - * @param type $exportAlias - */ - public function buildForm( - FormBuilderInterface $builder, - $exportAlias, - array $aggregatorAliases, - ) { - $builder->add('numerotation', ChoiceType::class, [ - 'choices' => [ - 'yes' => true, - 'no' => false, - ], - 'expanded' => true, - 'multiple' => false, - 'label' => 'Add a number on first column', - 'data' => true, - ]); - } - - public function getFormDefaultData(array $aggregatorAliases): array - { - return ['numerotation' => true]; - } - - public function getName() - { - return 'CSV horizontal list'; - } - - /** - * Generate a response from the data collected on differents ExportElementInterface. - * - * @param mixed[] $result The result, as given by the ExportInterface - * @param mixed[] $formatterData collected from the current form - * @param string $exportAlias the id of the current export - * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data - * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data - * - * @return Response The response to be shown - */ - public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData, - ) { - $this->result = $result; - $this->exportAlias = $exportAlias; - $this->exportData = $exportData; - $this->formatterData = $formatterData; - - $output = fopen('php://output', 'wb'); - - $i = 1; - $lines = []; - $this->prepareHeaders($lines); - - foreach ($result as $row) { - $j = 0; - - if (true === $this->formatterData['numerotation']) { - $lines[$j][] = $i; - ++$j; - } - - foreach ($row as $key => $value) { - $lines[$j][] = $this->getLabel($key, $value); - ++$j; - } - ++$i; - } - - // adding the lines to the csv output - foreach ($lines as $line) { - fputcsv($output, $line); - } - - $csvContent = stream_get_contents($output); - fclose($output); - - $response = new Response(); - $response->setStatusCode(200); - $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); - $response->headers->set('Content-Disposition', 'attachment; filename="export.csv"'); - - $response->setContent($csvContent); - - return $response; - } - - public function getType() - { - return FormatterInterface::TYPE_LIST; - } - - /** - * Give the label corresponding to the given key and value. - * - * @param string $key - * @param string $value - * - * @return string - * - * @throws \LogicException if the label is not found - */ - protected function getLabel($key, $value) - { - if (null === $this->labelsCache) { - $this->prepareCacheLabels(); - } - - return $this->labelsCache[$key]($value); - } - - /** - * Prepare the label cache which will be used by getLabel. This function - * should be called only once in the generation lifecycle. - */ - protected function prepareCacheLabels() - { - $export = $this->exportManager->getExport($this->exportAlias); - $keys = $export->getQueryKeys($this->exportData); - - foreach ($keys as $key) { - // get an array with all values for this key if possible - $values = \array_map(static fn ($v) => $v[$key], $this->result); - // store the label in the labelsCache property - $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); - } - } - - /** - * add the headers to lines array. - * - * @param array $lines the lines where the header will be added - */ - protected function prepareHeaders(array &$lines) - { - $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); - // we want to keep the order of the first row. So we will iterate on the first row of the results - $first_row = \count($this->result) > 0 ? $this->result[0] : []; - $header_line = []; - - if (true === $this->formatterData['numerotation']) { - $lines[] = [$this->translator->trans('Number')]; - } - - foreach ($first_row as $key => $value) { - $lines[] = [$this->getLabel($key, '_header')]; - } - } -} diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php index 76e17378f..87c46dfbb 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadSheetFormatter.php @@ -11,126 +11,32 @@ declare(strict_types=1); namespace Chill\MainBundle\Export\Formatter; -use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Export\ExportGenerationContext; +use Chill\MainBundle\Export\ExportInterface; +use Chill\MainBundle\Export\ExportManagerAwareInterface; +use Chill\MainBundle\Export\FormattedExportGeneration; use Chill\MainBundle\Export\FormatterInterface; +use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\FormType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; -class SpreadSheetFormatter implements FormatterInterface +final class SpreadSheetFormatter implements FormatterInterface, ExportManagerAwareInterface { - /** - * an array where keys are the aggregators aliases and - * values are the data. - * - * replaced when `getResponse` is called. - * - * @var type - */ - protected $aggregatorsData; + use ExportManagerAwareTrait; - /** - * The export. - * - * replaced when `getResponse` is called. - * - * @var \Chill\MainBundle\Export\ExportInterface - */ - protected $export; - - /** - * replaced when `getResponse` is called. - * - * @var type - */ - // protected $aggregators; - - /** - * array containing value of export form. - * - * replaced when `getResponse` is called. - * - * @var array - */ - protected $exportData; - - /** - * @var ExportManager - */ - protected $exportManager; - - /** - * replaced when `getResponse` is called. - * - * @var array - */ - protected $filtersData; - - /** - * replaced when `getResponse` is called. - * - * @var type - */ - protected $formatterData; - - /** - * The result, as returned by the export. - * - * replaced when `getResponse` is called. - * - * @var type - */ - protected $result; - - /** - * replaced when `getResponse` is called. - * - * @var array - */ - // protected $labels; - - /** - * temporary file to store spreadsheet. - * - * @var string - */ - protected $tempfile; - - /** - * @var TranslatorInterface - */ - protected $translator; - - /** - * cache for displayable result. - * - * This cache is reset when `getResponse` is called. - * - * The array's keys are the keys in the raw result, and - * values are the callable which will transform the raw result to - * displayable result. - */ - private ?array $cacheDisplayableResult = null; - - /** - * Whethe `cacheDisplayableResult` is initialized or not. - */ - private bool $cacheDisplayableResultIsInitialized = false; - - public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager) - { - $this->translator = $translatorInterface; - $this->exportManager = $exportManager; - } + public function __construct(private readonly TranslatorInterface $translator) {} public function buildForm( FormBuilderInterface $builder, $exportAlias, array $aggregatorAliases, - ) { + ): void { // choosing between formats $builder->add('format', ChoiceType::class, [ 'choices' => [ @@ -142,7 +48,7 @@ class SpreadSheetFormatter implements FormatterInterface ]); // ordering aggregators - $aggregators = $this->exportManager->getAggregators($aggregatorAliases); + $aggregators = $this->getExportManager()->getAggregators($aggregatorAliases); $nb = \count($aggregatorAliases); foreach ($aggregators as $alias => $aggregator) { @@ -155,11 +61,26 @@ class SpreadSheetFormatter implements FormatterInterface } } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return $formData; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return $formData; + } + public function getFormDefaultData(array $aggregatorAliases): array { $data = ['format' => 'xlsx']; - $aggregators = iterator_to_array($this->exportManager->getAggregators($aggregatorAliases)); + $aggregators = iterator_to_array($this->getExportManager()->getAggregators($aggregatorAliases)); foreach (array_keys($aggregators) as $index => $alias) { $data[$alias] = ['order' => $index + 1]; } @@ -172,6 +93,51 @@ class SpreadSheetFormatter implements FormatterInterface return 'SpreadSheet (xlsx, ods)'; } + public function generate( + $result, + $formatterData, + string $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData, + ExportGenerationContext $context, + ) { + // Initialize local variables instead of class properties + /** @var ExportInterface $export */ + $export = $this->getExportManager()->getExport($exportAlias); + + // Initialize cache variables + $cacheDisplayableResult = $this->initializeDisplayable($result, $export, $exportData, $aggregatorsData); + + $tempfile = \tempnam(\sys_get_temp_dir(), ''); + + if (false === $tempfile) { + throw new \RuntimeException('Unable to create temporary file'); + } + + $this->generateContent( + $context, + $tempfile, + $result, + $formatterData, + $export, + $exportData, + $filtersData, + $aggregatorsData, + $cacheDisplayableResult, + ); + + $result = new FormattedExportGeneration( + file_get_contents($tempfile), + $this->getContentType($formatterData['format']), + ); + + // remove the temp file from disk + \unlink($tempfile); + + return $result; + } + public function getResponse( $result, $formatterData, @@ -179,44 +145,22 @@ class SpreadSheetFormatter implements FormatterInterface array $exportData, array $filtersData, array $aggregatorsData, + ExportGenerationContext $context, ): Response { - // store all data when the process is initiated - $this->result = $result; - $this->formatterData = $formatterData; - $this->export = $this->exportManager->getExport($exportAlias); - $this->exportData = $exportData; - $this->filtersData = $filtersData; - $this->aggregatorsData = $aggregatorsData; + $formattedResult = $this->generate($result, $formatterData, $exportAlias, $exportData, $filtersData, $aggregatorsData, $context); - // reset cache - $this->cacheDisplayableResult = []; - $this->cacheDisplayableResultIsInitialized = false; - - $response = new Response(); - $response->headers->set( - 'Content-Type', - $this->getContentType($this->formatterData['format']) - ); - - $this->tempfile = \tempnam(\sys_get_temp_dir(), ''); - $this->generateContent(); - - $f = \fopen($this->tempfile, 'rb'); - $response->setContent(\stream_get_contents($f)); - fclose($f); - - // remove the temp file from disk - \unlink($this->tempfile); + $response = new BinaryFileResponse($formattedResult->content); + $response->headers->set('Content-Type', $formattedResult->contentType); return $response; } - public function getType() + public function getType(): string { return 'tabular'; } - protected function addContentTable( + private function addContentTable( Worksheet $worksheet, $sortedResults, $line, @@ -238,20 +182,21 @@ class SpreadSheetFormatter implements FormatterInterface * * @return int the line number after the last description */ - protected function addFiltersDescription(Worksheet &$worksheet) + private function addFiltersDescription(Worksheet &$worksheet, ExportGenerationContext $context, array $filtersData) { $line = 3; - foreach ($this->filtersData as $alias => $data) { - $filter = $this->exportManager->getFilter($alias); - $description = $filter->describeAction($data, 'string'); - + foreach ($filtersData as $alias => $data) { + $filter = $this->getExportManager()->getFilter($alias); + $description = $filter->describeAction($data, $context); if (\is_array($description)) { $description = $this->translator ->trans( $description[0], - $description[1] ?? [] + $description[1] ?? [], ); + } elseif ($description instanceof TranslatableInterface) { + $description = $description->trans($this->translator, $this->translator->getLocale()); } $worksheet->setCellValue('A'.$line, $description); @@ -266,23 +211,23 @@ class SpreadSheetFormatter implements FormatterInterface * * return the line number where the next content (i.e. result) should * be appended. - * - * @param int $line - * - * @return int */ - protected function addHeaders( + private function addHeaders( Worksheet &$worksheet, array $globalKeys, - $line, - ) { + int $line, + array $cacheDisplayableResult = [], + ): int { // get the displayable form of headers $displayables = []; - foreach ($globalKeys as $key) { - $displayables[] = $this->translator->trans( - $this->getDisplayableResult($key, '_header') - ); + $displayable = $this->getDisplayableResult($key, '_header', $cacheDisplayableResult); + + if ($displayable instanceof TranslatableInterface) { + $displayables[] = $displayable->trans($this->translator, $this->translator->getLocale()); + } else { + $displayables[] = $this->translator->trans($this->getDisplayableResult($key, '_header', $cacheDisplayableResult)); + } } // add headers on worksheet @@ -299,9 +244,9 @@ class SpreadSheetFormatter implements FormatterInterface * Add the title to the worksheet and merge the cell containing * the title. */ - protected function addTitleToWorkSheet(Worksheet &$worksheet) + private function addTitleToWorkSheet(Worksheet &$worksheet, $export) { - $worksheet->setCellValue('A1', $this->getTitle()); + $worksheet->setCellValue('A1', $this->getTitle($export)); $worksheet->mergeCells('A1:G1'); } @@ -310,14 +255,14 @@ class SpreadSheetFormatter implements FormatterInterface * * @return array where 1st member is spreadsheet, 2nd is worksheet */ - protected function createSpreadsheet() + private function createSpreadsheet($export) { $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); $worksheet = $spreadsheet->getActiveSheet(); // setting the worksheet title and code name $worksheet - ->setTitle($this->getTitle()) + ->setTitle($this->getTitle($export)) ->setCodeName('result'); return [$spreadsheet, $worksheet]; @@ -326,29 +271,38 @@ class SpreadSheetFormatter implements FormatterInterface /** * Generate the content and write it to php://temp. */ - protected function generateContent() - { - [$spreadsheet, $worksheet] = $this->createSpreadsheet(); + private function generateContent( + ExportGenerationContext $context, + string $tempfile, + $result, + $formatterData, + $export, + array $exportData, + array $filtersData, + array $aggregatorsData, + array $cacheDisplayableResult, + ) { + [$spreadsheet, $worksheet] = $this->createSpreadsheet($export); - $this->addTitleToWorkSheet($worksheet); - $line = $this->addFiltersDescription($worksheet); + $this->addTitleToWorkSheet($worksheet, $export); + $line = $this->addFiltersDescription($worksheet, $context, $filtersData); - // at this point, we are going to sort retsults for an easier manipulation + // at this point, we are going to sort results for an easier manipulation [$sortedResult, $exportKeys, $aggregatorKeys, $globalKeys] = - $this->sortResult(); + $this->sortResult($result, $export, $exportData, $aggregatorsData, $formatterData, $cacheDisplayableResult); - $line = $this->addHeaders($worksheet, $globalKeys, $line); + $line = $this->addHeaders($worksheet, $globalKeys, $line, $cacheDisplayableResult); - $line = $this->addContentTable($worksheet, $sortedResult, $line); + $this->addContentTable($worksheet, $sortedResult, $line); - $writer = match ($this->formatterData['format']) { + $writer = match ($formatterData['format']) { 'ods' => \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods'), 'xlsx' => \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx'), 'csv' => \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Csv'), default => throw new \LogicException(), }; - $writer->save($this->tempfile); + $writer->save($tempfile); } /** @@ -357,7 +311,7 @@ class SpreadSheetFormatter implements FormatterInterface * * @return string[] an array containing the keys of aggregators */ - protected function getAggregatorKeysSorted() + private function getAggregatorKeysSorted(array $aggregatorsData, array $formatterData) { // empty array for aggregators keys $keys = []; @@ -365,7 +319,7 @@ class SpreadSheetFormatter implements FormatterInterface // during sorting $aggregatorKeyAssociation = []; - foreach ($this->aggregatorsData as $alias => $data) { + foreach ($aggregatorsData as $alias => $data) { $aggregator = $this->exportManager->getAggregator($alias); $aggregatorsKeys = $aggregator->getQueryKeys($data); // append the keys from aggregator to the $keys existing array @@ -377,9 +331,9 @@ class SpreadSheetFormatter implements FormatterInterface } // sort the result using the form - usort($keys, function ($a, $b) use ($aggregatorKeyAssociation) { - $A = $this->formatterData[$aggregatorKeyAssociation[$a]]['order']; - $B = $this->formatterData[$aggregatorKeyAssociation[$b]]['order']; + usort($keys, function ($a, $b) use ($aggregatorKeyAssociation, $formatterData) { + $A = $formatterData[$aggregatorKeyAssociation[$a]]['order']; + $B = $formatterData[$aggregatorKeyAssociation[$b]]['order']; if ($A === $B) { return 0; @@ -395,7 +349,7 @@ class SpreadSheetFormatter implements FormatterInterface return $keys; } - protected function getContentType($format) + private function getContentType($format) { switch ($format) { case 'csv': @@ -412,25 +366,26 @@ class SpreadSheetFormatter implements FormatterInterface /** * Get the displayable result. - * - * @param string $key - * - * @return string */ - protected function getDisplayableResult($key, mixed $value) - { - if (false === $this->cacheDisplayableResultIsInitialized) { - $this->initializeCache($key); - } - + private function getDisplayableResult( + string $key, + mixed $value, + array $cacheDisplayableResult, + ): string|TranslatableInterface|\DateTimeInterface|int|float|bool { $value ??= ''; - return \call_user_func($this->cacheDisplayableResult[$key], $value); + return \call_user_func($cacheDisplayableResult[$key], $value); } - protected function getTitle() + private function getTitle($export): string { - $title = $this->translator->trans($this->export->getTitle()); + $original = $export->getTitle(); + + if ($original instanceof TranslatableInterface) { + $title = $original->trans($this->translator, $this->translator->getLocale()); + } else { + $title = $this->translator->trans($original); + } if (30 < strlen($title)) { return substr($title, 0, 30).'…'; @@ -439,8 +394,13 @@ class SpreadSheetFormatter implements FormatterInterface return $title; } - protected function initializeCache($key) - { + private function initializeDisplayable( + $result, + ExportInterface $export, + array $exportData, + array $aggregatorsData, + ): array { + $cacheDisplayableResult = []; /* * this function follows the following steps : * @@ -453,13 +413,12 @@ class SpreadSheetFormatter implements FormatterInterface // 1. create an associative array with key and export / aggregator $keysExportElementAssociation = []; // keys for export - foreach ($this->export->getQueryKeys($this->exportData) as $key) { - $keysExportElementAssociation[$key] = [$this->export, - $this->exportData, ]; + foreach ($export->getQueryKeys($exportData) as $key) { + $keysExportElementAssociation[$key] = [$export, $exportData]; } // keys for aggregator - foreach ($this->aggregatorsData as $alias => $data) { - $aggregator = $this->exportManager->getAggregator($alias); + foreach ($aggregatorsData as $alias => $data) { + $aggregator = $this->getExportManager()->getAggregator($alias); foreach ($aggregator->getQueryKeys($data) as $key) { $keysExportElementAssociation[$key] = [$aggregator, $data]; @@ -471,7 +430,7 @@ class SpreadSheetFormatter implements FormatterInterface $allValues = []; // store all the values in an array - foreach ($this->result as $row) { + foreach ($result as $row) { foreach ($keys as $key) { $allValues[$key][] = $row[$key]; } @@ -482,15 +441,14 @@ class SpreadSheetFormatter implements FormatterInterface foreach ($keysExportElementAssociation as $key => [$element, $data]) { // handle the case when there is not results lines (query is empty) if ([] === $allValues) { - $this->cacheDisplayableResult[$key] = $element->getLabels($key, ['_header'], $data); + $cacheDisplayableResult[$key] = $element->getLabels($key, ['_header'], $data); } else { - $this->cacheDisplayableResult[$key] = + $cacheDisplayableResult[$key] = $element->getLabels($key, \array_unique($allValues[$key]), $data); } } - // the cache is initialized ! - $this->cacheDisplayableResultIsInitialized = true; + return $cacheDisplayableResult; } /** @@ -528,23 +486,28 @@ class SpreadSheetFormatter implements FormatterInterface * ) * ``` */ - protected function sortResult() - { + private function sortResult( + $result, + ExportInterface $export, + array $exportData, + array $aggregatorsData, + array $formatterData, + array $cacheDisplayableResult, + ) { // get the keys for each row - $exportKeys = $this->export->getQueryKeys($this->exportData); - $aggregatorKeys = $this->getAggregatorKeysSorted(); - + $exportKeys = $export->getQueryKeys($exportData); + $aggregatorKeys = $this->getAggregatorKeysSorted($aggregatorsData, $formatterData); $globalKeys = \array_merge($aggregatorKeys, $exportKeys); - $sortedResult = \array_map(function ($row) use ($globalKeys) { + $sortedResult = \array_map(function ($row) use ($globalKeys, $cacheDisplayableResult) { $newRow = []; foreach ($globalKeys as $key) { - $newRow[] = $this->getDisplayableResult($key, $row[$key]); + $newRow[] = $this->getDisplayableResult($key, $row[$key], $cacheDisplayableResult); } return $newRow; - }, $this->result); + }, $result); \array_multisort($sortedResult); diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php index 0a35e087f..27ee7f44c 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php @@ -11,68 +11,42 @@ declare(strict_types=1); namespace Chill\MainBundle\Export\Formatter; -use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Export\ExportGenerationContext; +use Chill\MainBundle\Export\ExportManagerAwareInterface; +use Chill\MainBundle\Export\FormattedExportGeneration; use Chill\MainBundle\Export\FormatterInterface; +use Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; -// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff - /** * Create a CSV List for the export. */ -class SpreadsheetListFormatter implements FormatterInterface +class SpreadsheetListFormatter implements FormatterInterface, ExportManagerAwareInterface { - protected $exportAlias; + use ExportManagerAwareTrait; - protected $exportData; - - /** - * @var ExportManager - */ - protected $exportManager; - - protected $formatterData; - - /** - * This variable cache the labels internally. - * - * @var string[] - */ - protected $labelsCache; - - protected $result; - - /** - * @var TranslatorInterface - */ - protected $translator; - - public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager) - { - $this->translator = $translatorInterface; - $this->exportManager = $exportManager; - } + public function __construct(private readonly TranslatorInterface $translator) {} /** * build a form, which will be used to collect data required for the execution * of this formatter. * * @uses appendAggregatorForm - * - * @param string $exportAlias */ public function buildForm( FormBuilderInterface $builder, - $exportAlias, + string $exportAlias, array $aggregatorAliases, - ) { + ): void { $builder ->add('format', ChoiceType::class, [ 'choices' => [ @@ -92,58 +66,52 @@ class SpreadsheetListFormatter implements FormatterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['format' => $formData['format'], 'numerotation' => $formData['numerotation']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['format' => $formData['format'], 'numerotation' => $formData['numerotation']]; + } + public function getFormDefaultData(array $aggregatorAliases): array { return ['numerotation' => true, 'format' => 'xlsx']; } - public function getName() + public function getName(): string|TranslatableInterface { return 'Spreadsheet list formatter (.xlsx, .ods)'; } - /** - * Generate a response from the data collected on differents ExportElementInterface. - * - * @param mixed[] $result The result, as given by the ExportInterface - * @param mixed[] $formatterData collected from the current form - * @param string $exportAlias the id of the current export - * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data - * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data - * - * @return Response The response to be shown - */ - public function getResponse( - $result, - $formatterData, - $exportAlias, - array $exportData, - array $filtersData, - array $aggregatorsData, - ) { - $this->result = $result; - $this->exportAlias = $exportAlias; - $this->exportData = $exportData; - $this->formatterData = $formatterData; - + public function generate($result, $formatterData, string $exportAlias, array $exportData, array $filtersData, array $aggregatorsData, ExportGenerationContext $context): FormattedExportGeneration + { $spreadsheet = new Spreadsheet(); $worksheet = $spreadsheet->getActiveSheet(); + $cacheLabels = $this->prepareCacheLabels($result, $exportAlias, $exportData); - $this->prepareHeaders($worksheet); + $this->prepareHeaders($cacheLabels, $worksheet, $result, $formatterData, $exportAlias, $exportData); $i = 1; foreach ($result as $row) { - if (true === $this->formatterData['numerotation']) { + if (true === $formatterData['numerotation']) { $worksheet->setCellValue('A'.($i + 1), (string) $i); } - $a = $this->formatterData['numerotation'] ? 'B' : 'A'; + $a = $formatterData['numerotation'] ? 'B' : 'A'; foreach ($row as $key => $value) { $row = $a.($i + 1); - $formattedValue = $this->getLabel($key, $value); + $formattedValue = $this->getLabel($cacheLabels, $key, $value, $result, $exportAlias, $exportData); if ($formattedValue instanceof \DateTimeInterface) { $worksheet->setCellValue($row, Date::PHPToExcel($formattedValue)); @@ -157,6 +125,8 @@ class SpreadsheetListFormatter implements FormatterInterface ->getNumberFormat() ->setFormatCode(NumberFormat::FORMAT_DATE_DATETIME); } + } elseif ($formattedValue instanceof TranslatableInterface) { + $worksheet->setCellValue($row, $formattedValue->trans($this->translator)); } else { $worksheet->setCellValue($row, $formattedValue); } @@ -166,7 +136,7 @@ class SpreadsheetListFormatter implements FormatterInterface ++$i; } - switch ($this->formatterData['format']) { + switch ($formatterData['format']) { case 'ods': $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Ods'); $contentType = 'application/vnd.oasis.opendocument.spreadsheet'; @@ -189,26 +159,52 @@ class SpreadsheetListFormatter implements FormatterInterface default: // this should not happen // throw an exception to ensure that the error is catched - throw new \OutOfBoundsException('The format '.$this->formatterData['format'].' is not supported'); + throw new \OutOfBoundsException('The format '.$formatterData['format'].' is not supported'); } - $response = new Response(); - $response->headers->set('content-type', $contentType); - $tempfile = \tempnam(\sys_get_temp_dir(), ''); $writer->save($tempfile); - $f = \fopen($tempfile, 'rb'); - $response->setContent(\stream_get_contents($f)); - fclose($f); + $generated = new FormattedExportGeneration( + file_get_contents($tempfile), + $contentType, + ); // remove the temp file from disk \unlink($tempfile); + return $generated; + } + + /** + * Generate a response from the data collected on differents ExportElementInterface. + * + * @param mixed[] $result The result, as given by the ExportInterface + * @param mixed[] $formatterData collected from the current form + * @param string $exportAlias the id of the current export + * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data + * + * @return Response The response to be shown + */ + public function getResponse( + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData, + ExportGenerationContext $context, + ) { + $generated = $this->generate($result, $formatterData, $exportAlias, $exportData, $filtersData, $aggregatorsData, $context); + + $response = new BinaryFileResponse($generated->content); + $response->headers->set('Content-Type', $generated->contentType); + return $response; } - public function getType() + public function getType(): string { return FormatterInterface::TYPE_LIST; } @@ -216,34 +212,29 @@ class SpreadsheetListFormatter implements FormatterInterface /** * Give the label corresponding to the given key and value. * - * @param string $key - * @param string $value - * - * @return string + * @return string|\DateTimeInterface|int|float|TranslatableInterface|null * * @throws \LogicException if the label is not found */ - protected function getLabel($key, $value) + private function getLabel(array $labelsCache, $key, $value, array $result, string $exportAlias, array $exportData) { - if (null === $this->labelsCache) { - $this->prepareCacheLabels(); + if (!\array_key_exists($key, $labelsCache)) { + throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($labelsCache)))); } - if (!\array_key_exists($key, $this->labelsCache)) { - throw new \OutOfBoundsException(sprintf('The key "%s" is not present in the list of keys handled by this query. Check your `getKeys` and `getLabels` methods. Available keys are %s.', $key, \implode(', ', \array_keys($this->labelsCache)))); - } - - return $this->labelsCache[$key]($value); + return $labelsCache[$key]($value); } /** - * Prepare the label cache which will be used by getLabel. This function - * should be called only once in the generation lifecycle. + * Prepare the label cache which will be used by getLabel. + * + * @return array The labels cache */ - protected function prepareCacheLabels() + private function prepareCacheLabels(array $result, string $exportAlias, array $exportData): array { - $export = $this->exportManager->getExport($this->exportAlias); - $keys = $export->getQueryKeys($this->exportData); + $labelsCache = []; + $export = $this->getExportManager()->getExport($exportAlias); + $keys = $export->getQueryKeys($exportData); foreach ($keys as $key) { // get an array with all values for this key if possible @@ -253,29 +244,31 @@ class SpreadsheetListFormatter implements FormatterInterface } return $v[$key]; - }, $this->result); - // store the label in the labelsCache property - $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); + }, $result); + // store the label in the labelsCache + $labelsCache[$key] = $export->getLabels($key, $values, $exportData); } + + return $labelsCache; } /** * add the headers to the csv file. */ - protected function prepareHeaders(Worksheet $worksheet) + protected function prepareHeaders(array $labelsCache, Worksheet $worksheet, array $result, array $formatterData, string $exportAlias, array $exportData) { - $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); + $keys = $this->getExportManager()->getExport($exportAlias)->getQueryKeys($exportData); // we want to keep the order of the first row. So we will iterate on the first row of the results - $first_row = \count($this->result) > 0 ? $this->result[0] : []; + $first_row = \count($result) > 0 ? $result[0] : []; $header_line = []; - if (true === $this->formatterData['numerotation']) { + if (true === $formatterData['numerotation']) { $header_line[] = $this->translator->trans('Number'); } foreach ($first_row as $key => $value) { $header_line[] = $this->translator->trans( - $this->getLabel($key, '_header') + $this->getLabel($labelsCache, $key, '_header', $result, $exportAlias, $exportData) ); } diff --git a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php index 37ed788f4..ac9c3e903 100644 --- a/src/Bundle/ChillMainBundle/Export/FormatterInterface.php +++ b/src/Bundle/ChillMainBundle/Export/FormatterInterface.php @@ -12,7 +12,11 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Contracts\Translation\TranslatableInterface; +/** + * @method generate($result, $formatterData, string $exportAlias, array $exportData, array $filtersData, array $aggregatorsData, ExportGenerationContext $context): FormattedExportGeneration + */ interface FormatterInterface { public const TYPE_LIST = 'list'; @@ -30,16 +34,16 @@ interface FormatterInterface */ public function buildForm( FormBuilderInterface $builder, - $exportAlias, + string $exportAlias, array $aggregatorAliases, - ); + ): void; /** * get the default data for the form build by buildForm. */ public function getFormDefaultData(array $aggregatorAliases): array; - public function getName(); + public function getName(): string|TranslatableInterface; /** * Generate a response from the data collected on differents ExportElementInterface. @@ -47,19 +51,28 @@ interface FormatterInterface * @param mixed[] $result The result, as given by the ExportInterface * @param mixed[] $formatterData collected from the current form * @param string $exportAlias the id of the current export - * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data * * @return \Symfony\Component\HttpFoundation\Response The response to be shown + * + * @deprecated use generate instead */ public function getResponse( - $result, - $formatterData, - $exportAlias, + array $result, + array $formatterData, + string $exportAlias, array $exportData, array $filtersData, array $aggregatorsData, + ExportGenerationContext $context, ); - public function getType(); + public function getType(): string; + + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + + public function getNormalizationVersion(): int; } diff --git a/src/Bundle/ChillMainBundle/Export/Helper/ExportManagerAwareTrait.php b/src/Bundle/ChillMainBundle/Export/Helper/ExportManagerAwareTrait.php new file mode 100644 index 000000000..eb89f7970 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Helper/ExportManagerAwareTrait.php @@ -0,0 +1,34 @@ +exportManager = $exportManager; + } + + public function getExportManager(): ExportManager + { + if (null === $this->exportManager) { + throw new ExportRuntimeException('ExportManager not set'); + } + + return $this->exportManager; + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ListInterface.php b/src/Bundle/ChillMainBundle/Export/ListInterface.php index 9b88525ca..3fe9315ae 100644 --- a/src/Bundle/ChillMainBundle/Export/ListInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ListInterface.php @@ -11,6 +11,9 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Doctrine\ORM\NativeQuery; +use Doctrine\ORM\QueryBuilder; + /** * Define methods to export list. * @@ -19,5 +22,10 @@ namespace Chill\MainBundle\Export; * (and list does not support aggregation on their data). * * When used, the `ExportManager` will not handle aggregator for this class. + * + * @template Q of QueryBuilder|NativeQuery + * @template D of array + * + * @template-extends ExportInterface */ interface ListInterface extends ExportInterface {} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessage.php b/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessage.php new file mode 100644 index 000000000..cb6c757b8 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessage.php @@ -0,0 +1,31 @@ +id = $exportGeneration->getId(); + $this->userId = $user->getId(); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessageHandler.php b/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessageHandler.php new file mode 100644 index 000000000..94610a995 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/ExportRequestGenerationMessageHandler.php @@ -0,0 +1,77 @@ +logger->info( + self::LOG_PREFIX.'Handle generation message', + [ + 'exportId' => (string) $exportRequestGenerationMessage->id, + ] + ); + + if (null === $exportGeneration = $this->repository->find($exportRequestGenerationMessage->id)) { + throw new \UnexpectedValueException('ExportRequestGenerationMessage not found'); + } + + if (null === $user = $this->userRepository->find($exportRequestGenerationMessage->userId)) { + throw new \UnexpectedValueException('User not found'); + } + + if (StoredObject::STATUS_PENDING !== $exportGeneration->getStatus()) { + throw new UnrecoverableMessageHandlingException('object already generated'); + } + + $generated = $this->exportGenerator->generate($exportGeneration->getExportAlias(), $exportGeneration->getOptions(), $user); + $this->storedObjectManager->write($exportGeneration->getStoredObject(), $generated->content, $generated->contentType); + $exportGeneration->getStoredObject()->setStatus(StoredObject::STATUS_READY); + + $this->entityManager->flush(); + + $end = microtime(true); + + $this->logger->notice(self::LOG_PREFIX.'Export generation successfully finished', [ + 'exportId' => (string) $exportRequestGenerationMessage->id, + 'exportAlias' => $exportGeneration->getExportAlias(), + 'full_generation_duration' => $end - $exportGeneration->getCreatedAt()->getTimestamp(), + 'message_handler_duration' => $end - $start, + ]); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/OnExportGenerationFails.php b/src/Bundle/ChillMainBundle/Export/Messenger/OnExportGenerationFails.php new file mode 100644 index 000000000..04c07f5cf --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/OnExportGenerationFails.php @@ -0,0 +1,74 @@ + 'onMessageFailed', + ]; + } + + public function onMessageFailed(WorkerMessageFailedEvent $event): void + { + if ($event->willRetry()) { + return; + } + + $message = $event->getEnvelope()->getMessage(); + + if (!$message instanceof ExportRequestGenerationMessage) { + return; + } + + if (null === $exportGeneration = $this->repository->find($message->id)) { + throw new \UnexpectedValueException('ExportRequestGenerationMessage not found'); + } + + $this->logger->error(self::LOG_PREFIX.'ExportRequestGenerationMessage failed to execute generation', [ + 'exportId' => (string) $message->id, + 'userId' => $message->userId, + 'alias' => $exportGeneration->getExportAlias(), + 'throwable_message' => $event->getThrowable()->getMessage(), + 'throwable_trace' => $event->getThrowable()->getTraceAsString(), + 'throwable' => $event->getThrowable()::class, + 'full_generation_duration_failure' => microtime(true) - $exportGeneration->getCreatedAt()->getTimestamp(), + ]); + + $this->markObjectAsFailed($event, $exportGeneration); + $this->entityManager->flush(); + } + + private function markObjectAsFailed(WorkerMessageFailedEvent $event, ExportGeneration $exportGeneration): void + { + $exportGeneration->getStoredObject()->addGenerationErrors($event->getThrowable()->getMessage()); + $exportGeneration->getStoredObject()->setStatus(StoredObject::STATUS_FAILURE); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessage.php b/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessage.php new file mode 100644 index 000000000..31e68a82d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessage.php @@ -0,0 +1,24 @@ +exportGenerationId = $exportGeneration->getId()->toString(); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessageHandler.php b/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessageHandler.php new file mode 100644 index 000000000..c1f59a8fd --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Messenger/RemoveExportGenerationMessageHandler.php @@ -0,0 +1,49 @@ +exportGenerationRepository->find($message->exportGenerationId); + + if (null === $exportGeneration) { + $this->logger->error(self::LOG_PREFIX.'ExportGeneration not found'); + throw new UnrecoverableMessageHandlingException(self::LOG_PREFIX.'ExportGeneration not found'); + } + + $storedObject = $exportGeneration->getStoredObject(); + $storedObject->setDeleteAt($this->clock->now()); + + $this->entityManager->remove($exportGeneration); + $this->entityManager->flush(); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/Migrator/SavedExportOptionsMigrator.php b/src/Bundle/ChillMainBundle/Export/Migrator/SavedExportOptionsMigrator.php new file mode 100644 index 000000000..36e9c5f7d --- /dev/null +++ b/src/Bundle/ChillMainBundle/Export/Migrator/SavedExportOptionsMigrator.php @@ -0,0 +1,124 @@ + array_map( + self::mapFormData(...), + array_filter( + $fromOptions['export']['export']['export'] ?? [], + static fn (string $key) => !in_array($key, ['filters', 'aggregators', 'pick_formatter'], true), + ARRAY_FILTER_USE_KEY, + ), + ), + 'version' => 1, + ]; + + $to['pick_formatter'] = $fromOptions['export']['export']['pick_formatter']['alias'] ?? null; + $to['centers'] = [ + 'centers' => array_values(array_map(static fn ($id) => (int) $id, $fromOptions['centers']['centers']['c'] ?? $fromOptions['centers']['centers']['center'] ?? [])), + 'regroupments' => array_values(array_map(static fn ($id) => (int) $id, $fromOptions['centers']['centers']['regroupment'] ?? [])), + ]; + $to['formatter'] = [ + 'form' => $fromOptions['formatter']['formatter'] ?? [], + 'version' => 1, + ]; + + return $to; + } + + private static function mapEnabledStatus(array $modifiersData): array + { + if ('1' === ($modifiersData['enabled'] ?? '0')) { + return [ + 'form' => array_map(self::mapFormData(...), $modifiersData['form'] ?? []), + 'version' => 1, + 'enabled' => true, + ]; + } + + return ['enabled' => false]; + } + + private static function mapFormData(array|string $formData): array|string|null + { + if (is_array($formData) && array_key_exists('roll', $formData)) { + return self::refactorRollingDate($formData); + } + + if (is_string($formData)) { + // we try different date formats + if (false !== \DateTimeImmutable::createFromFormat('d-m-Y', $formData)) { + return $formData; + } + if (false !== \DateTimeImmutable::createFromFormat('Y-m-d', $formData)) { + return $formData; + } + + // we try json content + try { + $data = json_decode($formData, true, 512, JSON_THROW_ON_ERROR); + + if (is_array($data)) { + if (array_key_exists('type', $data) && array_key_exists('id', $data) && in_array($data['type'], ['person', 'thirdParty', 'user'], true)) { + return $data['id']; + } + $response = []; + foreach ($data as $item) { + if (array_key_exists('type', $item) && array_key_exists('id', $item) && in_array($item['type'], ['person', 'thirdParty', 'user'], true)) { + $response[] = $item['id']; + } + } + if ([] !== $response) { + return $response; + } + } + } catch (\JsonException) { + return $formData; + } + } + + return $formData; + } + + private static function refactorRollingDate(array $formData): ?array + { + if ('' === $formData['roll']) { + return null; + } + + $fixedDate = null !== ($formData['fixedDate'] ?? null) && '' !== $formData['fixedDate'] ? + \DateTimeImmutable::createFromFormat('Y-m-d H:i:s', sprintf('%s 00:00:00', $formData['fixedDate']), new \DateTimeZone(date_default_timezone_get())) : null; + + return (new RollingDate( + $formData['roll'], + $fixedDate, + ))->normalize(); + } +} diff --git a/src/Bundle/ChillMainBundle/Export/ModifierInterface.php b/src/Bundle/ChillMainBundle/Export/ModifierInterface.php index d3ec9de83..9596e026b 100644 --- a/src/Bundle/ChillMainBundle/Export/ModifierInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ModifierInterface.php @@ -37,12 +37,12 @@ interface ModifierInterface extends ExportElementInterface * @param QueryBuilder $qb the QueryBuilder initiated by the Export (and eventually modified by other Modifiers) * @param mixed[] $data the data from the Form (builded by buildForm) */ - public function alterQuery(QueryBuilder $qb, $data); + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void; /** * On which type of Export this ModifiersInterface may apply. * * @return string the type on which the Modifiers apply */ - public function applyOn(); + public function applyOn(): string; } diff --git a/src/Bundle/ChillMainBundle/Export/SortExportElement.php b/src/Bundle/ChillMainBundle/Export/SortExportElement.php index 6228109ed..7fdac9be1 100644 --- a/src/Bundle/ChillMainBundle/Export/SortExportElement.php +++ b/src/Bundle/ChillMainBundle/Export/SortExportElement.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Export; +use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; final readonly class SortExportElement @@ -19,12 +20,21 @@ final readonly class SortExportElement private TranslatorInterface $translator, ) {} + private function trans(string|TranslatableInterface $message): string + { + if ($message instanceof TranslatableInterface) { + return $message->trans($this->translator, $this->translator->getLocale()); + } + + return $this->translator->trans($message); + } + /** * @param array $elements */ public function sortFilters(array &$elements): void { - uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle())); + uasort($elements, fn (FilterInterface $a, FilterInterface $b) => $this->trans($a->getTitle()) <=> $this->trans($b->getTitle())); } /** @@ -32,6 +42,6 @@ final readonly class SortExportElement */ public function sortAggregators(array &$elements): void { - uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->translator->trans($a->getTitle()) <=> $this->translator->trans($b->getTitle())); + uasort($elements, fn (AggregatorInterface $a, AggregatorInterface $b) => $this->trans($a->getTitle()) <=> $this->trans($b->getTitle())); } } diff --git a/src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php b/src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php deleted file mode 100644 index 2a19c8e0b..000000000 --- a/src/Bundle/ChillMainBundle/Form/DataMapper/ExportPickCenterDataMapper.php +++ /dev/null @@ -1,56 +0,0 @@ - $form */ - $form = iterator_to_array($forms); - - $form['center']->setData($viewData); - - // NOTE: we do not map back the regroupments - } - - public function mapFormsToData(\Traversable $forms, &$viewData): void - { - /** @var array $forms */ - $forms = iterator_to_array($forms); - - $centers = []; - - foreach ($forms['center']->getData() as $center) { - $centers[spl_object_hash($center)] = $center; - } - - if (\array_key_exists('regroupment', $forms)) { - /** @var Regroupment $regroupment */ - foreach ($forms['regroupment']->getData() as $regroupment) { - foreach ($regroupment->getCenters() as $center) { - $centers[spl_object_hash($center)] = $center; - } - } - } - - $viewData = array_values($centers); - } -} diff --git a/src/Bundle/ChillMainBundle/Form/SavedExportType.php b/src/Bundle/ChillMainBundle/Form/SavedExportType.php index 16aa4e42e..beccfbd19 100644 --- a/src/Bundle/ChillMainBundle/Form/SavedExportType.php +++ b/src/Bundle/ChillMainBundle/Form/SavedExportType.php @@ -13,15 +13,22 @@ namespace Chill\MainBundle\Form; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Form\Type\ChillTextareaType; +use Chill\MainBundle\Form\Type\PickUserGroupOrUserDynamicType; +use Chill\MainBundle\Security\Authorization\SavedExportVoter; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Security; class SavedExportType extends AbstractType { + public function __construct(private readonly Security $security) {} + public function buildForm(FormBuilderInterface $builder, array $options) { + $savedExport = $options['data']; + $builder ->add('title', TextType::class, [ 'required' => true, @@ -29,6 +36,14 @@ class SavedExportType extends AbstractType ->add('description', ChillTextareaType::class, [ 'required' => false, ]); + + if ($this->security->isGranted(SavedExportVoter::SHARE, $savedExport)) { + $builder->add('share', PickUserGroupOrUserDynamicType::class, [ + 'multiple' => true, + 'required' => false, + 'label' => 'saved_export.Share', + ]); + } } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php index 8f0126049..cf78cd33d 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php +++ b/src/Bundle/ChillMainBundle/Form/Type/DataTransformer/EntityToJsonTransformer.php @@ -66,8 +66,11 @@ class EntityToJsonTransformer implements DataTransformerInterface ]); } - private function denormalizeOne(array $item) + private function denormalizeOne(array|string $item) { + if ('me' === $item) { + return $item; + } if (!\array_key_exists('type', $item)) { throw new TransformationFailedException('the key "type" is missing on element'); } @@ -98,5 +101,6 @@ class EntityToJsonTransformer implements DataTransformerInterface 'json', $context, ); + } } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php index 72e501108..cc6e583d5 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/AggregatorType.php @@ -21,15 +21,18 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class AggregatorType extends AbstractType { + public const ENABLED_FIELD = 'enabled'; + public function buildForm(FormBuilderInterface $builder, array $options): void { $exportManager = $options['export_manager']; $aggregator = $exportManager->getAggregator($options['aggregator_alias']); $builder - ->add('enabled', CheckboxType::class, [ + ->add(self::ENABLED_FIELD, CheckboxType::class, [ 'value' => true, 'required' => false, + 'disabled' => $options['disable_enable_field'], ]); $aggregatorFormBuilder = $builder->create('form', FormType::class, [ @@ -53,6 +56,7 @@ class AggregatorType extends AbstractType { $resolver->setRequired('aggregator_alias') ->setRequired('export_manager') + ->setDefault('disable_enable_field', false) ->setDefault('compound', true) ->setDefault('error_bubbling', false); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php index fe4c43830..8f654ced2 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/ExportType.php @@ -35,7 +35,7 @@ class ExportType extends AbstractType public function __construct( private readonly ExportManager $exportManager, private readonly SortExportElement $sortExportElement, - protected ParameterBagInterface $parameterBag, + ParameterBagInterface $parameterBag, ) { $this->personFieldsConfig = $parameterBag->get('chill_person.person_fields'); } @@ -43,6 +43,8 @@ class ExportType extends AbstractType public function buildForm(FormBuilderInterface $builder, array $options) { $export = $this->exportManager->getExport($options['export_alias']); + /** @var bool $canEditFull */ + $canEditFull = $options['can_edit_full']; $exportOptions = [ 'compound' => true, @@ -59,8 +61,18 @@ class ExportType extends AbstractType if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { // add filters - $filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); + $filterAliases = $options['allowed_filters']; + $filters = []; + if (is_iterable($filterAliases)) { + foreach ($filterAliases as $alias => $filter) { + $filters[$alias] = $filter; + } + } else { + $filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); + } + $this->sortExportElement->sortFilters($filters); + $filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, ['compound' => true]); foreach ($filters as $alias => $filter) { @@ -70,15 +82,26 @@ class ExportType extends AbstractType 'constraints' => [ new ExportElementConstraint(['element' => $filter]), ], + 'disable_enable_field' => !$canEditFull, ]); } $builder->add($filterBuilder); // add aggregators - $aggregators = $this->exportManager - ->getAggregatorsApplyingOn($export, $options['picked_centers']); + $aggregatorsAliases = $options['allowed_aggregators']; + $aggregators = []; + if (is_iterable($aggregatorsAliases)) { + foreach ($aggregatorsAliases as $alias => $aggregator) { + $aggregators[$alias] = $aggregator; + } + } else { + $aggregators = $this->exportManager + ->getAggregatorsApplyingOn($export, $options['picked_centers']); + } + $this->sortExportElement->sortAggregators($aggregators); + $aggregatorBuilder = $builder->create( self::AGGREGATOR_KEY, FormType::class, @@ -96,11 +119,11 @@ class ExportType extends AbstractType } } - $aggregatorBuilder->add($alias, AggregatorType::class, [ 'aggregator_alias' => $alias, 'export_manager' => $this->exportManager, 'label' => $aggregator->getTitle(), + 'disable_enable_field' => !$canEditFull, 'constraints' => [ new ExportElementConstraint(['element' => $aggregator]), ], @@ -125,8 +148,13 @@ class ExportType extends AbstractType public function configureOptions(OptionsResolver $resolver) { - $resolver->setRequired(['export_alias', 'picked_centers']) + $resolver->setRequired(['export_alias', 'picked_centers', 'can_edit_full']) ->setAllowedTypes('export_alias', ['string']) + ->setAllowedValues('can_edit_full', [true, false]) + ->setDefault('allowed_filters', null) + ->setAllowedTypes('allowed_filters', ['iterable', 'null']) + ->setDefault('allowed_aggregators', null) + ->setAllowedTypes('allowed_aggregators', ['iterable', 'null']) ->setDefault('compound', true) ->setDefault('constraints', [ // new \Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint() diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php index d4873df83..7e1550878 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/FilterType.php @@ -34,6 +34,7 @@ class FilterType extends AbstractType ->add(self::ENABLED_FIELD, CheckboxType::class, [ 'value' => true, 'required' => false, + 'disabled' => $options['disable_enable_field'], ]); $filterFormBuilder = $builder->create('form', FormType::class, [ @@ -58,6 +59,7 @@ class FilterType extends AbstractType $resolver ->setRequired('filter') ->setAllowedTypes('filter', [FilterInterface::class]) + ->setDefault('disable_enable_field', false) ->setDefault('compound', true) ->setDefault('error_bubbling', false); } diff --git a/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php b/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php index fa8caf488..9196b05c0 100644 --- a/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php +++ b/src/Bundle/ChillMainBundle/Form/Type/Export/PickCenterType.php @@ -14,9 +14,9 @@ namespace Chill\MainBundle\Form\Type\Export; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\Regroupment; use Chill\MainBundle\Export\ExportManager; -use Chill\MainBundle\Form\DataMapper\ExportPickCenterDataMapper; use Chill\MainBundle\Repository\RegroupmentRepository; use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; +use Chill\MainBundle\Service\Regroupement\RegroupementFiltering; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -27,27 +27,26 @@ use Symfony\Component\OptionsResolver\OptionsResolver; */ final class PickCenterType extends AbstractType { - public const CENTERS_IDENTIFIERS = 'c'; - public function __construct( private readonly ExportManager $exportManager, private readonly RegroupmentRepository $regroupmentRepository, private readonly AuthorizationHelperForCurrentUserInterface $authorizationHelper, + private readonly RegroupementFiltering $regroupementFiltering, ) {} public function buildForm(FormBuilderInterface $builder, array $options) { $export = $this->exportManager->getExport($options['export_alias']); $centers = $this->authorizationHelper->getReachableCenters( - $export->requiredRole() + $export->requiredRole(), ); $centersActive = array_filter($centers, fn (Center $c) => $c->getIsActive()); // order alphabetically - usort($centersActive, fn (Center $a, Center $b) => $a->getCenter() <=> $b->getName()); + usort($centersActive, fn (Center $a, Center $b) => $a->getName() <=> $b->getName()); - $builder->add('center', EntityType::class, [ + $builder->add('centers', EntityType::class, [ 'class' => Center::class, 'choices' => $centersActive, 'label' => 'center', @@ -56,18 +55,22 @@ final class PickCenterType extends AbstractType 'choice_label' => static fn (Center $c) => $c->getName(), ]); - if (\count($this->regroupmentRepository->findAllActive()) > 0) { - $builder->add('regroupment', EntityType::class, [ + $groups = $this->regroupementFiltering + ->filterContainsAtLeastOneCenter($this->regroupmentRepository->findAllActive(), $centersActive); + + // order alphabetically + usort($groups, fn (Regroupment $a, Regroupment $b) => $a->getName() <=> $b->getName()); + + if (\count($groups) > 0) { + $builder->add('regroupments', EntityType::class, [ 'class' => Regroupment::class, 'label' => 'regroupment', 'multiple' => true, 'expanded' => true, - 'choices' => $this->regroupmentRepository->findAllActive(), + 'choices' => $groups, 'choice_label' => static fn (Regroupment $r) => $r->getName(), ]); } - - $builder->setDataMapper(new ExportPickCenterDataMapper()); } public function configureOptions(OptionsResolver $resolver) diff --git a/src/Bundle/ChillMainBundle/Form/Type/PickUserOrMeDynamicType.php b/src/Bundle/ChillMainBundle/Form/Type/PickUserOrMeDynamicType.php new file mode 100644 index 000000000..93ecd3f4e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Form/Type/PickUserOrMeDynamicType.php @@ -0,0 +1,82 @@ +addViewTransformer(new EntityToJsonTransformer($this->denormalizer, $this->serializer, $options['multiple'], 'user')); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['multiple'] = $options['multiple']; + $view->vars['types'] = ['user']; + $view->vars['uniqid'] = uniqid('pick_user_or_me_dyn'); + $view->vars['suggested'] = []; + $view->vars['as_id'] = true === $options['as_id'] ? '1' : '0'; + $view->vars['submit_on_adding_new_entity'] = true === $options['submit_on_adding_new_entity'] ? '1' : '0'; + + foreach ($options['suggested'] as $user) { + $view->vars['suggested'][] = $this->normalizer->normalize($user, 'json', ['groups' => 'read']); + } + // $user = /* should come from context */ $options['context']; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('multiple', false) + ->setAllowedTypes('multiple', ['bool']) + ->setDefault('compound', false) + ->setDefault('suggested', []) + // if set to true, only the id will be set inside the content. The denormalization will not work. + ->setDefault('as_id', false) + ->setAllowedTypes('as_id', ['bool']) + ->setDefault('submit_on_adding_new_entity', false) + ->setAllowedTypes('submit_on_adding_new_entity', ['bool']); + } + + public function getBlockPrefix() + { + return 'pick_entity_dynamic'; + } +} diff --git a/src/Bundle/ChillMainBundle/Form/UserGroupType.php b/src/Bundle/ChillMainBundle/Form/UserGroupType.php index 2e3819560..2ab7ea358 100644 --- a/src/Bundle/ChillMainBundle/Form/UserGroupType.php +++ b/src/Bundle/ChillMainBundle/Form/UserGroupType.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Form; +use Chill\MainBundle\Entity\UserGroup; use Chill\MainBundle\Form\Type\PickUserDynamicType; use Chill\MainBundle\Form\Type\TranslatableStringFormType; use Symfony\Component\Form\AbstractType; @@ -23,6 +24,9 @@ class UserGroupType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { + /** @var UserGroup $userGroup */ + $userGroup = $options['data']; + $builder ->add('label', TranslatableStringFormType::class, [ 'label' => 'user_group.Label', @@ -46,20 +50,25 @@ class UserGroupType extends AbstractType 'help' => 'user_group.ExcludeKeyHelp', 'required' => false, 'empty_data' => '', - ]) - ->add('users', PickUserDynamicType::class, [ - 'label' => 'user_group.Users', - 'multiple' => true, - 'required' => false, - 'empty_data' => [], - ]) - ->add('adminUsers', PickUserDynamicType::class, [ - 'label' => 'user_group.adminUsers', - 'multiple' => true, - 'required' => false, - 'empty_data' => [], - 'help' => 'user_group.adminUsersHelp', - ]) - ; + ]); + + if (!$userGroup->hasUserJob()) { + $builder + ->add('users', PickUserDynamicType::class, [ + 'label' => 'user_group.Users', + 'multiple' => true, + 'required' => false, + 'empty_data' => [], + ]) + ->add('adminUsers', PickUserDynamicType::class, [ + 'label' => 'user_group.adminUsers', + 'multiple' => true, + 'required' => false, + 'empty_data' => [], + 'help' => 'user_group.adminUsersHelp', + ]) + ; + } + } } diff --git a/src/Bundle/ChillMainBundle/Repository/ExportGenerationRepository.php b/src/Bundle/ChillMainBundle/Repository/ExportGenerationRepository.php new file mode 100644 index 000000000..bb70a22ba --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/ExportGenerationRepository.php @@ -0,0 +1,85 @@ + + * + * @implements AssociatedEntityToStoredObjectInterface + */ +class ExportGenerationRepository extends ServiceEntityRepository implements AssociatedEntityToStoredObjectInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, ExportGeneration::class); + } + + public function findAssociatedEntityToStoredObject(StoredObject $storedObject): ?ExportGeneration + { + return $this->createQueryBuilder('e') + ->where('e.storedObject = :storedObject') + ->setParameter('storedObject', $storedObject) + ->getQuery() + ->getOneOrNullResult(); + } + + /** + * @return list + */ + public function findExportGenerationByAliasAndUser(string $alias, User $user, int $limit = 100, int $offset = 0): array + { + return $this->createQueryBuilder('e') + ->where('e.createdBy = :user') + ->andWhere('e.exportAlias LIKE :alias') + ->orderBy('e.createdAt', 'DESC') + ->setParameter('user', $user) + ->setParameter('alias', $alias) + ->setFirstResult($offset) + ->setMaxResults($limit) + ->getQuery() + ->getResult(); + } + + /** + * @return list + */ + public function findExportGenerationBySavedExportAndUser(SavedExport $savedExport, User $user, int $limit = 100, int $offset = 0): array + { + return $this->createQueryBuilder('e') + ->where('e.createdBy = :user') + ->andWhere('e.savedExport = :savedExport') + ->orderBy('e.createdAt', 'DESC') + ->setParameter('user', $user) + ->setParameter('savedExport', $savedExport) + ->setFirstResult($offset) + ->setMaxResults($limit) + ->getQuery() + ->getResult(); + } + + public function findExpiredExportGeneration(\DateTimeImmutable $atDate): iterable + { + return $this->createQueryBuilder('e') + ->where('e.deleteAt < :atDate') + ->setParameter('atDate', $atDate) + ->getQuery() + ->toIterable(); + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php index 35ec31ff3..ec24421f8 100644 --- a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepository.php @@ -13,6 +13,7 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\GeographicalUnit; +use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\Expr\Join; @@ -42,7 +43,7 @@ final readonly class GeographicalUnitRepository implements GeographicalUnitRepos $qb = $this->buildQueryGeographicalUnitContainingAddress($address); return $qb - ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class)) + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', SimpleGeographicalUnitDTO::class)) ->addOrderBy('IDENTITY(gu.layer)') ->addOrderBy('gu.unitName') ->getQuery() @@ -58,7 +59,7 @@ final readonly class GeographicalUnitRepository implements GeographicalUnitRepos ; return $qb - ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class)) + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', SimpleGeographicalUnitDTO::class)) ->innerJoin(Address::class, 'address', Join::WITH, 'ST_CONTAINS(gu.geom, address.point) = TRUE') ->where($qb->expr()->eq('address', ':address')) ->setParameter('address', $address) @@ -70,6 +71,19 @@ final readonly class GeographicalUnitRepository implements GeographicalUnitRepos return $this->repository->find($id); } + public function findSimpleGeographicalUnit(int $id): ?SimpleGeographicalUnitDTO + { + $qb = $this->repository + ->createQueryBuilder('gu'); + + return $qb + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', SimpleGeographicalUnitDTO::class)) + ->where('gu.id = :id') + ->setParameter('id', $id) + ->getQuery() + ->getOneOrNullResult(); + } + /** * Will return only partial object, where the @see{GeographicalUnit::geom} property is not loaded. * @@ -79,7 +93,7 @@ final readonly class GeographicalUnitRepository implements GeographicalUnitRepos { return $this->repository ->createQueryBuilder('gu') - ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', GeographicalUnit\SimpleGeographicalUnitDTO::class)) + ->select(sprintf('NEW %s(gu.id, gu.unitName, gu.unitRefId, IDENTITY(gu.layer))', SimpleGeographicalUnitDTO::class)) ->addOrderBy('IDENTITY(gu.layer)') ->addOrderBy('gu.unitName') ->getQuery() diff --git a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php index b00b0cd98..695474036 100644 --- a/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php +++ b/src/Bundle/ChillMainBundle/Repository/GeographicalUnitRepositoryInterface.php @@ -27,4 +27,6 @@ interface GeographicalUnitRepositoryInterface extends ObjectRepository public function findGeographicalUnitContainingAddress(Address $address, int $offset = 0, int $limit = 50): array; public function countGeographicalUnitContainingAddress(Address $address): int; + + public function findSimpleGeographicalUnit(int $id): ?SimpleGeographicalUnitDTO; } diff --git a/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php index 793015f5b..b78e5ccc1 100644 --- a/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepository.php @@ -16,9 +16,8 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; -use Doctrine\Persistence\ObjectRepository; -final readonly class RegroupmentRepository implements ObjectRepository +final readonly class RegroupmentRepository implements RegroupmentRepositoryInterface { private EntityRepository $repository; diff --git a/src/Bundle/ChillMainBundle/Repository/RegroupmentRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepositoryInterface.php new file mode 100644 index 000000000..501cee8c5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/RegroupmentRepositoryInterface.php @@ -0,0 +1,34 @@ + + */ +interface RegroupmentRepositoryInterface extends ObjectRepository +{ + /** + * @throws NonUniqueResultException + * @throws NoResultException + */ + public function findOneByName(string $name): ?Regroupment; + + /** + * @return array + */ + public function findRegroupmentAssociatedToNoCenter(): array; +} diff --git a/src/Bundle/ChillMainBundle/Repository/SavedExportOrExportGenerationRepository.php b/src/Bundle/ChillMainBundle/Repository/SavedExportOrExportGenerationRepository.php new file mode 100644 index 000000000..81174fd1e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Repository/SavedExportOrExportGenerationRepository.php @@ -0,0 +1,32 @@ +savedExportRepository->find($uuid)) { + return $savedExport; + } + + return $this->exportGenerationRepository->find($uuid); + } +} diff --git a/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php b/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php index 702fdf7d6..9b0d5a2ef 100644 --- a/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/SavedExportRepository.php @@ -13,9 +13,12 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\SavedExport; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\UserGroup; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; +use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; +use Symfony\Component\String\UnicodeString; /** * @implements ObjectRepository @@ -55,6 +58,51 @@ class SavedExportRepository implements SavedExportRepositoryInterface ->where($qb->expr()->eq('se.user', ':user')) ->setParameter('user', $user); + return $this->prepareResult($qb, $orderBy, $limit, $offset); + } + + public function findSharedWithUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null, array $filters = []): array + { + $qb = $this->repository->createQueryBuilder('se'); + + $qb + ->where( + $qb->expr()->orX( + $qb->expr()->eq('se.user', ':user'), + $qb->expr()->isMemberOf(':user', 'se.sharedWithUsers'), + $qb->expr()->exists( + sprintf('SELECT 1 FROM %s ug WHERE ug MEMBER OF se.sharedWithGroups AND :user MEMBER OF ug.users', UserGroup::class) + ) + ) + ) + ->setParameter('user', $user); + + foreach ($filters as $key => $filter) { + if (self::FILTER_TITLE === ($key & self::FILTER_TITLE) + || self::FILTER_DESCRIPTION === ($key & self::FILTER_DESCRIPTION)) { + $filter = new UnicodeString($filter); + + $i = 0; + foreach ($filter->split(' ') as $word) { + $orx = $qb->expr()->orX(); + if (self::FILTER_TITLE === ($key & self::FILTER_TITLE)) { + $orx->add($qb->expr()->like('LOWER(se.title)', 'LOWER(:qs'.$i.')')); + } + if (self::FILTER_DESCRIPTION === ($key & self::FILTER_DESCRIPTION)) { + $orx->add($qb->expr()->like('LOWER(se.description)', 'LOWER(:qs'.$i.')')); + } + $qb->andWhere($orx); + $qb->setParameter('qs'.$i, '%'.$word->trim().'%'); + ++$i; + } + } + } + + return $this->prepareResult($qb, $orderBy, $limit, $offset); + } + + private function prepareResult(QueryBuilder $qb, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array + { if (null !== $limit) { $qb->setMaxResults($limit); } diff --git a/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php index 3b168505f..c62e0f29c 100644 --- a/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php +++ b/src/Bundle/ChillMainBundle/Repository/SavedExportRepositoryInterface.php @@ -20,6 +20,9 @@ use Doctrine\Persistence\ObjectRepository; */ interface SavedExportRepositoryInterface extends ObjectRepository { + public const FILTER_TITLE = 0x01; + public const FILTER_DESCRIPTION = 0x10; + public function find($id): ?SavedExport; /** @@ -34,6 +37,15 @@ interface SavedExportRepositoryInterface extends ObjectRepository */ public function findByUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array; + /** + * Get the saved export created by and the user and the ones shared with the user. + * + * @param array $filters filters where keys are one of the constant starting with FILTER_ + * + * @return list + */ + public function findSharedWithUser(User $user, ?array $orderBy = [], ?int $limit = null, ?int $offset = null, array $filters = []): array; + public function findOneBy(array $criteria): ?SavedExport; public function getClassName(): string; diff --git a/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php b/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php index 6b793a2c8..ff8e5ffd2 100644 --- a/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/UserJobRepository.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Repository; +use Chill\MainBundle\Entity\UserGroup; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Doctrine\ORM\EntityManagerInterface; @@ -30,9 +31,6 @@ readonly class UserJobRepository implements UserJobRepositoryInterface return $this->repository->find($id); } - /** - * @return array|UserJob[] - */ public function findAll(): array { return $this->repository->findAll(); @@ -56,12 +54,20 @@ readonly class UserJobRepository implements UserJobRepositoryInterface return $jobs; } - /** - * @param mixed|null $limit - * @param mixed|null $offset - * - * @return array|object[]|UserJob[] - */ + public function findAllNotAssociatedWithUserGroup(): array + { + $qb = $this->repository->createQueryBuilder('u'); + $qb->select('u'); + + $qb->where( + $qb->expr()->not( + $qb->expr()->exists(sprintf('SELECT 1 FROM %s ug WHERE ug.userJob = u', UserGroup::class)) + ) + ); + + return $qb->getQuery()->getResult(); + } + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null) { return $this->repository->findBy($criteria, $orderBy, $limit, $offset); diff --git a/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php b/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php index 75c2a5671..df042101c 100644 --- a/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php +++ b/src/Bundle/ChillMainBundle/Repository/UserJobRepositoryInterface.php @@ -14,18 +14,15 @@ namespace Chill\MainBundle\Repository; use Chill\MainBundle\Entity\UserJob; use Doctrine\Persistence\ObjectRepository; +/** + * @template-extends ObjectRepository + */ interface UserJobRepositoryInterface extends ObjectRepository { public function find($id): ?UserJob; - /** - * @return array|UserJob[] - */ public function findAll(): array; - /** - * @return array|UserJob[] - */ public function findAllActive(): array; /** @@ -36,11 +33,14 @@ interface UserJobRepositoryInterface extends ObjectRepository public function findAllOrderedByName(): array; /** - * @param mixed|null $limit - * @param mixed|null $offset + * Find all the user job which are not related to a UserGroup. * - * @return array|object[]|UserJob[] + * This is useful for synchronizing UserGroups with jobs. + * + * @return list */ + public function findAllNotAssociatedWithUserGroup(): array; + public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null); public function findOneBy(array $criteria): ?UserJob; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts new file mode 100644 index 000000000..7e350b102 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts @@ -0,0 +1,18 @@ +import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; +import { ExportGeneration } from "ChillMainAssets/types"; + +export const fetchExportGenerationStatus = async ( + exportGenerationId: string, +): Promise => + makeFetch( + "GET", + `/api/1.0/main/export-generation/${exportGenerationId}/object`, + ); + +export const generateFromSavedExport = async ( + savedExportUuid: string, +): Promise => + makeFetch( + "POST", + `/api/1.0/main/export/export-generation/create-from-saved-export/${savedExportUuid}`, + ); diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts new file mode 100644 index 000000000..e444aa2e5 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts @@ -0,0 +1,3 @@ +export function buildReturnPath(location: Location): string { + return location.pathname + location.search; +} diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js index 1b3ee4594..9f6bb753e 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js @@ -12,6 +12,11 @@ function loadDynamicPicker(element) { let apps = element.querySelectorAll('[data-module="pick-dynamic"]'); apps.forEach(function (el) { + let suggested; + let as_id; + let submit_on_adding_new_entity; + let label; + let isCurrentUserPicker; const isMultiple = parseInt(el.dataset.multiple) === 1, uniqId = el.dataset.uniqid, input = element.querySelector( @@ -22,12 +27,13 @@ function loadDynamicPicker(element) { ? JSON.parse(input.value) : input.value === "[]" || input.value === "" ? null - : [JSON.parse(input.value)], - suggested = JSON.parse(el.dataset.suggested), - as_id = parseInt(el.dataset.asId) === 1, - submit_on_adding_new_entity = - parseInt(el.dataset.submitOnAddingNewEntity) === 1, - label = el.dataset.label; + : [JSON.parse(input.value)]; + suggested = JSON.parse(el.dataset.suggested); + as_id = parseInt(el.dataset.asId) === 1; + submit_on_adding_new_entity = + parseInt(el.dataset.submitOnAddingNewEntity) === 1; + label = el.dataset.label; + isCurrentUserPicker = uniqId.startsWith("pick_user_or_me_dyn"); if (!isMultiple) { if (input.value === "[]") { @@ -44,6 +50,7 @@ function loadDynamicPicker(element) { ':uniqid="uniqid" ' + ':suggested="notPickedSuggested" ' + ':label="label" ' + + ':isCurrentUserPicker="isCurrentUserPicker" ' + '@addNewEntity="addNewEntity" ' + '@removeEntity="removeEntity" ' + '@addNewEntityProcessEnded="addNewEntityProcessEnded"' + @@ -61,6 +68,7 @@ function loadDynamicPicker(element) { as_id, submit_on_adding_new_entity, label, + isCurrentUserPicker, }; }, computed: { @@ -89,7 +97,8 @@ function loadDynamicPicker(element) { const ids = this.picked.map((el) => el.id); input.value = ids.join(","); } - console.log(entity); + console.log(this.picked); + // console.log(entity); } } else { if ( diff --git a/src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js b/src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js deleted file mode 100644 index 164e8e97e..000000000 --- a/src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js +++ /dev/null @@ -1,14 +0,0 @@ -import { download_report } from "../../lib/download-report/download-report"; - -window.addEventListener("DOMContentLoaded", function (e) { - const export_generate_url = window.export_generate_url; - - if (typeof export_generate_url === "undefined") { - console.error("Alias not found!"); - throw new Error("Alias not found!"); - } - - const query = window.location.search, - container = document.querySelector("#download_container"); - download_report(export_generate_url + query.toString(), container); -}); diff --git a/src/Bundle/ChillMainBundle/Resources/public/types.ts b/src/Bundle/ChillMainBundle/Resources/public/types.ts index b2ba6d948..2cd83bc64 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/types.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/types.ts @@ -1,4 +1,5 @@ import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc"; +import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types"; export interface DateTime { datetime: string; @@ -201,6 +202,16 @@ export interface WorkflowAttachment { genericDoc: null | GenericDoc; } +export interface ExportGeneration { + id: string; + type: "export_generation"; + exportAlias: string; + createdBy: User | null; + createdAt: DateTime | null; + status: StoredObjectStatus; + storedObject: StoredObject; +} + export interface PrivateCommentEmbeddable { comments: Record; } diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue new file mode 100644 index 000000000..033eb6cac --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue @@ -0,0 +1,141 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts new file mode 100644 index 000000000..e970e51b9 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts @@ -0,0 +1,15 @@ +import { createApp } from "vue"; +import App from "./App.vue"; + +const el = document.getElementById("app"); + +if (null === el) { + console.error("div element app was not found"); + throw new Error("div element app was not found"); +} + +const exportGenerationId = el?.dataset.exportGenerationId as string; +const title = el?.dataset.exportTitle as string; +const createdDate = el?.dataset.exportGenerationDate as string; + +createApp(App, { exportGenerationId, title, createdDate }).mount(el); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue index 56e324133..dde8c2dca 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue @@ -1,10 +1,26 @@ - + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/App.vue new file mode 100644 index 000000000..6f021ac80 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/App.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/Component/GenerateButton.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/Component/GenerateButton.vue new file mode 100644 index 000000000..f4dafd006 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/Component/GenerateButton.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/index.ts b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/index.ts new file mode 100644 index 000000000..518746dea --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/SavedExportButtons/index.ts @@ -0,0 +1,13 @@ +import { createApp } from "vue"; + +import App from "./App.vue"; + +const buttons = document.querySelectorAll( + "[data-generate-export-button]", +); + +buttons.forEach((button) => { + const savedExportUuid = button.dataset.savedExportUuid as string; + + createApp(App, { savedExportUuid, savedExportAlias: "" }).mount(button); +}); diff --git a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig index 927e496f9..3e32fdef1 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Dev/dev.assets.html.twig @@ -446,11 +446,31 @@ Toutes les classes btn-* de bootstrap sont fonctionnelles
    -

    Badges

    +

    Entity badges

    Action d'accompagnement Type d'échange Rendez-vous
    + +

    Badges

    + +

    + Primary + Secondary + Success + Danger + Warning + Info + Light + Dark + chill-blue + chill-green + chill-yellow + chill-orange + chill-red + chill-beige +

    + {% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig index a7d7216b4..6ae68f240 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Export/_navbar.html.twig @@ -1,12 +1,14 @@ \ No newline at end of file + diff --git a/src/Bundle/ChillMainBundle/Resources/views/Export/layout.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Export/layout.html.twig index 873ace4ac..9f5b5e99f 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Export/layout.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Export/layout.html.twig @@ -1,5 +1,5 @@ {# - * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, + * Copyright (C) 2014-2015, Champs Libres Cooperative SCRLFS, / * * This program is free software: you can redistribute it and/or modify @@ -20,6 +20,49 @@ {% block title %}{{ 'Exports list'|trans }}{% endblock %} +{% block css %} + {{ parent() }} + +{% endblock %} + +{% macro render_export_card(export, export_alias, generations) %} +
    +
    +
    +

    {{ export.title|trans }}

    +

    {{ export.description|trans }}

    +
    + {% if generations|length > 0 %} +
      + {% for generation in generations %} +
    • + {{ generation.createdAt|format_datetime('short', 'short') }} + {% if generation.status == 'pending' %} +  {{ 'export.generation.Export generation is pending_short'|trans }} + {% elseif generation.status == 'failure' %} +  {{ 'export.generation.Error_short'|trans }} + {% endif %} +
    • + {% endfor %} +
    + {% endif %} + +
    +
    +{% endmacro %} {% block content %} @@ -28,45 +71,23 @@
    - + {% for group, exports in grouped_exports %}{% if group != '_' %} -

    {{ group|trans }}

    -
    +

    {{ group|trans }}

    +
    {% for export_alias, export in exports %} -
    -
    -

    {{ export.title|trans }}

    -

    {{ export.description|trans }}

    -

    - - {{ 'Create an export'|trans }} - -

    -
    -
    + {{ _self.render_export_card(export, export_alias, last_executions[export_alias]) }} {% endfor %}
    {% endif %}{% endfor %} - + {% if grouped_exports|keys|length > 1 and grouped_exports['_']|length > 0 %} -

    {{ 'Ungrouped exports'|trans }}

    +

    {{ 'Ungrouped exports'|trans }}

    {% endif %} - -
    + +
    {% for export_alias,export in grouped_exports['_'] %} - -
    -
    -

    {{ export.title|trans }}

    -

    {{ export.description|trans }}

    -

    - - {{ 'Create an export'|trans }} - -

    -
    -
    - + {{ _self.render_export_card(export, export_alias, last_executions[export_alias]) }} {% endfor %}
    diff --git a/src/Bundle/ChillMainBundle/Resources/views/Export/new_centers_step.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Export/new_centers_step.html.twig index 2e2dc0ec6..42d2d4574 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Export/new_centers_step.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Export/new_centers_step.html.twig @@ -40,15 +40,15 @@

    {{ 'Center'|trans }}

    - {{ form_widget(form.centers.center) }} + {{ form_widget(form.centers.centers) }}
    - {% if form.centers.regroupment is defined %} + {% if form.centers.regroupments is defined %}

    {{ 'Pick aggregated centers'|trans }}

    - {{ form_widget(form.centers.regroupment) }} + {{ form_widget(form.centers.regroupments) }} {% endif %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/ExportGeneration/wait.html.twig b/src/Bundle/ChillMainBundle/Resources/views/ExportGeneration/wait.html.twig new file mode 100644 index 000000000..75bf0fa75 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Resources/views/ExportGeneration/wait.html.twig @@ -0,0 +1,61 @@ +{% extends '@ChillMain/layout.html.twig' %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('page_download_exports') }} +{% endblock %} + +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('page_download_exports') }} +{% endblock %} + + {% block title exportGeneration.linkedToSavedExport ? exportGeneration.savedExport.title : ('Download export'|trans) %} + +{% block content %} +

    {{ block('title') }}

    +
    + + +{% endblock content %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/edit.html.twig b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/edit.html.twig index 98cfd6281..f1f741d1a 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/edit.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/edit.html.twig @@ -2,6 +2,16 @@ {% block title %}{{ 'saved_export.Edit'|trans }}{% endblock %} +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_pickentity_type') }} +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_pickentity_type') }} +{% endblock %} + {% block content %}

    {{ block('title') }}

    @@ -10,9 +20,13 @@ {{ form_row(form.title) }} {{ form_row(form.description) }} + {% if form.share is defined %} + {{ form_row(form.share) }} + {% endif %} + {{ form_end(form) }}
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig index 7e0517e31..6ccadbcdf 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/index.html.twig @@ -1,6 +1,88 @@ {% extends "@ChillMain/layout.html.twig" %} -{% block title %}{{ 'saved_export.My saved exports'|trans }}{% endblock %} +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_saved_export_button') }} + +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_saved_export_button') }} +{% endblock %} + +{% block title %}{{ 'saved_export.Saved exports'|trans }}{% endblock %} + + +{% macro render_export_card(saved, export, export_alias, generations) %} +
    +
    +
    + {{ export.title|trans }} +
    +
    +

    {{ saved.title }}

    + {% if app.user is same as saved.user %} +

    + {% if app.user is same as saved.user %}{{ 'saved_export.Owner'|trans }}{% endif %} + {% if saved.isShared() %}{{ 'saved_export.Shared with others'|trans }}{% endif %} +

    + {% else %} +

    + Partagé par {{ saved.user|chill_entity_render_box }} +

    + {% endif %} +

    {{ saved.description|chill_markdown_to_html }}

    +
    + {% if generations|length > 0 %} +
      + {% for generation in generations %} +
    • + {{ generation.createdAt|format_datetime('short', 'short') }} + {% if generation.status == 'pending' %} +  {{ 'export.generation.Export generation is pending_short'|trans }} + {% elseif generation.status == 'failure' %} +  {{ 'export.generation.Error_short'|trans }} + {% endif %} +
    • + {% endfor %} +
    + {% endif %} + +
    +
    +{% endmacro %} {% block content %}
    @@ -8,6 +90,7 @@ {{ include('@ChillMain/Export/_navbar.html.twig', {'current' : 'my'}) }}
    + {{ filter|chill_render_filter_order_helper }} {% if total == 0 %}

    {{ 'saved_export.Any saved export'|trans }}

    @@ -15,71 +98,23 @@ {% for group, saveds in grouped_exports %} {% if group != '_' %} -

    {{ group }}

    -
    +

    {{ group }}

    +
    {% for s in saveds %} -
    -
    -

    {{ s.export.title|trans }}

    -

    {{ s.saved.title }}

    - -
    {{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}
    - -
    - {{ s.saved.description|chill_markdown_to_html }} -
    - - -
    -
    + {{ _self.render_export_card(s.saved, s.export, s.saved.exportAlias, last_executions[s.saved.id.toString()]) }} {% endfor %}
    {% endif %} {% endfor %} {% if grouped_exports|keys|length > 1 and grouped_exports['_']|default([])|length > 0 %} -

    {{ 'Ungrouped exports'|trans }}

    +

    {{ 'Ungrouped exports'|trans }}

    {% endif %}
    {% for saveds in grouped_exports['_']|default([]) %} {% for s in saveds %} -
    -
    -

    {{ s.export.title|trans }}

    -

    {{ s.saved.title }}

    - -
    {{ 'saved_export.Created on %date%'|trans({'%date%': s.saved.createdAt|format_datetime('long', 'short')}) }}
    - -
    - {{ s.saved.description|chill_markdown_to_html }} -
    - -
    -
      -
    • -
    • -
    • -
    -
    -
    -
    + {{ _self.render_export_card(s.saved, s.export, s.saved.exportAlias, last_executions[s.saved.id.toString()]) }} {% endfor %} {% endfor %}
    diff --git a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/new.html.twig b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/new.html.twig index 317f53e42..d6e3e3eee 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/SavedExport/new.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/SavedExport/new.html.twig @@ -2,14 +2,35 @@ {% block title %}{{ 'saved_export.New'|trans }}{% endblock %} +{% block css %} + {{ parent() }} + {{ encore_entry_link_tags('mod_pickentity_type') }} +{% endblock %} + +{% block js %} + {{ parent() }} + {{ encore_entry_script_tags('mod_pickentity_type') }} +{% endblock %} + {% block content %}

    {{ block('title') }}

    {{ form_start(form) }} {{ form_row(form.title) }} + + {% if showWarningAutoGeneratedDescription|default(false) %} + + {% endif %} + {{ form_row(form.description) }} + {% if form.share is defined %} + {{ form_row(form.share) }} + {% endif %} + {{ form_end(form) }}
    -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/src/Bundle/ChillMainBundle/Resources/views/layoutWithVerticalMenu.html.twig b/src/Bundle/ChillMainBundle/Resources/views/layoutWithVerticalMenu.html.twig index c192eaaac..cacd082c9 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/layoutWithVerticalMenu.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/layoutWithVerticalMenu.html.twig @@ -10,19 +10,19 @@ {% for flashMessage in app.session.flashbag.get('success') %}
    - {{ flashMessage|raw }} + {{ flashMessage|trans }}
    {% endfor %} {% for flashMessage in app.session.flashbag.get('error') %}
    - {{ flashMessage|raw }} + {{ flashMessage|trans }}
    {% endfor %} {% for flashMessage in app.session.flashbag.get('notice') %}
    - {{ flashMessage|raw }} + {{ flashMessage|trans }}
    {% endfor %} diff --git a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php index ab73932b5..0bfea342c 100644 --- a/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php +++ b/src/Bundle/ChillMainBundle/Routing/MenuBuilder/SectionMenuBuilder.php @@ -49,9 +49,9 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface ); } - if ($this->authorizationChecker->isGranted(ChillExportVoter::EXPORT)) { + if ($this->authorizationChecker->isGranted(ChillExportVoter::GENERATE_SAVED_EXPORT)) { $menu->addChild($this->translator->trans('Export Menu'), [ - 'route' => 'chill_main_export_index', + 'route' => 'chill_main_export_saved_list_my', ]) ->setExtras([ 'icons' => ['upload'], diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php index 10552d1f7..4e6719d0c 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/ChillExportVoter.php @@ -11,12 +11,21 @@ declare(strict_types=1); namespace Chill\MainBundle\Security\Authorization; +use Chill\MainBundle\Security\ProvideRoleHierarchyInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class ChillExportVoter extends Voter +class ChillExportVoter extends Voter implements ProvideRoleHierarchyInterface { - final public const EXPORT = 'chill_export'; + /** + * Role which give access to the creation of new export from the export itself. + */ + final public const COMPOSE_EXPORT = 'CHILL_MAIN_COMPOSE_EXPORT'; + + /** + * Role which give access to the execution and edition to the saved exports, but not for creating new ones. + */ + final public const GENERATE_SAVED_EXPORT = 'CHILL_MAIN_GENERATE_SAVED_EXPORT'; private readonly VoterHelperInterface $helper; @@ -24,7 +33,7 @@ class ChillExportVoter extends Voter { $this->helper = $voterHelperFactory ->generate(self::class) - ->addCheckFor(null, [self::EXPORT]) + ->addCheckFor(null, [self::COMPOSE_EXPORT, self::GENERATE_SAVED_EXPORT]) ->build(); } @@ -37,4 +46,21 @@ class ChillExportVoter extends Voter { return $this->helper->voteOnAttribute($attribute, $subject, $token); } + + public function getRolesWithHierarchy(): array + { + return ['export.role.export_role' => [ + self::COMPOSE_EXPORT, self::GENERATE_SAVED_EXPORT, + ]]; + } + + public function getRoles(): array + { + return [self::COMPOSE_EXPORT, self::GENERATE_SAVED_EXPORT]; + } + + public function getRolesWithoutScope(): array + { + return $this->getRoles(); + } } diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/ExportGenerationVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/ExportGenerationVoter.php new file mode 100644 index 000000000..008f76b40 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Authorization/ExportGenerationVoter.php @@ -0,0 +1,32 @@ +getUser()->getUserIdentifier() === $subject->getCreatedBy()->getUserIdentifier(); + } +} diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php index 0e9a7da52..ec749f725 100644 --- a/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php +++ b/src/Bundle/ChillMainBundle/Security/Authorization/SavedExportVoter.php @@ -12,23 +12,37 @@ declare(strict_types=1); namespace Chill\MainBundle\Security\Authorization; use Chill\MainBundle\Entity\SavedExport; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportManager; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; -class SavedExportVoter extends Voter +final class SavedExportVoter extends Voter { - final public const DELETE = 'CHLL_MAIN_EXPORT_SAVED_DELETE'; + final public const DELETE = 'CHILL_MAIN_EXPORT_SAVED_DELETE'; - final public const EDIT = 'CHLL_MAIN_EXPORT_SAVED_EDIT'; + final public const EDIT = 'CHILL_MAIN_EXPORT_SAVED_EDIT'; - final public const GENERATE = 'CHLL_MAIN_EXPORT_SAVED_GENERATE'; + final public const GENERATE = 'CHILL_MAIN_EXPORT_SAVED_GENERATE'; + + final public const DUPLICATE = 'CHILL_MAIN_EXPORT_SAVED_DUPLICATE'; + + final public const SHARE = 'CHILL_MAIN_EXPORT_SAVED_SHARE'; private const ALL = [ self::DELETE, self::EDIT, self::GENERATE, + self::SHARE, + self::DUPLICATE, ]; + public function __construct( + private readonly ExportManager $exportManager, + private readonly AccessDecisionManagerInterface $accessDecisionManager, + ) {} + protected function supports($attribute, $subject): bool { return $subject instanceof SavedExport && \in_array($attribute, self::ALL, true); @@ -36,9 +50,30 @@ class SavedExportVoter extends Voter protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool { + /* @var SavedExport $subject */ + $user = $token->getUser(); + + if (!$user instanceof User) { + return false; + } + return match ($attribute) { - self::DELETE, self::EDIT, self::GENERATE => $subject->getUser() === $token->getUser(), + self::DELETE, self::EDIT => $subject->getUser() === $token->getUser(), + self::SHARE => $subject->getUser() === $token->getUser() && $this->accessDecisionManager->decide($token, [ChillExportVoter::COMPOSE_EXPORT]), + self::DUPLICATE => $this->accessDecisionManager->decide($token, [ChillExportVoter::COMPOSE_EXPORT]) && $this->accessDecisionManager->decide($token, [self::EDIT], $subject) , + self::GENERATE => $this->canUserGenerate($user, $subject), default => throw new \UnexpectedValueException('attribute not supported: '.$attribute), }; } + + private function canUserGenerate(User $user, SavedExport $savedExport): bool + { + if (!($savedExport->getUser() === $user || $savedExport->isSharedWithUser($user))) { + return false; + } + + $export = $this->exportManager->getExport($savedExport->getExportAlias()); + + return $this->exportManager->isGrantedForElement($export); + } } diff --git a/src/Bundle/ChillMainBundle/Security/Authorization/StoredObject/ExportGenerationStoredObjectVoter.php b/src/Bundle/ChillMainBundle/Security/Authorization/StoredObject/ExportGenerationStoredObjectVoter.php new file mode 100644 index 000000000..4979ffec2 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Security/Authorization/StoredObject/ExportGenerationStoredObjectVoter.php @@ -0,0 +1,43 @@ +repository->findAssociatedEntityToStoredObject($subject); + } + + public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool + { + if (StoredObjectRoleEnum::EDIT === $attribute) { + return false; + } + + if (null === $generation = $this->repository->findAssociatedEntityToStoredObject($subject)) { + throw new \UnexpectedValueException('generation not found'); + } + + return $this->security->isGranted(ExportGenerationVoter::VIEW, $generation); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/Regroupement/CenterRegroupementResolver.php b/src/Bundle/ChillMainBundle/Service/Regroupement/CenterRegroupementResolver.php new file mode 100644 index 000000000..9f1813c5c --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/Regroupement/CenterRegroupementResolver.php @@ -0,0 +1,44 @@ + $groups + * @param list
    $centers + * + * @return list
    + */ + public function resolveCenters(array $groups, array $centers = []): array + { + $centersByHash = []; + + foreach ($groups as $group) { + foreach ($group->getCenters() as $center) { + $centersByHash[spl_object_hash($center)] = $center; + } + } + + foreach ($centers as $center) { + $centersByHash[spl_object_hash($center)] = $center; + } + + return array_values($centersByHash); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/Regroupement/RegroupementFiltering.php b/src/Bundle/ChillMainBundle/Service/Regroupement/RegroupementFiltering.php new file mode 100644 index 000000000..76e4a892a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/Regroupement/RegroupementFiltering.php @@ -0,0 +1,37 @@ + $group->containsAtLeastOneCenter($centers)), + ); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php b/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php index 1f7f01dbc..ea673b872 100644 --- a/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php +++ b/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDate.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\MainBundle\Service\RollingDate; +use Doctrine\Instantiator\Exception\UnexpectedValueException; + class RollingDate { final public const ALL_T = [ @@ -61,6 +63,8 @@ class RollingDate final public const T_YEAR_PREVIOUS_START = 'year_previous_start'; + private const NORMALIZATION_FORMAT = 'Y-m-d-H:i:s.u e'; + /** * @param string|self::T_* $roll * @param \DateTimeImmutable|null $fixedDate Only to insert if $roll equals @see{self::T_FIXED_DATE} @@ -68,7 +72,7 @@ class RollingDate public function __construct( private readonly string $roll, private readonly ?\DateTimeImmutable $fixedDate = null, - private readonly \DateTimeImmutable $pivotDate = new \DateTimeImmutable('now'), + private readonly ?\DateTimeImmutable $pivotDate = null, ) {} public function getFixedDate(): ?\DateTimeImmutable @@ -76,7 +80,7 @@ class RollingDate return $this->fixedDate; } - public function getPivotDate(): \DateTimeImmutable + public function getPivotDate(): ?\DateTimeImmutable { return $this->pivotDate; } @@ -85,4 +89,31 @@ class RollingDate { return $this->roll; } + + public function normalize(): array + { + return [ + 'roll' => $this->getRoll(), + 'fixed_date' => $this->getFixedDate()?->format(self::NORMALIZATION_FORMAT), + 'pivot_date' => $this->getPivotDate()?->format(self::NORMALIZATION_FORMAT), + 'v' => 1, + ]; + } + + public static function fromNormalized(?array $data): ?self + { + if (null === $data) { + return null; + } + + if (1 === $data['v']) { + return new self( + $data['roll'], + null !== $data['fixed_date'] ? \DateTimeImmutable::createFromFormat(self::NORMALIZATION_FORMAT, (string) $data['fixed_date']) : null, + null !== $data['pivot_date'] ? \DateTimeImmutable::createFromFormat(self::NORMALIZATION_FORMAT, (string) $data['pivot_date']) : null, + ); + } + + throw new UnexpectedValueException('Format of the rolling date unknow, no version information'); + } } diff --git a/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDateConverter.php b/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDateConverter.php index f569ebf18..c2633cebb 100644 --- a/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDateConverter.php +++ b/src/Bundle/ChillMainBundle/Service/RollingDate/RollingDateConverter.php @@ -11,8 +11,12 @@ declare(strict_types=1); namespace Chill\MainBundle\Service\RollingDate; -class RollingDateConverter implements RollingDateConverterInterface +use Symfony\Component\Clock\ClockInterface; + +final readonly class RollingDateConverter implements RollingDateConverterInterface { + public function __construct(private readonly ClockInterface $clock) {} + public function convert(?RollingDate $rollingDate): ?\DateTimeImmutable { if (null === $rollingDate) { @@ -21,43 +25,43 @@ class RollingDateConverter implements RollingDateConverterInterface switch ($rollingDate->getRoll()) { case RollingDate::T_MONTH_CURRENT_START: - return $this->toBeginOfMonth($rollingDate->getPivotDate()); + return $this->toBeginOfMonth($rollingDate->getPivotDate() ?? $this->clock->now()); case RollingDate::T_MONTH_NEXT_START: - return $this->toBeginOfMonth($rollingDate->getPivotDate()->add(new \DateInterval('P1M'))); + return $this->toBeginOfMonth(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P1M'))); case RollingDate::T_MONTH_PREVIOUS_START: - return $this->toBeginOfMonth($rollingDate->getPivotDate()->sub(new \DateInterval('P1M'))); + return $this->toBeginOfMonth(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P1M'))); case RollingDate::T_QUARTER_CURRENT_START: - return $this->toBeginOfQuarter($rollingDate->getPivotDate()); + return $this->toBeginOfQuarter($rollingDate->getPivotDate() ?? $this->clock->now()); case RollingDate::T_QUARTER_NEXT_START: - return $this->toBeginOfQuarter($rollingDate->getPivotDate()->add(new \DateInterval('P3M'))); + return $this->toBeginOfQuarter(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P3M'))); case RollingDate::T_QUARTER_PREVIOUS_START: - return $this->toBeginOfQuarter($rollingDate->getPivotDate()->sub(new \DateInterval('P3M'))); + return $this->toBeginOfQuarter(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P3M'))); case RollingDate::T_WEEK_CURRENT_START: - return $this->toBeginOfWeek($rollingDate->getPivotDate()); + return $this->toBeginOfWeek($rollingDate->getPivotDate() ?? $this->clock->now()); case RollingDate::T_WEEK_NEXT_START: - return $this->toBeginOfWeek($rollingDate->getPivotDate()->add(new \DateInterval('P1W'))); + return $this->toBeginOfWeek(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P1W'))); case RollingDate::T_WEEK_PREVIOUS_START: - return $this->toBeginOfWeek($rollingDate->getPivotDate()->sub(new \DateInterval('P1W'))); + return $this->toBeginOfWeek(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P1W'))); case RollingDate::T_YEAR_CURRENT_START: - return $this->toBeginOfYear($rollingDate->getPivotDate()); + return $this->toBeginOfYear($rollingDate->getPivotDate() ?? $this->clock->now()); case RollingDate::T_YEAR_PREVIOUS_START: - return $this->toBeginOfYear($rollingDate->getPivotDate()->sub(new \DateInterval('P1Y'))); + return $this->toBeginOfYear(($rollingDate->getPivotDate() ?? $this->clock->now())->sub(new \DateInterval('P1Y'))); case RollingDate::T_YEAR_NEXT_START: - return $this->toBeginOfYear($rollingDate->getPivotDate()->add(new \DateInterval('P1Y'))); + return $this->toBeginOfYear(($rollingDate->getPivotDate() ?? $this->clock->now())->add(new \DateInterval('P1Y'))); case RollingDate::T_TODAY: - return $rollingDate->getPivotDate(); + return $rollingDate->getPivotDate() ?? $this->clock->now(); case RollingDate::T_FIXED_DATE: if (null === $rollingDate->getFixedDate()) { @@ -75,7 +79,7 @@ class RollingDateConverter implements RollingDateConverterInterface { return \DateTimeImmutable::createFromFormat( 'Y-m-d His', - sprintf('%s-%s-01 000000', $date->format('Y'), $date->format('m')) + sprintf('%s-%s-01 000000', $date->format('Y'), $date->format('m')), ); } @@ -90,7 +94,7 @@ class RollingDateConverter implements RollingDateConverterInterface return \DateTimeImmutable::createFromFormat( 'Y-m-d His', - sprintf('%s-%s-01 000000', $date->format('Y'), $month) + sprintf('%s-%s-01 000000', $date->format('Y'), $month), ); } diff --git a/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSync.php b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSync.php new file mode 100644 index 000000000..bc602cf2a --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSync.php @@ -0,0 +1,129 @@ +createNotExistingUserGroups(); + + $connection = $this->entityManager->getConnection(); + $stats = $connection->transactional(function (Connection $connection) { + $removed = $this->removeUserNotRelatedToJob($connection); + $created = $this->createNewAssociations($connection); + + return ['association_removed' => $removed, 'association_created' => $created]; + }); + + $fullStats = ['userjob_created' => $created, ...$stats]; + + $this->logger->info(self::LOG_PREFIX.'Executed synchronisation', $fullStats); + + return $fullStats; + } + + private function createNotExistingUserGroups(): int + { + $jobs = $this->userJobRepository->findAllNotAssociatedWithUserGroup(); + $counter = 0; + + foreach ($jobs as $job) { + $userGroup = new UserGroup(); + $userGroup->setUserJob($job); + $userGroup->setLabel( + [ + $this->translator->getLocale() => $this->translator->trans( + 'user_group.label_related_to_user_job', + ['job' => $this->translatableStringHelper->localize($job->getLabel())] + )] + ); + $userGroup->setBackgroundColor('#e5a50a')->setForegroundColor('#f6f5f4'); + + $this->entityManager->persist($userGroup); + $this->logger->info(self::LOG_PREFIX.'Will create user group', ['job' => $this->translatableStringHelper->localize($userGroup->getLabel())]); + ++$counter; + } + + $this->entityManager->flush(); + + return $counter; + } + + private function removeUserNotRelatedToJob(Connection $connection): int + { + $sql = <<<'SQL' + DELETE FROM chill_main_user_group_user + USING users AS u, chill_main_user_group ug + WHERE + chill_main_user_group_user.usergroup_id = ug.id + AND chill_main_user_group_user.user_id = u.id + -- only where user_group.userjob_id is set (we ignore groups not automatically created) + AND ug.userjob_id IS NOT NULL + AND ( + -- Case 1: User has no job history records matching the time period + NOT EXISTS ( + SELECT 1 FROM chill_main_user_job_history jh + WHERE jh.user_id = u.id + AND tsrange(jh.startdate, jh.enddate) @> localtimestamp + ) + OR + -- Case 2: User has job history but with different job_id or user is disabled + EXISTS ( + SELECT 1 FROM chill_main_user_job_history jh + WHERE jh.user_id = u.id + AND tsrange(jh.startdate, jh.enddate) @> localtimestamp + AND (jh.job_id <> ug.userjob_id OR u.enabled IS FALSE OR jh.job_id IS NULL) + ) + ) + SQL; + + $result = $connection->executeQuery($sql); + + return $result->rowCount(); + } + + private function createNewAssociations(Connection $connection): int + { + $sql = <<<'SQL' + INSERT INTO chill_main_user_group_user (usergroup_id, user_id) + SELECT cmug.id, jh.user_id + FROM chill_main_user_group cmug + JOIN chill_main_user_job_history jh ON jh.job_id = cmug.userjob_id AND tsrange(jh.startdate, jh.enddate) @> localtimestamp + JOIN users u ON u.id = jh.user_id + WHERE cmug.userjob_id IS NOT NULL AND u.enabled IS TRUE + ON CONFLICT DO NOTHING + SQL; + + $result = $connection->executeQuery($sql); + + return $result->rowCount(); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncCronJob.php b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncCronJob.php new file mode 100644 index 000000000..673578f80 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncCronJob.php @@ -0,0 +1,42 @@ +getLastStart() < $this->clock->now()->sub(new \DateInterval('P1D')); + } + + public function getKey(): string + { + return self::KEY; + } + + public function run(array $lastExecutionData): ?array + { + return ($this->userJobSync)(); + } +} diff --git a/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncInterface.php b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncInterface.php new file mode 100644 index 000000000..e596cafc3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Service/UserGroup/UserGroupRelatedToUserJobSyncInterface.php @@ -0,0 +1,20 @@ +get(EntityManagerInterface::class); + if (null === $user = $em->createQueryBuilder()->select('u') + ->from(User::class, 'u') + ->setMaxResults(1) + ->getQuery()->getOneOrNullResult()) { + throw new \RuntimeException('User not found'); + } + + return $user; + } + /** * Compare aliases array before and after that aggregator alter query. * @@ -75,7 +90,7 @@ abstract class AbstractAggregatorTest extends KernelTestCase { $aliases = $qb->getAllAliases(); - $this->getAggregator()->alterQuery($qb, $data); + $this->getAggregator()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $alteredQuery = $qb->getAllAliases(); @@ -115,7 +130,7 @@ abstract class AbstractAggregatorTest extends KernelTestCase */ public function testQueryExecution(QueryBuilder $qb, array $data): void { - $this->getAggregator()->alterQuery($qb, $data); + $this->getAggregator()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $actual = $qb->getQuery()->getResult(); @@ -158,7 +173,7 @@ abstract class AbstractAggregatorTest extends KernelTestCase $nbOfSelect = null !== $query->getDQLPart('select') ? \count($query->getDQLPart('select')) : 0; - $this->getAggregator()->alterQuery($query, $data); + $this->getAggregator()->alterQuery($query, $data, new ExportGenerationContext($this->getUser())); $this->assertGreaterThanOrEqual( $nbOfFrom, @@ -218,6 +233,44 @@ abstract class AbstractAggregatorTest extends KernelTestCase ); } + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $aggregator = $this->getAggregator(); + + $normalized = $aggregator->normalizeFormData($data); + $actual = $aggregator->denormalizeFormData($normalized, $version); + + self::assertEqualsCanonicalizing(array_keys($data), array_keys($actual)); + + foreach ($data as $key => $value) { + self::assertArrayHasKey($key, $actual); + if (array_key_exists($key, $customAssert)) { + call_user_func($customAssert[$key], $actual[$key], $value); + } elseif (is_iterable($value)) { + continue; + } elseif (is_object($value) && method_exists($value, 'getId')) { + self::assertEquals($value->getId(), $actual[$key]->getId()); + } else { + self::assertEquals($value, $actual[$key]); + } + } + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + foreach (static::getFormData() as $data) { + yield [$data, 1, []]; + } + } + /** * Test that the query keys are strings. * @@ -280,7 +333,7 @@ abstract class AbstractAggregatorTest extends KernelTestCase $qb->setMaxResults(1); $queryKeys = $this->getAggregator()->getQueryKeys($data); - $this->getAggregator()->alterQuery($qb, $data); + $this->getAggregator()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $results = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php index 9e9a87833..7e0ecf166 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractExportTest.php @@ -11,7 +11,9 @@ declare(strict_types=1); namespace Chill\MainBundle\Test\Export; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\DirectExportInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Test\PrepareClientTrait; use Doctrine\ORM\EntityManagerInterface; @@ -99,6 +101,19 @@ abstract class AbstractExportTest extends WebTestCase */ abstract public static function getModifiersCombination(): array; + protected function getUser(): User + { + $em = static::getContainer()->get(EntityManagerInterface::class); + if (null === $user = $em->createQueryBuilder()->select('u') + ->from(User::class, 'u') + ->setMaxResults(1) + ->getQuery()->getOneOrNullResult()) { + throw new \RuntimeException('User not found'); + } + + return $user; + } + protected function getParameters(bool $filterStatsByCenter): ParameterBagInterface { return new ParameterBag(['chill_main' => ['acl' => ['filter_stats_by_center' => $filterStatsByCenter]]]); @@ -118,6 +133,55 @@ abstract class AbstractExportTest extends WebTestCase return [$exports]; } + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $export = $this->getExport(); + + if (is_iterable($export)) { + foreach ($export as $e) { + $this->testOneDataNormalization($e, $data, $version, $customAssert); + } + } else { + $this->testOneDataNormalization($export, $data, $version, $customAssert); + } + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + foreach (static::getFormData() as $data) { + yield [$data, 1, []]; + } + } + + private function testOneDataNormalization(ExportInterface|DirectExportInterface $export, array $data, int $version, array $customAssert): void + { + $normalized = $export->normalizeFormData($data); + $actual = $export->denormalizeFormData($normalized, $version); + + self::assertEqualsCanonicalizing(array_keys($data), array_keys($actual)); + + foreach ($data as $key => $value) { + self::assertArrayHasKey($key, $actual); + if (array_key_exists($key, $customAssert)) { + call_user_func($customAssert[$key], $actual[$key], $value); + } elseif (is_iterable($value)) { + continue; + } elseif (is_object($value) && method_exists($value, 'getId')) { + self::assertEquals($value->getId(), $actual[$key]->getId()); + } else { + self::assertEquals($value, $actual[$key]); + } + } + } + /** * Test the formatters type are string. */ @@ -205,7 +269,7 @@ abstract class AbstractExportTest extends WebTestCase // due to the fact that testing both methods use the same tools. $queryKeys = $export->getQueryKeys($data); - $query = $export->initiateQuery($modifiers, $acl, $data); + $query = $export->initiateQuery($modifiers, $acl, $data, $exportGenerationContext = new ExportGenerationContext($this->getUser())); // limit the result for the query for performance reason (only for QueryBuilder, // not possible in NativeQuery) @@ -213,7 +277,7 @@ abstract class AbstractExportTest extends WebTestCase $query->setMaxResults(1); } - $results = $export->getResult($query, $data); + $results = $export->getResult($query, $data, $exportGenerationContext); $this->assertIsArray( $results, @@ -295,7 +359,7 @@ abstract class AbstractExportTest extends WebTestCase public function testInitiateQuery(mixed $modifiers, mixed $acl, mixed $data) { foreach ($this->getExports() as $export) { - $query = $export->initiateQuery($modifiers, $acl, $data); + $query = $export->initiateQuery($modifiers, $acl, $data, new ExportGenerationContext($this->getUser())); $this->assertTrue( $query instanceof QueryBuilder || $query instanceof NativeQuery, @@ -350,7 +414,7 @@ abstract class AbstractExportTest extends WebTestCase public function testSupportsModifier(mixed $modifiers, mixed $acl, mixed $data) { foreach ($this->getExports() as $export) { - $query = $export->initiateQuery($modifiers, $acl, $data); + $query = $export->initiateQuery($modifiers, $acl, $data, new ExportGenerationContext($this->getUser())); if ($query instanceof QueryBuilder) { $this->assertContainsOnly( diff --git a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php index 05b7231b3..e76d7d504 100644 --- a/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php +++ b/src/Bundle/ChillMainBundle/Test/Export/AbstractFilterTest.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\MainBundle\Test\Export; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; use Prophecy\PhpUnit\ProphecyTrait; @@ -23,16 +25,6 @@ abstract class AbstractFilterTest extends KernelTestCase { use ProphecyTrait; - /** - * @var \Prophecy\Prophet - */ - private $prophet; - - protected function setUp(): void - { - $this->prophet = $this->getProphet(); - } - public static function tearDownAfterClass(): void { if (null !== self::getContainer()) { @@ -43,6 +35,19 @@ abstract class AbstractFilterTest extends KernelTestCase self::ensureKernelShutdown(); } + protected function getUser(): User + { + $em = static::getContainer()->get(EntityManagerInterface::class); + if (null === $user = $em->createQueryBuilder()->select('u') + ->from(User::class, 'u') + ->setMaxResults(1) + ->getQuery()->getOneOrNullResult()) { + throw new \RuntimeException('User not found'); + } + + return $user; + } + /** * Create a filter which will be used in tests. * @@ -74,6 +79,44 @@ abstract class AbstractFilterTest extends KernelTestCase */ abstract public static function getQueryBuilders(); + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $filter = $this->getFilter(); + + $normalized = $filter->normalizeFormData($data); + $actual = $filter->denormalizeFormData($normalized, $version); + + self::assertEqualsCanonicalizing(array_keys($data), array_keys($actual)); + + foreach ($data as $key => $value) { + self::assertArrayHasKey($key, $actual); + if (array_key_exists($key, $customAssert)) { + call_user_func($customAssert[$key], $actual[$key], $value); + } elseif (is_iterable($value)) { + continue; + } elseif (is_object($value) && method_exists($value, 'getId')) { + self::assertEquals($value->getId(), $actual[$key]->getId()); + } else { + self::assertEquals($value, $actual[$key]); + } + } + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + foreach (static::getFormData() as $data) { + yield [$data, 1, []]; + } + } + /** * Compare aliases array before and after that filter alter query. * @@ -83,7 +126,7 @@ abstract class AbstractFilterTest extends KernelTestCase { $aliases = $qb->getAllAliases(); - $this->getFilter()->alterQuery($qb, $data); + $this->getFilter()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $alteredQuery = $qb->getAllAliases(); @@ -129,7 +172,9 @@ abstract class AbstractFilterTest extends KernelTestCase $nbOfSelect = null !== $query->getDQLPart('select') ? \count($query->getDQLPart('select')) : 0; - $this->getFilter()->alterQuery($query, $data); + $context = new ExportGenerationContext($this->getUser()); + + $this->getFilter()->alterQuery($query, $data, $context); $this->assertGreaterThanOrEqual( $nbOfFrom, @@ -170,7 +215,7 @@ abstract class AbstractFilterTest extends KernelTestCase */ public function testQueryExecution(QueryBuilder $qb, mixed $data): void { - $this->getFilter()->alterQuery($qb, $data); + $this->getFilter()->alterQuery($qb, $data, new ExportGenerationContext($this->getUser())); $actual = $qb->getQuery()->getResult(); @@ -206,7 +251,8 @@ abstract class AbstractFilterTest extends KernelTestCase */ public function testDescriptionAction($data) { - $description = $this->getFilter()->describeAction($data); + $context = new ExportGenerationContext((new User())->setLabel('test user')); + $description = $this->getFilter()->describeAction($data, $context); $this->assertTrue( \is_string($description) || \is_array($description), @@ -262,15 +308,4 @@ abstract class AbstractFilterTest extends KernelTestCase yield [$data]; } } - - public function testGetTitle() - { - $title = $this->getFilter()->getTitle(); - - $this->assertIsString($title); - $this->assertNotEmpty( - $title, - 'test that the title is not empty' - ); - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Authorization/ExportGenerationCreateFromSavedExportControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Authorization/ExportGenerationCreateFromSavedExportControllerTest.php new file mode 100644 index 000000000..8e9e65963 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Authorization/ExportGenerationCreateFromSavedExportControllerTest.php @@ -0,0 +1,81 @@ +setOptions($exportOptions = ['test' => 'content'])->setExportAlias('dummy_export_alias'); + + $user = new User(); + $reflection = new \ReflectionClass($user); + $id = $reflection->getProperty('id'); + $id->setValue($user, 1); + + $security = $this->prophesize(Security::class); + $security->isGranted(SavedExportVoter::GENERATE, $savedExport)->shouldBeCalled()->willReturn(true); + $security->getUser()->willReturn($user); + + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->persist(Argument::that( + static fn ($arg) => $arg instanceof ExportGeneration && $arg->getOptions() === $exportOptions && $arg->getSavedExport() === $savedExport, + ))->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + + $messenger = $this->prophesize(MessageBusInterface::class); + $messenger->dispatch(Argument::type(ExportRequestGenerationMessage::class))->shouldBeCalled()->will( + static fn (array $args): Envelope => new Envelope($args[0]), + ); + + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->serialize(Argument::type(ExportGeneration::class), 'json', ['groups' => ['read']])->shouldBeCalled()->willReturn('{"test": "export-generation"}'); + + $controller = new ExportGenerationCreateFromSavedExportController( + $security->reveal(), + $entityManager->reveal(), + $messenger->reveal(), + new MockClock(), + $serializer->reveal() + ); + + $response = $controller($savedExport); + + self::assertInstanceOf(JsonResponse::class, $response); + self::assertEquals('{"test": "export-generation"}', $response->getContent()); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/ExportGenerationControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/ExportGenerationControllerTest.php new file mode 100644 index 000000000..eff86072e --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Controller/ExportGenerationControllerTest.php @@ -0,0 +1,56 @@ +prophesize(Security::class); + $security->isGranted('ROLE_USER')->willReturn(true); + $environment = $this->prophesize(Environment::class); + $serializer = $this->prophesize(SerializerInterface::class); + $serializer->serialize(Argument::any(), 'json', ['groups' => ['read']])->willReturn('{}'); + $exportManager = $this->prophesize(ExportManager::class); + + $pending = new ExportGeneration('dummy', []); + + $controller = new ExportGenerationController($security->reveal(), $environment->reveal(), $serializer->reveal(), $exportManager->reveal()); + + $actual = $controller->objectStatus($pending); + self::assertEquals('{}', $actual->getContent()); + + $generated = new ExportGeneration('dummy', []); + $generated->getStoredObject()->setStatus(StoredObject::STATUS_READY); + + self:assertEquals('{}', $controller->objectStatus($generated)->getContent()); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php b/src/Bundle/ChillMainBundle/Tests/Controller/ExportIndexControllerTest.php similarity index 84% rename from src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php rename to src/Bundle/ChillMainBundle/Tests/Controller/ExportIndexControllerTest.php index 6c7ade9bb..beaff9b5c 100644 --- a/src/Bundle/ChillMainBundle/Tests/Controller/ExportControllerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Controller/ExportIndexControllerTest.php @@ -21,13 +21,13 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; * * @coversNothing */ -final class ExportControllerTest extends WebTestCase +final class ExportIndexControllerTest extends WebTestCase { use PrepareClientTrait; public function testIndex() { - $client = $this->getClientAuthenticatedAsAdmin(); + $client = $this->getClientAuthenticated(); $client->request('GET', '/fr/exports/'); diff --git a/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/SavedExportTest.php b/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/SavedExportTest.php new file mode 100644 index 000000000..cd98df66f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Entity/Workflow/SavedExportTest.php @@ -0,0 +1,52 @@ +addUser($user2); + + // Create a SavedExport entity + $savedExport = new SavedExport(); + + // Share the saved export with user1 + $savedExport->addShare($user1); + + // Share the saved export with the group + $savedExport->addShare($group); + + // Assertions + $this->assertTrue($savedExport->isSharedWithUser($user1), 'User1 should have access to the saved export.'); + $this->assertTrue($savedExport->isSharedWithUser($user2), 'User2 (via group) should have access to the saved export.'); + $this->assertFalse($savedExport->isSharedWithUser($user3), 'User3 should not have access to the saved export.'); + + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Cronjob/RemoveExpiredExportGenerationCronJobTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Cronjob/RemoveExpiredExportGenerationCronJobTest.php new file mode 100644 index 000000000..c47c4c878 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Cronjob/RemoveExpiredExportGenerationCronJobTest.php @@ -0,0 +1,117 @@ +prophesize(ExportGenerationRepository::class); + $bus = $this->prophesize(MessageBusInterface::class); + + $cronJob = new RemoveExpiredExportGenerationCronJob( + $clock, + $repo->reveal(), + $bus->reveal() + ); + + $this->assertTrue($cronJob->canRun(null)); + } + + public function testCanRunReturnsTrueWhenLastStartIsOlderThan24Hours() + { + $clock = new MockClock(new \DateTimeImmutable('2024-06-25 10:00:00')); + $repo = $this->prophesize(ExportGenerationRepository::class); + $bus = $this->prophesize(MessageBusInterface::class); + + $cronJob = new RemoveExpiredExportGenerationCronJob( + $clock, + $repo->reveal(), + $bus->reveal() + ); + + $execution = new CronJobExecution('remove-expired-export-generation'); + $execution->setLastStart(new \DateTimeImmutable('2024-06-24 09:59:59')); + + $this->assertTrue($cronJob->canRun($execution)); + } + + public function testCanRunReturnsFalseWhenLastStartIsWithin24Hours() + { + $clock = new MockClock(new \DateTimeImmutable('2024-06-25 10:00:00')); + $repo = $this->prophesize(ExportGenerationRepository::class); + $bus = $this->prophesize(MessageBusInterface::class); + + $cronJob = new RemoveExpiredExportGenerationCronJob( + $clock, + $repo->reveal(), + $bus->reveal() + ); + + $execution = new CronJobExecution('remove-expired-export-generation'); + $execution->setLastStart(new \DateTimeImmutable('2024-06-24 10:01:00')); + + $this->assertFalse($cronJob->canRun($execution)); + } + + public function testRunDispatchesMessagesForExpiredExportsAndReturnsLastDeletion() + { + $clock = new MockClock(new \DateTimeImmutable('2024-06-25 11:21:00')); + $repo = $this->prophesize(ExportGenerationRepository::class); + $bus = $this->prophesize(MessageBusInterface::class); + + $expiredExports = [ + new ExportGeneration('dummy', []), + ]; + + $repo->findExpiredExportGeneration(Argument::that(fn ($dateTime) => + // Ensure the repository is called with the current clock time + $dateTime instanceof \DateTimeImmutable + && $dateTime->getTimestamp() === $clock->now()->getTimestamp()))->willReturn($expiredExports); + + // Expect one RemoveExportGenerationMessage for each expired export + $bus->dispatch(Argument::that(fn (Envelope $envelope) => $envelope->getMessage() instanceof RemoveExportGenerationMessage)) + ->shouldBeCalledTimes(1) + ->will(fn ($args) => $args[0]); + + $cronJob = new RemoveExpiredExportGenerationCronJob( + $clock, + $repo->reveal(), + $bus->reveal() + ); + + $result = $cronJob->run([]); + + $this->assertIsArray($result); + $this->assertEquals(['last-deletion' => $clock->now()->getTimestamp()], $result); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php new file mode 100644 index 000000000..34137ff26 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportConfigNormalizerTest.php @@ -0,0 +1,333 @@ +prophesize(FilterInterface::class); + $filterEnabled->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $filterEnabled->getNormalizationVersion()->willReturn(1); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->normalizeFormData(['default' => '0'])->shouldNotBeCalled(); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $aggregatorEnabled->getNormalizationVersion()->willReturn(1); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->normalizeFormData(['default' => '0'])->shouldNotBeCalled(); + + $export = $this->prophesize(ExportInterface::class); + $export->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $export->getNormalizationVersion()->willReturn(1); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->normalizeFormData(['test' => '0'])->shouldBeCalled()->willReturn(['test' => '0']); + $formatter->getNormalizationVersion()->willReturn(1); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class); + + $center = $this->prophesize(Center::class); + $center->getId()->willReturn(10); + + $formData = [ + 'centers' => ['centers' => [$center->reveal()]], + 'export' => ['test' => '0'], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'filterDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'aggregatorDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => ['test' => '0'], + ]; + + $expected = [ + 'export' => ['form' => ['test' => '0'], 'version' => 1], + 'centers' => ['centers' => [10], 'regroupments' => []], + 'filters' => [ + 'filtersEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => ['test' => '0'], + 'version' => 1, + ], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer( + $exportManager->reveal(), + $this->prophesize(CenterRepositoryInterface::class)->reveal(), + $regroupmentRepository->reveal() + ); + + $actual = $exportConfigNormalizer->normalizeConfig('export', $formData); + + self::assertEqualsCanonicalizing($expected, $actual); + } + + public function testDenormalizeConfig(): void + { + $filterEnabled = $this->prophesize(FilterInterface::class); + $filterEnabled->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $filterDisabled->getFormDefaultData()->willReturn(['default' => '0']); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $aggregatorDisabled->getFormDefaultData()->willReturn(['default' => '0']); + + $export = $this->prophesize(ExportInterface::class); + $export->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->denormalizeFormData(['test' => '0'], 1)->shouldBeCalled()->willReturn(['test' => '0']); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->find(10)->willReturn($center = new Center()); + + $regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class); + + $serialized = [ + 'centers' => ['regroupments' => [], 'centers' => [10]], + 'export' => ['form' => ['test' => '0'], 'version' => 1], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0'], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => ['test' => '0'], + 'version' => 1, + ], + ]; + + $expected = [ + 'export' => ['test' => '0'], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'filterDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => ['test' => '0']], + 'aggregatorDisabled' => ['enabled' => false, 'form' => ['default' => '0']], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => ['test' => '0'], + 'centers' => ['centers' => [$center], 'regroupments' => []], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal(), $regroupmentRepository->reveal()); + $actual = $exportConfigNormalizer->denormalizeConfig('export', $serialized, true); + + self::assertEquals($expected, $actual); + } + + public function testNormalizeConfigEmptyData(): void + { + $filterEnabled = $this->prophesize(FilterInterface::class); + $filterEnabled->normalizeFormData([])->shouldBeCalled()->willReturn([]); + $filterEnabled->getNormalizationVersion()->willReturn(1); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->normalizeFormData([])->shouldNotBeCalled(); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->normalizeFormData([])->shouldBeCalled()->willReturn([]); + $aggregatorEnabled->getNormalizationVersion()->willReturn(1); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->normalizeFormData([])->shouldNotBeCalled(); + + $export = $this->prophesize(ExportInterface::class); + $export->normalizeFormData([])->shouldBeCalled()->willReturn([]); + $export->getNormalizationVersion()->willReturn(1); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->normalizeFormData([])->shouldBeCalled()->willReturn([]); + $formatter->getNormalizationVersion()->willReturn(1); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $center = $this->prophesize(Center::class); + $center->getId()->willReturn(10); + + $regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class); + + $formData = [ + 'centers' => ['centers' => [$center->reveal()]], + 'export' => [], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => []], + 'filterDisabled' => ['enabled' => false, 'form' => []], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => []], + 'aggregatorDisabled' => ['enabled' => false, 'form' => []], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [], + ]; + + $expected = [ + 'export' => ['form' => [], 'version' => 1], + 'centers' => ['centers' => [10], 'regroupments' => []], + 'filters' => [ + 'filtersEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => [], + 'version' => 1, + ], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $this->prophesize(CenterRepositoryInterface::class)->reveal(), $regroupmentRepository->reveal()); + + $actual = $exportConfigNormalizer->normalizeConfig('export', $formData); + + self::assertEqualsCanonicalizing($expected, $actual); + } + + public function testDenormalizeConfigWithEmptyData(): void + { + $filterEnabled = $this->prophesize(FilterInterface::class); + $filterEnabled->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]); + $filterDisabled = $this->prophesize(FilterInterface::class); + $filterDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $filterDisabled->getFormDefaultData()->willReturn([]); + + $aggregatorEnabled = $this->prophesize(AggregatorInterface::class); + $aggregatorEnabled->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]); + $aggregatorDisabled = $this->prophesize(AggregatorInterface::class); + $aggregatorDisabled->denormalizeFormData(Argument::any(), Argument::type('int'))->shouldNotBeCalled(); + $aggregatorDisabled->getFormDefaultData()->willReturn([]); + + $export = $this->prophesize(ExportInterface::class); + $export->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]); + + $formatter = $this->prophesize(FormatterInterface::class); + $formatter->denormalizeFormData([], 1)->shouldBeCalled()->willReturn([]); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getFormatter('xlsx')->shouldBeCalled()->willReturn($formatter->reveal()); + $exportManager->getFilter('filterEnabled')->willReturn($filterEnabled->reveal()); + $exportManager->getFilter('filterDisabled')->willReturn($filterDisabled->reveal()); + $exportManager->getAggregator('aggregatorEnabled')->willReturn($aggregatorEnabled->reveal()); + $exportManager->getAggregator('aggregatorDisabled')->willReturn($aggregatorDisabled->reveal()); + $exportManager->getExport('export')->willReturn($export->reveal()); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->find(10)->willReturn($center = new Center()); + + $regroupmentRepository = $this->prophesize(RegroupmentRepositoryInterface::class); + + $serialized = [ + 'centers' => ['centers' => [10], 'regroupments' => []], + 'export' => ['form' => [], 'version' => 1], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], + 'filterDisabled' => ['enabled' => false], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => [], 'version' => 1], + 'aggregatorDisabled' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [ + 'form' => [], + 'version' => 1, + ], + ]; + + $expected = [ + 'export' => [], + 'filters' => [ + 'filterEnabled' => ['enabled' => true, 'form' => []], + 'filterDisabled' => ['enabled' => false, 'form' => []], + ], + 'aggregators' => [ + 'aggregatorEnabled' => ['enabled' => true, 'form' => []], + 'aggregatorDisabled' => ['enabled' => false, 'form' => []], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => [], + 'centers' => ['centers' => [$center], 'regroupments' => []], + ]; + + $exportConfigNormalizer = new ExportConfigNormalizer($exportManager->reveal(), $centerRepository->reveal(), $regroupmentRepository->reveal()); + $actual = $exportConfigNormalizer->denormalizeConfig('export', $serialized, true); + + self::assertEquals($expected, $actual); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportDataNormalizerTraitTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportDataNormalizerTraitTest.php new file mode 100644 index 000000000..0e3d3d502 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportDataNormalizerTraitTest.php @@ -0,0 +1,121 @@ +normalizeDoctrineEntity($entity); + } + + public function denormalizeEntity(mixed $entity, ObjectRepository $repository) + { + return $this->denormalizeDoctrineEntity($entity, $repository); + } + + public function normalizeD(\DateTimeImmutable|\DateTime $date): string + { + return $this->normalizeDate($date); + } + + public function denormalizeD(string $date): \DateTimeImmutable|\DateTime + { + return $this->denormalizeDate($date); + } + }; + } + + public function testNormalizationDoctrineEntitySingle(): void + { + $entity = new class () { + public function getId(): int + { + return 1; + } + }; + + $repository = $this->prophesize(ObjectRepository::class); + $repository->find(1)->willReturn($entity); + + $normalized = $this->buildTrait()->normalizeEntity($entity); + $actual = $this->buildTrait()->denormalizeEntity($normalized, $repository->reveal()); + + self::assertSame($entity, $actual); + } + + public function testNormalizationDoctrineEntityMulti(): void + { + $entityA = new class () { + public function getId(): int + { + return 1; + } + }; + + $entityB = new class () { + public function getId(): int + { + return 2; + } + }; + + $repository = $this->prophesize(ObjectRepository::class); + $repository->findBy( + Argument::that(static fn ($arg): bool => in_array(1, $arg['id'] ?? []) && in_array(2, $arg['id'] ?? [])) + )->willReturn([$entityA, $entityB]); + + $normalized = $this->buildTrait()->normalizeEntity([$entityA, $entityB]); + $actual = $this->buildTrait()->denormalizeEntity($normalized, $repository->reveal()); + + self::assertContains(1, array_map(static fn (object $item) => $item->getId(), $actual)); + self::assertContains(2, array_map(static fn (object $item) => $item->getId(), $actual)); + self::assertCount(2, $actual); + } + + /** + * @dataProvider provideDate + */ + public function testNormalizationDate(\DateTimeImmutable|\DateTime $date): void + { + $normalized = $this->buildTrait()->normalizeD($date); + $actual = $this->buildTrait()->denormalizeD($normalized); + + self::assertEquals($date, $actual); + } + + public static function provideDate(): iterable + { + yield [new \DateTimeImmutable('2024-01-15T18:57:20', new \DateTimeZone('Europe/Athens'))]; + yield [new \DateTimeImmutable('2024-01-15T18:57:30', new \DateTimeZone('America/Havana'))]; + yield [new \DateTime('2024-01-15T18:57:40', new \DateTimeZone('Europe/Madrid'))]; + yield [new \DateTime('2024-01-15T18:57:50', new \DateTimeZone('Africa/Kinshasa'))]; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportDescriptionHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportDescriptionHelperTest.php new file mode 100644 index 000000000..75638dd0f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportDescriptionHelperTest.php @@ -0,0 +1,174 @@ +prophesize(Security::class); + $security->getUser()->willReturn($user = new User()); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('my_export', Argument::type('array'))->willReturn($options); + + $export = $this->prophesize(ExportInterface::class); + $export->getTitle()->willReturn('Title'); + + $myFilterString = $this->prophesize(FilterInterface::class); + $myFilterString->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))->willReturn($string0 = 'This is a filter description'); + $myFilterArray = $this->prophesize(FilterInterface::class); + $myFilterArray->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class))->willReturn([$string1 = 'This is a filter with %argument%', $arg1 = ['%argument%' => 'zero']]); + $myFilterTranslatable = $this->prophesize(FilterInterface::class); + $myFilterTranslatable->describeAction(Argument::type('array'), Argument::type(ExportGenerationContext::class)) + ->willReturn(new class () implements TranslatableInterface { + public function trans(TranslatorInterface $translator, ?string $locale = null): string + { + return 'translatable'; + } + }); + + $myAggregator = $this->prophesize(AggregatorInterface::class); + $myAggregator->getTitle()->willReturn('Some aggregator'); + + $token = new UsernamePasswordToken($user, 'main', ['ROLE_USER']); + $tokenStorage = new TokenStorage(); + $tokenStorage->setToken($token); + + $exportManager = new ExportManager( + new NullLogger(), + $security->reveal(), + $this->prophesize(AuthorizationHelperInterface::class)->reveal(), + $tokenStorage, + ['my_export' => $export->reveal()], + ['my_aggregator' => $myAggregator->reveal()], + [ + 'my_filter_string' => $myFilterString->reveal(), + 'my_filter_array' => $myFilterArray->reveal(), + 'my_filter_translatable' => $myFilterTranslatable->reveal(), + ], + [], + ); + + $exportConfigProcessor = new ExportConfigProcessor($exportManager); + + $translator = $this->prophesize(TranslatorInterface::class); + $translator->trans('Title')->shouldBeCalled()->willReturn('Title'); + $translator->trans($string0)->shouldBeCalled()->willReturn($string0); + $translator->trans($string1, $arg1)->shouldBeCalled()->willReturn($string1); + $translator->trans('Some aggregator')->shouldBeCalled()->willReturn('Some aggregator'); + + $exportDescriptionHelper = new ExportDescriptionHelper( + $exportManager, + $exportConfigNormalizer->reveal(), + $exportConfigProcessor, + $translator->reveal(), + $security->reveal(), + ); + + $actual = $exportDescriptionHelper->describe('my_export', $options); + + self::assertIsArray($actual); + self::assertEquals($actual[0], 'Title'); + self::assertEquals($actual[1], 'This is a filter description'); + self::assertEquals($actual[2], 'This is a filter with %argument%'); + self::assertEquals($actual[3], 'translatable'); + self::assertEquals($actual[4], 'Some aggregator'); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php new file mode 100644 index 000000000..e01dc6566 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportGeneratorTest.php @@ -0,0 +1,413 @@ + ['acl' => ['filter_stats_by_center' => $filterStat]]] + ); + } + + public function testGenerateHappyScenario() + { + $initialData = ['initial' => 'test']; + $fullConfig = [ + 'export' => $formExportData = ['key' => 'form1'], + 'filters' => [ + 'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']], + 'disabled_filter' => ['enabled' => false], + ], + 'aggregators' => [ + 'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']], + 'disabled_aggregator' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => $formatterData = ['key' => 'form4'], + 'centers' => ['centers' => [$centerA = new Center()], 'regroupments' => [(new Regroupment())->addCenter($centerB = new Center())]], + ]; + $user = new User(); + + $export = $this->prophesize(ExportInterface::class); + $filter = $this->prophesize(FilterInterface::class); + $filter->applyOn()->willReturn('tagada'); + $aggregator = $this->prophesize(AggregatorInterface::class); + $aggregator->applyOn()->willReturn('tsointsoin'); + $formatter = $this->prophesize(FormatterInterface::class); + + $query = $this->prophesize(QueryBuilder::class); + $query->getDQL()->willReturn('dummy'); + $dqlQuery = $this->prophesize(Query::class); + $dqlQuery->getSQL()->willReturn('dummy'); + $query->getQuery()->willReturn($dqlQuery->reveal()); + + // required methods + $export->initiateQuery( + ['tagada', 'tsointsoin'], + Argument::that(function ($arg) use ($centerB, $centerA) { + if (!is_array($arg)) { + return false; + } + if (2 !== count($arg)) { + return false; + } + + foreach ($arg as $item) { + if ([] !== $item['circles']) { + return false; + } + if (!in_array($item['center'], [$centerA, $centerB], true)) { + return false; + } + } + + return true; + }), + $formExportData, + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + )->shouldBeCalled()->willReturn($query->reveal()); + $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled()->willReturn([['result0' => '0']]); + $export->requiredRole()->willReturn('dummy_role'); + + $filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + $aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + + $formatter->generate( + [['result0' => '0']], + $formatterData, + 'dummy', + $formExportData, + ['dummy_filter' => $formFilterData], + ['dummy_aggregator' => $formAggregatorData], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + ) + ->shouldBeCalled() + ->willReturn(new FormattedExportGeneration('export result', 'text/text')); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy')->willReturn($export->reveal()); + $exportManager->getFilter('dummy_filter')->willReturn($filter->reveal()); + $exportManager->hasFilter('dummy_filter')->willReturn(true); + $exportManager->hasFilter('disabled_filter')->willReturn(true); + $exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal()); + $exportManager->hasAggregator('dummy_aggregator')->willReturn(true); + $exportManager->hasAggregator('disabled_aggregator')->willReturn(true); + $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal()); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->shouldNotBeCalled(); + + $generator = new ExportGenerator( + $exportManager->reveal(), + $exportConfigNormalizer->reveal(), + new NullLogger(), + $authorizationHelper->reveal(), + new CenterRegroupementResolver(), + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(true), + $centerRepository->reveal(), + ); + + $actual = $generator->generate('dummy', $initialData, $user); + + self::assertEquals('export result', $actual->content); + self::assertEquals('text/text', $actual->contentType); + } + + public function testGenerateNativeSqlHappyScenario() + { + $initialData = ['initial' => 'test']; + $fullConfig = [ + 'export' => $formExportData = ['key' => 'form1'], + 'filters' => [], + 'aggregators' => [], + 'pick_formatter' => 'xlsx', + 'formatter' => $formatterData = ['key' => 'form4'], + 'centers' => ['centers' => [$centerA = new Center(), $centerB = new Center()], 'regroupments' => []], + ]; + $user = new User(); + + $export = $this->prophesize(ExportInterface::class); + $formatter = $this->prophesize(FormatterInterface::class); + + $query = $this->prophesize(NativeQuery::class); + + // required methods + $export->initiateQuery( + [], + [['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]], + ['key' => 'form1'], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + )->shouldBeCalled()->willReturn($query->reveal()); + $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled()->willReturn([['result0' => '0']]); + $export->supportsModifiers()->willReturn([]); + $export->requiredRole()->willReturn('dummy_role'); + + $formatter->generate( + [['result0' => '0']], + $formatterData, + 'dummy', + $formExportData, + [], + [], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + ) + ->shouldBeCalled() + ->willReturn(new FormattedExportGeneration('export result', 'text/text')); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy')->willReturn($export->reveal()); + $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal()); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->shouldNotBeCalled(); + + $generator = new ExportGenerator( + $exportManager->reveal(), + $exportConfigNormalizer->reveal(), + new NullLogger(), + $authorizationHelper->reveal(), + new CenterRegroupementResolver(), + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(true), + $centerRepository->reveal(), + ); + + $actual = $generator->generate('dummy', $initialData, $user); + + self::assertInstanceOf(FormattedExportGeneration::class, $actual); + self::assertEquals('export result', $actual->content); + self::assertEquals('text/text', $actual->contentType); + } + + public function testGenerateDirectExportHappyScenario() + { + $initialData = ['initial' => 'test']; + $fullConfig = [ + 'export' => $formExportData = ['key' => 'form1'], + 'filters' => [], + 'aggregators' => [], + 'pick_formatter' => 'xlsx', + 'formatter' => ['form' => $formatterData = ['key' => 'form4']], + 'centers' => ['centers' => [$centerA = new Center(), $centerB = new Center()], 'regroupments' => []], + ]; + $user = new User(); + + $export = $this->prophesize(DirectExportInterface::class); + + // required methods + $export->generate( + [['center' => $centerA, 'circles' => []], ['center' => $centerB, 'circles' => []]], + ['key' => 'form1'], + Argument::that(static fn (ExportGenerationContext $context) => $user === $context->byUser), + )->shouldBeCalled() + ->willReturn(new FormattedExportGeneration('export result', 'text/text')); + $export->requiredRole()->willReturn('dummy_role'); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy')->willReturn($export->reveal()); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters($user, 'dummy_role')->willReturn([$centerA, $centerB]); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->shouldNotBeCalled(); + + $generator = new ExportGenerator( + $exportManager->reveal(), + $exportConfigNormalizer->reveal(), + new NullLogger(), + $authorizationHelper->reveal(), + new CenterRegroupementResolver(), + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(true), + $centerRepository->reveal(), + ); + + $actual = $generator->generate('dummy', $initialData, $user); + + self::assertInstanceOf(FormattedExportGeneration::class, $actual); + self::assertEquals('export result', $actual->content); + self::assertEquals('text/text', $actual->contentType); + } + + public function testGenerateHappyScenarioWithoutCenterFiltering() + { + $initialData = ['initial' => 'test']; + $fullConfig = [ + 'export' => $formExportData = ['key' => 'form1'], + 'filters' => [ + 'dummy_filter' => ['enabled' => true, 'form' => $formFilterData = ['key' => 'form2']], + 'disabled_filter' => ['enabled' => false], + ], + 'aggregators' => [ + 'dummy_aggregator' => ['enabled' => true, 'form' => $formAggregatorData = ['key' => 'form3']], + 'disabled_aggregator' => ['enabled' => false], + ], + 'pick_formatter' => 'xlsx', + 'formatter' => $formatterData = ['key' => 'form4'], + 'centers' => ['centers' => [], 'regroupments' => []], + ]; + $user = new User(); + $centerA = new Center(); + $centerB = new Center(); + + $export = $this->prophesize(ExportInterface::class); + $filter = $this->prophesize(FilterInterface::class); + $filter->applyOn()->willReturn('tagada'); + $aggregator = $this->prophesize(AggregatorInterface::class); + $aggregator->applyOn()->willReturn('tsointsoin'); + $formatter = $this->prophesize(FormatterInterface::class); + + $query = $this->prophesize(QueryBuilder::class); + $query->getDQL()->willReturn('dummy'); + $dqlQuery = $this->prophesize(Query::class); + $dqlQuery->getSQL()->willReturn('dummy'); + $query->getQuery()->willReturn($dqlQuery->reveal()); + + // required methods + $export->initiateQuery( + ['tagada', 'tsointsoin'], + Argument::that(function ($arg) use ($centerB, $centerA) { + if (!is_array($arg)) { + return false; + } + if (2 !== count($arg)) { + return false; + } + + foreach ($arg as $item) { + if ([] !== $item['circles']) { + return false; + } + if (!in_array($item['center'], [$centerA, $centerB], true)) { + return false; + } + } + + return true; + }), + ['key' => 'form1'], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + )->shouldBeCalled()->willReturn($query->reveal()); + $export->getResult($query->reveal(), $formExportData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled()->willReturn([['result0' => '0']]); + $export->requiredRole()->willReturn('dummy_role'); + + $filter->alterQuery($query->reveal(), $formFilterData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + $aggregator->alterQuery($query->reveal(), $formAggregatorData, Argument::that(static fn (ExportGenerationContext $context) => $context->byUser === $user)) + ->shouldBeCalled(); + + $formatter->generate( + [['result0' => '0']], + $formatterData, + 'dummy', + $formExportData, + ['dummy_filter' => $formFilterData], + ['dummy_aggregator' => $formAggregatorData], + Argument::that(static fn ($context) => $context instanceof ExportGenerationContext && $context->byUser === $user), + ) + ->shouldBeCalled() + ->willReturn(new FormattedExportGeneration('export result', 'text/text')); + + $exportConfigNormalizer = $this->prophesize(ExportConfigNormalizer::class); + $exportConfigNormalizer->denormalizeConfig('dummy', $initialData)->willReturn($fullConfig); + + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy')->willReturn($export->reveal()); + $exportManager->getFilter('dummy_filter')->willReturn($filter->reveal()); + $exportManager->hasFilter('dummy_filter')->willReturn(true); + $exportManager->hasFilter('disabled_filter')->willReturn(true); + $exportManager->getAggregator('dummy_aggregator')->willReturn($aggregator->reveal()); + $exportManager->hasAggregator('dummy_aggregator')->willReturn(true); + $exportManager->hasAggregator('disabled_aggregator')->willReturn(true); + $exportManager->getFormatter('xlsx')->willReturn($formatter->reveal()); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters($user, 'dummy_role')->shouldNotBeCalled(); + + $centerRepository = $this->prophesize(CenterRepositoryInterface::class); + $centerRepository->findActive()->willReturn([$centerA, $centerB])->shouldBeCalled(); + + $generator = new ExportGenerator( + $exportManager->reveal(), + $exportConfigNormalizer->reveal(), + new NullLogger(), + $authorizationHelper->reveal(), + new CenterRegroupementResolver(), + new ExportConfigProcessor($exportManager->reveal()), + $this->buildParameter(false), + $centerRepository->reveal(), + ); + + $actual = $generator->generate('dummy', $initialData, $user); + + self::assertEquals('export result', $actual->content); + self::assertEquals('text/text', $actual->contentType); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php b/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php index e2d0e1393..6004a6012 100644 --- a/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Export/ExportManagerTest.php @@ -14,10 +14,10 @@ namespace Chill\MainBundle\Tests\Export; use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Export\AggregatorInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\ExportManager; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\Export\ExportType; use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Security\Authorization\AuthorizationHelper; use Chill\MainBundle\Test\PrepareCenterTrait; @@ -30,12 +30,10 @@ use Prophecy\Prophet; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Contracts\Translation\TranslatorInterface; /** * Test the export manager. @@ -194,176 +192,6 @@ final class ExportManagerTest extends KernelTestCase $this->assertNotContains($formatterBar->reveal(), $obtained); } - /** - * Test the generation of an export. - */ - public function testGenerate() - { - $center = $this->prepareCenter(100, 'center'); - $user = $this->prepareUser([]); - - $authorizationChecker = $this->prophet->prophesize(); - $authorizationChecker->willImplement(AuthorizationCheckerInterface::class); - $authorizationChecker->isGranted('CHILL_STAT_DUMMY', $center) - ->willReturn(true); - $exports = []; - $filters = []; - $aggregators = []; - - $em = self::getContainer()->get(EntityManagerInterface::class); - - $export = $this->prophet->prophesize(); - $export->willImplement(ExportInterface::class); - $export->initiateQuery( - Argument::is(['foo']), - Argument::Type('array'), - Argument::is(['a' => 'b']) - ) - ->will(static function () use ($em) { - $qb = $em->createQueryBuilder(); - - return $qb->addSelect('COUNT(user.id) as export') - ->from(User::class, 'user'); - }); - $export->initiateQuery( - Argument::is(['foo']), - Argument::Type('array'), - Argument::is(['a' => 'b']) - )->shouldBeCalled(); - $export->supportsModifiers()->willReturn(['foo']); - $export->requiredRole()->willReturn('CHILL_STAT_DUMMY'); - $export->getResult(Argument::Type(QueryBuilder::class), Argument::Type('array'))->willReturn([ - [ - 'aggregator' => 'cat a', - 'export' => 0, - ], - [ - 'aggregator' => 'cat b', - 'export' => 1, - ], - ]); - $export->getLabels( - Argument::is('export'), - Argument::is([0, 1]), - Argument::Type('array') - ) - ->willReturn(static function ($value) { - switch ($value) { - case 0: - case 1: - return $value; - - case '_header': - return 'export'; - - default: throw new \RuntimeException(sprintf('The value %s is not valid', $value)); - } - }); - - $export->getQueryKeys(Argument::Type('array'))->willReturn(['export']); - $export->getTitle()->willReturn('dummy title'); - $exports['dummy'] = $export->reveal(); - - $filter = $this->prophet->prophesize(); - $filter->willImplement(FilterInterface::class); - $filter->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->willReturn(null); - $filter->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->shouldBeCalled(); - $filter->addRole()->shouldBeCalled(); - $filter->addRole()->willReturn(null); - $filter->applyOn()->willReturn('foo'); - $filter->describeAction(Argument::cetera())->willReturn('filtered string'); - $filters['filter_foo'] = $filter->reveal(); - - $aggregator = $this->prophet->prophesize(); - $aggregator->willImplement(AggregatorInterface::class); - $aggregator->addRole()->willReturn(null); - $aggregator->applyOn()->willReturn('foo'); - $aggregator->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->willReturn(null); - $aggregator->alterQuery(Argument::Type(QueryBuilder::class), Argument::Type('array')) - ->shouldBeCalled(); - $aggregator->getQueryKeys(Argument::Type('array'))->willReturn(['aggregator']); - $aggregator->getLabels( - Argument::is('aggregator'), - Argument::is(['cat a', 'cat b']), - Argument::is([]) - ) - ->willReturn(static fn ($value) => match ($value) { - '_header' => 'foo_header', - 'cat a' => 'label cat a', - 'cat b' => 'label cat b', - default => throw new \RuntimeException(sprintf('This value (%s) is not valid', $value)), - }); - $aggregator->addRole()->willReturn(null); - $aggregator->addRole()->shouldBeCalled(); - $aggregators['aggregator_foo'] = $aggregator->reveal(); - - $exportManager = $this->createExportManager( - null, - null, - $authorizationChecker->reveal(), - null, - $user, - $exports, - $aggregators, - $filters - ); - - // add formatter interface - $formatter = new \Chill\MainBundle\Export\Formatter\SpreadSheetFormatter( - self::getContainer()->get(TranslatorInterface::class), - $exportManager - ); - - $exportManager->addFormatter($formatter, 'spreadsheet'); - - $response = $exportManager->generate( - 'dummy', - [$center], - [ - ExportType::FILTER_KEY => [ - 'filter_foo' => [ - 'enabled' => true, - 'form' => [], - ], - ], - ExportType::AGGREGATOR_KEY => [ - 'aggregator_foo' => [ - 'enabled' => true, - 'form' => [], - ], - ], - ExportType::PICK_FORMATTER_KEY => [ - 'alias' => 'spreadsheet', - ], - ExportType::EXPORT_KEY => [ - 'a' => 'b', - ], - ], - [ - 'format' => 'csv', - 'aggregator_foo' => [ - 'order' => 1, - ], - ] - ); - - $this->assertInstanceOf(Response::class, $response); - $expected = <<<'EOT' - "dummy title","" - "","" - "filtered string","" - "foo_header","export" - "label cat a","0" - "label cat b","1" - - EOT; - - $this->assertEquals($expected, $response->getContent()); - } - public function testIsGrantedForElementWithExportAndUserIsGranted() { $center = $this->prepareCenter(100, 'center A'); @@ -506,6 +334,7 @@ final class ExportManagerTest extends KernelTestCase array $exports = [], array $aggregators = [], array $filters = [], + array $formatters = [], ): ExportManager { $localUser = $user ?? self::getContainer()->get( UserRepositoryInterface::class @@ -516,13 +345,14 @@ final class ExportManagerTest extends KernelTestCase $tokenStorage->setToken($token); return new ExportManager( - $logger ?? self::getContainer()->get('logger'), + $logger ?? self::getContainer()->get(LoggerInterface::class), $authorizationChecker ?? self::getContainer()->get('security.authorization_checker'), $authorizationHelper ?? self::getContainer()->get('chill.main.security.authorization.helper'), $tokenStorage, $exports, $aggregators, - $filters + $filters, + $formatters, ); } } @@ -534,19 +364,34 @@ class DummyFilterWithApplying implements FilterInterface private readonly string $applyOn, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'dummy'; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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 describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['dummy filter', []]; } @@ -556,9 +401,9 @@ class DummyFilterWithApplying implements FilterInterface return $this->role; } - public function alterQuery(QueryBuilder $qb, $data) {} + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void {} - public function applyOn() + public function applyOn(): string { return $this->applyOn; } @@ -574,13 +419,28 @@ class DummyExport implements ExportInterface private readonly array $supportedModifiers, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'dummy'; } public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -601,24 +461,24 @@ class DummyExport implements ExportInterface return []; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return []; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return []; } - public function getType() + public function getType(): string { return 'dummy'; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { - return null; + throw new \RuntimeException('not implemented'); } public function requiredRole(): string diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Formatter/SpreadsheetFormatterTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Formatter/SpreadsheetFormatterTest.php new file mode 100644 index 000000000..bf1da2701 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Formatter/SpreadsheetFormatterTest.php @@ -0,0 +1,138 @@ +prophesize(\Symfony\Contracts\Translation\TranslatorInterface::class); + $translator->getLocale()->willReturn('en'); + $exportManager = $this->prophesize(ExportManager::class); + + $result = + [ + ['export_count_activity' => 1, 'person_age' => 65, 'aggregator_some' => 'label0'], // row 0 + ]; + $exportAlias = 'count_activity_linked_to_person'; + $formatterData = + ['format' => 'xlsx', 'person_age_aggregator' => ['order' => 1], 'aggregator2' => ['order' => 2]]; + $exportData = []; + $filtersData = + [ + 'person_age_filter' => ['min_age' => 18, 'max_age' => 120, 'date_calc' => new RollingDate(RollingDate::T_TODAY)], + 'filter2' => [], + ]; + $aggregatorsData = + [ + 'person_age_aggregator' => ['date_age_calculation' => new RollingDate(RollingDate::T_TODAY)], + 'aggregator2' => [], + ]; + $context = + new ExportGenerationContext($user = new User()); + + $export = $this->prophesize(ExportInterface::class); + $export->getTitle()->willReturn('Count activity linked to person'); + $translator->trans('Count activity linked to person')->willReturn('Count activity linked to person'); + $export->getQueryKeys($exportData)->willReturn(['export_count_activity']); + $export->getLabels('export_count_activity', [1], $exportData) + ->willReturn(fn (int|string $value): int|string => '_header' === $value ? 'Count activities' : $value); + $translator->trans('Count activities')->willReturn('Count activities'); + $exportManager->getExport($exportAlias)->willReturn($export->reveal()); + + $aggregator = $this->prophesize(\Chill\MainBundle\Export\AggregatorInterface::class); + $aggregator->getTitle()->willReturn('Person age'); + $aggregator->getQueryKeys($aggregatorsData['person_age_aggregator'])->willReturn(['person_age']); + $aggregator->getLabels('person_age', [65], $aggregatorsData['person_age_aggregator']) + ->willReturn(fn (int|string $value): int|string => '_header' === $value ? 'Group by age' : $value); + $translator->trans('Group by age')->willReturn('Group by age'); + $exportManager->getAggregator('person_age_aggregator')->willReturn($aggregator->reveal()); + + $aggregator2 = $this->prophesize(\Chill\MainBundle\Export\AggregatorInterface::class); + $aggregator2->getTitle()->willReturn(new TranslatableMessage('Some')); + $aggregator2->getQueryKeys($aggregatorsData['aggregator2'])->willReturn(['aggregator_some']); + $aggregator2->getLabels('aggregator_some', ['label0'], $aggregatorsData['aggregator2']) + ->willReturn(fn (int|string $value): TranslatableMessage => new TranslatableMessage('_header' === $value ? 'Aggregator 2 header' : $value)); + $translator->trans('Aggregator 2 header', [], null, 'en')->willReturn('Aggregator 2 header'); + $translator->trans('label0', [], null, 'en')->willReturn('label0'); + $exportManager->getAggregator('aggregator2')->willReturn($aggregator2->reveal()); + + $filter = $this->prophesize(\Chill\MainBundle\Export\FilterInterface::class); + $filter->getTitle()->willReturn('Person by age'); + $filter->describeAction($filtersData['person_age_filter'], $context) + ->willReturn(['Filter by age, from {{ start }} to {{ end }}', ['{{ start }}' => '18', '{{ end }}' => '120']]); + $translator->trans('Filter by age, from {{ start }} to {{ end }}', ['{{ start }}' => '18', '{{ end }}' => '120']) + ->willReturn('Filter by age, from 18 to 120'); + $exportManager->getFilter('person_age_filter')->willReturn($filter->reveal()); + + $filter2 = $this->prophesize(\Chill\MainBundle\Export\FilterInterface::class); + $filter2->getTitle()->willReturn(new TranslatableMessage('Some other filter')); + $filter2->describeAction($filtersData['filter2'], $context) + ->willReturn(new TranslatableMessage('Other filter description')); + $translator->trans('Other filter description', [], null, 'en') + ->willReturn('Some other filter description'); + $exportManager->getFilter('filter2')->willReturn($filter2->reveal()); + + + // create the formatter + $formatter = new SpreadSheetFormatter($translator->reveal()); + $formatter->setExportManager($exportManager->reveal()); + + $result = $formatter->generate( + $result, + $formatterData, + $exportAlias, + $exportData, + $filtersData, + $aggregatorsData, + $context, + ); + + $tempFile = tempnam(sys_get_temp_dir(), 'test_spreadsheet_formatter_'); + file_put_contents($tempFile, $result->content); + $spreadsheet = IOFactory::load($tempFile); + $cells = $spreadsheet->getActiveSheet()->rangeToArray( + 'A1:G6', + null, + false, + true, + true, + ); + unlink($tempFile); + + self::assertEquals('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', $result->contentType); + self::assertEquals($cells[1], ['A' => 'Count activity linked to perso…', 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[2], ['A' => null, 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[3], ['A' => 'Filter by age, from 18 to 120', 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[4], ['A' => 'Some other filter description', 'B' => null, 'C' => null, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[5], ['A' => 'Group by age', 'B' => 'Aggregator 2 header', 'C' => 'Count activities', 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + self::assertEquals($cells[6], ['A' => 65, 'B' => 'label0', 'C' => 1, 'D' => null, 'E' => null, 'F' => null, 'G' => null]); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Messenger/OnExportGenerationFailsTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Messenger/OnExportGenerationFailsTest.php new file mode 100644 index 000000000..ef19ccaaa --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Messenger/OnExportGenerationFailsTest.php @@ -0,0 +1,62 @@ +setCreatedAt(new \DateTimeImmutable('10 seconds ago')); + + $repository = $this->prophesize(ExportGenerationRepository::class); + $repository->find($exportGeneration->getId())->willReturn($exportGeneration); + + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->flush()->shouldBeCalled(); + + $user = $this->prophesize(User::class); + $user->getId()->willReturn(1); + + $subscriber = new OnExportGenerationFails(new NullLogger(), $repository->reveal(), $entityManager->reveal()); + + $subscriber->onMessageFailed(new WorkerMessageFailedEvent( + new Envelope(new ExportRequestGenerationMessage($exportGeneration, $user->reveal())), + 'dummyReceiver', + new ExportGenerationException('dummy_exception'), + )); + + self::assertEquals(StoredObject::STATUS_FAILURE, $exportGeneration->getStoredObject()->getStatus()); + self::assertStringContainsString('dummy_exception', $exportGeneration->getStoredObject()->getGenerationErrors()); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Messenger/RemoveExportGenerationMessageHandlerTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Messenger/RemoveExportGenerationMessageHandlerTest.php new file mode 100644 index 000000000..70f6f9f75 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Messenger/RemoveExportGenerationMessageHandlerTest.php @@ -0,0 +1,76 @@ + 'bar']); + $storedObject = $exportGeneration->getStoredObject(); + + // 3. Mock ExportGenerationRepository to return the ExportGeneration + $exportGenerationRepository = $this->prophesize(ExportGenerationRepository::class); + $exportGenerationRepository + ->find($exportGeneration->getId()) + ->willReturn($exportGeneration); + + // 4. Mock EntityManagerInterface and set expectations + $entityManager = $this->prophesize(EntityManagerInterface::class); + $entityManager->remove($exportGeneration)->shouldBeCalled(); + $entityManager->flush()->shouldBeCalled(); + + // 6. Create message + $message = new RemoveExportGenerationMessage($exportGeneration); + + // 7. Handler instantiation + $handler = new RemoveExportGenerationMessageHandler( + $exportGenerationRepository->reveal(), + $entityManager->reveal(), + new NullLogger(), + $clock + ); + + // Pre-condition: deleteAt not set. + $this->assertNull($storedObject->getDeleteAt()); + + // Act + $handler->__invoke($message); + + // Assert + $this->assertEquals($now, $storedObject->getDeleteAt(), 'deleteAt of stored object was updated'); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Export/Migrator/SavedExportOptionsMigratorTest.php b/src/Bundle/ChillMainBundle/Tests/Export/Migrator/SavedExportOptionsMigratorTest.php new file mode 100644 index 000000000..6cfa21faa --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Export/Migrator/SavedExportOptionsMigratorTest.php @@ -0,0 +1,586 @@ +getTitle() instanceof TranslatableInterface) { + continue; + } + if (null === $previousName) { $previousName = $translator->trans($filter->getTitle()); continue; @@ -119,24 +125,39 @@ class SortExportElementTest extends KernelTestCase return new class ($title) implements AggregatorInterface { public function __construct(private readonly string $title) {} - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return fn ($v) => $v; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return []; } - public function getTitle() + public function getTitle(): string|TranslatableInterface { return $this->title; } @@ -146,11 +167,11 @@ class SortExportElementTest extends KernelTestCase return null; } - public function alterQuery(QueryBuilder $qb, $data) {} + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void {} - public function applyOn() + public function applyOn(): string { - return []; + return ''; } }; } @@ -160,19 +181,34 @@ class SortExportElementTest extends KernelTestCase return new class ($title) implements FilterInterface { public function __construct(private readonly string $title) {} - public function getTitle() + public function getTitle(): string|TranslatableInterface { return $this->title; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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 describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|TranslatableInterface|array { return ['a', []]; } @@ -182,11 +218,11 @@ class SortExportElementTest extends KernelTestCase return null; } - public function alterQuery(QueryBuilder $qb, $data) {} + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void {} - public function applyOn() + public function applyOn(): string { - return []; + return ''; } }; } diff --git a/src/Bundle/ChillMainBundle/Tests/Security/Authorization/SavedExportVoterTest.php b/src/Bundle/ChillMainBundle/Tests/Security/Authorization/SavedExportVoterTest.php new file mode 100644 index 000000000..f2eefa055 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Security/Authorization/SavedExportVoterTest.php @@ -0,0 +1,145 @@ +prophesize(ExportInterface::class); + $exportManager = $this->prophesize(ExportManager::class); + $exportManager->getExport('dummy_export')->willReturn($export->reveal()); + $exportManager->isGrantedForElement(Argument::any())->willReturn($isGranted); + + $accessDecisionManager = $this->prophesize(AccessDecisionManagerInterface::class); + + $voter = new SavedExportVoter($exportManager->reveal(), $accessDecisionManager->reveal()); + $token = new UsernamePasswordToken($user, 'default', ['ROLE_USER']); + + self::assertEquals($expectedResult, $voter->vote($token, $savedExport, [$attribute])); + } + + public static function voteProvider(): iterable + { + $alls = [SavedExportVoter::GENERATE, SavedExportVoter::GENERATE, SavedExportVoter::EDIT, SavedExportVoter::DELETE]; + $userA = new User(); + $userB = new User(); + $userC = new User(); + $group = new UserGroup(); + $group->addUser($userC); + + $savedExport = new SavedExport(); + $savedExport->setExportAlias('dummy_export'); + $savedExport->setUser($userA); + + // abstain + foreach ($alls as $attribute) { + yield [ + $attribute, + new \stdClass(), + $userA, + VoterInterface::ACCESS_ABSTAIN, + true, + ]; + } + + yield [ + 'dummy', + $savedExport, + $userA, + VoterInterface::ACCESS_ABSTAIN, + false, + ]; + + foreach ($alls as $attribute) { + yield [ + $attribute, + $savedExport, + $userA, + VoterInterface::ACCESS_GRANTED, + true, + ]; + } + + yield [ + SavedExportVoter::GENERATE, + $savedExport, + $userA, + VoterInterface::ACCESS_DENIED, + false, + ]; + + foreach ($alls as $attribute) { + yield [ + $attribute, + $savedExport, + $userB, + VoterInterface::ACCESS_DENIED, + true, + ]; + } + + $savedExport = new SavedExport(); + $savedExport->setExportAlias('dummy_export'); + $savedExport->setUser($userA); + $savedExport->addShare($userB); + + yield [ + SavedExportVoter::GENERATE, + $savedExport, + $userB, + VoterInterface::ACCESS_DENIED, + false, + ]; + + yield [ + SavedExportVoter::GENERATE, + $savedExport, + $userB, + VoterInterface::ACCESS_GRANTED, + true, + ]; + + foreach ([SavedExportVoter::EDIT, SavedExportVoter::DELETE] as $attribute) { + yield [ + $attribute, + $savedExport, + $userB, + VoterInterface::ACCESS_DENIED, + true, + ]; + } + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/CenterRegroupementResolverTest.php b/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/CenterRegroupementResolverTest.php new file mode 100644 index 000000000..4f7c2431f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/CenterRegroupementResolverTest.php @@ -0,0 +1,78 @@ +resolveCenters($groups, $centers); + + self::assertEquals(count($expected), count($actual)); + + foreach ($expected as $center) { + self::assertContains($center, $actual); + } + } + + public static function provideData(): iterable + { + $centerA = new Center(); + $centerB = new Center(); + $centerC = new Center(); + $centerD = new Center(); + + $groupA = new Regroupment(); + $groupA->addCenter($centerA)->addCenter($centerB); + + $groupB = new Regroupment(); + $groupB->addCenter($centerA)->addCenter($centerB)->addCenter($centerC); + + yield [ + [$groupA], + [], + [$centerA, $centerB], + ]; + + yield [ + [$groupA, $groupB], + [], + [$centerA, $centerB, $centerC], + ]; + + yield [ + [$groupA, $groupB], + [$centerB, $centerD], + [$centerA, $centerB, $centerC, $centerD], + ]; + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/RegroupementFilteringTest.php b/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/RegroupementFilteringTest.php new file mode 100644 index 000000000..f6976054f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Services/Regroupement/RegroupementFilteringTest.php @@ -0,0 +1,96 @@ +filterContainsAtLeastOneCenter($groups, $centers); + + self::assertEquals(count($expected), count($actual)); + self::assertTrue(array_is_list($actual)); + + foreach ($expected as $center) { + self::assertContains($center, $actual); + } + } + + public static function provideDataForFilterContainsAtLeastOnCenter(): iterable + { + + $centerA = new Center(); + $centerB = new Center(); + $centerC = new Center(); + $centerD = new Center(); + + $groupA = new Regroupment(); + $groupA->addCenter($centerA)->addCenter($centerB); + + $groupB = new Regroupment(); + $groupB->addCenter($centerA)->addCenter($centerB)->addCenter($centerC); + + $groupC = new Regroupment(); + $groupC->addCenter($centerA)->addCenter($centerD); + + yield [ + [$groupA, $groupB], + [], + [], + ]; + + yield [ + [$groupA, $groupB], + [$centerA, $centerB, $centerC], + [$groupA, $groupB], + ]; + + yield [ + [$groupA, $groupC], + [$centerD], + [$groupC], + ]; + + yield [ + [$groupA], + [$centerB, $centerD], + [$groupA], + ]; + + yield [ + [$groupA], + [new Center()], + [], + ]; + + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php index bb255f5e7..3031a4d80 100644 --- a/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateConverterTest.php @@ -14,6 +14,7 @@ namespace Services\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverter; use PHPUnit\Framework\TestCase; +use Symfony\Component\Clock\MockClock; /** * @internal @@ -22,11 +23,9 @@ use PHPUnit\Framework\TestCase; */ final class RollingDateConverterTest extends TestCase { - private RollingDateConverter $converter; - - protected function setUp(): void + private function buildConverter(\DateTimeImmutable|string $pivot = 'now'): RollingDateConverter { - $this->converter = new RollingDateConverter(); + return new RollingDateConverter(new MockClock($pivot)); } public function testConversionFixedDate() @@ -35,7 +34,7 @@ final class RollingDateConverterTest extends TestCase $this->assertEquals( '2022-01-01', - $this->converter->convert($rollingDate)->format('Y-m-d') + $this->buildConverter()->convert($rollingDate)->format('Y-m-d') ); } @@ -43,7 +42,7 @@ final class RollingDateConverterTest extends TestCase { $rollingDate = new RollingDate(RollingDate::T_YEAR_PREVIOUS_START); - $actual = $this->converter->convert($rollingDate); + $actual = $this->buildConverter()->convert($rollingDate); $this->assertEquals( (int) (new \DateTimeImmutable('now'))->format('Y') - 1, @@ -63,7 +62,21 @@ final class RollingDateConverterTest extends TestCase $this->assertEquals( \DateTime::createFromFormat($format, $expectedDateTime), - $this->converter->convert($rollingDate) + $this->buildConverter()->convert($rollingDate) + ); + } + + /** + * @dataProvider generateDataConversionDate + */ + public function testConvertOnClock(string $roll, string $expectedDateTime, string $format) + { + $pivot = \DateTimeImmutable::createFromFormat('Y-m-d His', '2022-11-07 000000'); + $rollingDate = new RollingDate($roll, null); + + $this->assertEquals( + \DateTime::createFromFormat($format, $expectedDateTime), + $this->buildConverter($pivot)->convert($rollingDate) ); } diff --git a/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateTest.php b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateTest.php new file mode 100644 index 000000000..a700fc642 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Services/RollingDate/RollingDateTest.php @@ -0,0 +1,45 @@ +normalize()); + + self::assertEquals(RollingDate::T_YEAR_PREVIOUS_START, $actual->getRoll()); + self::assertNull($actual->getFixedDate()); + self::assertEquals($date->getPivotDate()?->getTimestamp(), $actual->getPivotDate()?->getTimestamp()); + } + + public function testNormalizationDenormalizationProcessWithPivotDate(): void + { + $date = new RollingDate(RollingDate::T_FIXED_DATE, $fixed = new \DateTimeImmutable('now')); + + $actual = RollingDate::fromNormalized($date->normalize()); + + self::assertEquals(RollingDate::T_FIXED_DATE, $actual->getRoll()); + self::assertEquals($fixed, $actual->getFixedDate()); + self::assertEquals($date->getPivotDate()?->getTimestamp(), $actual->getPivotDate()?->getTimestamp()); + } +} diff --git a/src/Bundle/ChillMainBundle/Tests/Services/UserGroup/UserGroupRelatedToUserJobSyncCronJobTest.php b/src/Bundle/ChillMainBundle/Tests/Services/UserGroup/UserGroupRelatedToUserJobSyncCronJobTest.php new file mode 100644 index 000000000..1228aa30f --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Services/UserGroup/UserGroupRelatedToUserJobSyncCronJobTest.php @@ -0,0 +1,58 @@ +prophesize(UserGroupRelatedToUserJobSyncInterface::class); + + $cronJob = new UserGroupRelatedToUserJobSyncCronJob($clock, $job->reveal()); + + if (null !== $lastStartExecution) { + $lastExecution = new CronJobExecution('user-group-related-to-user-job-sync'); + $lastExecution->setLastStart($lastStartExecution); + } + + $actual = $cronJob->canRun($lastExecution ?? null); + + self::assertEquals($exected, $actual); + } + + public static function canRunDataProvider(): iterable + { + $now = new \DateTimeImmutable('2025-04-27T00:00:00Z'); + + yield 'never executed' => [$now, null, true]; + yield 'executed 12 hours ago' => [$now, new \DateTimeImmutable('2025-04-26T12:00:00Z'), false]; + yield 'executed more than 12 hours ago' => [$now, new \DateTimeImmutable('2025-04-25T12:00:00Z'), true]; + } +} diff --git a/src/Bundle/ChillMainBundle/chill.api.specs.yaml b/src/Bundle/ChillMainBundle/chill.api.specs.yaml index 8d204803f..d87a0eb71 100644 --- a/src/Bundle/ChillMainBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillMainBundle/chill.api.specs.yaml @@ -1111,3 +1111,47 @@ paths: 204: description: "resource was deleted successfully" + /1.0/main/export-generation/{id}/object: + get: + tags: + - export + summary: get the object status and details of an export-generation + parameters: + - name: id + in: path + required: true + description: The entity export generation id + schema: + type: string + format: uuid + responses: + 403: + description: Access denied + 200: + description: "ok" + content: + application/json: + schema: + type: object + /1.0/main/export/export-generation/create-from-saved-export/{id}: + post: + tags: + - export + summary: Create an export generation from an existing saved export + parameters: + - name: id + in: path + required: true + description: The entity saved export's id + schema: + type: string + format: uuid + responses: + 403: + description: Access denied + 200: + description: "ok" + content: + application/json: + schema: + type: object diff --git a/src/Bundle/ChillMainBundle/chill.webpack.config.js b/src/Bundle/ChillMainBundle/chill.webpack.config.js index 6cc890797..011fc9559 100644 --- a/src/Bundle/ChillMainBundle/chill.webpack.config.js +++ b/src/Bundle/ChillMainBundle/chill.webpack.config.js @@ -34,7 +34,7 @@ module.exports = function (encore, entries) { ); encore.addEntry( "page_download_exports", - __dirname + "/Resources/public/page/export/download-export.js", + __dirname + "/Resources/public/vuejs/DownloadExport/index.ts", ); // Modules entrypoints @@ -106,6 +106,10 @@ module.exports = function (encore, entries) { "mod_workflow_attachment", __dirname + "/Resources/public/vuejs/WorkflowAttachment/index", ); + encore.addEntry( + "mod_saved_export_button", + __dirname + "/Resources/public/vuejs/SavedExportButtons/index.ts", + ); // Vue entrypoints encore.addEntry( @@ -116,4 +120,5 @@ module.exports = function (encore, entries) { "vue_onthefly", __dirname + "/Resources/public/vuejs/OnTheFly/index.js", ); + }; diff --git a/src/Bundle/ChillMainBundle/config/services.yaml b/src/Bundle/ChillMainBundle/config/services.yaml index a9829f99d..14f0f851a 100644 --- a/src/Bundle/ChillMainBundle/config/services.yaml +++ b/src/Bundle/ChillMainBundle/config/services.yaml @@ -89,10 +89,7 @@ services: $exports: !tagged_iterator { tag: chill.export, index_by: alias } $aggregators: !tagged_iterator { tag: chill.export_aggregator, index_by: alias } $filters: !tagged_iterator { tag: chill.export_filter, index_by: alias } - # for an unknown reason, iterator_to_array($formatter) cause a segmentation fault error (php-fpm code 11). removed temporarily - # $formatters: !tagged_iterator { tag: chill.export_formatter, index_by: alias } - # remove until we can properly test it - # $exportElementProvider: !tagged_iterator { tag: chill.export_elements_provider, index_by: prefix } + $formatters: !tagged_iterator { tag: chill.export_formatter, index_by: alias } Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverDispatcher' diff --git a/src/Bundle/ChillMainBundle/config/services/export.yaml b/src/Bundle/ChillMainBundle/config/services/export.yaml index ece7ae902..72d862bf7 100644 --- a/src/Bundle/ChillMainBundle/config/services/export.yaml +++ b/src/Bundle/ChillMainBundle/config/services/export.yaml @@ -6,8 +6,25 @@ services: Chill\MainBundle\Export\Helper\: resource: '../../Export/Helper' + Chill\MainBundle\Export\Cronjob\: + resource: '../../Export/Cronjob' + + Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessageHandler: ~ + + Chill\MainBundle\Export\Messenger\RemoveExportGenerationMessageHandler: ~ + + Chill\MainBundle\Export\Messenger\OnExportGenerationFails: ~ + Chill\MainBundle\Export\ExportFormHelper: ~ + Chill\MainBundle\Export\ExportGenerator: ~ + + Chill\MainBundle\Export\ExportConfigNormalizer: ~ + + Chill\MainBundle\Export\ExportConfigProcessor: ~ + + Chill\MainBundle\Export\ExportDescriptionHelper: ~ + chill.main.export_element_validator: class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator tags: @@ -23,36 +40,14 @@ services: chill.main.export.spreadsheet_formatter: class: Chill\MainBundle\Export\Formatter\SpreadSheetFormatter - arguments: - $translatorInterface: '@Symfony\Contracts\Translation\TranslatorInterface' - $exportManager: '@Chill\MainBundle\Export\ExportManager' tags: - { name: chill.export_formatter, alias: 'spreadsheet' } - chill.main.export.list_formatter: - class: Chill\MainBundle\Export\Formatter\CSVListFormatter - arguments: - $translatorInterface: '@Symfony\Contracts\Translation\TranslatorInterface' - $exportManager: '@Chill\MainBundle\Export\ExportManager' - tags: - - { name: chill.export_formatter, alias: 'csvlist' } - chill.main.export.list_spreadsheet_formatter: class: Chill\MainBundle\Export\Formatter\SpreadsheetListFormatter - arguments: - $translatorInterface: '@Symfony\Contracts\Translation\TranslatorInterface' - $exportManager: '@Chill\MainBundle\Export\ExportManager' tags: - { name: chill.export_formatter, alias: 'spreadlist' } - chill.main.export.pivoted_list_formatter: - class: Chill\MainBundle\Export\Formatter\CSVPivotedListFormatter - arguments: - $translatorInterface: '@Symfony\Contracts\Translation\TranslatorInterface' - $exportManager: '@Chill\MainBundle\Export\ExportManager' - tags: - - { name: chill.export_formatter, alias: 'csv_pivoted_list' } - Chill\MainBundle\Export\AccompanyingCourseExportHelper: ~ Chill\MainBundle\Export\SortExportElement: ~ diff --git a/src/Bundle/ChillMainBundle/config/services/form.yaml b/src/Bundle/ChillMainBundle/config/services/form.yaml index f6b50cb57..f31829915 100644 --- a/src/Bundle/ChillMainBundle/config/services/form.yaml +++ b/src/Bundle/ChillMainBundle/config/services/form.yaml @@ -146,3 +146,5 @@ services: Chill\MainBundle\Form\DataTransformer\IdToLocationDataTransformer: ~ Chill\MainBundle\Form\DataTransformer\IdToUserDataTransformer: ~ Chill\MainBundle\Form\DataTransformer\IdToUsersDataTransformer: ~ + + Chill\MainBundle\Form\SavedExportType: ~ diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250219130532.php b/src/Bundle/ChillMainBundle/migrations/Version20250219130532.php new file mode 100644 index 000000000..140254acc --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250219130532.php @@ -0,0 +1,40 @@ +addSql('CREATE TABLE chill_main_export_generation (id UUID NOT NULL, deleteAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, exportAlias TEXT DEFAULT \'\' NOT NULL, options JSON DEFAULT \'[]\' NOT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, storedObject_id INT NOT NULL, createdBy_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_E644B77D6C99C13A ON chill_main_export_generation (storedObject_id)'); + $this->addSql('CREATE INDEX IDX_E644B77D3174800F ON chill_main_export_generation (createdBy_id)'); + $this->addSql('COMMENT ON COLUMN chill_main_export_generation.id IS \'(DC2Type:uuid)\''); + $this->addSql('COMMENT ON COLUMN chill_main_export_generation.deleteAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN chill_main_export_generation.createdAt IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE chill_main_export_generation ADD CONSTRAINT FK_E644B77D6C99C13A FOREIGN KEY (storedObject_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_export_generation ADD CONSTRAINT FK_E644B77D3174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE chill_main_export_generation'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250313165611.php b/src/Bundle/ChillMainBundle/migrations/Version20250313165611.php new file mode 100644 index 000000000..159f12b66 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250313165611.php @@ -0,0 +1,38 @@ +addSql('ALTER TABLE chill_main_export_generation ADD savedExport_id UUID DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN chill_main_export_generation.savedExport_id IS \'(DC2Type:uuid)\''); + $this->addSql('ALTER TABLE chill_main_export_generation ADD CONSTRAINT FK_E644B77DA61D6F69 FOREIGN KEY (savedExport_id) REFERENCES chill_main_saved_export (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_E644B77DA61D6F69 ON chill_main_export_generation (savedExport_id)'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_export_generation DROP CONSTRAINT FK_E644B77DA61D6F69'); + $this->addSql('DROP INDEX IDX_E644B77DA61D6F69'); + $this->addSql('ALTER TABLE chill_main_export_generation DROP savedExport_id'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250404123326.php b/src/Bundle/ChillMainBundle/migrations/Version20250404123326.php new file mode 100644 index 000000000..84330385e --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250404123326.php @@ -0,0 +1,49 @@ +addSql('ALTER TABLE chill_main_saved_export ADD COLUMN options_backup JSONB default \'[]\''); + $this->addSql('UPDATE chill_main_saved_export SET options_backup = options'); + + $result = $this->connection->executeQuery('SELECT id, options FROM chill_main_saved_export'); + + foreach ($result->iterateAssociative() as $row) { + $options = json_decode((string) $row['options'], true, 512, JSON_THROW_ON_ERROR); + $this->addSql( + 'UPDATE chill_main_saved_export SET options = :new_options WHERE id = :id', + ['id' => $row['id'], 'new_options' => SavedExportOptionsMigrator::migrate($options)], + ['id' => Types::STRING, 'new_options' => Types::JSON], + ); + } + + } + + public function down(Schema $schema): void + { + $this->addSql('UPDATE chill_main_saved_export SET options = options_backup'); + $this->addSql('ALTER TABLE chill_main_saved_export DROP COLUMN options_backup'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250410145342.php b/src/Bundle/ChillMainBundle/migrations/Version20250410145342.php new file mode 100644 index 000000000..21f4c6d0a --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250410145342.php @@ -0,0 +1,49 @@ +addSql('CREATE TABLE chill_main_saved_export_users (savedexport_id UUID NOT NULL, user_id INT NOT NULL, PRIMARY KEY(savedexport_id, user_id))'); + $this->addSql('CREATE INDEX IDX_4A2B71EC24ECEDCA ON chill_main_saved_export_users (savedexport_id)'); + $this->addSql('CREATE INDEX IDX_4A2B71ECA76ED395 ON chill_main_saved_export_users (user_id)'); + $this->addSql('COMMENT ON COLUMN chill_main_saved_export_users.savedexport_id IS \'(DC2Type:uuid)\''); + $this->addSql('CREATE TABLE chill_main_saved_export_usergroups (savedexport_id UUID NOT NULL, usergroup_id INT NOT NULL, PRIMARY KEY(savedexport_id, usergroup_id))'); + $this->addSql('CREATE INDEX IDX_A12F30824ECEDCA ON chill_main_saved_export_usergroups (savedexport_id)'); + $this->addSql('CREATE INDEX IDX_A12F308D2112630 ON chill_main_saved_export_usergroups (usergroup_id)'); + $this->addSql('COMMENT ON COLUMN chill_main_saved_export_usergroups.savedexport_id IS \'(DC2Type:uuid)\''); + $this->addSql('ALTER TABLE chill_main_saved_export_users ADD CONSTRAINT FK_4A2B71EC24ECEDCA FOREIGN KEY (savedexport_id) REFERENCES chill_main_saved_export (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_saved_export_users ADD CONSTRAINT FK_4A2B71ECA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_saved_export_usergroups ADD CONSTRAINT FK_A12F30824ECEDCA FOREIGN KEY (savedexport_id) REFERENCES chill_main_saved_export (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_saved_export_usergroups ADD CONSTRAINT FK_A12F308D2112630 FOREIGN KEY (usergroup_id) REFERENCES chill_main_user_group (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE chill_main_saved_export_users DROP CONSTRAINT FK_4A2B71EC24ECEDCA'); + $this->addSql('ALTER TABLE chill_main_saved_export_users DROP CONSTRAINT FK_4A2B71ECA76ED395'); + $this->addSql('ALTER TABLE chill_main_saved_export_usergroups DROP CONSTRAINT FK_A12F30824ECEDCA'); + $this->addSql('ALTER TABLE chill_main_saved_export_usergroups DROP CONSTRAINT FK_A12F308D2112630'); + $this->addSql('DROP TABLE chill_main_saved_export_users'); + $this->addSql('DROP TABLE chill_main_saved_export_usergroups'); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250417135712.php b/src/Bundle/ChillMainBundle/migrations/Version20250417135712.php new file mode 100644 index 000000000..2ba6e2d82 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250417135712.php @@ -0,0 +1,67 @@ +addSql( + <<<'SQL' + CREATE TEMPORARY TABLE to_create AS ( + SELECT DISTINCT permissionsgroup_rolescope.permissionsgroup_id, 'CHILL_MAIN_COMPOSE_EXPORT' AS role + FROM permissionsgroup_rolescope + JOIN public.role_scopes rs on permissionsgroup_rolescope.rolescope_id = rs.id + WHERE role LIKE '%STATS%' or role LIKE '%LIST%' + ) + SQL + ); + + $this->addSql( + <<<'SQL' + INSERT INTO role_scopes(id, scope_id, role) + SELECT nextval('role_scopes_id_seq'), null, 'CHILL_MAIN_COMPOSE_EXPORT' + WHERE NOT EXISTS (SELECT 1 FROM role_scopes s WHERE role like 'CHILL_MAIN_COMPOSE_EXPORT') + SQL + ); + + $this->addSql('ALTER TABLE to_create ADD COLUMN rolescope_id INT'); + + $this->addSql( + <<<'SQL' + UPDATE to_create SET rolescope_id = ( + SELECT id FROM role_scopes + WHERE to_create.role = role_scopes.role) + SQL + ); + + $this->addSql( + <<<'SQL' + INSERT INTO permissionsgroup_rolescope (permissionsgroup_id, rolescope_id) + SELECT to_create.permissionsgroup_id, to_create.rolescope_id FROM to_create + SQL + ); + } + + public function down(Schema $schema): void + { + $this->throwIrreversibleMigrationException(); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250425093948.php b/src/Bundle/ChillMainBundle/migrations/Version20250425093948.php new file mode 100644 index 000000000..ebd0f89b3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250425093948.php @@ -0,0 +1,49 @@ +addSql(<<<'SQL' + ALTER TABLE chill_main_user_group ADD userJob_id INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_user_group ADD CONSTRAINT FK_6576E74D64B65C5B FOREIGN KEY (userJob_id) REFERENCES chill_main_user_job (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_6576E74D64B65C5B ON chill_main_user_group (userJob_id) + SQL); + } + + public function down(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_user_group DROP CONSTRAINT FK_6576E74D64B65C5B + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_6576E74D64B65C5B + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_user_group DROP userJob_id + SQL); + } +} diff --git a/src/Bundle/ChillMainBundle/migrations/Version20250617141354.php b/src/Bundle/ChillMainBundle/migrations/Version20250617141354.php new file mode 100644 index 000000000..69c2076b3 --- /dev/null +++ b/src/Bundle/ChillMainBundle/migrations/Version20250617141354.php @@ -0,0 +1,43 @@ +addSql(<<<'SQL' + ALTER TABLE chill_main_export_generation DROP CONSTRAINT FK_E644B77DA61D6F69 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_export_generation ADD CONSTRAINT FK_E644B77DA61D6F69 FOREIGN KEY (savedExport_id) REFERENCES chill_main_saved_export (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + } + + public function down(Schema $schema): void + { + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_export_generation DROP CONSTRAINT fk_e644b77da61d6f69 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE chill_main_export_generation ADD CONSTRAINT fk_e644b77da61d6f69 FOREIGN KEY (savedexport_id) REFERENCES chill_main_saved_export (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + } +} diff --git a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml index e86484114..2982d94db 100644 --- a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml @@ -15,6 +15,7 @@ user_group: } user_removed: L'utilisateur {user} est enlevé du groupe {user_group} avec succès user_added: L'utilisateur {user} est ajouté groupe {user_group} avec succès + label_related_to_user_job: Groupe {job} (Groupe métier) notification: My notifications with counter: >- diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index c883cb91c..d3498cba9 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -15,7 +15,7 @@ Login to %installation_name%: Connexion à %installation_name% Enabled: Activé enabled: activé disabled: désactivé -Disabled: Désacdtivé +Disabled: Désactivé Id: identifiant Homepage: Accueil Welcome: Bienvenue @@ -49,6 +49,7 @@ Name: Nom Label: Nom user: + current_user: Utilisateur courant profile: title: Mon profil Phonenumber successfully updated!: Numéro de téléphone mis à jour! @@ -715,8 +716,20 @@ notification: mark_as_read: Marquer comme lu mark_as_unread: Marquer comme non-lu +CHILL_MAIN_COMPOSE_EXPORT: Exécuter des exports et les sauvegarder +CHILL_MAIN_GENERATE_SAVED_EXPORT: Exécuter et modifier des exports préalablement sauvegardés export: + role: + export_role: Exports + generation: + Export generation is pending: La génération de l'export est en cours + Export generation is pending_short: En cours + Come back later: Retour à l'index + Too many retries: Le nombre de vérification de la disponibilité de l'export a échoué. Essayez de recharger la page. + Error while generating export: Erreur interne lors de la génération de l'export + Error_short: En erreur + Export ready: L'export est prêt à être téléchargé address_helper: id: Identifiant de l'adresse street: Voie @@ -781,14 +794,23 @@ saved_export: Edit: Modifier un export enregistré Delete saved ?: Supprimer un export enregistré ? Are you sure you want to delete this saved ?: Êtes-vous sûr·e de vouloir supprimer cet export ? - My saved exports: Mes exports enregistrés + Saved exports: Exports enregistrés Export is deleted: L'export est supprimé Saved export is saved!: L'export est enregistré Created on %date%: Créé le %date% update_title_and_description: Modifier le titre et la description update_filters_aggregators_and_execute: Modifier les filtres et regroupements et télécharger - execute: Télécharger + execute: Générer Update existing: Mettre à jour le rapport enregistré existant + Owner: Propriétaire + Shared with others: Partagé + Save to new saved export: Créer un nouvel export enregistré + Update current saved export: Modifier la configuration de l'export existant + Duplicate: Dupliquer + Duplicated: Dupliqué + Options updated successfully: La configuration de l'export a été mise à jour + Share: Partage + Alert auto generated description: La description ci-dessous a été générée automatiquement, comme si l'export était exécutée immédiatement. Veillez à l'adapter pour tenir compte des paramètres qui peuvent être modifiés (utilisateurs courant, dates glissantes, etc.). absence: # single letter for absence diff --git a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php index 084fe8ff5..de1eac37d 100644 --- a/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php +++ b/src/Bundle/ChillPersonBundle/AccompanyingPeriod/Events/UserRefEventSubscriber.php @@ -25,7 +25,7 @@ class UserRefEventSubscriber implements EventSubscriberInterface { public function __construct(private readonly Security $security, private readonly TranslatorInterface $translator, private readonly \Twig\Environment $engine, private readonly NotificationPersisterInterface $notificationPersister) {} - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ 'workflow.accompanying_period_lifecycle.entered' => [ diff --git a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php index 40393f79e..ffff789c2 100644 --- a/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php +++ b/src/Bundle/ChillPersonBundle/DependencyInjection/ChillPersonExtension.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\PersonBundle\DependencyInjection; use Chill\MainBundle\DependencyInjection\MissingBundleException; -use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Chill\PersonBundle\Controller\AccompanyingPeriodCommentApiController; use Chill\PersonBundle\Controller\AccompanyingPeriodResourceApiController; use Chill\PersonBundle\Controller\AdministrativeStatusController; @@ -1027,8 +1026,6 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac 'role_hierarchy' => [ PersonVoter::UPDATE => [PersonVoter::SEE], PersonVoter::CREATE => [PersonVoter::SEE], - PersonVoter::LISTS => [ChillExportVoter::EXPORT], - PersonVoter::STATS => [ChillExportVoter::EXPORT], // accompanying period AccompanyingPeriodVoter::SEE_DETAILS => [AccompanyingPeriodVoter::SEE], AccompanyingPeriodVoter::CREATE => [AccompanyingPeriodVoter::SEE_DETAILS], diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/AdministrativeLocationAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/AdministrativeLocationAggregator.php index 522b2ecdd..859e9868b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/AdministrativeLocationAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/AdministrativeLocationAggregator.php @@ -27,7 +27,7 @@ class AdministrativeLocationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acploc', $qb->getAllAliases(), true)) { $qb->leftJoin('acp.administrativeLocation', 'acploc'); @@ -42,17 +42,32 @@ class AdministrativeLocationAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -74,7 +89,7 @@ class AdministrativeLocationAggregator implements AggregatorInterface return ['location_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group by administrative location'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ByActionNumberAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ByActionNumberAggregator.php index 8f4797302..ae0a09930 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ByActionNumberAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ByActionNumberAggregator.php @@ -24,7 +24,7 @@ class ByActionNumberAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('(SELECT COUNT(acp_by_action_action.id) FROM '.AccompanyingPeriodWork::class.' acp_by_action_action WHERE acp_by_action_action.accompanyingPeriod = acp) AS acp_by_action_number_aggregator') ->addGroupBy('acp_by_action_number_aggregator'); @@ -40,12 +40,27 @@ class ByActionNumberAggregator implements AggregatorInterface // 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) + public function getLabels($key, array $values, $data): callable { return static function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php index ab6bb6170..27f0a3351 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingDateAggregator.php @@ -39,6 +39,21 @@ final readonly class ClosingDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return [ @@ -46,7 +61,7 @@ final readonly class ClosingDateAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (?string $value): string { if ('_header' === $value) { @@ -57,12 +72,12 @@ final readonly class ClosingDateAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_closing_date']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course.by_closing_date.title'; } @@ -72,7 +87,7 @@ final readonly class ClosingDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -81,7 +96,7 @@ final readonly class ClosingDateAggregator implements AggregatorInterface $qb->addOrderBy("{$p}_closing_date", 'DESC'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingMotiveAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingMotiveAggregator.php index 259d5fb66..f8ded2b9c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingMotiveAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ClosingMotiveAggregator.php @@ -27,7 +27,7 @@ class ClosingMotiveAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(acp.closingMotive) AS closingmotive_aggregator'); $qb->addGroupBy('closingmotive_aggregator'); @@ -38,17 +38,32 @@ class ClosingMotiveAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -67,7 +82,7 @@ class ClosingMotiveAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['closingmotive_aggregator']; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ConfidentialAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ConfidentialAggregator.php index 0e3e5f735..dfd125824 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ConfidentialAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ConfidentialAggregator.php @@ -26,7 +26,7 @@ class ConfidentialAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('acp.confidential AS confidential_aggregator'); $qb->addGroupBy('confidential_aggregator'); @@ -37,17 +37,32 @@ class ConfidentialAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/CreatorJobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/CreatorJobAggregator.php index 94202d958..236504735 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/CreatorJobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/CreatorJobAggregator.php @@ -34,7 +34,7 @@ class CreatorJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -78,14 +78,29 @@ class CreatorJobAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php index f5dc99115..83148ec0b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/DurationAggregator.php @@ -33,7 +33,7 @@ final readonly class DurationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { match ($data['precision']) { 'day' => $qb->addSelect('(COALESCE(acp.closingDate, :now) - acp.openingDate) AS duration_aggregator'), @@ -54,7 +54,7 @@ final readonly class DurationAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('precision', ChoiceType::class, [ 'choices' => array_combine(self::CHOICES, self::CHOICES), @@ -65,12 +65,27 @@ final readonly class DurationAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['precision' => $formData['precision']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['precision' => $formData['precision']]; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value) use ($data) { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EmergencyAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EmergencyAggregator.php index 0217166d2..557ae37df 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EmergencyAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EmergencyAggregator.php @@ -26,7 +26,7 @@ class EmergencyAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('acp.emergency AS emergency_aggregator'); $qb->addGroupBy('emergency_aggregator'); @@ -37,17 +37,32 @@ class EmergencyAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EvaluationAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EvaluationAggregator.php index a90896ccd..c21e711a9 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EvaluationAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/EvaluationAggregator.php @@ -27,7 +27,7 @@ final readonly class EvaluationAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpw', $qb->getAllAliases(), true)) { $qb->leftJoin('acp.works', 'acpw'); @@ -46,17 +46,32 @@ final readonly class EvaluationAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php index fde825a88..3bfc6d740 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/GeographicalUnitStatAggregator.php @@ -28,14 +28,20 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class GeographicalUnitStatAggregator implements AggregatorInterface { - public function __construct(private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, private TranslatableStringHelperInterface $translatableStringHelper, private RollingDateConverterInterface $rollingDateConverter) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct( + private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, + private TranslatableStringHelperInterface $translatableStringHelper, + private RollingDateConverterInterface $rollingDateConverter, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->leftJoin('acp.locationHistories', 'acp_geog_agg_location_history'); @@ -105,7 +111,7 @@ final readonly class GeographicalUnitStatAggregator implements AggregatorInterfa return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -123,12 +129,27 @@ final readonly class GeographicalUnitStatAggregator implements AggregatorInterfa ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize(), 'level' => $this->normalizeDoctrineEntity($formData['level'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'level' => $this->denormalizeDoctrineEntity($formData['level'], $this->geographicalUnitLayerRepository)]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return match ($key) { 'acp_geog_agg_unitname' => static function ($value): string { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/IntensityAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/IntensityAggregator.php index de42039c1..4736a8394 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/IntensityAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/IntensityAggregator.php @@ -26,7 +26,7 @@ class IntensityAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('acp.intensity AS intensity_aggregator'); $qb->addGroupBy('intensity_aggregator'); @@ -37,17 +37,32 @@ class IntensityAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobWorkingOnCourseAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobWorkingOnCourseAggregator.php index bac34096f..c153d5036 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobWorkingOnCourseAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobWorkingOnCourseAggregator.php @@ -35,7 +35,7 @@ final readonly class JobWorkingOnCourseAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -72,14 +72,29 @@ final readonly class JobWorkingOnCourseAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $jobId) { if (null === $jobId || '' === $jobId) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php index d0d121c2a..8ed8f9b89 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OpeningDateAggregator.php @@ -39,6 +39,21 @@ final readonly class OpeningDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return [ @@ -46,7 +61,7 @@ final readonly class OpeningDateAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (?string $value): string { if ('_header' === $value) { @@ -57,12 +72,12 @@ final readonly class OpeningDateAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_opening_date']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course.by_opening_date.title'; } @@ -72,7 +87,7 @@ final readonly class OpeningDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -81,7 +96,7 @@ final readonly class OpeningDateAggregator implements AggregatorInterface $qb->addOrderBy("{$p}_opening_date", 'DESC'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OriginAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OriginAggregator.php index 070122f2e..1198cd2d8 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OriginAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/OriginAggregator.php @@ -36,7 +36,7 @@ final readonly class OriginAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acporigin', $qb->getAllAliases(), true)) { $qb->leftJoin('acp.origin', 'acporigin'); @@ -51,17 +51,32 @@ final readonly class OriginAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php index 3859eded5..1e719a176 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/PersonParticipatingAggregator.php @@ -25,17 +25,32 @@ final readonly class PersonParticipatingAggregator implements AggregatorInterfac private LabelPersonHelper $labelPersonHelper, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to do here } + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return match ($key) { self::KEY => $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.course.by-user.header'), @@ -43,12 +58,12 @@ final readonly class PersonParticipatingAggregator implements AggregatorInterfac }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course.by-user.title'; } @@ -58,7 +73,7 @@ final readonly class PersonParticipatingAggregator implements AggregatorInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $k = self::KEY; @@ -70,7 +85,7 @@ final readonly class PersonParticipatingAggregator implements AggregatorInterfac ->addGroupBy($k); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php index 2df724517..e94869747 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerAggregator.php @@ -39,7 +39,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface, DataTran return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('IDENTITY('.self::A.'.user) AS referrer_aggregator') @@ -66,7 +66,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface, DataTran return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -79,6 +79,26 @@ final readonly class ReferrerAggregator implements AggregatorInterface, DataTran ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $defaultData = $this->getFormDefaultData(); + + return [ + 'start_date' => array_key_exists('start_date', $formData) ? RollingDate::fromNormalized($formData['start_date']) : $defaultData['start_date'], + 'end_date' => array_key_exists('end_date', $formData) ? RollingDate::fromNormalized($formData['end_date']) : $defaultData['end_date'], + ]; + } + public function getFormDefaultData(): array { return [ @@ -102,7 +122,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface, DataTran return $data; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregator.php index 30ac95027..e2420cc63 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregator.php @@ -39,7 +39,7 @@ readonly class ReferrerScopeAggregator implements AggregatorInterface, DataTrans return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -76,7 +76,7 @@ readonly class ReferrerScopeAggregator implements AggregatorInterface, DataTrans return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -89,6 +89,26 @@ readonly class ReferrerScopeAggregator implements AggregatorInterface, DataTrans ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $default = $this->getFormDefaultData(); + + return [ + 'start_date' => array_key_exists('start_date', $formData) ? RollingDate::fromNormalized($formData['start_date']) : $default['start_date'], + 'end_date' => array_key_exists('end_date', $formData) ? RollingDate::fromNormalized($formData['end_date']) : $default['end_date'], + ]; + } + public function getFormDefaultData(): array { return [ @@ -108,7 +128,7 @@ readonly class ReferrerScopeAggregator implements AggregatorInterface, DataTrans return $data; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/RequestorAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/RequestorAggregator.php index ec168ceaf..c71e3e525 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/RequestorAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/RequestorAggregator.php @@ -26,7 +26,7 @@ final readonly class RequestorAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acppart', $qb->getAllAliases(), true)) { $qb->join('acp.participations', 'acppart'); @@ -58,17 +58,32 @@ final readonly class RequestorAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeAggregator.php index 06dbc906d..d28399acd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeAggregator.php @@ -27,7 +27,7 @@ final readonly class ScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpscope', $qb->getAllAliases(), true)) { $qb->leftJoin('acp.scopes', 'acpscope'); @@ -42,17 +42,32 @@ final readonly class ScopeAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeWorkingOnCourseAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeWorkingOnCourseAggregator.php index dd33603b4..3ca9be3c3 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeWorkingOnCourseAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/ScopeWorkingOnCourseAggregator.php @@ -35,7 +35,7 @@ final readonly class ScopeWorkingOnCourseAggregator implements AggregatorInterfa return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -72,14 +72,29 @@ final readonly class ScopeWorkingOnCourseAggregator implements AggregatorInterfa return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $scopeId) { if (null === $scopeId || '' === $scopeId) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialActionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialActionAggregator.php index 7abad2602..a67895b0c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialActionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialActionAggregator.php @@ -27,7 +27,7 @@ final readonly class SocialActionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpw', $qb->getAllAliases(), true)) { // here, we will only see accompanying period linked with a socialAction @@ -43,17 +43,32 @@ final readonly class SocialActionAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialIssueAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialIssueAggregator.php index 8c0cbfbd5..b42dbebc5 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialIssueAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/SocialIssueAggregator.php @@ -27,7 +27,7 @@ final readonly class SocialIssueAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpsocialissue', $qb->getAllAliases(), true)) { // we will see accompanying period linked with social issues @@ -43,17 +43,32 @@ final readonly class SocialIssueAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php index a9439a63f..3c6f2aaaa 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/StepAggregator.php @@ -34,7 +34,7 @@ final readonly class StepAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array(self::A, $qb->getAllAliases(), true)) { $qb->leftJoin('acp.stepHistories', self::A); @@ -63,17 +63,32 @@ final readonly class StepAggregator implements AggregatorInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { switch ($value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php index 903d3c9f4..d8a5fbd22 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php @@ -39,7 +39,7 @@ final readonly class UserJobAggregator implements AggregatorInterface, DataTrans return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -76,7 +76,7 @@ final readonly class UserJobAggregator implements AggregatorInterface, DataTrans return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -89,6 +89,21 @@ final readonly class UserJobAggregator implements AggregatorInterface, DataTrans ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -108,7 +123,7 @@ final readonly class UserJobAggregator implements AggregatorInterface, DataTrans return $data; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserWorkingOnCourseAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserWorkingOnCourseAggregator.php index 321801e28..3bac86567 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserWorkingOnCourseAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserWorkingOnCourseAggregator.php @@ -29,17 +29,32 @@ final readonly class UserWorkingOnCourseAggregator implements AggregatorInterfac private UserRepositoryInterface $userRepository, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + 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): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $userId) { if (null === $userId || '' === $userId) { @@ -58,12 +73,12 @@ final readonly class UserWorkingOnCourseAggregator implements AggregatorInterfac }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::COLUMN_NAME]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course.by_user_working.title'; } @@ -73,7 +88,7 @@ final readonly class UserWorkingOnCourseAggregator implements AggregatorInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!in_array('acpinfo', $qb->getAllAliases(), true)) { $qb->leftJoin( @@ -92,7 +107,7 @@ final readonly class UserWorkingOnCourseAggregator implements AggregatorInterfac $qb->addGroupBy('acpinfo_user.id'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php index 6b6a5ce4a..c17fea02d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByClosingMotiveAggregator.php @@ -27,17 +27,32 @@ final readonly class ByClosingMotiveAggregator implements AggregatorInterface private ClosingMotiveRender $closingMotiveRender, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing to add here } + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -52,14 +67,14 @@ final readonly class ByClosingMotiveAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ self::KEY, ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.step_history.by_closing_motive.title'; } @@ -69,14 +84,14 @@ final readonly class ByClosingMotiveAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('IDENTITY(acpstephistory.closingMotive) AS '.self::KEY) ->addGroupBy(self::KEY); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php index fbd80c7a5..20bdec4ec 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByDateAggregator.php @@ -26,7 +26,7 @@ final readonly class ByDateAggregator implements AggregatorInterface { private const KEY = 'acpstephistory_by_date_agg'; - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => array_combine( @@ -39,12 +39,27 @@ final readonly class ByDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => DateGroupingChoiceEnum::YEAR->value]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (?string $value): string { if ('_header' === $value) { @@ -59,12 +74,12 @@ final readonly class ByDateAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::KEY]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.step_history.by_date.title'; } @@ -74,7 +89,7 @@ final readonly class ByDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::KEY; @@ -83,7 +98,7 @@ final readonly class ByDateAggregator implements AggregatorInterface $qb->addOrderBy($p, 'DESC'); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php index b3be83da2..09924a9e0 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingPeriodStepHistoryAggregators/ByStepAggregator.php @@ -29,17 +29,32 @@ final readonly class ByStepAggregator implements AggregatorInterface private TranslatorInterface $translator, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // nothing in this form } + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (?string $step): string { if ('_header' === $step) { @@ -54,14 +69,14 @@ final readonly class ByStepAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ self::KEY, ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.step_history.by_step.title'; } @@ -71,14 +86,14 @@ final readonly class ByStepAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('acpstephistory.step AS '.self::KEY) ->addGroupBy(self::KEY); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByEndDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByEndDateAggregator.php index 07c182421..9d9139c43 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByEndDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByEndDateAggregator.php @@ -32,7 +32,7 @@ final class ByEndDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $fmt = match ($data['frequency']) { 'week' => 'YYYY-IW', @@ -51,7 +51,7 @@ final class ByEndDateAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -61,12 +61,27 @@ final class ByEndDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByMaxDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByMaxDateAggregator.php index 9193dc5a9..421632c76 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByMaxDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByMaxDateAggregator.php @@ -32,7 +32,7 @@ final class ByMaxDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $fmt = match ($data['frequency']) { 'week' => 'YYYY-IW', @@ -51,7 +51,7 @@ final class ByMaxDateAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -61,12 +61,27 @@ final class ByMaxDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByStartDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByStartDateAggregator.php index e797d5ae7..d07cd60e9 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByStartDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/ByStartDateAggregator.php @@ -32,7 +32,7 @@ final class ByStartDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $fmt = match ($data['frequency']) { 'week' => 'YYYY-IW', @@ -51,7 +51,7 @@ final class ByStartDateAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('frequency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -61,12 +61,27 @@ final class ByStartDateAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['frequency' => $formData['frequency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['frequency' => $formData['frequency']]; + } + public function getFormDefaultData(): array { return ['frequency' => self::DEFAULT_CHOICE]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php index 9ff2ad50a..d4e337581 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/EvaluationTypeAggregator.php @@ -27,7 +27,7 @@ class EvaluationTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('IDENTITY(workeval.evaluation) AS eval_evaluationtype_aggregator'); $qb->addGroupBy('eval_evaluationtype_aggregator'); @@ -38,17 +38,32 @@ class EvaluationTypeAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/HavingEndDateAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/HavingEndDateAggregator.php index 4dfddab81..1af0e6678 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/HavingEndDateAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/EvaluationAggregators/HavingEndDateAggregator.php @@ -26,7 +26,7 @@ class HavingEndDateAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect('CASE WHEN workeval.endDate IS NULL THEN true ELSE false END AS eval_enddate_aggregator') @@ -38,17 +38,32 @@ class HavingEndDateAggregator implements AggregatorInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php index e013c2f0f..f4d2cb5db 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/ChildrenNumberAggregator.php @@ -29,7 +29,7 @@ class ChildrenNumberAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('composition_children', $qb->getAllAliases(), true)) { $clause = $qb->expr()->andX( @@ -58,17 +58,32 @@ class ChildrenNumberAggregator implements AggregatorInterface return Declarations::HOUSEHOLD_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return static function (int|string|null $value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php index 3dc3a1398..88e68feb3 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/HouseholdAggregators/CompositionAggregator.php @@ -31,7 +31,7 @@ class CompositionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('composition_type', $qb->getAllAliases(), true)) { $clause = $qb->expr()->andX( @@ -60,17 +60,32 @@ class CompositionAggregator implements AggregatorInterface return Declarations::HOUSEHOLD_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AdministrativeStatusAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AdministrativeStatusAggregator.php index 8bdc74f7f..313d7bcb8 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AdministrativeStatusAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AdministrativeStatusAggregator.php @@ -27,7 +27,7 @@ final readonly class AdministrativeStatusAggregator implements AggregatorInterfa return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->leftJoin('person.administrativeStatus', 'admin_status'); $qb->addSelect('admin_status.id as administrative_status_aggregator'); @@ -35,19 +35,34 @@ final readonly class AdministrativeStatusAggregator implements AggregatorInterfa $qb->addGroupBy('administrative_status_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -64,12 +79,12 @@ final readonly class AdministrativeStatusAggregator implements AggregatorInterfa }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['administrative_status_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by administrative status'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php index 2ca286b57..dc1913f7d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/AgeAggregator.php @@ -31,31 +31,46 @@ final readonly class AgeAggregator implements AggregatorInterface, ExportElement return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->addSelect('DATE_DIFF(:date_age_calculation, person.birthdate)/365 as person_age'); $qb->setParameter('date_age_calculation', $this->rollingDateConverter->convert($data['date_age_calculation'])); $qb->addGroupBy('person_age'); } - public function applyOn() + public function applyOn(): string { return 'person'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_age_calculation', PickRollingDateType::class, [ 'label' => 'Calculate age in relation to this date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_age_calculation' => $formData['date_age_calculation']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_age_calculation' => RollingDate::fromNormalized($formData['date_age_calculation'])]; + } + public function getFormDefaultData(): array { return ['date_age_calculation' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -66,19 +81,19 @@ final readonly class AgeAggregator implements AggregatorInterface, ExportElement }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ 'person_age', ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by age'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['date_age_calculation']) { $context->buildViolation('The date should not be empty') diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/ByHouseholdCompositionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/ByHouseholdCompositionAggregator.php index af1018f6e..763c99088 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/ByHouseholdCompositionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/ByHouseholdCompositionAggregator.php @@ -34,7 +34,7 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -71,24 +71,39 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface ->addGroupBy("{$p}_select"); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_calc', PickRollingDateType::class, [ 'label' => 'export.aggregator.person.by_household_composition.Calc date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -107,12 +122,12 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_select']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_household_composition.Group course by household composition'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CenterAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CenterAggregator.php index cd48a3f51..6f4082d46 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CenterAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CenterAggregator.php @@ -29,13 +29,28 @@ final readonly class CenterAggregator implements AggregatorInterface private RollingDateConverterInterface $rollingDateConverter, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('at_date', PickRollingDateType::class, [ 'label' => 'export.aggregator.person.by_center.at_date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['at_date' => $formData['at_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['at_date' => RollingDate::fromNormalized($formData['at_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -43,7 +58,7 @@ final readonly class CenterAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, $data): \Closure + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value) { if (null === $value || '' === $value) { @@ -58,12 +73,12 @@ final readonly class CenterAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::COLUMN_NAME]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_center.title'; } @@ -73,7 +88,7 @@ final readonly class CenterAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $alias = 'pers_center_agg'; $atDate = 'pers_center_agg_at_date'; @@ -94,7 +109,7 @@ final readonly class CenterAggregator implements AggregatorInterface $qb->addGroupBy(self::COLUMN_NAME); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php index b8d204dc5..fbdcb6455 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/CountryOfBirthAggregator.php @@ -32,7 +32,7 @@ final readonly class CountryOfBirthAggregator implements AggregatorInterface, Ex return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // add a clause in select part if ('country' === $data['group_by_level']) { @@ -75,12 +75,12 @@ final readonly class CountryOfBirthAggregator implements AggregatorInterface, Ex $qb->addGroupBy('country_of_birth_aggregator'); } - public function applyOn() + public function applyOn(): string { return 'person'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('group_by_level', ChoiceType::class, [ 'choices' => [ @@ -92,12 +92,27 @@ final readonly class CountryOfBirthAggregator implements AggregatorInterface, Ex ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['group_by_level' => $formData['group_by_level']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['group_by_level' => $formData['group_by_level']]; + } + public function getFormDefaultData(): array { return []; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { $labels = []; @@ -138,17 +153,17 @@ final readonly class CountryOfBirthAggregator implements AggregatorInterface, Ex return static fn (?string $value): string => $labels[(string) $value]; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['country_of_birth_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by country of birth'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['group_by_level']) { $context->buildViolation('You should select an option') diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/EmploymentStatusAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/EmploymentStatusAggregator.php index 359e48cf3..caf174cb3 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/EmploymentStatusAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/EmploymentStatusAggregator.php @@ -27,7 +27,7 @@ final readonly class EmploymentStatusAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->leftJoin('person.employmentStatus', 'es'); $qb->addSelect('es.id as employment_status_aggregator'); @@ -35,19 +35,34 @@ final readonly class EmploymentStatusAggregator implements AggregatorInterface $qb->addGroupBy('employment_status_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -64,12 +79,12 @@ final readonly class EmploymentStatusAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['employment_status_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by employment status'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GenderAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GenderAggregator.php index f7ae26dca..4333e7f1d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GenderAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GenderAggregator.php @@ -28,7 +28,7 @@ final readonly class GenderAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb->leftJoin('person.gender', 'g'); $qb->addSelect('g.id as gender'); @@ -36,19 +36,34 @@ final readonly class GenderAggregator implements AggregatorInterface $qb->addGroupBy('gender'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function (int|string|null $value) { if (null === $value || '' === $value) { @@ -67,12 +82,12 @@ final readonly class GenderAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['gender']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by gender'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php index 8ce51a3ab..3227099b0 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/GeographicalUnitAggregator.php @@ -24,16 +24,22 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class GeographicalUnitAggregator implements AggregatorInterface +final readonly class GeographicalUnitAggregator implements AggregatorInterface { - public function __construct(private readonly GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly RollingDateConverterInterface $rollingDateConverter) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct( + private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, + private TranslatableStringHelperInterface $translatableStringHelper, + private RollingDateConverterInterface $rollingDateConverter, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->leftJoin( @@ -73,12 +79,12 @@ class GeographicalUnitAggregator implements AggregatorInterface ->addGroupBy('geog_unit_key'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -96,6 +102,21 @@ class GeographicalUnitAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize(), 'level' => $this->normalizeDoctrineEntity($formData['level'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'level' => $this->denormalizeDoctrineEntity($formData['level'], $this->geographicalUnitLayerRepository)]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; @@ -106,7 +127,7 @@ class GeographicalUnitAggregator implements AggregatorInterface return 'person_geog_agg'; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return match ($key) { 'geog_unit_name' => static function ($value): string { @@ -135,12 +156,12 @@ class GeographicalUnitAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['geog_unit_name', 'geog_unit_key']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by geographical unit based on his address'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/HouseholdPositionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/HouseholdPositionAggregator.php index eb1e52d9b..f28ccfd61 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/HouseholdPositionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/HouseholdPositionAggregator.php @@ -35,7 +35,7 @@ final readonly class HouseholdPositionAggregator implements AggregatorInterface, return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('householdmember', $qb->getAllAliases(), true)) { $qb->join(HouseholdMember::class, 'householdmember', Expr\Join::WITH, 'householdmember.person = person'); @@ -62,24 +62,39 @@ final readonly class HouseholdPositionAggregator implements AggregatorInterface, $qb->addGroupBy('household_position_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_position', PickRollingDateType::class, [ 'label' => 'Household position in relation to this date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_position' => $formData['date_position']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_position' => RollingDate::fromNormalized($formData['date_position'])]; + } + public function getFormDefaultData(): array { return ['date_position' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value) { if ('_header' === $value) { @@ -96,19 +111,19 @@ final readonly class HouseholdPositionAggregator implements AggregatorInterface, }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [ 'household_position_aggregator', ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Aggregate by household position'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['date_position']) { $context->buildViolation('The date should not be empty') diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/MaritalStatusAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/MaritalStatusAggregator.php index 1555a5a12..4a193fbd7 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/MaritalStatusAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/MaritalStatusAggregator.php @@ -27,7 +27,7 @@ final readonly class MaritalStatusAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('personmarital', $qb->getAllAliases(), true)) { $qb->join('person.maritalStatus', 'personmarital'); @@ -37,22 +37,37 @@ final readonly class MaritalStatusAggregator implements AggregatorInterface $qb->addGroupBy('marital_status_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { @@ -69,12 +84,12 @@ final readonly class MaritalStatusAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['marital_status_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by marital status'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/NationalityAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/NationalityAggregator.php index 9daca5b34..a7b11f28f 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/NationalityAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/NationalityAggregator.php @@ -31,7 +31,7 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { // add a clause in select part if ('country' === $data['group_by_level']) { @@ -70,12 +70,12 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor $qb->addGroupBy('nationality_aggregator'); } - public function applyOn() + public function applyOn(): string { return 'person'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('group_by_level', ChoiceType::class, [ 'choices' => [ @@ -87,6 +87,21 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['group_by_level' => $formData['group_by_level']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['group_by_level' => $formData['group_by_level']]; + } + public function getFormDefaultData(): array { return [ @@ -94,7 +109,7 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor ]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { $labels = []; @@ -135,17 +150,17 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor return static fn ($value): string => $labels[$value]; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['nationality_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group people by nationality'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['group_by_level']) { $context->buildViolation('You should select an option') diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/PostalCodeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/PostalCodeAggregator.php index a8ec614d4..7a0240e9c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/PostalCodeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/PersonAggregators/PostalCodeAggregator.php @@ -28,7 +28,7 @@ final readonly class PostalCodeAggregator implements AggregatorInterface private RollingDateConverterInterface $rollingDateConverter, ) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('calc_date', PickRollingDateType::class, [ @@ -36,12 +36,27 @@ final readonly class PostalCodeAggregator implements AggregatorInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function getLabels($key, array $values, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function (int|string|null $value): string { if ('_header' === $value) { @@ -56,12 +71,12 @@ final readonly class PostalCodeAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_postal_code_code', self::PREFIX.'_postal_code_label']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.person.by_postal_code.title'; } @@ -71,7 +86,7 @@ final readonly class PostalCodeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -92,7 +107,7 @@ final readonly class PostalCodeAggregator implements AggregatorInterface ; } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ActionTypeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ActionTypeAggregator.php index 9abf5c1e7..a667a8a43 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ActionTypeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ActionTypeAggregator.php @@ -29,7 +29,7 @@ final readonly class ActionTypeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpwsocialaction', $qb->getAllAliases(), true)) { $qb->leftJoin('acpw.socialAction', 'acpwsocialaction'); @@ -46,22 +46,37 @@ final readonly class ActionTypeAggregator implements AggregatorInterface ->addGroupBy('social_action_type_aggregator'); } - public function applyOn() + public function applyOn(): string { return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return match ($key) { 'action_type_aggregator' => function ($value): string { @@ -90,12 +105,12 @@ final readonly class ActionTypeAggregator implements AggregatorInterface }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['social_action_type_aggregator', 'action_type_aggregator']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Group social work actions by action type'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorAggregator.php index 46cc85de6..6f3faeece 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorAggregator.php @@ -32,7 +32,7 @@ class CreatorAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -46,14 +46,29 @@ class CreatorAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorJobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorJobAggregator.php index c1b7c248b..e5f08c03d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorJobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorJobAggregator.php @@ -34,7 +34,7 @@ class CreatorJobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -63,14 +63,29 @@ class CreatorJobAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorScopeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorScopeAggregator.php index b38096079..1e59ad991 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorScopeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CreatorScopeAggregator.php @@ -34,7 +34,7 @@ class CreatorScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -63,14 +63,29 @@ class CreatorScopeAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CurrentActionAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CurrentActionAggregator.php index a9f8e020a..b6c74c2c4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CurrentActionAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/CurrentActionAggregator.php @@ -26,7 +26,7 @@ class CurrentActionAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $qb ->addSelect(' @@ -46,12 +46,27 @@ class CurrentActionAggregator implements AggregatorInterface // 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalAggregator.php index ce1e381f2..d5be32ca8 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalAggregator.php @@ -27,7 +27,7 @@ final readonly class GoalAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('goal', $qb->getAllAliases(), true)) { $qb->leftJoin('acpw.goals', 'goal'); @@ -42,17 +42,32 @@ final readonly class GoalAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalResultAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalResultAggregator.php index e1549f315..befdf8904 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalResultAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/GoalResultAggregator.php @@ -28,7 +28,7 @@ class GoalResultAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('goal', $qb->getAllAliases(), true)) { $qb->leftJoin('acpw.goals', 'goal'); @@ -48,17 +48,32 @@ class GoalResultAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value) use ($key): string { if (null === $value || '' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php index f58246a25..d5f335184 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/HandlingThirdPartyAggregator.php @@ -27,27 +27,42 @@ final readonly class HandlingThirdPartyAggregator implements AggregatorInterface public function __construct(private LabelThirdPartyHelper $labelThirdPartyHelper) {} - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form needed here } + 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, mixed $data) + public function getLabels($key, array $values, mixed $data): callable { return $this->labelThirdPartyHelper->getLabel($key, $values, 'export.aggregator.course_work.by_handling_third_party.header'); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return [self::PREFIX.'_h3party']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.aggregator.course_work.by_handling_third_party.title'; } @@ -57,7 +72,7 @@ final readonly class HandlingThirdPartyAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -66,7 +81,7 @@ final readonly class HandlingThirdPartyAggregator implements AggregatorInterface ->addGroupBy("{$p}_h3party"); } - public function applyOn() + public function applyOn(): string { return Declarations::SOCIAL_WORK_ACTION_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/JobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/JobAggregator.php index 282a36db4..c795e0193 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/JobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/JobAggregator.php @@ -35,7 +35,7 @@ final readonly class JobAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -58,14 +58,29 @@ final readonly class JobAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ReferrerAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ReferrerAggregator.php index 7543da857..c2ccbed8a 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ReferrerAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ReferrerAggregator.php @@ -36,7 +36,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -56,13 +56,36 @@ final readonly class ReferrerAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('referrer_at', PickRollingDateType::class, [ 'label' => 'export.aggregator.course_work.by_treating_agent.Calc date', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + if (!array_key_exists('referrer_at', $formData)) { + return ['referrer_at' => (new RollingDate(RollingDate::T_TODAY))->normalize()]; + } + + return ['referrer_at' => $formData['referrer_at']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + if (!array_key_exists('referrer_at', $formData)) { + return ['referrer_at' => new RollingDate(RollingDate::T_TODAY)]; + } + + return ['referrer_at' => RollingDate::fromNormalized($formData['referrer_at'])]; + } + public function getFormDefaultData(): array { return [ @@ -70,7 +93,7 @@ final readonly class ReferrerAggregator implements AggregatorInterface ]; } - public function getLabels($key, array $values, $data) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ResultAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ResultAggregator.php index 63a037f21..df856cc56 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ResultAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ResultAggregator.php @@ -27,7 +27,7 @@ final readonly class ResultAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { if (!\in_array('result', $qb->getAllAliases(), true)) { $qb->leftJoin('acpw.results', 'result'); @@ -42,17 +42,32 @@ final readonly class ResultAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { // no form } + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ScopeAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ScopeAggregator.php index 0bedaf267..9c1e37469 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ScopeAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/SocialWorkAggregators/ScopeAggregator.php @@ -35,7 +35,7 @@ final readonly class ScopeAggregator implements AggregatorInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -58,14 +58,29 @@ final readonly class ScopeAggregator implements AggregatorInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) {} + public function buildForm(FormBuilderInterface $builder): void {} + + 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) + public function getLabels($key, array $values, $data): callable { return function ($value): string { if ('_header' === $value) { diff --git a/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod.php index 18a0c54b0..e7f93a062 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod.php @@ -36,6 +36,21 @@ class AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod implements ExportInt public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -70,7 +85,7 @@ class AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod implements ExportInt return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -85,7 +100,7 @@ class AvgDurationAPWorkPersonAssociatedOnAccompanyingPeriod implements ExportInt return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnWork.php b/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnWork.php index a8750e42c..3448e93bc 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnWork.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/AvgDurationAPWorkPersonAssociatedOnWork.php @@ -36,6 +36,21 @@ class AvgDurationAPWorkPersonAssociatedOnWork implements ExportInterface, Groupe public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -70,7 +85,7 @@ class AvgDurationAPWorkPersonAssociatedOnWork implements ExportInterface, Groupe return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -85,7 +100,7 @@ class AvgDurationAPWorkPersonAssociatedOnWork implements ExportInterface, Groupe return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php index 1118b2cc4..73055c6a2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourse.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; @@ -25,6 +26,7 @@ use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Translation\TranslatableMessage; class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface { @@ -44,11 +46,26 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface // Nothing to add here } + public function getNormalizationVersion(): int + { + return 1; + } + public function getFormDefaultData(): array { return []; } + public function normalizeFormData(array $formData): array + { + return $formData; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return $formData; + } + public function getAllowedFormattersTypes(): array { return [FormatterInterface::TYPE_TABULAR]; @@ -81,14 +98,14 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle(): string + public function getTitle(): TranslatableMessage { - return 'Count accompanying courses'; + return new TranslatableMessage('Count accompanying courses'); } public function getType(): string @@ -96,7 +113,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface return Declarations::ACP_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php index 4f620fa34..5fcc8a6f7 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingCourseStepHistory.php @@ -44,6 +44,21 @@ class CountAccompanyingCourseStepHistory implements ExportInterface, GroupedExpo // Nothing to add here } + 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 []; @@ -78,7 +93,7 @@ class CountAccompanyingCourseStepHistory implements ExportInterface, GroupedExpo return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ class CountAccompanyingCourseStepHistory implements ExportInterface, GroupedExpo return Declarations::ACP_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php index a73e4f037..922101f95 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php @@ -41,6 +41,21 @@ class CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements // No form necessary? } + 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 []; @@ -78,7 +93,7 @@ class CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ class CountAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod implements return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnWork.php b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnWork.php index 4f43a78b0..ea7f1c805 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnWork.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountAccompanyingPeriodWorkAssociatePersonOnWork.php @@ -41,6 +41,21 @@ class CountAccompanyingPeriodWorkAssociatePersonOnWork implements ExportInterfac // No form necessary? } + 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 []; @@ -78,7 +93,7 @@ class CountAccompanyingPeriodWorkAssociatePersonOnWork implements ExportInterfac return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ class CountAccompanyingPeriodWorkAssociatePersonOnWork implements ExportInterfac return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php b/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php index 0cbe7b3e8..acf035424 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountEvaluation.php @@ -37,6 +37,21 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface public function buildForm(FormBuilderInterface $builder) {} + 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 []; @@ -74,7 +89,7 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -89,7 +104,7 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface return Declarations::EVAL_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountHouseholdInPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/CountHouseholdInPeriod.php index a65dcb217..e7e2d20da 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountHouseholdInPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountHouseholdInPeriod.php @@ -49,6 +49,21 @@ class CountHouseholdInPeriod implements ExportInterface, GroupedExportInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -93,7 +108,7 @@ class CountHouseholdInPeriod implements ExportInterface, GroupedExportInterface return ['household_export_result', 'acp_export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -108,7 +123,7 @@ class CountHouseholdInPeriod implements ExportInterface, GroupedExportInterface return Declarations::HOUSEHOLD_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php b/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php index a1e855b0e..9a620148a 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountPerson.php @@ -39,6 +39,21 @@ class CountPerson implements ExportInterface, GroupedExportInterface // No form necessary } + 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 []; @@ -71,32 +86,30 @@ class CountPerson implements ExportInterface, GroupedExportInterface return static fn ($value) => $labels[$value]; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Count people'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } /** * Initiate the query. - * - * @return QueryBuilder */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork.php b/src/Bundle/ChillPersonBundle/Export/Export/CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork.php index 13dd0d512..b3d08a4d8 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork.php @@ -41,6 +41,21 @@ class CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork implements Export // No form necessary? } + 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 []; @@ -78,7 +93,7 @@ class CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork implements Export return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -93,7 +108,7 @@ class CountPersonOnAccompanyingPeriodWorkAssociatePersonOnWork implements Export return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/CountPersonWithAccompanyingCourse.php b/src/Bundle/ChillPersonBundle/Export/Export/CountPersonWithAccompanyingCourse.php index ef000603a..51dcfdb90 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/CountPersonWithAccompanyingCourse.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/CountPersonWithAccompanyingCourse.php @@ -43,6 +43,21 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor // TODO: Implement buildForm() method. } + 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 []; @@ -80,7 +95,7 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor return ['export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -95,7 +110,7 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor return Declarations::HOUSEHOLD_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php index 7d0e52169..8ac6ffeef 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriod.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\ListInterface; @@ -46,6 +47,21 @@ final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExp ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -73,27 +89,27 @@ final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExp return $this->listAccompanyingPeriodHelper->getLabels($key, $values, $data); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return $this->listAccompanyingPeriodHelper->getQueryKeys($data); } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.list.acp.List of accompanying periods'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $qb = $this->entityManager->createQueryBuilder(); @@ -102,7 +118,7 @@ final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExp ->andWhere('acp.step != :list_acp_step') ->setParameter('list_acp_step', AccompanyingPeriod::STEP_DRAFT); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); $this->listAccompanyingPeriodHelper->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date'])); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php index 8f63b1584..f93e030f7 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeriod.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\Helper\AggregateStringHelper; @@ -103,6 +104,21 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeri ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -177,12 +193,12 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeri }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return self::FIELDS; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -197,7 +213,7 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeri return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); $calcDate = $data['calc_date'] ?? new RollingDate(RollingDate::T_TODAY); @@ -217,7 +233,7 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnAccompanyingPeri ->andWhere('acppart.startDate <= :calc_date AND (acppart.endDate > :calc_date OR acppart.endDate IS NULL)') ->setParameter('calc_date', $this->rollingDateConverter->convert($calcDate)); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnWork.php b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnWork.php index fe200ca02..13822697d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnWork.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListAccompanyingPeriodWorkAssociatePersonOnWork.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\Helper\AggregateStringHelper; @@ -103,6 +104,21 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnWork implements ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -177,12 +193,12 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnWork implements }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return self::FIELDS; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -197,7 +213,7 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnWork implements return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); $calcDate = $data['calc_date'] ?? new RollingDate(RollingDate::T_TODAY); @@ -212,7 +228,7 @@ final readonly class ListAccompanyingPeriodWorkAssociatePersonOnWork implements ->join('acpw.persons', 'person') ; - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php b/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php index 71d9924be..201e5a234 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListEvaluation.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\Helper\AggregateStringHelper; @@ -93,6 +94,21 @@ final readonly class ListEvaluation implements ListInterface, GroupedExportInter ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -165,12 +181,12 @@ final readonly class ListEvaluation implements ListInterface, GroupedExportInter }; } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return self::FIELDS; } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -185,7 +201,7 @@ final readonly class ListEvaluation implements ListInterface, GroupedExportInter return Declarations::EVAL_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); $calcDate = $data['calc_date'] ?? new RollingDate(RollingDate::T_TODAY); @@ -206,7 +222,7 @@ final readonly class ListEvaluation implements ListInterface, GroupedExportInter ->andWhere('acppart.startDate <= :calc_date AND (acppart.endDate > :calc_date OR acppart.endDate IS NULL)') ->setParameter('calc_date', $this->rollingDateConverter->convert($calcDate)); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php index b69330002..b455d3f65 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListHouseholdInPeriod.php @@ -67,6 +67,21 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -114,7 +129,7 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface ); } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } @@ -129,7 +144,7 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface return Declarations::HOUSEHOLD_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php index 2b8bfad20..55ced4cc2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPerson.php @@ -33,6 +33,7 @@ use Symfony\Component\Form\FormBuilderInterface; */ class ListPerson implements ListInterface, GroupedExportInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private array $slugs = []; private readonly bool $filterStatsByCenters; @@ -56,6 +57,21 @@ class ListPerson implements ListInterface, GroupedExportInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['address_date' => $this->normalizeDate($formData['address_date'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['address_date' => $this->denormalizeDate($formData['address_date'])]; + } + public function getFormDefaultData(): array { return ['address_date' => new \DateTimeImmutable()]; @@ -85,7 +101,7 @@ class ListPerson implements ListInterface, GroupedExportInterface return $this->getLabelForCustomField($key, $values, $data); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { $fields = []; @@ -97,17 +113,17 @@ class ListPerson implements ListInterface, GroupedExportInterface return [...$fields, ...\array_keys($this->slugs)]; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'List peoples'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } @@ -115,7 +131,7 @@ class ListPerson implements ListInterface, GroupedExportInterface /** * param array{fields: string[], address_date: DateTimeImmutable} $data. */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php index 69d00a612..c4b178111 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonDuplicate.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\DirectExportInterface; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\FormattedExportGeneration; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\ORM\EntityManagerInterface; @@ -20,9 +21,6 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Xlsx; use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\HttpFoundation\BinaryFileResponse; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\ResponseHeaderBag; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -65,12 +63,27 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['precision' => $formData['precision']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['precision' => $formData['precision']]; + } + public function getFormDefaultData(): array { return ['precision' => self::PRECISION_DEFAULT_VALUE]; } - public function generate(array $acl, array $data = []): Response + public function generate(array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): FormattedExportGeneration { $values = []; $values[] = $this->getHeaders(); @@ -105,11 +118,10 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat $temp_file = sys_get_temp_dir().'/'.uniqid('export_').'.xlsx'; $writer->save($temp_file); - $response = new BinaryFileResponse($temp_file); - $response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'export_duplicate.xlsx'); + $formatted = new FormattedExportGeneration(file_get_contents($temp_file), 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + unlink($temp_file); - return $response; + return $formatted; } public function getDescription(): string @@ -125,7 +137,7 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat /** * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'List duplicates'; } @@ -135,7 +147,7 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat return PersonVoter::DUPLICATE; } - public function validateForm($data, ExecutionContextInterface $context) {} + public function validateForm($data, ExecutionContextInterface $context): void {} protected function getHeaders(): array { diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php index 4a44f2dc5..de1f495cd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonHavingAccompanyingPeriod.php @@ -56,6 +56,21 @@ final readonly class ListPersonHavingAccompanyingPeriod implements ListInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['address_date_rolling' => $formData['address_date_rolling']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['address_date_rolling' => RollingDate::fromNormalized($formData['address_date_rolling'])]; + } + public function getFormDefaultData(): array { return ['address_date_rolling' => new RollingDate(RollingDate::T_TODAY)]; @@ -81,22 +96,22 @@ final readonly class ListPersonHavingAccompanyingPeriod implements ListInterface return $this->listPersonHelper->getLabels($key, $values, $data); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return $this->listPersonHelper->getAllKeys(); } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.list.person_with_acp.List peoples having an accompanying period'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } @@ -104,7 +119,7 @@ final readonly class ListPersonHavingAccompanyingPeriod implements ListInterface /** * param array{fields: string[], address_date: DateTimeImmutable} $data. */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php index 42a2205a1..6c641914b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/ListPersonWithAccompanyingPeriodDetails.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Export; use Chill\MainBundle\Export\AccompanyingCourseExportHelper; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; use Chill\MainBundle\Export\ListInterface; @@ -51,6 +52,21 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['address_date' => $formData['address_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['address_date' => RollingDate::fromNormalized($formData['address_date'])]; + } + public function getFormDefaultData(): array { return ['address_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -80,7 +96,7 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte return $this->listAccompanyingPeriodHelper->getLabels($key, $values, $data); } - public function getQueryKeys($data) + public function getQueryKeys($data): array { return array_merge( $this->listPersonHelper->getAllKeys(), @@ -88,17 +104,17 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte ); } - public function getResult($query, $data) + public function getResult($query, $data, ExportGenerationContext $context): array { return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.list.person_with_acp.List peoples having an accompanying period with period details'; } - public function getType() + public function getType(): string { return Declarations::PERSON_TYPE; } @@ -106,7 +122,7 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte /** * param array{fields: string[], address_date: DateTimeImmutable} $data. */ - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); @@ -117,7 +133,7 @@ final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInte ->join('acppart.accompanyingPeriod', 'acp') ->andWhere($qb->expr()->neq('acp.step', "'".AccompanyingPeriod::STEP_DRAFT."'")); - $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data); + $this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $context->byUser, $data); $this->listPersonHelper->addSelect($qb, $this->rollingDateConverter->convert($data['address_date'])); $this->listAccompanyingPeriodHelper->addSelectClauses($qb, $this->rollingDateConverter->convert($data['address_date'])); diff --git a/src/Bundle/ChillPersonBundle/Export/Export/StatAccompanyingCourseDuration.php b/src/Bundle/ChillPersonBundle/Export/Export/StatAccompanyingCourseDuration.php index 58f06ab7e..5c5b5eee6 100644 --- a/src/Bundle/ChillPersonBundle/Export/Export/StatAccompanyingCourseDuration.php +++ b/src/Bundle/ChillPersonBundle/Export/Export/StatAccompanyingCourseDuration.php @@ -51,6 +51,21 @@ final readonly class StatAccompanyingCourseDuration implements ExportInterface, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['closingdate_rolling' => $formData['closingdate_rolling']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['closingdate_rolling' => RollingDate::fromNormalized($formData['closingdate_rolling'])]; + } + public function getFormDefaultData(): array { return ['closingdate_rolling' => new RollingDate(RollingDate::T_TODAY)]; @@ -97,7 +112,7 @@ final readonly class StatAccompanyingCourseDuration implements ExportInterface, return ['avg_export_result', 'count_acp_export_result', 'count_acppart_export_result', 'count_pers_export_result']; } - public function getResult($query, $data) + public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } @@ -112,7 +127,7 @@ final readonly class StatAccompanyingCourseDuration implements ExportInterface, return Declarations::ACP_TYPE; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOnDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOnDateFilter.php index 6bcb4f8b8..f0571bc32 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOnDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOnDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class ActiveOnDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -59,18 +60,33 @@ class ActiveOnDateFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by actives courses: active on %ondate%', [ '%ondate%' => $this->rollingDateConverter->convert($data['on_date'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOneDayBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOneDayBetweenDatesFilter.php index 3b7c4fe25..1a319b3ea 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOneDayBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ActiveOneDayBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,7 +29,7 @@ class ActiveOneDayBetweenDatesFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = "OVERLAPSI (acp.openingDate, acp.closingDate), (:datefrom, :dateto) = 'TRUE'"; @@ -48,19 +49,34 @@ class ActiveOneDayBetweenDatesFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, []) ->add('date_to', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by actives courses: at least one day between %datefrom% and %dateto%', [ '%datefrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php index 915a2c9e6..cd502421e 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/AdministrativeLocationFilter.php @@ -11,23 +11,27 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickUserLocationType; +use Chill\MainBundle\Repository\LocationTypeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class AdministrativeLocationFilter implements FilterInterface +final readonly class AdministrativeLocationFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private LocationTypeRepository $locationTypeRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->in('acp.administrativeLocation', ':locations'); $qb @@ -40,7 +44,7 @@ class AdministrativeLocationFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_locations', PickUserLocationType::class, [ 'label' => 'Accepted locations', @@ -48,12 +52,27 @@ class AdministrativeLocationFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_locations' => $this->normalizeDoctrineEntity($formData['accepted_locations'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_locations' => $this->denormalizeDoctrineEntity($formData['accepted_locations'], $this->locationTypeRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $locations = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ClosingMotiveFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ClosingMotiveFilter.php index 7fb032050..3813a780f 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ClosingMotiveFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ClosingMotiveFilter.php @@ -11,25 +11,29 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Repository\AccompanyingPeriod\ClosingMotiveRepositoryInterface; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class ClosingMotiveFilter implements FilterInterface +final readonly class ClosingMotiveFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private ClosingMotiveRepositoryInterface $closingMotiveRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->in('acp.closingMotive', ':closingmotive'); @@ -49,7 +53,7 @@ class ClosingMotiveFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_closingmotives', EntityType::class, [ 'class' => ClosingMotive::class, @@ -59,12 +63,27 @@ class ClosingMotiveFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_closingmotives' => $this->normalizeDoctrineEntity($formData['accepted_closingmotives'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_closingmotives' => $this->denormalizeDoctrineEntity($formData['accepted_closingmotives'], $this->closingMotiveRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $motives = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ConfidentialFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ConfidentialFilter.php index 4c8baf147..bfd454eea 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ConfidentialFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ConfidentialFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\Query\Expr\Andx; @@ -35,7 +36,7 @@ class ConfidentialFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->eq('acp.confidential', ':confidential'); @@ -55,7 +56,7 @@ class ConfidentialFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_confidentials', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -65,12 +66,27 @@ class ConfidentialFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_confidentials' => $formData['accepted_confidentials']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_confidentials' => $formData['accepted_confidentials']]; + } + public function getFormDefaultData(): array { return ['accepted_confidentials' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by confidential: only %confidential%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorFilter.php index 4fa2c2d1d..8dc10381a 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorFilter.php @@ -12,21 +12,27 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class CreatorFilter implements FilterInterface +final readonly class CreatorFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private readonly UserRepositoryInterface $userRepository) {} + public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acp_creator', $qb->getAllAliases(), true)) { $qb->join('acp.createdBy', 'acp_creator'); @@ -34,7 +40,7 @@ class CreatorFilter implements FilterInterface $qb ->andWhere($qb->expr()->in('acp_creator', ':creators')) - ->setParameter('creators', $data['accepted_creators']); + ->setParameter('creators', $this->userOrMe($data['accepted_creators'], $exportGenerationContext)); } public function applyOn(): string @@ -42,21 +48,36 @@ class CreatorFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('accepted_creators', PickUserDynamicType::class, [ + ->add('accepted_creators', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => false, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_creators' => $this->normalizeUserOrMe($formData['accepted_creators'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_creators' => $this->denormalizeUserOrMe($formData['accepted_creators'], $this->userRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by creator: only %creators%', [ @@ -64,7 +85,7 @@ class CreatorFilter implements FilterInterface ', ', array_map( static fn (User $u) => $u->getLabel(), - $data['accepted_creators'] instanceof Collection ? $data['accepted_creators']->toArray() : $data['accepted_creators'] + $data['accepted_creators'] instanceof Collection ? $this->userOrMe($data['accepted_creators']->toArray(), $context) : $this->userOrMe($data['accepted_creators'], $context) ) ), ], ]; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php index dd1264bd2..45bdfc15d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/CreatorJobFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -21,14 +22,16 @@ use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Translation\TranslatableMessage; -class CreatorJobFilter implements FilterInterface +final readonly class CreatorJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_filter_creator_job'; public function __construct( - private readonly TranslatableStringHelper $translatableStringHelper, - private readonly UserJobRepositoryInterface $userJobRepository, + private TranslatableStringHelper $translatableStringHelper, + private UserJobRepositoryInterface $userJobRepository, ) {} public function addRole(): ?string @@ -36,7 +39,7 @@ class CreatorJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -83,7 +86,7 @@ class CreatorJobFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('creator_job', EntityType::class, [ @@ -98,19 +101,34 @@ class CreatorJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['creator_job' => $this->normalizeDoctrineEntity($formData['creator_job'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['creator_job' => $this->denormalizeDoctrineEntity($formData['creator_job'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): TranslatableMessage { $creatorJobs = []; foreach ($data['creator_job'] as $j) { $creatorJobs[] = $this->translatableStringHelper->localize( - $j->getLabel() + $j->getLabel(), ); } - return ['export.filter.course.creator_job.Filtered by creator job: only %jobs%', [ + return new TranslatableMessage('export.filter.course.creator_job.Filtered by creator job: only %jobs%', [ '%jobs%' => implode(', ', $creatorJobs), - ]]; + ]); } public function getFormDefaultData(): array @@ -120,8 +138,8 @@ class CreatorJobFilter implements FilterInterface ]; } - public function getTitle(): string + public function getTitle(): TranslatableMessage { - return 'export.filter.course.creator_job.Filter by creator job'; + return new TranslatableMessage('export.filter.course.creator_job.Filter by creator job'); } } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EmergencyFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EmergencyFilter.php index 671b87407..516996f44 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EmergencyFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EmergencyFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\Query\Expr\Andx; @@ -35,7 +36,7 @@ class EmergencyFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->eq('acp.emergency', ':emergency'); @@ -55,7 +56,7 @@ class EmergencyFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_emergency', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -65,12 +66,27 @@ class EmergencyFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_emergency' => $formData['accepted_emergency']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_emergency' => $formData['accepted_emergency']]; + } + public function getFormDefaultData(): array { return ['accepted_emergency' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by emergency: only %emergency%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php index bec01c249..f50b2eedc 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/EvaluationFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\SocialWork\Evaluation; @@ -20,16 +21,18 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class EvaluationFilter implements FilterInterface +final readonly class EvaluationFilter implements FilterInterface { - public function __construct(private readonly EvaluationRepositoryInterface $evaluationRepository, private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private EvaluationRepositoryInterface $evaluationRepository, private TranslatableStringHelper $translatableStringHelper) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpw', $qb->getAllAliases(), true)) { $qb->join('acp.works', 'acpw'); @@ -53,7 +56,7 @@ class EvaluationFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_evaluations', EntityType::class, [ 'class' => Evaluation::class, @@ -65,12 +68,27 @@ class EvaluationFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_evaluations' => $this->normalizeDoctrineEntity($formData['accepted_evaluations'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_evaluations' => $this->denormalizeDoctrineEntity($formData['accepted_evaluations'], $this->evaluationRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $evaluations = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php index 7d6d90e64..48fccb882 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/GeographicalUnitStatFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\Address; use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface; @@ -45,7 +46,7 @@ class GeographicalUnitStatFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $subQueryDql = 'SELECT @@ -86,7 +87,7 @@ class GeographicalUnitStatFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -106,12 +107,38 @@ class GeographicalUnitStatFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return [ + 'date_calc' => $formData['date_calc']->normalize(), + 'units' => array_map(static fn (SimpleGeographicalUnitDTO $unitDTO) => $unitDTO->id, $formData['units']), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $units = array_filter( + array_map( + fn (int $id) => $this->geographicalUnitRepository->findSimpleGeographicalUnit($id), + $formData['units'], + ), + static fn (?SimpleGeographicalUnitDTO $unitDTO) => null !== $unitDTO, + ); + + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'units' => $units]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by geographic unit: computed at %date%, only in %units%', [ '%date%' => $this->rollingDateConverter->convert($data['date_calc'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HandlingThirdPartyFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HandlingThirdPartyFilter.php index 8a9f734aa..eb3376247 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HandlingThirdPartyFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HandlingThirdPartyFilter.php @@ -11,10 +11,12 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Form\Type\PickThirdpartyDynamicType; +use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; @@ -22,18 +24,20 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class HandlingThirdPartyFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_handling_3party_filter'; public function __construct( private ThirdPartyRender $thirdPartyRender, + private ThirdPartyRepository $thirdPartyRepository, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.work.by_handling3party.title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('handling_3parties', PickThirdpartyDynamicType::class, [ 'label' => 'export.filter.work.by_handling3party.pick_3parties', @@ -41,12 +45,27 @@ final readonly class HandlingThirdPartyFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['handling_3parties' => $this->normalizeDoctrineEntity($formData['handling_3parties'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['handling_3parties' => $this->denormalizeDoctrineEntity($formData['handling_3parties'], $this->thirdPartyRepository)]; + } + public function getFormDefaultData(): array { return ['handling_3parties' => []]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.work.by_handling3party.Only 3 parties %3parties%', @@ -67,7 +86,7 @@ final readonly class HandlingThirdPartyFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -75,7 +94,7 @@ final readonly class HandlingThirdPartyFilter implements FilterInterface $qb->setParameter("{$p}_3ps", $data['handling_3parties']); } - public function applyOn() + public function applyOn(): string { return Declarations::SOCIAL_WORK_ACTION_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoActionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoActionFilter.php index 3ac083b32..7a87a10b2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoActionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoActionFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Export\Declarations; @@ -24,7 +25,7 @@ class HasNoActionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere('NOT EXISTS (SELECT 1 FROM '.AccompanyingPeriodWork::class.' work WHERE work.accompanyingPeriod = acp)'); } @@ -39,12 +40,27 @@ class HasNoActionFilter implements FilterInterface // no form } + 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 describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered acp which has no actions']; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoReferrerFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoReferrerFilter.php index 72bcce39e..9724ea9ed 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoReferrerFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasNoReferrerFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class HasNoReferrerFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere(' @@ -54,7 +55,7 @@ class HasNoReferrerFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('calc_date', PickRollingDateType::class, [ @@ -62,12 +63,27 @@ class HasNoReferrerFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered acp which has no referrer on date: %date%', [ '%date%' => $this->rollingDateConverter->convert($data['calc_date'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php index 778e4181a..64e319f35 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HasTemporaryLocationFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class HasTemporaryLocationFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->join('acp.locationHistories', 'acp_having_temporarily_location') @@ -52,7 +53,7 @@ class HasTemporaryLocationFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('having_temporarily', ChoiceType::class, [ @@ -73,12 +74,27 @@ class HasTemporaryLocationFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['having_temporarily' => $formData['having_temporarily'], 'calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['having_temporarily' => $formData['having_temporarily'], 'calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return match ($data['having_temporarily']) { true => ['export.filter.course.having_temporarily.Having a temporarily location', []], diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HavingAnAccompanyingPeriodInfoWithinDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HavingAnAccompanyingPeriodInfoWithinDatesFilter.php index d6436f8b4..37e6648a1 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HavingAnAccompanyingPeriodInfoWithinDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/HavingAnAccompanyingPeriodInfoWithinDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -42,6 +43,21 @@ final readonly class HavingAnAccompanyingPeriodInfoWithinDatesFilter implements ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -52,7 +68,7 @@ final readonly class HavingAnAccompanyingPeriodInfoWithinDatesFilter implements return 'export.filter.course.having_info_within_interval.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.course.having_info_within_interval.Only course with events between %startDate% and %endDate%', @@ -68,7 +84,7 @@ final readonly class HavingAnAccompanyingPeriodInfoWithinDatesFilter implements return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $ai = 'having_ai_within_interval_acc_info'; $as = 'having_ai_within_interval_start_date'; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/IntensityFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/IntensityFilter.php index 3eb8bbb24..7a48fe635 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/IntensityFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/IntensityFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\Query\Expr\Andx; @@ -35,7 +36,7 @@ class IntensityFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->eq('acp.intensity', ':intensity'); @@ -55,7 +56,7 @@ class IntensityFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_intensities', ChoiceType::class, [ 'choices' => self::CHOICES, @@ -65,12 +66,27 @@ class IntensityFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_intensities' => $formData['accepted_intensities']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_intensities' => $formData['accepted_intensities']]; + } + public function getFormDefaultData(): array { return ['accepted_intensities' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'Filtered by intensity: only %intensity%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/JobWorkingOnCourseFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/JobWorkingOnCourseFilter.php index 622aa5801..80a0b2fed 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/JobWorkingOnCourseFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/JobWorkingOnCourseFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\UserJobRepositoryInterface; @@ -33,6 +34,7 @@ use Symfony\Component\Form\FormBuilderInterface; */ readonly class JobWorkingOnCourseFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_filter_user_job_working_on_course'; public function __construct( @@ -46,7 +48,7 @@ readonly class JobWorkingOnCourseFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -105,7 +107,22 @@ readonly class JobWorkingOnCourseFilter implements FilterInterface ; } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs']), 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository), 'start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.course.by_job_working.Filtered by job working on course: only %jobs%, between %start_date% and %end_date%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php index 60486371e..4fc2af355 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/NotAssociatedWithAReferenceAddressFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,18 +29,33 @@ final readonly class NotAssociatedWithAReferenceAddressFilter implements FilterI private RollingDateConverterInterface $rollingDateConverter, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.course.not_having_address_reference.title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_calc', PickRollingDateType::class, [ 'label' => 'export.filter.course.not_having_address_reference.adress_at', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return [ @@ -47,7 +63,7 @@ final readonly class NotAssociatedWithAReferenceAddressFilter implements FilterI ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'exports.filter.course.not_having_address_reference.describe', @@ -62,7 +78,7 @@ final readonly class NotAssociatedWithAReferenceAddressFilter implements FilterI return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $k = 'acp_not_associated_ref_filter'; @@ -91,7 +107,7 @@ final readonly class NotAssociatedWithAReferenceAddressFilter implements FilterI $qb->setParameter("{$k}_date_calc", $this->rollingDateConverter->convert($data['date_calc'])); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OpenBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OpenBetweenDatesFilter.php index 1b85f6cd7..d116f3d5c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OpenBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OpenBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,7 +30,7 @@ class OpenBetweenDatesFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $clause = $qb->expr()->andX( $qb->expr()->gte('acp.openingDate', ':datefrom'), @@ -46,19 +47,34 @@ class OpenBetweenDatesFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_from', PickRollingDateType::class, []) ->add('date_to', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_MONTH_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by opening dates: between %datefrom% and %dateto%', [ '%datefrom%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OriginFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OriginFilter.php index 617577cde..b0b2093bd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OriginFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/OriginFilter.php @@ -11,25 +11,29 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Repository\AccompanyingPeriod\OriginRepository; use Doctrine\ORM\Query\Expr\Andx; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class OriginFilter implements FilterInterface +final readonly class OriginFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private OriginRepository $originRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->in('acp.origin', ':origin'); @@ -49,7 +53,7 @@ class OriginFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_origins', EntityType::class, [ 'class' => Origin::class, @@ -59,12 +63,27 @@ class OriginFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_origins' => $this->normalizeDoctrineEntity($formData['accepted_origins'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_origins' => $this->denormalizeDoctrineEntity($formData['accepted_origins'], $this->originRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $origins = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php index 30f67f664..fd668a1fa 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilter.php @@ -11,31 +11,36 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; +use Chill\MainBundle\Templating\Entity\UserRender; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class ReferrerFilter implements FilterInterface +final readonly class ReferrerFilter implements FilterInterface { - private const A = 'acp_referrer_filter_uhistory'; + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + private const A = 'acp_referrer_filter_uhistory_x'; private const P = 'acp_referrer_filter_date'; private const PU = 'acp_referrer_filter_users'; - public function __construct(private readonly RollingDateConverterInterface $rollingDateConverter) {} + public function __construct(private RollingDateConverterInterface $rollingDateConverter, private UserRepositoryInterface $userRepository, private UserRender $userRender) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->join('acp.userHistories', self::A) @@ -51,7 +56,7 @@ class ReferrerFilter implements FilterInterface ->andWhere( $qb->expr()->in(self::A.'.user', ':'.self::PU) ) - ->setParameter(self::PU, $data['accepted_referrers']) + ->setParameter(self::PU, $this->userOrMe($data['accepted_referrers'], $exportGenerationContext)) ->setParameter( self::P, $this->rollingDateConverter->convert($data['date_calc']) @@ -63,10 +68,10 @@ class ReferrerFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('accepted_referrers', PickUserDynamicType::class, [ + ->add('accepted_referrers', PickUserOrMeDynamicType::class, [ 'multiple' => true, ]) ->add('date_calc', PickRollingDateType::class, [ @@ -75,17 +80,32 @@ class ReferrerFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_referrers' => $this->normalizeUserOrMe($formData['accepted_referrers']), 'date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_referrers' => $this->denormalizeUserOrMe($formData['accepted_referrers'], $this->userRepository), 'date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY), 'accepted_referrers' => []]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_referrers'] as $r) { - $users[] = $r; + foreach ($this->userOrMe($data['accepted_referrers'], $context) as $r) { + $users[] = $this->userRender->renderString($r, []); } return [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php index 6116e968b..929336feb 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDates.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepository; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\Entity\UserRender; @@ -32,6 +34,7 @@ use Symfony\Component\Form\FormBuilderInterface; */ final readonly class ReferrerFilterBetweenDates implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const A = 'acp_referrer_filter_uhistory'; private const P = 'acp_referrer_filter_date_start'; @@ -42,6 +45,7 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface public function __construct( private RollingDateConverterInterface $rollingDateConverter, private UserRender $userRender, + private UserRepository $userRepository, ) {} public function addRole(): ?string @@ -49,7 +53,7 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $history = self::A; $start = self::P; @@ -64,7 +68,7 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface ->andWhere( "{$history}.user IN (:{$users})", ) - ->setParameter($users, $data['accepted_referrers']) + ->setParameter($users, $this->userOrMe($data['accepted_referrers'], $exportGenerationContext)) ->setParameter($start, $this->rollingDateConverter->convert($data['start_date'])) ->setParameter($end, $this->rollingDateConverter->convert($data['end_date'])); } @@ -74,10 +78,10 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('accepted_referrers', PickUserDynamicType::class, [ + ->add('accepted_referrers', PickUserOrMeDynamicType::class, [ 'multiple' => true, ]) ->add('start_date', PickRollingDateType::class, [ @@ -90,6 +94,23 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_referrers' => $this->normalizeUserOrMe($formData['accepted_referrers']), + 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_referrers' => $this->denormalizeUserOrMe($formData['accepted_referrers'], $this->userRepository), + 'start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -99,11 +120,11 @@ final readonly class ReferrerFilterBetweenDates implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_referrers'] as $r) { + foreach ($this->userOrMe($data['accepted_referrers'], $context) as $r) { $users[] = $this->userRender->renderString($r, []); } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/RequestorFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/RequestorFilter.php index 0b7ce6994..8fa1ae766 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/RequestorFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/RequestorFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\EntityManagerInterface; @@ -38,7 +39,7 @@ final readonly class RequestorFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -107,7 +108,7 @@ final readonly class RequestorFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_choices', ChoiceType::class, [ 'choices' => self::REQUESTOR_CHOICES, @@ -117,12 +118,27 @@ final readonly class RequestorFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_choices' => $formData['accepted_choices']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_choices' => $formData['accepted_choices']]; + } + public function getFormDefaultData(): array { return ['accepted_choices' => self::DEFAULT_CHOICE]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $choice = array_flip(self::REQUESTOR_CHOICES)[$data['accepted_choices']]; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ScopeWorkingOnCourseFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ScopeWorkingOnCourseFilter.php index 63d93805f..f2240ae2c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ScopeWorkingOnCourseFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/ScopeWorkingOnCourseFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\ScopeRepositoryInterface; @@ -33,6 +34,7 @@ use Symfony\Component\Form\FormBuilderInterface; */ readonly class ScopeWorkingOnCourseFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_filter_user_scope_working_on_course'; public function __construct( @@ -46,7 +48,7 @@ readonly class ScopeWorkingOnCourseFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -100,7 +102,22 @@ readonly class ScopeWorkingOnCourseFilter implements FilterInterface ; } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes']), 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository), 'start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.course.by_scope_working.Filtered by scope working on course: only %scopes%, between %start_date% and %end_date%', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialActionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialActionFilter.php index 545c35f5e..e3ce3928c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialActionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialActionFilter.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; @@ -18,6 +20,7 @@ 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\Repository\SocialWork\SocialActionRepository; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; @@ -25,12 +28,15 @@ use Symfony\Contracts\Translation\TranslatorInterface; final readonly class SocialActionFilter implements FilterInterface { + use ExportDataNormalizerTrait; + private const PREFIX = 'acp_by_social_action_filter'; public function __construct( private SocialActionRender $actionRender, private RollingDateConverterInterface $rollingDateConverter, private TranslatorInterface $translator, + private SocialActionRepository $socialActionRepository, ) {} public function addRole(): ?string @@ -38,7 +44,7 @@ final readonly class SocialActionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -81,13 +87,18 @@ final readonly class SocialActionFilter implements FilterInterface return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { + $actions = $this->socialActionRepository->findAllOrdered(); + $builder ->add('accepted_socialactions', PickSocialActionType::class, [ 'multiple' => true, 'label' => 'export.filter.course.by_social_action.Accepted socialactions', 'help' => 'export.filter.course.by_social_action.accepted socialations help', + 'show_social_issue_parenthesis' => true, + 'show_deactivated' => true, + 'choices' => $actions, ]) ->add('start_date_after', PickRollingDateType::class, [ 'label' => 'export.filter.course.by_social_action.start date after', @@ -112,6 +123,29 @@ final readonly class SocialActionFilter implements FilterInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_socialactions' => $this->normalizeDoctrineEntity($formData['accepted_socialactions']), + 'start_date_after' => $formData['start_date_after']?->normalize(), + 'start_date_before' => $formData['start_date_before']?->normalize(), + 'end_date_after' => $formData['end_date_after']?->normalize(), + 'end_date_before' => $formData['end_date_before']?->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_socialactions' => $this->denormalizeDoctrineEntity($formData['accepted_socialactions'], $this->socialActionRepository), + 'start_date_after' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['start_date_after'] ?? null), + 'start_date_before' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['start_date_before'] ?? null), + 'end_date_after' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['end_date_after'] ?? null), + 'end_date_before' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['end_date_before'] ?? null)]; + } + public function getFormDefaultData(): array { return [ @@ -123,7 +157,7 @@ final readonly class SocialActionFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $actions = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialIssueFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialIssueFilter.php index afcdd15bc..30b00efb2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialIssueFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/SocialIssueFilter.php @@ -11,36 +11,33 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Export\Declarations; use Chill\PersonBundle\Form\Type\PickSocialIssueType; +use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository; use Chill\PersonBundle\Templating\Entity\SocialIssueRender; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Contracts\Translation\TranslatorInterface; class SocialIssueFilter implements FilterInterface { - /** - * @var TranslatorInterface - */ - protected $translator; + use ExportDataNormalizerTrait; public function __construct( - TranslatorInterface $translator, private readonly SocialIssueRender $socialIssueRender, - ) { - $this->translator = $translator; - } + private readonly SocialIssueRepository $socialIssueRepository, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('acpsocialissue', $qb->getAllAliases(), true)) { $qb->join('acp.socialIssues', 'acpsocialissue'); @@ -57,24 +54,39 @@ class SocialIssueFilter implements FilterInterface ); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('accepted_socialissues', PickSocialIssueType::class, [ 'multiple' => true, ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_socialissues' => $this->normalizeDoctrineEntity($formData['accepted_socialissues'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_socialissues' => $this->denormalizeDoctrineEntity($formData['accepted_socialissues'], $this->socialIssueRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $issues = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php index dfd627eda..176fa99d1 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterBetweenDates.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -46,7 +47,7 @@ class StepFilterBetweenDates implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $alias = 'acp_filter_by_step_between_dat_alias'; $steps = 'acp_filter_by_step_between_dat_steps'; @@ -66,12 +67,12 @@ class StepFilterBetweenDates implements FilterInterface ->setParameter($steps, $data['accepted_steps_multi']); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('accepted_steps_multi', ChoiceType::class, [ @@ -88,6 +89,21 @@ class StepFilterBetweenDates implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_steps_multi' => $formData['accepted_steps_multi'], 'date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_steps_multi' => $formData['accepted_steps_multi'], 'date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return [ @@ -97,7 +113,7 @@ class StepFilterBetweenDates implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $steps = array_map( fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), @@ -111,7 +127,7 @@ class StepFilterBetweenDates implements FilterInterface ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.course.by_step.Filter by step between dates'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php index 8b36b1b5b..54cdbd0dd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/StepFilterOnDate.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -25,6 +27,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class StepFilterOnDate implements FilterInterface { + use ExportDataNormalizerTrait; + private const A = 'acp_filter_bystep_stephistories'; private const DEFAULT_CHOICE = [ @@ -50,7 +54,7 @@ class StepFilterOnDate implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array(self::A, $qb->getAllAliases(), true)) { $qb->leftJoin('acp.stepHistories', self::A); @@ -73,12 +77,12 @@ class StepFilterOnDate implements FilterInterface ->setParameter('acp_filter_by_step_steps', $data['accepted_steps_multi']); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('accepted_steps_multi', ChoiceType::class, [ @@ -92,6 +96,27 @@ class StepFilterOnDate implements FilterInterface ]); } + public function normalizeFormData(array $formData): array + { + return [ + 'accepted_steps_multi' => $formData['accepted_steps_multi'], + 'calc_date' => $formData['calc_date']->normalize(), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'accepted_steps_multi' => $formData['accepted_steps_multi'], + 'calc_date' => RollingDate::fromNormalized($formData['calc_date']), + ]; + } + + public function getNormalizationVersion(): int + { + return 1; + } + public function getFormDefaultData(): array { return [ @@ -100,7 +125,7 @@ class StepFilterOnDate implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): array { $steps = array_map( fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), @@ -112,7 +137,7 @@ class StepFilterOnDate implements FilterInterface ]]; } - public function getTitle() + public function getTitle(): string { return 'export.filter.course.by_step.Filter by step'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php index c328e9b21..b3da6d50b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php @@ -14,6 +14,8 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Export\DataTransformerInterface; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\UserJobRepositoryInterface; @@ -29,6 +31,8 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class UserJobFilter implements FilterInterface, DataTransformerInterface { + use ExportDataNormalizerTrait; + private const PREFIX = 'acp_filter_user_job'; public function __construct( @@ -42,7 +46,7 @@ final readonly class UserJobFilter implements FilterInterface, DataTransformerIn return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -74,12 +78,12 @@ final readonly class UserJobFilter implements FilterInterface, DataTransformerIn ; } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -99,7 +103,7 @@ final readonly class UserJobFilter implements FilterInterface, DataTransformerIn ; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'exports.filter.course.by_user_job.Filtered by user job: only job', [ @@ -125,6 +129,31 @@ final readonly class UserJobFilter implements FilterInterface, DataTransformerIn ]; } + public function normalizeFormData(array $formData): array + { + return [ + 'jobs' => $this->normalizeDoctrineEntity($formData['jobs']), + 'start_date' => $formData['start_date']->normalize(), + 'end_date' => $formData['end_date']->normalize(), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $default = $this->getFormDefaultData(); + + return [ + 'jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository), + 'start_date' => array_key_exists('start_date', $formData) ? RollingDate::fromNormalized($formData['start_date']) : $default['start_date'], + 'end_date' => array_key_exists('end_date', $formData) ? RollingDate::fromNormalized($formData['end_date']) : $default['end_date'], + ]; + } + + public function getNormalizationVersion(): int + { + return 1; + } + public function transformData(?array $before): array { $default = $this->getFormDefaultData(); diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php index 3489b8cb3..4bd8a1abb 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserScopeFilter.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; use Chill\MainBundle\Export\DataTransformerInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\ScopeRepositoryInterface; @@ -29,6 +30,7 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class UserScopeFilter implements FilterInterface, DataTransformerInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_filter_main_scope'; public function __construct( @@ -42,7 +44,7 @@ final readonly class UserScopeFilter implements FilterInterface, DataTransformer return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -78,7 +80,7 @@ final readonly class UserScopeFilter implements FilterInterface, DataTransformer return Declarations::ACP_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -96,7 +98,28 @@ final readonly class UserScopeFilter implements FilterInterface, DataTransformer ]); } - public function describeAction($data, $format = 'string') + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes']), 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $default = $this->getFormDefaultData(); + + return [ + 'scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository), + 'start_date' => array_key_exists('start_date', $formData) ? RollingDate::fromNormalized($formData['start_date']) : $default['start_date'], + 'end_date' => array_key_exists('end_date', $formData) ? RollingDate::fromNormalized($formData['end_date']) : $default['end_date'], + ]; + } + + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'exports.filter.course.by_user_scope.Filtered by user main scope: only scopes', [ diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserWorkingOnCourseFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserWorkingOnCourseFilter.php index 85d5db9d6..30aefbce7 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserWorkingOnCourseFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserWorkingOnCourseFilter.php @@ -12,15 +12,16 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\Entity\UserRender; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Export\Declarations; -use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; @@ -31,15 +32,18 @@ use Symfony\Component\Form\FormBuilderInterface; */ final readonly class UserWorkingOnCourseFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private UserRender $userRender, private RollingDateConverterInterface $rollingDateConverter, + private UserRepositoryInterface $userRepository, ) {} public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('users', PickUserDynamicType::class, [ + ->add('users', PickUserOrMeDynamicType::class, [ 'multiple' => true, ]) ->add('start_date', PickRollingDateType::class, [ @@ -51,6 +55,21 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['users' => $this->normalizeUserOrMe($formData['users']), 'start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['users' => $this->denormalizeUserOrMe($formData['users'], $this->userRepository), 'start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -65,7 +84,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface return 'export.filter.course.by_user_working.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.course.by_user_working.Filtered by user working on course: only %users%, between %start_date% and %end_date%', [ @@ -73,7 +92,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface ', ', array_map( fn (User $u) => $this->userRender->renderString($u, []), - $data['users'] instanceof Collection ? $data['users']->toArray() : $data['users'] + $this->userOrMe($data['users'], $context) ) ), '%start_date%' => $this->rollingDateConverter->convert($data['start_date'])?->format('d-m-Y'), @@ -87,7 +106,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $ai_alias = 'user_working_on_course_filter_acc_info'; $ai_users = 'user_working_on_course_filter_users'; @@ -101,7 +120,7 @@ final readonly class UserWorkingOnCourseFilter implements FilterInterface "WHERE {$ai_alias}.user IN (:{$ai_users}) AND IDENTITY({$ai_alias}.accompanyingPeriod) = acp.id AND {$ai_alias}.infoDate >= :{$start} and {$ai_alias}.infoDate < :{$end}" ) ) - ->setParameter($ai_users, $data['users']) + ->setParameter($ai_users, $this->userOrMe($data['users'], $exportGenerationContext)) ->setParameter($start, $this->rollingDateConverter->convert($data['start_date'])) ->setParameter($end, $this->rollingDateConverter->convert($data['end_date'])) ; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php index 84a5814b3..79f5288fa 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -35,12 +36,12 @@ final readonly class ByDateFilter implements FilterInterface private RollingDateConverterInterface $rollingDateConverter, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.step_history.by_date.title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('start_date', PickRollingDateType::class, [ @@ -51,6 +52,21 @@ final readonly class ByDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return [ @@ -59,7 +75,7 @@ final readonly class ByDateFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'exports.filter.step_history.by_date.description', @@ -75,7 +91,7 @@ final readonly class ByDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $startDate = 'acp_step_history_by_date_start_filter'; $endDate = 'acp_step_history_by_date_end_filter'; @@ -88,7 +104,7 @@ final readonly class ByDateFilter implements FilterInterface ->setParameter($endDate, $this->rollingDateConverter->convert($data['end_date'])); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php index 337c30a89..0ead9d2c5 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingPeriodStepHistoryFilters/ByStepFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\AccompanyingPeriodStepHistoryFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Export\Declarations; @@ -25,12 +26,12 @@ final readonly class ByStepFilter implements FilterInterface private TranslatorInterface $translator, ) {} - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.step_history.by_step.title'; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $steps = [ AccompanyingPeriod::STEP_CONFIRMED, @@ -50,6 +51,21 @@ final readonly class ByStepFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['steps' => $formData['steps']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['steps' => $formData['steps']]; + } + public function getFormDefaultData(): array { return [ @@ -57,7 +73,7 @@ final readonly class ByStepFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.step_history.by_step.description', @@ -72,14 +88,14 @@ final readonly class ByStepFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere('acpstephistory.step IN (:acpstephistory_by_step_filter_steps)') ->setParameter('acpstephistory_by_step_filter_steps', $data['steps']); } - public function applyOn() + public function applyOn(): string { return Declarations::ACP_STEP_HISTORY; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByEndDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByEndDateFilter.php index a0d8d0033..e3b254295 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByEndDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByEndDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,7 +29,7 @@ class ByEndDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere('workeval.endDate BETWEEN :work_eval_by_end_date_start_date and :work_eval_by_end_date_end_date') @@ -58,12 +59,27 @@ class ByEndDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return ['start_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by end date: between %start_date% and %end_date%', [ '%start_date%' => $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByStartDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByStartDateFilter.php index 401cd79a5..206597ef6 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByStartDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/ByStartDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -28,7 +29,7 @@ class ByStartDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb ->andWhere('workeval.startDate BETWEEN :work_eval_by_start_date_start_date and :work_eval_by_start_date_end_date') @@ -58,12 +59,27 @@ class ByStartDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return ['start_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by start date: between %start_date% and %end_date%', [ '%start_date%' => $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y'), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/CurrentEvaluationsFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/CurrentEvaluationsFilter.php index 360b47f2d..11302acc4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/CurrentEvaluationsFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/CurrentEvaluationsFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; @@ -23,7 +24,7 @@ class CurrentEvaluationsFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere('workeval.endDate IS NULL'); } @@ -38,12 +39,27 @@ class CurrentEvaluationsFilter implements FilterInterface // 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 describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by current evaluations']; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php index 20472420c..ceed94352 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/EvaluationTypeFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\SocialWork\Evaluation; @@ -22,6 +23,8 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class EvaluationTypeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct(private TranslatableStringHelper $translatableStringHelper, private EvaluationRepositoryInterface $evaluationRepository) {} public function addRole(): ?string @@ -29,7 +32,7 @@ final readonly class EvaluationTypeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere( $qb->expr()->in('workeval.evaluation', ':evaluationtype') @@ -42,7 +45,7 @@ final readonly class EvaluationTypeFilter implements FilterInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $evaluations = $this->evaluationRepository->findAllActive(); @@ -58,12 +61,27 @@ final readonly class EvaluationTypeFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_evaluationtype' => $this->normalizeDoctrineEntity($formData['accepted_evaluationtype'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_evaluationtype' => $this->denormalizeDoctrineEntity($formData['accepted_evaluationtype'], $this->evaluationRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $evals = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php index 6094d56ee..3c67e7f66 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/EvaluationFilters/MaxDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\EvaluationFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; @@ -32,7 +33,7 @@ class MaxDateFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (true === $data['maxdate']) { $clause = $qb->expr()->isNotNull('workeval.maxDate'); @@ -48,7 +49,7 @@ class MaxDateFilter implements FilterInterface return Declarations::EVAL_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('maxdate', ChoiceType::class, [ 'choices' => self::MAXDATE_CHOICES, @@ -57,12 +58,27 @@ class MaxDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['maxdate' => $formData['maxdate']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['maxdate' => $formData['maxdate']]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered by maxdate: only %choice%', [ '%choice%' => $this->translator->trans( diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php index b9f9075ed..0a1b6c3f2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/HouseholdFilters/CompositionFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\HouseholdFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -18,6 +19,7 @@ use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\Household\HouseholdCompositionType; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Repository\Household\HouseholdCompositionTypeRepositoryInterface; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; @@ -25,9 +27,12 @@ use Symfony\Component\Form\FormBuilderInterface; readonly class CompositionFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct( private TranslatableStringHelper $translatableStringHelper, private RollingDateConverterInterface $rollingDateConverter, + private HouseholdCompositionTypeRepositoryInterface $householdCompositionTypeRepository, ) {} public function addRole(): ?string @@ -35,7 +40,7 @@ readonly class CompositionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { // there is no test on the aliases here: the name should be unique $clause = @@ -78,12 +83,27 @@ readonly class CompositionFilter implements FilterInterface ->add('on_date', PickRollingDateType::class, []); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['accepted_composition' => $this->normalizeDoctrineEntity($formData['accepted_composition']), 'on_date' => $formData['on_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['accepted_composition' => $this->denormalizeDoctrineEntity($formData['accepted_composition'], $this->householdCompositionTypeRepository), 'on_date' => RollingDate::fromNormalized($formData['on_date'])]; + } + public function getFormDefaultData(): array { return ['on_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $compositions = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php index 8cd675abe..b46eb2853 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AddressRefStatusFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; @@ -30,7 +31,7 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $subQuery = 'SELECT 1 @@ -57,12 +58,12 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface ->setParameter('person_filter_having_address_to_review_ref_statuses', $data['ref_statuses'] ?? [Address::ADDR_REFERENCE_STATUS_TO_REVIEW]); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -78,12 +79,27 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize(), 'ref_statuses' => $formData['ref_statuses']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'ref_statuses' => $formData['ref_statuses']]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY), 'ref_statuses' => [Address::ADDR_REFERENCE_STATUS_TO_REVIEW]]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.person.by_address_ref_status.Filtered by person\'s address status computed at %datecalc%, only %statuses%', diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php index aa97ad54c..475e3a9fc 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/AgeFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -32,7 +33,7 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -65,12 +66,12 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface $qb->setParameter('max_date', $maxDate); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('min_age', IntegerType::class, [ 'label' => 'Minimum age', @@ -86,6 +87,21 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['min_age' => $formData['min_age'], 'max_age' => $formData['max_age'], 'date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['min_age' => $formData['min_age'], 'max_age' => $formData['max_age'], 'date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return [ @@ -95,7 +111,7 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s age: ' .'between %min_age% and %max_age%', [ @@ -104,12 +120,12 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s age'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { $min = $data['min_age']; $max = $data['max_age']; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/BirthdateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/BirthdateFilter.php index dd14c71ef..55eb45211 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/BirthdateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/BirthdateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -31,7 +32,7 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -57,12 +58,12 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac ); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_from', PickRollingDateType::class, [ 'label' => 'Born after this date', @@ -73,12 +74,27 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s birthdate: ' .'between %date_from% and %date_to%', [ @@ -87,12 +103,12 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s birthdate'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { $date_from = $data['date_from']; $date_to = $data['date_to']; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ByHouseholdCompositionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ByHouseholdCompositionFilter.php index e873a6598..fb88a0087 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ByHouseholdCompositionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ByHouseholdCompositionFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -29,6 +30,8 @@ use Symfony\Component\Form\FormBuilderInterface; class ByHouseholdCompositionFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + public function __construct(private readonly HouseholdCompositionTypeRepositoryInterface $householdCompositionTypeRepository, private readonly RollingDateConverterInterface $rollingDateConverter, private readonly TranslatableStringHelperInterface $translatableStringHelper) {} public function addRole(): ?string @@ -36,7 +39,7 @@ class ByHouseholdCompositionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = 'person_by_household_compo_filter'; @@ -55,12 +58,12 @@ class ByHouseholdCompositionFilter implements FilterInterface ->setParameter("{$p}_date", $this->rollingDateConverter->convert($data['calc_date']), Types::DATE_IMMUTABLE); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('compositions', EntityType::class, [ @@ -76,12 +79,27 @@ class ByHouseholdCompositionFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['compositions' => $this->normalizeDoctrineEntity($formData['compositions']), 'calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['compositions' => $this->denormalizeDoctrineEntity($formData['compositions'], $this->householdCompositionTypeRepository), 'calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $compos = array_map( fn (HouseholdCompositionType $compositionType) => $this->translatableStringHelper->localize($compositionType->getLabel()), @@ -94,7 +112,7 @@ class ByHouseholdCompositionFilter implements FilterInterface ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.person.by_composition.Filter by household composition'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeadOrAliveFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeadOrAliveFilter.php index 82cda25ed..f224e16c4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeadOrAliveFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeadOrAliveFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -30,7 +31,7 @@ class DeadOrAliveFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); @@ -78,12 +79,12 @@ class DeadOrAliveFilter implements FilterInterface $qb->setParameter('date_calc', $calc); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('person_state', ChoiceType::class, [ 'choices' => [ @@ -99,12 +100,27 @@ class DeadOrAliveFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['person_state' => $formData['person_state'], 'date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['person_state' => $formData['person_state'], 'date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by a state of %deadOrAlive%: ' .'at this date %date_calc%', [ @@ -113,7 +129,7 @@ class DeadOrAliveFilter implements FilterInterface ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return "Filter by person's that are alive or have deceased at a certain date"; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeathdateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeathdateFilter.php index 1bc32d9bb..2d4420490 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeathdateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/DeathdateFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -31,7 +32,7 @@ class DeathdateFilter implements ExportElementValidatedInterface, FilterInterfac return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -57,12 +58,12 @@ class DeathdateFilter implements ExportElementValidatedInterface, FilterInterfac ); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_from', PickRollingDateType::class, [ 'label' => 'Death after this date', @@ -73,12 +74,27 @@ class DeathdateFilter implements ExportElementValidatedInterface, FilterInterfac ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s deathdate: ' .'between %date_from% and %date_to%', [ @@ -87,12 +103,12 @@ class DeathdateFilter implements ExportElementValidatedInterface, FilterInterfac ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s deathdate'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { $date_from = $data['date_from']; $date_to = $data['date_to']; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GenderFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GenderFilter.php index fc09e385a..6b73275cd 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GenderFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GenderFilter.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Entity\Gender; use Chill\MainBundle\Export\DataTransformerInterface; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\GenderRepository; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; @@ -41,9 +42,8 @@ class GenderFilter implements return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { - $where = $qb->getDQLPart('where'); $isIn = $qb->expr()->in('person.gender', ':person_gender'); $acceptedGenders = $data['accepted_genders_entity']; @@ -59,12 +59,12 @@ class GenderFilter implements $qb->setParameter('person_gender', array_filter($acceptedGenders ?? [], fn ($gender) => null !== $gender)); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $genderChoices = $this->genderRepository->findByActiveOrdered(); $choices = ['None' => null]; @@ -81,6 +81,55 @@ class GenderFilter implements ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + if (array_key_exists('accepted_genders', $formData)) { + $genders = []; + foreach ($formData['accepted_genders'] as $beforeGender) { + foreach ($this->genderRepository->findByGenderTranslation(match ($beforeGender) { + 'both' => 'neutral', default => $beforeGender, + }) as $gender) { + if (null !== $gender) { + if ('null' === $beforeGender) { + $genders[] = null; + } + $genders[] = $gender->getId(); + } + } + } + + return ['accepted_genders_entity' => array_unique($genders)]; + } + + return ['accepted_genders_entity' => $formData['accepted_genders_entity']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + if (array_key_exists('accepted_genders', $formData)) { + $transformedData = []; + foreach ($formData['accepted_genders'] as $genderBefore) { + foreach ($this->genderRepository->findByGenderTranslation( + match ($genderBefore) { + 'both' => 'neutral', + default => $genderBefore, + } + ) as $gender) { + $transformedData[] = $gender; + } + } + + return ['accepted_genders_entity' => $transformedData]; + } + + return ['accepted_genders_entity' => $formData['accepted_genders_entity']]; + } + public function transformData(?array $before): array { $transformedData = []; @@ -107,7 +156,7 @@ class GenderFilter implements return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $genders = []; @@ -115,7 +164,11 @@ class GenderFilter implements if (null === $g) { $genders[] = $this->translator->trans('export.filter.person.gender.no_gender'); } else { - $genders[] = $this->translatableStringHelper->localize($this->genderRepository->find($g)->getLabel()); + $g = $this->genderRepository->find($g); + if (null === $g) { + continue; + } + $genders[] = $this->translatableStringHelper->localize($g->getLabel()); } } @@ -130,12 +183,12 @@ class GenderFilter implements * * @return string */ - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person gender'; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (!\is_iterable($data['accepted_genders_entity']) || 0 === \count($data['accepted_genders_entity'])) { $context->buildViolation('You should select an option') diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php index e2041c3fa..d06604af4 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/GeographicalUnitFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface; use Chill\MainBundle\Repository\GeographicalUnitRepositoryInterface; @@ -25,16 +26,21 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; -class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface +final readonly class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface { - public function __construct(private readonly GeographicalUnitRepositoryInterface $geographicalUnitRepository, private readonly GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly RollingDateConverterInterface $rollingDateConverter) {} + public function __construct( + private GeographicalUnitRepositoryInterface $geographicalUnitRepository, + private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository, + private TranslatableStringHelperInterface $translatableStringHelper, + private RollingDateConverterInterface $rollingDateConverter, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $subQuery = 'SELECT 1 @@ -63,12 +69,12 @@ class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface ->setParameter('person_filter_geog_units', array_map(static fn (SimpleGeographicalUnitDTO $unitDTO) => $unitDTO->id, $data['units'])); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('date_calc', PickRollingDateType::class, [ @@ -88,12 +94,38 @@ class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return [ + 'date_calc' => $formData['date_calc']->normalize(), + 'units' => array_map(static fn (SimpleGeographicalUnitDTO $unitDTO) => $unitDTO->id, $formData['units']), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + $units = array_filter( + array_map( + fn (int $id) => $this->geographicalUnitRepository->findSimpleGeographicalUnit($id), + $formData['units'], + ), + static fn (?SimpleGeographicalUnitDTO $unitDTO) => null !== $unitDTO, + ); + + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc']), 'units' => $units]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return [ 'export.filter.by_geog_unit.Filtered by person\'s geographical unit (based on address) computed at %datecalc%, only %units%', diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php index 6c4cdf802..f7c8865a5 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/MaritalStatusFilter.php @@ -11,22 +11,26 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\MaritalStatus; use Chill\PersonBundle\Export\Declarations; +use Chill\PersonBundle\Repository\MaritalStatusRepositoryInterface; use Symfony\Bridge\Doctrine\Form\Type\EntityType; -class MaritalStatusFilter implements FilterInterface +final readonly class MaritalStatusFilter implements FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private MaritalStatusRepositoryInterface $maritalStatusRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data) + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere( $qb->expr()->in('person.maritalStatus', ':maritalStatus') @@ -34,12 +38,12 @@ class MaritalStatusFilter implements FilterInterface $qb->setParameter('maritalStatus', $data['maritalStatus']); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) + public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder): void { $builder->add('maritalStatus', EntityType::class, [ 'class' => MaritalStatus::class, @@ -51,17 +55,32 @@ class MaritalStatusFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['maritalStatus' => $this->normalizeDoctrineEntity($formData['maritalStatus'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['maritalStatus' => $this->denormalizeDoctrineEntity($formData['maritalStatus'], $this->maritalStatusRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s marital status']; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by person\'s marital status'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/NationalityFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/NationalityFilter.php index 4c83fafe3..ad1818f4b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/NationalityFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/NationalityFilter.php @@ -13,8 +13,10 @@ namespace Chill\PersonBundle\Export\Filter\PersonFilters; use Chill\MainBundle\Entity\Country; use Chill\MainBundle\Export\ExportElementValidatedInterface; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\Select2CountryType; +use Chill\MainBundle\Repository\CountryRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\Query\Expr; @@ -22,18 +24,20 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface; -class NationalityFilter implements +final readonly class NationalityFilter implements ExportElementValidatedInterface, FilterInterface { - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct(private TranslatableStringHelper $translatableStringHelper, private CountryRepository $countryRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->in('person.nationality', ':person_nationality'); @@ -48,24 +52,39 @@ class NationalityFilter implements $qb->setParameter('person_nationality', [$data['nationalities']]); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('nationalities', Select2CountryType::class, [ 'placeholder' => 'Choose countries', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['nationalities' => $this->normalizeDoctrineEntity($formData['nationalities'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['nationalities' => $this->denormalizeDoctrineEntity($formData['nationalities'], $this->countryRepository)]; + } + public function getFormDefaultData(): array { return []; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $countries = $data['nationalities']; @@ -77,12 +96,12 @@ class NationalityFilter implements ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return "Filter by person's nationality"; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { if (null === $data['nationalities']) { $context->buildViolation('A nationality must be selected') diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtThirdpartyFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtThirdpartyFilter.php index ea8e01baf..36f62e3c9 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtThirdpartyFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtThirdpartyFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -19,21 +20,28 @@ use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\Person\ResidentialAddress; use Chill\PersonBundle\Export\Declarations; use Chill\ThirdPartyBundle\Entity\ThirdPartyCategory; +use Chill\ThirdPartyBundle\Repository\ThirdPartyCategoryRepository; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class ResidentialAddressAtThirdpartyFilter implements FilterInterface +final readonly class ResidentialAddressAtThirdpartyFilter implements FilterInterface { - public function __construct(private readonly RollingDateConverterInterface $rollingDateConverter, private readonly TranslatableStringHelper $translatableStringHelper) {} + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; + + public function __construct( + private RollingDateConverterInterface $rollingDateConverter, + private TranslatableStringHelper $translatableStringHelper, + private ThirdPartyCategoryRepository $thirdPartyCategoryRepository, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('resaddr', $qb->getAllAliases(), true)) { $qb->join(ResidentialAddress::class, 'resaddr', Expr\Join::WITH, 'resaddr.person = person'); @@ -79,12 +87,12 @@ class ResidentialAddressAtThirdpartyFilter implements FilterInterface $qb->add('where', $where); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('thirdparty_cat', EntityType::class, [ 'class' => ThirdPartyCategory::class, @@ -99,12 +107,27 @@ class ResidentialAddressAtThirdpartyFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['thirdparty_cat' => $this->normalizeDoctrineEntity($formData['thirdparty_cat']), 'date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['thirdparty_cat' => $this->denormalizeDoctrineEntity($formData['thirdparty_cat'], $this->thirdPartyCategoryRepository), 'date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by person\'s who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%', [ '%thirdparty_type%' => $this->translatableStringHelper->localize($data['thirdparty_cat'][0]->getName()), @@ -112,7 +135,7 @@ class ResidentialAddressAtThirdpartyFilter implements FilterInterface ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return "Filter by person's who have a residential address located at a thirdparty of type"; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtUserFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtUserFilter.php index 3c2d7a3a6..12b6fac8e 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtUserFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/ResidentialAddressAtUserFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -30,7 +31,7 @@ class ResidentialAddressAtUserFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (!\in_array('resaddr', $qb->getAllAliases(), true)) { $qb->join(ResidentialAddress::class, 'resaddr', Expr\Join::WITH, 'resaddr.person = person'); @@ -66,29 +67,44 @@ class ResidentialAddressAtUserFilter implements FilterInterface $qb->add('where', $where); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) + public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder): void { $builder->add('date_calc', PickRollingDateType::class, [ 'label' => 'Date during which residential address was valid', ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_calc' => $formData['date_calc']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_calc' => RollingDate::fromNormalized($formData['date_calc'])]; + } + public function getFormDefaultData(): array { return ['date_calc' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ["Filtered by person's who have a residential address located at another user"]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return "Filter by person's who have a residential address located at another user"; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php index 289af5f07..792851137 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithParticipationBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -32,7 +33,7 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = 'with_participation_between_dates_filter'; @@ -54,7 +55,7 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_after', PickRollingDateType::class, [ 'label' => 'export.filter.person.with_participation_between_dates.date_after', @@ -65,6 +66,21 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_after' => $formData['date_after']->normalize(), 'date_before' => $formData['date_before']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_after' => RollingDate::fromNormalized($formData['date_after']), 'date_before' => RollingDate::fromNormalized($formData['date_before'])]; + } + public function getFormDefaultData(): array { return [ @@ -73,7 +89,7 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['export.filter.person.with_participation_between_dates.Filtered by participations during period: between %dateafter% and %datebefore%', [ '%dateafter%' => $this->rollingDateConverter->convert($data['date_after'])->format('d-m-Y'), @@ -81,7 +97,7 @@ final readonly class WithParticipationBetweenDatesFilter implements FilterInterf ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.person.with_participation_between_dates.title'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutHouseholdComposition.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutHouseholdComposition.php index c34c7eb40..340aa041f 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutHouseholdComposition.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutHouseholdComposition.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -31,7 +32,7 @@ class WithoutHouseholdComposition implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = 'person_by_household_no_compo_filter'; @@ -50,12 +51,12 @@ class WithoutHouseholdComposition implements FilterInterface ->setParameter("{$p}_date", $this->rollingDateConverter->convert($data['calc_date']), Types::DATE_IMMUTABLE); } - public function applyOn() + public function applyOn(): string { return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('calc_date', PickRollingDateType::class, [ @@ -63,19 +64,34 @@ class WithoutHouseholdComposition implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['calc_date' => $formData['calc_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['calc_date' => RollingDate::fromNormalized($formData['calc_date'])]; + } + public function getFormDefaultData(): array { return ['calc_date' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['export.filter.person.by_no_composition.Persons filtered by no composition at %date%', [ '%date%' => $this->rollingDateConverter->convert($data['calc_date'])->format('d-m-Y'), ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.person.by_no_composition.Filter persons without household composition'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutParticipationBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutParticipationBetweenDatesFilter.php index d1e3e9b2f..fbd8f2acb 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutParticipationBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/PersonFilters/WithoutParticipationBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\PersonFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -32,7 +33,7 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = 'without_participation_between_dates_filter'; @@ -56,7 +57,7 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt return Declarations::PERSON_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder->add('date_after', PickRollingDateType::class, [ 'label' => 'export.filter.person.without_participation_between_dates.date_after', @@ -67,6 +68,21 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_after' => $formData['date_after']->normalize(), 'date_before' => $formData['date_before']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_after' => RollingDate::fromNormalized($formData['date_after']), 'date_before' => RollingDate::fromNormalized($formData['date_before'])]; + } + public function getFormDefaultData(): array { return [ @@ -75,7 +91,7 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt ]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['exports.filter.person.without_participation_between_dates.Filtered by having no participations during period: between', [ 'dateafter' => $this->rollingDateConverter->convert($data['date_after']), @@ -83,7 +99,7 @@ final readonly class WithoutParticipationBetweenDatesFilter implements FilterInt ]]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'export.filter.person.without_participation_between_dates.title'; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkEndDateBetweenDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkEndDateBetweenDateFilter.php index 05d4633e2..801c64df2 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkEndDateBetweenDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkEndDateBetweenDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -41,9 +42,28 @@ final readonly class AccompanyingPeriodWorkEndDateBetweenDateFilter implements F ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize(), 'keep_null' => $formData['keep_null']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'start_date' => RollingDate::fromNormalized($formData['start_date']), + 'end_date' => RollingDate::fromNormalized($formData['end_date']), + 'keep_null' => (bool) ($formData['keep_null'] ?? true), + ]; + } + public function getFormDefaultData(): array { - return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; + return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY), 'keep_null' => true]; } public function getTitle(): string @@ -51,7 +71,7 @@ final readonly class AccompanyingPeriodWorkEndDateBetweenDateFilter implements F return 'export.filter.work.end_between_dates.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.work.end_between_dates.Only where start date is between %startDate% and %endDate%', @@ -67,7 +87,7 @@ final readonly class AccompanyingPeriodWorkEndDateBetweenDateFilter implements F return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $as = 'acc_pe_work_end_between_filter_start'; $ae = 'acc_pe_work_end_between_filter_end'; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkStartDateBetweenDateFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkStartDateBetweenDateFilter.php index 880543355..b061ab550 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkStartDateBetweenDateFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkStartDateBetweenDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -41,9 +42,28 @@ final readonly class AccompanyingPeriodWorkStartDateBetweenDateFilter implements ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize(), 'keep_null' => $formData['keep_null']]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'start_date' => RollingDate::fromNormalized($formData['start_date']), + 'end_date' => RollingDate::fromNormalized($formData['end_date']), + 'keep_null' => (bool) ($formData['keep_null'] ?? true), + ]; + } + public function getFormDefaultData(): array { - return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; + return ['start_date' => new RollingDate(RollingDate::T_TODAY), 'end_date' => new RollingDate(RollingDate::T_TODAY), 'keep_null' => true]; } public function getTitle(): string @@ -51,7 +71,7 @@ final readonly class AccompanyingPeriodWorkStartDateBetweenDateFilter implements return 'export.filter.work.start_between_dates.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.work.start_between_dates.Only where start date is between %startDate% and %endDate%', @@ -67,7 +87,7 @@ final readonly class AccompanyingPeriodWorkStartDateBetweenDateFilter implements return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $as = 'acc_pe_work_start_between_filter_start'; $ae = 'acc_pe_work_start_between_filter_end'; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter.php index e0c4da1e9..9bca9bc26 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -37,6 +38,21 @@ final readonly class AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter impl ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['start_date' => $formData['start_date']->normalize(), 'end_date' => $formData['end_date']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['start_date' => RollingDate::fromNormalized($formData['start_date']), 'end_date' => RollingDate::fromNormalized($formData['end_date'])]; + } + public function getFormDefaultData(): array { return ['start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START), 'end_date' => new RollingDate(RollingDate::T_TODAY)]; @@ -47,7 +63,7 @@ final readonly class AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter impl return 'export.filter.work.evaluation_between_dates.title'; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.work.evaluation_between_dates.description', @@ -63,7 +79,7 @@ final readonly class AccompanyingPeriodWorkWithEvaluationBetweenDatesFilter impl return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $s = 'workeval_between_filter_start'; $e = 'workeval_between_filter_end'; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorFilter.php index c990e658e..36f7989cc 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorFilter.php @@ -12,30 +12,35 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; -class CreatorFilter implements FilterInterface +final readonly class CreatorFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_filter_creator'; + public function __construct(private UserRepositoryInterface $userRepository) {} + public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; $qb ->leftJoin('acpw.createdBy', "{$p}_creator") ->andWhere($qb->expr()->in("{$p}_creator", ":{$p}_creators")) - ->setParameter("{$p}_creators", $data['creators']); + ->setParameter("{$p}_creators", $this->userOrMe($data['creators'], $exportGenerationContext)); } public function applyOn(): string @@ -43,23 +48,38 @@ class CreatorFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('creators', PickUserDynamicType::class, [ + ->add('creators', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'export.filter.work.by_creator.Creators', ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['creators' => $this->normalizeUserOrMe($formData['creators'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['creators' => $this->denormalizeUserOrMe($formData['creators'], $this->userRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { return [ 'export.filter.work.by_creator.Filtered by creator: only %creators%', [ '%creators%' => implode( ', ', array_map( - static fn (User $u) => $u->getLabel(), + fn (User|string $u) => $this->userOrMe($u, $context)->getLabel(), $data['creators'] instanceof Collection ? $data['creators']->toArray() : $data['creators'] ) ), diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorJobFilter.php index 3087d156e..fe6eac7e9 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorJobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorJobFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\User\UserJobHistory; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -22,13 +23,14 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -class CreatorJobFilter implements FilterInterface +final readonly class CreatorJobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_filter_creator_job'; public function __construct( - private readonly UserJobRepository $userJobRepository, - private readonly TranslatableStringHelper $translatableStringHelper, + private UserJobRepository $userJobRepository, + private TranslatableStringHelper $translatableStringHelper, ) {} public function addRole(): ?string @@ -36,7 +38,7 @@ class CreatorJobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class CreatorJobFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('jobs', EntityType::class, [ @@ -78,7 +80,22 @@ class CreatorJobFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['jobs' => $this->normalizeDoctrineEntity($formData['jobs'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['jobs' => $this->denormalizeDoctrineEntity($formData['jobs'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $creatorJobs = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorScopeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorScopeFilter.php index 414740c7a..d6185c287 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorScopeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CreatorScopeFilter.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User\UserScopeHistory; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepository; use Chill\MainBundle\Templating\TranslatableStringHelper; @@ -24,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface; class CreatorScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_filter_creator_scope'; public function __construct( @@ -36,7 +38,7 @@ class CreatorScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -65,7 +67,7 @@ class CreatorScopeFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scopes', EntityType::class, [ @@ -78,7 +80,22 @@ class CreatorScopeFilter implements FilterInterface ]); } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scopes' => $this->normalizeDoctrineEntity($formData['scopes'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scopes' => $this->denormalizeDoctrineEntity($formData['scopes'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $creatorScopes = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CurrentActionFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CurrentActionFilter.php index b11d2a93c..38da6bd7c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CurrentActionFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/CurrentActionFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; @@ -23,7 +24,7 @@ class CurrentActionFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data): void + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $qb->andWhere('acpw.endDate IS NULL'); } @@ -38,12 +39,27 @@ class CurrentActionFilter implements FilterInterface // no form } + 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 describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { return ['Filtered actions without end date']; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/JobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/JobFilter.php index 25e762a9c..4da612506 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/JobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/JobFilter.php @@ -12,25 +12,25 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\UserJob; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\UserJobRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; -use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkReferrerHistory; use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Contracts\Translation\TranslatorInterface; -class JobFilter implements FilterInterface +final readonly class JobFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_work_action_filter_user_job'; public function __construct( - protected TranslatorInterface $translator, - private readonly TranslatableStringHelper $translatableStringHelper, - private readonly UserJobRepositoryInterface $userJobRepository, + private TranslatableStringHelperInterface $translatableStringHelper, + private UserJobRepositoryInterface $userJobRepository, ) {} public function addRole(): ?string @@ -38,7 +38,7 @@ class JobFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -57,7 +57,7 @@ class JobFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('job', EntityType::class, [ @@ -72,7 +72,22 @@ class JobFilter implements FilterInterface ; } - public function describeAction($data, $format = 'string') + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['job' => $this->normalizeDoctrineEntity($formData['job'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['job' => $this->denormalizeDoctrineEntity($formData['job'], $this->userJobRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { $userjobs = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ReferrerFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ReferrerFilter.php index 4d456fdb2..1b3b3a6c6 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ReferrerFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ReferrerFilter.php @@ -11,9 +11,11 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; -use Chill\MainBundle\Form\Type\PickUserDynamicType; +use Chill\MainBundle\Form\Type\PickUserOrMeDynamicType; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\PersonBundle\Export\Declarations; @@ -22,16 +24,17 @@ use Symfony\Component\Form\FormBuilderInterface; final readonly class ReferrerFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acpw_referrer_filter'; - public function __construct(private RollingDateConverterInterface $rollingDateConverter) {} + public function __construct(private RollingDateConverterInterface $rollingDateConverter, private UserRepositoryInterface $userRepository) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -41,7 +44,7 @@ final readonly class ReferrerFilter implements FilterInterface ->andWhere("{$p}_acpwusers_history.user IN (:{$p}_agents)"); $qb - ->setParameter("{$p}_agents", $data['accepted_agents']) + ->setParameter("{$p}_agents", $this->userOrMe($data['accepted_agents'], $exportGenerationContext)) ->setParameter("{$p}_calc_date", $this->rollingDateConverter->convert( $data['agent_at'] ?? new RollingDate(RollingDate::T_TODAY) )) @@ -53,10 +56,10 @@ final readonly class ReferrerFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder - ->add('accepted_agents', PickUserDynamicType::class, [ + ->add('accepted_agents', PickUserOrMeDynamicType::class, [ 'multiple' => true, 'label' => 'export.filter.work.by_treating_agent.Accepted agents', ]) @@ -67,6 +70,27 @@ final readonly class ReferrerFilter implements FilterInterface ; } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return [ + 'accepted_agents' => $this->normalizeUserOrMe($formData['accepted_agents']), + 'agent_at' => array_key_exists('agent_at', $formData) ? $formData['agent_at']?->normalize() : (new RollingDate(RollingDate::T_TODAY))->normalize(), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'accepted_agents' => $this->denormalizeUserOrMe($formData['accepted_agents'], $this->userRepository), + 'agent_at' => array_key_exists('agent_at', $formData) ? RollingDate::fromNormalized($formData['agent_at']) : new RollingDate(RollingDate::T_TODAY), + ]; + } + public function getFormDefaultData(): array { return [ @@ -75,11 +99,11 @@ final readonly class ReferrerFilter implements FilterInterface ]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $users = []; - foreach ($data['accepted_agents'] as $r) { + foreach ($this->userOrMe($data['accepted_agents'], $context) as $r) { $users[] = $r; } diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ScopeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ScopeFilter.php index 9f2041aaa..42c666879 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ScopeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/ScopeFilter.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -21,16 +22,15 @@ use Chill\PersonBundle\Export\Declarations; use Doctrine\ORM\QueryBuilder; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Contracts\Translation\TranslatorInterface; -class ScopeFilter implements FilterInterface +final readonly class ScopeFilter implements FilterInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; private const PREFIX = 'acp_work_action_filter_user_scope'; public function __construct( - protected TranslatorInterface $translator, - private readonly TranslatableStringHelper $translatableStringHelper, - private readonly ScopeRepositoryInterface $scopeRepository, + private TranslatableStringHelper $translatableStringHelper, + private ScopeRepositoryInterface $scopeRepository, ) {} public function addRole(): ?string @@ -38,7 +38,7 @@ class ScopeFilter implements FilterInterface return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $p = self::PREFIX; @@ -51,12 +51,12 @@ class ScopeFilter implements FilterInterface ->setParameter("{$p}_scope", $data['scope']); } - public function applyOn() + public function applyOn(): string { return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('scope', EntityType::class, [ @@ -71,7 +71,22 @@ class ScopeFilter implements FilterInterface ; } - public function describeAction($data, $format = 'string'): array + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['scope' => $this->normalizeDoctrineEntity($formData['scope'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['scope' => $this->denormalizeDoctrineEntity($formData['scope'], $this->scopeRepository)]; + } + + public function describeAction($data, ExportGenerationContext $context): array { $scopes = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php index c84ed7f40..85ea60f3d 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/SocialWorkFilters/SocialWorkTypeFilter.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters; +use Chill\MainBundle\Export\ExportDataNormalizerTrait; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\SocialWork\Goal; @@ -26,14 +28,20 @@ use Symfony\Component\Form\FormBuilderInterface; class SocialWorkTypeFilter implements FilterInterface { - public function __construct(private readonly SocialActionRender $socialActionRender, private readonly TranslatableStringHelper $translatableStringHelper, private readonly EntityManagerInterface $em) {} + use ExportDataNormalizerTrait; + + public function __construct( + private readonly SocialActionRender $socialActionRender, + private readonly TranslatableStringHelper $translatableStringHelper, + private readonly EntityManagerInterface $em, + ) {} public function addRole(): ?string { return null; } - public function alterQuery(QueryBuilder $qb, $data) + public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { if (\count($data['actionType']) > 0) { $qb @@ -73,7 +81,7 @@ class SocialWorkTypeFilter implements FilterInterface return Declarations::SOCIAL_WORK_ACTION_TYPE; } - public function buildForm(FormBuilderInterface $builder) + public function buildForm(FormBuilderInterface $builder): void { $builder ->add('actionType', HiddenType::class) @@ -95,12 +103,44 @@ class SocialWorkTypeFilter implements FilterInterface ); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return [ + 'actionType' => $this->normalizeDoctrineEntity($formData['actionType']), + 'goal' => $this->normalizeDoctrineEntity($formData['goal']), + 'result' => $this->normalizeDoctrineEntity($formData['result']), + ]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return [ + 'actionType' => $this->denormalizeDoctrineEntity($this->denormalizeStringRepresentation($formData['actionType']), $this->em->getRepository(SocialAction::class)), + 'goal' => $this->denormalizeDoctrineEntity($this->denormalizeStringRepresentation($formData['goal']), $this->em->getRepository(Goal::class)), + 'result' => $this->denormalizeDoctrineEntity($this->denormalizeStringRepresentation($formData['result']), $this->em->getRepository(Result::class)), + ]; + } + + private function denormalizeStringRepresentation(array|string $ids): array + { + if (is_array($ids)) { + return $ids; + } + + return array_map(fn (string $id) => (int) $id, explode(',', $ids)); + } + public function getFormDefaultData(): array { return ['action_type' => [], 'goal' => [], 'result' => []]; } - public function describeAction($data, $format = 'string'): array + public function describeAction($data, ExportGenerationContext $context): array { $actionTypes = []; $goals = []; diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php index 2c42d931f..e631051a0 100644 --- a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php +++ b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelper.php @@ -13,13 +13,12 @@ namespace Chill\PersonBundle\Export\Helper; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\CenterRepositoryInterface; -use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation; use Chill\PersonBundle\Entity\Person\PersonCenterHistory; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Doctrine\ORM\QueryBuilder; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; -use Symfony\Component\Security\Core\Security; /** * Filter accompanying period list and related, removing confidential ones @@ -30,27 +29,20 @@ final readonly class FilterListAccompanyingPeriodHelper implements FilterListAcc private bool $filterStatsByCenters; public function __construct( - private Security $security, private CenterRepositoryInterface $centerRepository, - private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser, + private AuthorizationHelperInterface $authorizationHelper, ParameterBagInterface $parameterBag, ) { $this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center']; } - public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, array $data = []): void + public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, User $user, array $data = []): void { $centers = match ($this->filterStatsByCenters) { true => array_map(static fn ($el) => $el['center'], $acl), false => $this->centerRepository->findAll(), }; - $user = $this->security->getUser(); - - if (!$user instanceof User) { - throw new \RuntimeException('only a regular user can run this export'); - } - // add filtering on confidential accompanying period. The confidential is applyed on the current status of // the accompanying period (we do not use the 'calc_date' here $aclConditionsOrX = $qb->expr()->orX( @@ -61,22 +53,23 @@ final readonly class FilterListAccompanyingPeriodHelper implements FilterListAcc $i = 0; foreach ($centers as $center) { - $scopes = $this->authorizationHelperForCurrentUser->getReachableScopes(AccompanyingPeriodVoter::SEE_DETAILS, $center); + $scopes = $this->authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_DETAILS, $center); $scopesConfidential = - $this->authorizationHelperForCurrentUser->getReachableScopes(AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, $center); + $this->authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, $center); $orScopes = $qb->expr()->orX(); foreach ($scopes as $scope) { $scopeCondition = match (in_array($scope, $scopesConfidential, true)) { - true => ":scope_{$i} MEMBER OF acp.scopes", + true => sprintf(':scope_%s MEMBER OF acp.scopes', $i), false => $qb->expr()->andX( 'acp.confidential = FALSE', - ":scope_{$i} MEMBER OF acp.scopes", + sprintf(':scope_%s MEMBER OF acp.scopes', $i), ), }; - $orScopes->add($scopeCondition); + $orScopes->add($qb->expr()->orX(sprintf('acp.user = :user_%d', $i), $scopeCondition)); $qb->setParameter("scope_{$i}", $scope); + $qb->setParameter("user_{$i}", $user); ++$i; } diff --git a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php index cde6aceec..eafa6b8fb 100644 --- a/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php +++ b/src/Bundle/ChillPersonBundle/Export/Helper/FilterListAccompanyingPeriodHelperInterface.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\PersonBundle\Export\Helper; +use Chill\MainBundle\Entity\User; use Doctrine\ORM\QueryBuilder; /** @@ -19,5 +20,5 @@ use Doctrine\ORM\QueryBuilder; */ interface FilterListAccompanyingPeriodHelperInterface { - public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, array $data = []): void; + public function addFilterAccompanyingPeriods(QueryBuilder &$qb, array $requiredModifiers, array $acl, User $user, array $data = []): void; } diff --git a/src/Bundle/ChillPersonBundle/Form/Type/PickSocialActionType.php b/src/Bundle/ChillPersonBundle/Form/Type/PickSocialActionType.php index 19258f23d..d6115bd26 100644 --- a/src/Bundle/ChillPersonBundle/Form/Type/PickSocialActionType.php +++ b/src/Bundle/ChillPersonBundle/Form/Type/PickSocialActionType.php @@ -16,6 +16,7 @@ use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository; use Chill\PersonBundle\Templating\Entity\SocialActionRender; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class PickSocialActionType extends AbstractType @@ -28,14 +29,23 @@ class PickSocialActionType extends AbstractType ->setDefaults([ 'class' => SocialAction::class, 'choices' => $this->actionRepository->findAllActive(), - 'choice_label' => fn (SocialAction $sa) => $this->actionRender->renderString($sa, []), 'placeholder' => 'Pick a social action', 'required' => false, 'attr' => ['class' => 'select2'], 'label' => 'Social actions', 'multiple' => false, + 'show_social_issue_parenthesis' => false, + 'show_deactivated' => false, ]) - ->setAllowedTypes('multiple', ['bool']); + ->setNormalizer('choice_label', fn (Options $options, $value) => fn (SocialAction $sa) => $this->actionRender->renderString( + $sa, + [ + SocialActionRender::SHOW_SOCIAL_ISSUE => $options['show_social_issue_parenthesis'], + SocialActionRender::SHOW_DEACTIVATED => $options['show_deactivated'], + ] + )) + ->setAllowedTypes('multiple', ['bool']) + ->setAllowedTypes('show_social_issue_parenthesis', ['bool']); } public function getParent(): string diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php index 2e50386c8..726418b12 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriod/OriginRepository.php @@ -12,15 +12,16 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\Origin; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\EntityRepository; +use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\Persistence\ManagerRegistry; -final readonly class OriginRepository +/** + * @extends ServiceEntityRepository + */ +final class OriginRepository extends ServiceEntityRepository { - private EntityRepository $repository; - - public function __construct(EntityManagerInterface $entityManager) + public function __construct(ManagerRegistry $registry) { - $this->repository = $entityManager->getRepository(Origin::class); + parent::__construct($registry, Origin::class); } } diff --git a/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepositoryInterface.php index 1f51060e9..d5ff1c6bd 100644 --- a/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/MaritalStatusRepositoryInterface.php @@ -12,8 +12,9 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\MaritalStatus; +use Doctrine\Persistence\ObjectRepository; -interface MaritalStatusRepositoryInterface +interface MaritalStatusRepositoryInterface extends ObjectRepository { public function find($id): ?MaritalStatus; diff --git a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php index 4e609a620..3db5ac709 100644 --- a/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php +++ b/src/Bundle/ChillPersonBundle/Security/Authorization/AccompanyingPeriodVoter.php @@ -147,7 +147,7 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleH return []; } - protected function supports($attribute, $subject) + protected function supports($attribute, $subject): bool { return $this->voterHelper->supports($attribute, $subject); } diff --git a/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php index 657ed0f53..13e93cf22 100644 --- a/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php +++ b/src/Bundle/ChillPersonBundle/Templating/Entity/SocialActionRender.php @@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Templating\Entity; use Chill\MainBundle\Templating\Entity\ChillEntityRenderInterface; use Chill\MainBundle\Templating\TranslatableStringHelper; use Chill\PersonBundle\Entity\SocialWork\SocialAction; +use Symfony\Component\Clock\ClockInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** @@ -28,6 +29,8 @@ class SocialActionRender implements ChillEntityRenderInterface self::NO_BADGE => false, self::SHOW_AND_CHILDREN => false, self::AND_CHILDREN_MENTION => 'social_action.and children', + self::SHOW_SOCIAL_ISSUE => false, + self::SHOW_DEACTIVATED => false, ]; /** @@ -43,7 +46,27 @@ class SocialActionRender implements ChillEntityRenderInterface */ final public const SHOW_AND_CHILDREN = 'show_and_children'; - public function __construct(private readonly TranslatableStringHelper $translatableStringHelper, private readonly \Twig\Environment $engine, private readonly TranslatorInterface $translator) {} + /** + * Append the related social issue next to the social action name, in parenthesis. + * + * Currently only in string rendering. + */ + final public const SHOW_SOCIAL_ISSUE = 'show_social_issue'; + + /** + * Append a mention "deactivated" next to the social action name, in parenthesis, if the social action is deactivated. + * + * Currently only in string rendering. + */ + final public const SHOW_DEACTIVATED = 'show_deactivated'; + + public function __construct( + private readonly TranslatableStringHelper $translatableStringHelper, + private readonly \Twig\Environment $engine, + private readonly TranslatorInterface $translator, + private readonly SocialIssueRender $socialIssueRender, + private readonly ClockInterface $clock, + ) {} public function renderBox($socialAction, array $options): string { @@ -79,6 +102,14 @@ class SocialActionRender implements ChillEntityRenderInterface $title .= ' ('.$this->translator->trans($options[self::AND_CHILDREN_MENTION]).')'; } + if ($options[self::SHOW_SOCIAL_ISSUE]) { + $title .= ' ('.$this->socialIssueRender->renderString($socialAction->getIssue(), []).')'; + } + + if ($options[self::SHOW_DEACTIVATED] && $socialAction->isDesactivated(\DateTime::createFromImmutable($this->clock->now()))) { + $title .= ' ('.$this->translator->trans('Disabled').')'; + } + return $title; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregatorTest.php index 738769b7b..848cee27e 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregatorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/ReferrerScopeAggregatorTest.php @@ -23,6 +23,7 @@ use Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ReferrerS use Doctrine\ORM\EntityManagerInterface; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Component\Clock\MockClock; /** * @internal @@ -49,7 +50,7 @@ final class ReferrerScopeAggregatorTest extends AbstractAggregatorTest return new ReferrerScopeAggregator( $scopeRepository->reveal(), $translatableStringHelper->reveal(), - new RollingDateConverter(), + new RollingDateConverter(new MockClock()), ); } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/ReferrerAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/ReferrerAggregatorTest.php index fd1f0ffc5..f8ba356d2 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/ReferrerAggregatorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/SocialWorkAggregators/ReferrerAggregatorTest.php @@ -60,4 +60,29 @@ final class ReferrerAggregatorTest extends AbstractAggregatorTest ->from(AccompanyingPeriodWork::class, 'acpw'), ]; } + + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $aggregator = $this->getAggregator(); + + $normalized = $aggregator->normalizeFormData($data); + $actual = $aggregator->denormalizeFormData($normalized, $version); + + self::assertIsArray($actual); + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + foreach (self::getFormData() as $data) { + yield [$data, 1, []]; + } + } } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListAccompanyingPeriodTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListAccompanyingPeriodTest.php index 220ba0ad7..181d24548 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListAccompanyingPeriodTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Export/ListAccompanyingPeriodTest.php @@ -15,7 +15,7 @@ use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; -use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Test\Export\AbstractExportTest; @@ -27,7 +27,6 @@ use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Doctrine\ORM\EntityManagerInterface; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; -use Symfony\Component\Security\Core\Security; /** * @internal @@ -57,22 +56,13 @@ class ListAccompanyingPeriodTest extends AbstractExportTest $centerRepository = self::getContainer()->get(CenterRepositoryInterface::class); $scopeRepository = self::getContainer()->get(ScopeRepositoryInterface::class); - // mock security - $user = $em->createQuery('SELECT u FROM '.User::class.' u') - ->setMaxResults(1)->getSingleResult(); - if (null === $user) { - throw new \RuntimeException('no user found'); - } - $security = $this->prophesize(Security::class); - $security->getUser()->willReturn($user); - // mock authorization helper $scopes = $scopeRepository->findAll(); $scopesConfidentials = [] !== $scopes ? [$scopes[0]] : []; - $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); - $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_DETAILS, Argument::type(Center::class)) + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableScopes(Argument::type(User::class), AccompanyingPeriodVoter::SEE_DETAILS, Argument::type(Center::class)) ->willReturn($scopes); - $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, Argument::type(Center::class)) + $authorizationHelper->getReachableScopes(Argument::type(User::class), AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, Argument::type(Center::class)) ->willReturn($scopesConfidentials); yield new ListAccompanyingPeriod( @@ -80,7 +70,6 @@ class ListAccompanyingPeriodTest extends AbstractExportTest $rollingDateConverter, $listAccompanyingPeriodHelper, new FilterListAccompanyingPeriodHelper( - $security->reveal(), $centerRepository, $authorizationHelper->reveal(), $this->getParameters(true) @@ -92,7 +81,6 @@ class ListAccompanyingPeriodTest extends AbstractExportTest $rollingDateConverter, $listAccompanyingPeriodHelper, new FilterListAccompanyingPeriodHelper( - $security->reveal(), $centerRepository, $authorizationHelper->reveal(), $this->getParameters(false) diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php index 8415e2017..ae74c6fca 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterBetweenDatesTest.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace src\Bundle\ChillPersonBundle\Tests\Export\Filter\AccompanyingCourseFilters; use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Repository\UserRepositoryInterface; use Chill\MainBundle\Service\RollingDate\RollingDate; use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface; use Chill\MainBundle\Templating\Entity\UserRender; @@ -30,17 +31,20 @@ class ReferrerFilterBetweenDatesTest extends AbstractFilterTest private RollingDateConverterInterface $rollingDateConverter; private UserRender $userRender; + private UserRepositoryInterface $userRepository; + protected function setUp(): void { parent::setUp(); self::bootKernel(); $this->rollingDateConverter = self::getContainer()->get(RollingDateConverterInterface::class); $this->userRender = self::getContainer()->get(UserRender::class); + $this->userRepository = self::getContainer()->get(UserRepositoryInterface::class); } public function getFilter() { - return new ReferrerFilterBetweenDates($this->rollingDateConverter, $this->userRender); + return new ReferrerFilterBetweenDates($this->rollingDateConverter, $this->userRender, $this->userRepository); } public static function getFormData(): array @@ -61,6 +65,11 @@ class ReferrerFilterBetweenDatesTest extends AbstractFilterTest 'start_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'end_date' => new RollingDate(RollingDate::T_TODAY), ], + [ + 'accepted_referrers' => 'me', + 'start_date' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), + 'end_date' => new RollingDate(RollingDate::T_TODAY), + ], ]; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php index 45bb6df46..73088a9d4 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/ReferrerFilterTest.php @@ -55,6 +55,8 @@ final class ReferrerFilterTest extends AbstractFilterTest $data[] = ['accepted_referrers' => $u, 'date_calc' => new RollingDate(RollingDate::T_TODAY)]; } + $data[] = ['accepted_referrers' => 'me', 'date_calc' => new RollingDate(RollingDate::T_TODAY)]; + return $data; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterOnDateTest.php similarity index 96% rename from src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php rename to src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterOnDateTest.php index c0c2ec249..ad12a48ef 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/AccompanyingCourseFilters/StepFilterOnDateTest.php @@ -22,7 +22,7 @@ use Doctrine\ORM\EntityManagerInterface; * * @coversNothing */ -final class StepFilterTest extends AbstractFilterTest +final class StepFilterOnDateTest extends AbstractFilterTest { private StepFilterOnDate $filter; diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/GenderFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/GenderFilterTest.php index 7273e9abc..baf46de26 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/GenderFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/PersonFilters/GenderFilterTest.php @@ -41,17 +41,44 @@ final class GenderFilterTest extends AbstractFilterTest { return [ [ - 'accepted_genders' => ['man'], - ], - [ - 'accepted_genders' => ['woman'], - ], - [ - 'accepted_genders' => ['man', 'both'], + 'accepted_genders_entity' => [null, '1'], ], ]; } + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $customAssert): void + { + $filter = $this->getFilter(); + + $normalized = $filter->normalizeFormData($data); + $actual = $filter->denormalizeFormData($normalized, $version); + + self::assertIsArray($actual); + self::assertArrayHasKey('accepted_genders_entity', $actual); + } + + /** + * A list of data to normalize. + * + * @return iterable{array} + */ + public static function dataProviderFormDataToNormalize(): iterable + { + yield [ + ['accepted_genders_entity' => [null, '1']], + 1, + [], + ]; + yield [ + ['accepted_genders' => ['man']], + 1, + [], + ]; + } + public static function getQueryBuilders(): iterable { self::bootKernel(); diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/CreatorFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/CreatorFilterTest.php index e7c4ee8e6..ae7a6c244 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/CreatorFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/CreatorFilterTest.php @@ -50,6 +50,9 @@ class CreatorFilterTest extends AbstractFilterTest [ 'creators' => $creators, ], + [ + 'creators' => [...$creators, 'me'], + ], ]; } diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php index 46a1a95e1..9e071adb1 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/HandlingThirdPartyFilterTest.php @@ -15,6 +15,7 @@ use Chill\MainBundle\Test\Export\AbstractFilterTest; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\HandlingThirdPartyFilter; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository; use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender; use Doctrine\ORM\EntityManagerInterface; @@ -27,16 +28,19 @@ class HandlingThirdPartyFilterTest extends AbstractFilterTest { private ThirdPartyRender $thirdPartyRender; + private ThirdPartyRepository $thirdPartyRepository; + protected function setUp(): void { parent::setUp(); self::bootKernel(); $this->thirdPartyRender = self::getContainer()->get(ThirdPartyRender::class); + $this->thirdPartyRepository = self::getContainer()->get(ThirdPartyRepository::class); } public function getFilter() { - return new HandlingThirdPartyFilter($this->thirdPartyRender); + return new HandlingThirdPartyFilter($this->thirdPartyRender, $this->thirdPartyRepository); } public static function getFormData(): array diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/ReferrerFilterTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/ReferrerFilterTest.php index d185ac499..f298dea79 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/ReferrerFilterTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Filter/SocialWorkFilters/ReferrerFilterTest.php @@ -56,9 +56,57 @@ final class ReferrerFilterTest extends AbstractFilterTest ]; } + $data[] = [ + 'accepted_agents' => 'me', + 'agent_at' => new RollingDate(RollingDate::T_TODAY), + ]; + return $data; } + /** + * @dataProvider dataProviderFormDataToNormalize + */ + public function testDataNormalization(array $data, int $version, array $expected): void + { + $filter = $this->getFilter(); + + $normalized = $filter->normalizeFormData($data); + $actual = $filter->denormalizeFormData($normalized, $version); + + self::assertEqualsCanonicalizing(array_keys($expected), array_keys($actual)); + } + + public static function dataProviderFormDataToNormalize(): iterable + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $users = array_slice($em->getRepository(User::class)->findAll(), 0, 1); + + yield [ + [ + 'accepted_agents' => [$users[0]], + 'agent_at' => new RollingDate(RollingDate::T_TODAY), + ], + 1, + [ + 'accepted_agents' => [$users[0]], + 'agent_at' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + + yield [ + [ + 'accepted_agents' => [$users[0]], + ], + 1, + [ + 'accepted_agents' => [$users[0]], + 'agent_at' => new RollingDate(RollingDate::T_TODAY), + ], + ]; + } + public static function getQueryBuilders(): iterable { self::bootKernel(); diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php index 0cdbea864..68df9b427 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Helper/FilterListAccompanyingPeriodHelperTest.php @@ -16,7 +16,7 @@ use Chill\MainBundle\Entity\Scope; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\ScopeRepositoryInterface; -use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Export\Helper\FilterListAccompanyingPeriodHelper; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; @@ -63,26 +63,23 @@ final class FilterListAccompanyingPeriodHelperTest extends KernelTestCase if (null === $user) { throw new \RuntimeException('no user found'); } - $security = $this->prophesize(Security::class); - $security->getUser()->willReturn($user); // mock authorization helper $scopes = $this->scopeRepository->findAll(); $scopesConfidentials = [] !== $scopes ? [$scopes[0]] : []; - $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class); - $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_DETAILS, Argument::type(Center::class)) + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_DETAILS, Argument::type(Center::class)) ->willReturn($scopes); - $authorizationHelper->getReachableScopes(AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, Argument::type(Center::class)) + $authorizationHelper->getReachableScopes($user, AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL, Argument::type(Center::class)) ->willReturn($scopesConfidentials); $filter = new FilterListAccompanyingPeriodHelper( - $security->reveal(), $this->centerRepository, $authorizationHelper->reveal(), $parameterBag ); - $filter->addFilterAccompanyingPeriods($qb, [], $this->getACL(), []); + $filter->addFilterAccompanyingPeriods($qb, [], $this->getACL(), $user, []); $qb->setMaxResults(1); $result = $qb->getQuery()->getResult(); diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php index 82a2d1d40..306889547 100644 --- a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/AccompanyingPeriodValidity.php @@ -18,7 +18,7 @@ class AccompanyingPeriodValidity extends Constraint { public $messageSocialIssueCannotBeDeleted = 'The social %name% issue cannot be deleted because it is associated with an activity or an action'; - public function getTargets() + public function getTargets(): string { return self::CLASS_CONSTRAINT; } diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ConfidentialCourseMustHaveReferrer.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ConfidentialCourseMustHaveReferrer.php index 97e5be385..31525b939 100644 --- a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ConfidentialCourseMustHaveReferrer.php +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/ConfidentialCourseMustHaveReferrer.php @@ -18,7 +18,10 @@ class ConfidentialCourseMustHaveReferrer extends Constraint { public string $message = 'A confidential parcours must have a referrer'; - public function getTargets() + /** + * @return array + */ + public function getTargets(): array { return [self::CLASS_CONSTRAINT]; } diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php index 8e832da6e..74e5dafcd 100644 --- a/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/AccompanyingPeriod/LocationValidity.php @@ -20,7 +20,7 @@ class LocationValidity extends Constraint public $messagePersonLocatedMustBeAssociated = "The person where the course is located must be associated to the course. Change course's location before removing the person."; - public function getTargets() + public function getTargets(): string { return self::CLASS_CONSTRAINT; } diff --git a/src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolder.php b/src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolder.php index 5f45f69ac..b30a389c8 100644 --- a/src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolder.php +++ b/src/Bundle/ChillPersonBundle/Validator/Constraints/Household/MaxHolder.php @@ -20,7 +20,7 @@ class MaxHolder extends Constraint public $messageInfinity = 'household.max_holder_overflowed_infinity'; - public function getTargets() + public function getTargets(): string { return self::CLASS_CONSTRAINT; } diff --git a/src/Bundle/ChillReportBundle/Export/Export/ReportList.php b/src/Bundle/ChillReportBundle/Export/Export/ReportList.php index 1761e3522..da91602de 100644 --- a/src/Bundle/ChillReportBundle/Export/Export/ReportList.php +++ b/src/Bundle/ChillReportBundle/Export/Export/ReportList.php @@ -26,15 +26,21 @@ use Chill\PersonBundle\Export\Declarations; use Chill\ReportBundle\Entity\Report; use Chill\ReportBundle\Security\Authorization\ReportVoter; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\NativeQuery; use Doctrine\ORM\Query; +use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Validator\Constraints\Callback; use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Contracts\Translation\TranslatorInterface; +/** + * @template-implements ListInterface, address_date: \DateTime}> + */ class ReportList implements ExportElementValidatedInterface, ListInterface { + use \Chill\MainBundle\Export\ExportDataNormalizerTrait; protected array $fields = [ 'person_id', 'person_firstName', 'person_lastName', 'person_birthdate', 'person_placeOfBirth', 'person_gender', 'person_memo', 'person_email', 'person_phonenumber', @@ -97,6 +103,21 @@ class ReportList implements ExportElementValidatedInterface, ListInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['fields' => $formData['fields'], 'address_date' => $this->normalizeDate($formData['address_date'])]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['fields' => $formData['fields'], 'address_date' => $this->denormalizeDate($formData['address_date'])]; + } + public function getFormDefaultData(): array { return ['address_date' => new \DateTime()]; @@ -253,7 +274,7 @@ class ReportList implements ExportElementValidatedInterface, ListInterface } } - public function getQueryKeys($data) + public function getQueryKeys($data): array { $fields = []; @@ -267,12 +288,12 @@ class ReportList implements ExportElementValidatedInterface, ListInterface return [...$fields, ...\array_keys($this->slugs)]; } - public function getResult($query, $data) + public function getResult(QueryBuilder|NativeQuery $query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array { return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return $this->translator->trans( "List for report '%type%'", @@ -282,12 +303,12 @@ class ReportList implements ExportElementValidatedInterface, ListInterface ); } - public function getType() + public function getType(): string { return 'report'; } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): QueryBuilder { $centers = array_map(static fn ($el) => $el['center'], $acl); @@ -413,7 +434,7 @@ class ReportList implements ExportElementValidatedInterface, ListInterface return [Declarations::PERSON_TYPE, 'report']; } - public function validateForm($data, ExecutionContextInterface $context) + public function validateForm($data, ExecutionContextInterface $context): void { // get the field starting with address_ $addressFields = array_filter( diff --git a/src/Bundle/ChillReportBundle/Export/Export/ReportListProvider.php b/src/Bundle/ChillReportBundle/Export/Export/ReportListProvider.php index fcc21e726..55218f3ce 100644 --- a/src/Bundle/ChillReportBundle/Export/Export/ReportListProvider.php +++ b/src/Bundle/ChillReportBundle/Export/Export/ReportListProvider.php @@ -53,7 +53,7 @@ class ReportListProvider implements ExportElementsProviderInterface $this->customFieldProvider = $customFieldProvider; } - public function getExportElements() + public function getExportElements(): iterable { $groups = $this->em->getRepository(CustomFieldsGroup::class) ->findBy(['entity' => Report::class]); diff --git a/src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php b/src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php index 0346f2ab3..bbfcdbb1b 100644 --- a/src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php +++ b/src/Bundle/ChillReportBundle/Export/Filter/ReportDateFilter.php @@ -11,6 +11,7 @@ declare(strict_types=1); namespace Chill\ReportBundle\Export\Filter; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -26,7 +27,7 @@ class ReportDateFilter implements FilterInterface return null; } - public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data) + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void { $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( @@ -52,12 +53,12 @@ class ReportDateFilter implements FilterInterface ); } - public function applyOn() + public function applyOn(): string { return 'report'; } - public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder) + public function buildForm(\Symfony\Component\Form\FormBuilderInterface $builder): void { $builder->add('date_from', PickRollingDateType::class, [ 'label' => 'Report is after this date', @@ -68,12 +69,27 @@ class ReportDateFilter implements FilterInterface ]); } + public function getNormalizationVersion(): int + { + return 1; + } + + public function normalizeFormData(array $formData): array + { + return ['date_from' => $formData['date_from']->normalize(), 'date_to' => $formData['date_to']->normalize()]; + } + + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['date_from' => RollingDate::fromNormalized($formData['date_from']), 'date_to' => RollingDate::fromNormalized($formData['date_to'])]; + } + public function getFormDefaultData(): array { return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)]; } - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array { return ['Filtered by report\'s date: ' .'between %date_from% and %date_to%', [ @@ -82,7 +98,7 @@ class ReportDateFilter implements FilterInterface ], ]; } - public function getTitle() + public function getTitle(): string|\Symfony\Contracts\Translation\TranslatableInterface { return 'Filter by report\'s date'; } diff --git a/utils/rector/src/Rector/ChillBundleAddNormalizationMethodsOnExportRector.php b/utils/rector/src/Rector/ChillBundleAddNormalizationMethodsOnExportRector.php new file mode 100644 index 000000000..e4897f8f3 --- /dev/null +++ b/utils/rector/src/Rector/ChillBundleAddNormalizationMethodsOnExportRector.php @@ -0,0 +1,359 @@ +classAnalyzer->hasImplements($node, FilterInterface::class) + && !$this->classAnalyzer->hasImplements($node, AggregatorInterface::class) + && !$this->classAnalyzer->hasImplements($node, ExportInterface::class) + && !$this->classAnalyzer->hasImplements($node, DirectExportInterface::class) + && !$this->classAnalyzer->hasImplements($node, ListInterface::class) + && !$this->classAnalyzer->hasImplements($node, FormatterInterface::class) + ) { + return null; + } + + $hasDenormalizeMethod = false; + $hasNormalizedMethod = false; + $hasGetVersionMethod = false; + $buildFormStmtIndex = null; + $buildForm = null; + $hasTraitHelper = false; + + foreach ($node->stmts as $k => $stmt) { + if ($stmt instanceof Node\Stmt\TraitUse) { + $trait = $stmt->traits[0]; + + if (str_contains($trait->toString(), ExportDataNormalizerTrait::class)) { + $hasTraitHelper = true; + } + + continue; + } + if (!$stmt instanceof Node\Stmt\ClassMethod) { + continue; + } + + if ('buildForm' === $stmt->name->name) { + $buildForm = $stmt; + $buildFormStmtIndex = $k; + } elseif ('normalizeFormData' === $stmt->name->name) { + $hasNormalizedMethod = true; + } elseif ('denormalizeFormData' === $stmt->name->name) { + $hasDenormalizeMethod = true; + } elseif ('getNormalizationVersion' === $stmt->name->name) { + $hasGetVersionMethod = true; + } + } + + if ($hasDenormalizeMethod && $hasNormalizedMethod && $hasGetVersionMethod) { + return null; + } + + $toAddBefore = []; + $toAdd = []; + + $stmtBefore = array_slice($node->stmts, 0, $buildFormStmtIndex, false); + $stmtAfter = array_slice($node->stmts, $buildFormStmtIndex + 1); + $propertiesOnForm = $this->getPropertiesOnForm($buildForm); + + // if the trait is not present, we add it into the statements + if ( + !$hasTraitHelper + && (!$hasNormalizedMethod || !$hasDenormalizeMethod) + && array_reduce( + $propertiesOnForm, + function (bool $carry, string $item): bool { + if ('entity' === $item || 'date' === $item) { + return true; + } + + return $carry; + }, + false, + )) { + $toAddBefore[] = new Node\Stmt\TraitUse([ + new Node\Name('\\'.ExportDataNormalizerTrait::class), + ]); + } + + // if we do not have the `getNormalizerVersion` method + if (!$hasGetVersionMethod) { + $toAdd[] = $this->buildGetNormalizationVersionMethod(); + } + + // if we do not have the `normalizeFormData` method + if (!$hasNormalizedMethod) { + $toAdd[] = $this->buildNormalizeFormDataMethod($propertiesOnForm); + } + + if (!$hasDenormalizeMethod) { + $toAdd[] = $this->buildDenormalizeFormDataMethod($propertiesOnForm); + } + + $node->stmts = [ + ...array_values($toAddBefore), + ...array_values($stmtBefore), + $buildForm, + ...array_values($toAdd), + ...array_values($stmtAfter), + ]; + + return $node; + } + + private function buildDenormalizeFormDataMethod(array $propertiesOnForm): Node\Stmt\ClassMethod + { + $array = new Node\Expr\Array_([]); + + foreach ($propertiesOnForm as $name => $kind) { + $arrayDimFetch = new Node\Expr\ArrayDimFetch( + new Node\Expr\Variable('formData'), + new Node\Scalar\String_($name), + ); + + $array->items[] = new Node\Expr\ArrayItem( + match ($kind) { + 'entity' => new Node\Expr\MethodCall( + new Node\Expr\Variable('this'), + new Node\Identifier('denormalizeDoctrineEntity'), + [ + new Node\Arg($arrayDimFetch), + new Node\Arg( + new PropertyFetch( + new Node\Expr\Variable('this'), + 'someRepository', + ), + ), + ], + ), + 'rolling_date' => new Node\Expr\StaticCall( + new Node\Name\FullyQualified(RollingDate::class), + 'fromNormalized', + [new Node\Arg($arrayDimFetch)], + ), + 'date' => new Node\Expr\MethodCall( + new Node\Expr\Variable('this'), + new Node\Identifier('denormalizeDate'), + [ + new Node\Arg($arrayDimFetch), + ], + ), + 'scalar' => $arrayDimFetch, + default => $arrayDimFetch, + }, + new Node\Scalar\String_($name), + ); + } + + return new Node\Stmt\ClassMethod( + 'denormalizeFormData', + [ + 'flags' => Node\Stmt\Class_::MODIFIER_PUBLIC, + 'returnType' => new Node\Identifier('array'), + 'params' => [ + new Node\Param(new Node\Expr\Variable('formData'), type: new Node\Identifier('array')), + new Node\Param(new Node\Expr\Variable('fromVersion'), type: new Node\Identifier('int')), + ], + 'stmts' => [ + new Node\Stmt\Return_($array), + ], + ], + ); + } + + private function buildNormalizeFormDataMethod(array $propertiesOnForm): Node\Stmt\ClassMethod + { + $array = new Node\Expr\Array_([]); + + foreach ($propertiesOnForm as $name => $kind) { + $arrayDimFetch = new Node\Expr\ArrayDimFetch( + new Node\Expr\Variable('formData'), + new Node\Scalar\String_($name), + ); + + $array->items[] = new Node\Expr\ArrayItem( + match ($kind) { + 'entity' => new Node\Expr\MethodCall( + new Node\Expr\Variable('this'), + new Node\Identifier('normalizeDoctrineEntity'), + [ + new Node\Arg($arrayDimFetch), + ], + ), + 'rolling_date' => new Node\Expr\MethodCall( + $arrayDimFetch, + new Node\Identifier('normalize'), + ), + 'date' => new Node\Expr\MethodCall( + new Node\Expr\Variable('this'), + new Node\Identifier('normalizeDate'), + [ + new Node\Arg($arrayDimFetch), + ], + ), + 'scalar' => $arrayDimFetch, + default => $arrayDimFetch, + }, + new Node\Scalar\String_($name), + ); + } + + return new Node\Stmt\ClassMethod( + 'normalizeFormData', + [ + 'flags' => Node\Stmt\Class_::MODIFIER_PUBLIC, + 'returnType' => new Node\Identifier('array'), + 'params' => [ + new Node\Param(new Node\Expr\Variable('formData'), type: new Node\Identifier('array')), + ], + 'stmts' => [ + new Node\Stmt\Return_($array), + ], + ], + ); + } + + private function buildGetNormalizationVersionMethod(): Node\Stmt\ClassMethod + { + return new Node\Stmt\ClassMethod( + 'getNormalizationVersion', + [ + 'flags' => Node\Stmt\Class_::MODIFIER_PUBLIC, + 'returnType' => new Node\Identifier('int'), + 'stmts' => [ + new Node\Stmt\Return_( + new Node\Scalar\LNumber(1) + ), + ], + ], + ); + } + + /** + * @return array + */ + private function getPropertiesOnForm(Node\Stmt\ClassMethod $buildFormMethod): array + { + $builderName = $buildFormMethod->params[0]->var->name; + $values = []; + + foreach ($buildFormMethod->stmts as $stmt) { + if ($stmt instanceof Node\Stmt\Expression + // it must be a method call + && $stmt->expr instanceof Node\Expr\MethodCall + && 'add' === $stmt->expr->name->toString() + ) { + $newValues = $this->handleMethodCallAdd($stmt->expr, $builderName); + + if (false === $newValues) { + continue; + } + + $values = [...$values, ...$newValues]; + } + } + + return $values; + } + + private function handleMethodCallAdd(Node\Expr\MethodCall $call, string $builderName): array|false + { + if ($call->var instanceof Node\Expr\Variable) { + // in this case, the call is done on the form builder: $formBuilder->add + // or the last method call on the form builder, for instance $formBuilder->add( )->add( ) + if ($builderName !== $call->var->name) { + return false; + } + } elseif ($call->var instanceof Node\Expr\MethodCall && 'add' === $call->var->name->toString()) { + // in this case, we have a chained method call: something like $formbuilder->add()->add(). + // we have to go deeper into the call to get the information from them + $previous = $this->handleMethodCallAdd($call->var, $builderName); + + if (false === $previous) { + return false; + } + } + + $arg0 = $call->args[0] ?? null; + + if (null === $arg0) { + throw new \UnexpectedValueException("The first argument of an 'add' call method is empty"); + } + if (!$arg0->value instanceof Node\Scalar\String_) { + throw new \UnexpectedValueException("The first argument of an 'add' call is not a string"); + } + + $key = $arg0->value->value; + /** @var Node\Expr\ClassConstFetch $argType */ + $argType = $call->args[1]->value; + + return [ + ...$previous ?? [], + $key => match($argType->class->toString()) { + EntityType::class, PickUserDynamicType::class, PickUserLocationType::class, PickThirdpartyDynamicType::class, Select2CountryType::class => 'entity', + PickRollingDateType::class => 'rolling_date', + ChillDateType::class, ChillDateTimeType::class, DateTimeType::class, DateType::class => 'date', + default => 'scalar', + }, + ]; + } +} diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/aggregator-with-no-method-get-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/aggregator-with-no-method-get-form-default-data.php.inc index aa373e629..ea2c43cc3 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/aggregator-with-no-method-get-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/aggregator-with-no-method-get-form-default-data.php.inc @@ -46,7 +46,7 @@ class MyClass implements ExportInterface // TODO: Implement getType() method. } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // TODO: Implement initiateQuery() method. } @@ -115,7 +115,7 @@ class MyClass implements ExportInterface // TODO: Implement getType() method. } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // TODO: Implement initiateQuery() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data-with-chained-builder.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data-with-chained-builder.php.inc index ed46f6381..a99243521 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data-with-chained-builder.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data-with-chained-builder.php.inc @@ -2,6 +2,7 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -11,7 +12,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -57,6 +58,7 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -66,7 +68,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc index 5429d3c82..6b6f22424 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-multiple-reuse-data-on-form-default-data.php.inc @@ -2,6 +2,7 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -11,7 +12,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -55,6 +56,7 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -64,7 +66,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc index 285c16b50..326c9d2c6 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-no-data-on-builder.php.inc @@ -2,6 +2,7 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -11,7 +12,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -53,6 +54,7 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -62,7 +64,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc index b2e78e49c..9b0666f36 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-reuse-data-on-form-default-data.php.inc @@ -2,6 +2,7 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -10,7 +11,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -49,6 +50,7 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Chill\MainBundle\Form\Type\PickRollingDateType; use Chill\MainBundle\Service\RollingDate\RollingDate; @@ -57,7 +59,7 @@ use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc index 687bd9d0c..ceb855d10 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/filter-with-no-method-get-form-default-data.php.inc @@ -2,13 +2,14 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } @@ -44,13 +45,14 @@ class MyClass implements FilterInterface namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/list-with-no-method-get-form-default-data.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/list-with-no-method-get-form-default-data.php.inc index 99cea7155..44f7f78d1 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/list-with-no-method-get-form-default-data.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/list-with-no-method-get-form-default-data.php.inc @@ -47,7 +47,7 @@ class MyClass implements ListInterface // TODO: Implement getType() method. } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // TODO: Implement initiateQuery() method. } @@ -117,7 +117,7 @@ class MyClass implements ListInterface // TODO: Implement getType() method. } - public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder { // TODO: Implement initiateQuery() method. } diff --git a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc index 2fefc908d..517df6392 100644 --- a/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc +++ b/utils/rector/tests/ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector/Fixture/skip-filter-existing-get-form-default-data-method.php.inc @@ -2,13 +2,14 @@ namespace Utils\Rector\Tests\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector\Fixture; +use Chill\MainBundle\Export\ExportGenerationContext; use Chill\MainBundle\Export\FilterInterface; use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; class MyClass implements FilterInterface { - public function describeAction($data, $format = 'string') + public function describeAction($data, ExportGenerationContext $context) { // TODO: Implement describeAction() method. } diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/ChillBundleAddNormalizationMethodsOnExportRectorTest.php b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/ChillBundleAddNormalizationMethodsOnExportRectorTest.php new file mode 100644 index 000000000..81d3655a6 --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/ChillBundleAddNormalizationMethodsOnExportRectorTest.php @@ -0,0 +1,40 @@ +doTestFile($file); + } + + public static function provideData(): \Iterator + { + return self::yieldFilesFromDirectory(__DIR__.'/Fixture'); + } + + public function provideConfigFilePath(): string + { + return __DIR__.'/config/config.php'; + } +} diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-chained-field.php.inc b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-chained-field.php.inc new file mode 100644 index 000000000..22c29577a --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-chained-field.php.inc @@ -0,0 +1,120 @@ +add('field', ChoiceType::class, [ + 'choices' => ['one', 'two', 'three', 'four', 'five'], + 'multiple' => false, + ]) + ->add('entity', EntityType::class, []) + ->add('user', \Chill\MainBundle\Form\Type\PickUserDynamicType::class, []) + ->add('rolling_date', \Chill\MainBundle\Form\Type\PickRollingDateType::class) + ->add('date', \Chill\MainBundle\Form\Type\ChillDateType::class, []); + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> +----- +add('field', ChoiceType::class, [ + 'choices' => ['one', 'two', 'three', 'four', 'five'], + 'multiple' => false, + ]) + ->add('entity', EntityType::class, []) + ->add('user', \Chill\MainBundle\Form\Type\PickUserDynamicType::class, []) + ->add('rolling_date', \Chill\MainBundle\Form\Type\PickRollingDateType::class) + ->add('date', \Chill\MainBundle\Form\Type\ChillDateType::class, []); + } + public function getNormalizationVersion(): int + { + return 1; + } + public function normalizeFormData(array $formData): array + { + return ['field' => $formData['field'], 'entity' => $this->normalizeDoctrineEntity($formData['entity']), 'user' => $this->normalizeDoctrineEntity($formData['user']), 'rolling_date' => $formData['rolling_date']->normalize(), 'date' => $this->normalizeDate($formData['date'])]; + } + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['field' => $formData['field'], 'entity' => $this->denormalizeDoctrineEntity($formData['entity'], $this->someRepository), 'user' => $this->denormalizeDoctrineEntity($formData['user'], $this->someRepository), 'rolling_date' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['rolling_date']), 'date' => $this->denormalizeDate($formData['date'])]; + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-simple-field.php.inc b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-simple-field.php.inc new file mode 100644 index 000000000..94b8a9c28 --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-simple-field.php.inc @@ -0,0 +1,126 @@ +add('field', ChoiceType::class, [ + 'choices' => ['one', 'two', 'three', 'four', 'five'], + 'multiple' => false, + ]); + + $builder->add('entity', EntityType::class, []); + + $builder->add('user', \Chill\MainBundle\Form\Type\PickUserDynamicType::class, []); + + $builder->add('rolling_date', \Chill\MainBundle\Form\Type\PickRollingDateType::class); + + $builder->add('date', \Chill\MainBundle\Form\Type\ChillDateType::class, []); + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> +----- +add('field', ChoiceType::class, [ + 'choices' => ['one', 'two', 'three', 'four', 'five'], + 'multiple' => false, + ]); + + $builder->add('entity', EntityType::class, []); + + $builder->add('user', \Chill\MainBundle\Form\Type\PickUserDynamicType::class, []); + + $builder->add('rolling_date', \Chill\MainBundle\Form\Type\PickRollingDateType::class); + + $builder->add('date', \Chill\MainBundle\Form\Type\ChillDateType::class, []); + } + public function getNormalizationVersion(): int + { + return 1; + } + public function normalizeFormData(array $formData): array + { + return ['field' => $formData['field'], 'entity' => $this->normalizeDoctrineEntity($formData['entity']), 'user' => $this->normalizeDoctrineEntity($formData['user']), 'rolling_date' => $formData['rolling_date']->normalize(), 'date' => $this->normalizeDate($formData['date'])]; + } + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['field' => $formData['field'], 'entity' => $this->denormalizeDoctrineEntity($formData['entity'], $this->someRepository), 'user' => $this->denormalizeDoctrineEntity($formData['user'], $this->someRepository), 'rolling_date' => \Chill\MainBundle\Service\RollingDate\RollingDate::fromNormalized($formData['rolling_date']), 'date' => $this->denormalizeDate($formData['date'])]; + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-with-already-trait.php.inc b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-with-already-trait.php.inc new file mode 100644 index 000000000..0e74e7280 --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/add-method-with-already-trait.php.inc @@ -0,0 +1,109 @@ +add('entity', EntityType::class, []); + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> +----- +add('entity', EntityType::class, []); + } + public function getNormalizationVersion(): int + { + return 1; + } + public function normalizeFormData(array $formData): array + { + return ['entity' => $this->normalizeDoctrineEntity($formData['entity'])]; + } + public function denormalizeFormData(array $formData, int $fromVersion): array + { + return ['entity' => $this->denormalizeDoctrineEntity($formData['entity'], $this->someRepository)]; + } + + public function getFormDefaultData(): array + { + return []; + } + + public function describeAction($data, \Chill\MainBundle\Export\ExportGenerationContext $context) + { + return []; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(\Doctrine\ORM\QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext) + { + // nothing to add here + } + + public function applyOn() + { + return ['something']; + } +} + +?> diff --git a/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/skip-existing-method.php.inc b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/skip-existing-method.php.inc new file mode 100644 index 000000000..56ee32b29 --- /dev/null +++ b/utils/rector/tests/ChillBundleAddNormalizationMethodsOnExportRector/Fixture/skip-existing-method.php.inc @@ -0,0 +1,55 @@ +rule(Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class); +}; From 3b82ab0e7fb3aa9d68ffcba8d22e50e6a0f7a5d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 8 Jul 2025 15:59:04 +0200 Subject: [PATCH 65/83] Release v4.0.0 --- .changes/unreleased/DX-20250401-144728.yaml | 6 -- .changes/unreleased/DX-20250407-121010.yaml | 71 ------------------ .../unreleased/Feature-20250211-142243.yaml | 6 -- .../unreleased/Fixed-20250604-165550.yaml | 6 -- .../unreleased/Fixed-20250611-164623.yaml | 6 -- .../unreleased/Fixed-20250702-135534.yaml | 6 -- .changes/unreleased/UX-20250617-192650.yaml | 6 -- .changes/v4.0.0.md | 74 ++++++++++++++++++ CHANGELOG.md | 75 +++++++++++++++++++ 9 files changed, 149 insertions(+), 107 deletions(-) delete mode 100644 .changes/unreleased/DX-20250401-144728.yaml delete mode 100644 .changes/unreleased/DX-20250407-121010.yaml delete mode 100644 .changes/unreleased/Feature-20250211-142243.yaml delete mode 100644 .changes/unreleased/Fixed-20250604-165550.yaml delete mode 100644 .changes/unreleased/Fixed-20250611-164623.yaml delete mode 100644 .changes/unreleased/Fixed-20250702-135534.yaml delete mode 100644 .changes/unreleased/UX-20250617-192650.yaml create mode 100644 .changes/v4.0.0.md diff --git a/.changes/unreleased/DX-20250401-144728.yaml b/.changes/unreleased/DX-20250401-144728.yaml deleted file mode 100644 index a08ba1760..000000000 --- a/.changes/unreleased/DX-20250401-144728.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: DX -body: Allow TranslatableMessage in flash messages -time: 2025-04-01T14:47:28.814268801+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/DX-20250407-121010.yaml b/.changes/unreleased/DX-20250407-121010.yaml deleted file mode 100644 index 1f8a0496a..000000000 --- a/.changes/unreleased/DX-20250407-121010.yaml +++ /dev/null @@ -1,71 +0,0 @@ -kind: DX -body: | - Rewrite exports to run them asynchronously - -changelog: | - - Add new methods to serialize data using the rector rule - - Remove all references to the Request in filters, aggregators, filters. Actually, the most frequent occurence is `$security->getUser()`. - - Refactor manually the initializeQuery method - - Remove the injection of ExportManager into the constructor of each export element: - - ```diff - - - class MyFormatter implements FormatterInterface - + class MyFormatter implements FormatterInterface, \Chill\MainBundle\Export\ExportManagerAwareInterface - { - + use \Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; - - - public function __construct(private ExportManager $exportmanager) {} - - public function MyMethod(): void - { - - $this->exportManager->getFilter('alias'); - + $this->getExportManager()->getFilter('alias'); - } - } - ``` - - configure messenger to handle export in a queue: - - ```diff - # config/packages/messenger.yaml - framework: - messenger: - routing: - + 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority - ``` - - - add missing methods to exports, aggregators, filters, formatter: - - ```php - public function normalizeFormData(array $formData): array; - - public function denormalizeFormData(array $formData, int $fromVersion): array; - ``` - - There are rector rules to generate those methods: - - - `Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector` - - See: - - ```php - // upgrade chill exports - $rectorConfig->rules([\Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class]); - ``` - - This rule will create most of the work necessary, but some manuals changes are still necessary: - - - we must set manually the correct repository for method `denormalizeDoctrineEntity`; - - when the form data contains some entities, and the form type is not one of EntityType::class, PickUserDynamicType::class, PickUserLocationType::class, PickThirdpartyDynamicType::class, Select2CountryType::class, then we must handle the normalization manually (using the `\Chill\MainBundle\Export\ExportDataNormalizerTrait`) - - - - - - - - - time: 2025-04-07T12:10:10.682561327+02:00 -custom: - Issue: "" - SchemaChange: Add columns or tables diff --git a/.changes/unreleased/Feature-20250211-142243.yaml b/.changes/unreleased/Feature-20250211-142243.yaml deleted file mode 100644 index 4f0b25e88..000000000 --- a/.changes/unreleased/Feature-20250211-142243.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Feature -body: Allow the merge of two accompanying period works -time: 2025-02-11T14:22:43.134106669+01:00 -custom: - Issue: "359" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250604-165550.yaml b/.changes/unreleased/Fixed-20250604-165550.yaml deleted file mode 100644 index 0544c9402..000000000 --- a/.changes/unreleased/Fixed-20250604-165550.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Fixed -body: Display the list of participant in the results, even if there is only one participant and that the search result display the requestor -time: 2025-06-04T16:55:50.107852336+02:00 -custom: - Issue: "390" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250611-164623.yaml b/.changes/unreleased/Fixed-20250611-164623.yaml deleted file mode 100644 index 8bb956c34..000000000 --- a/.changes/unreleased/Fixed-20250611-164623.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Fixed -body: Fix admin entity edit actions for event admin entities and activity reason (category) entities -time: 2025-06-11T16:46:23.113506434+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250702-135534.yaml b/.changes/unreleased/Fixed-20250702-135534.yaml deleted file mode 100644 index 75a911301..000000000 --- a/.changes/unreleased/Fixed-20250702-135534.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Fixed -body: 'Fix translations for social action fields in admin form: results, goals, evaluations' -time: 2025-07-02T13:55:34.599050626+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/UX-20250617-192650.yaml b/.changes/unreleased/UX-20250617-192650.yaml deleted file mode 100644 index 810758b10..000000000 --- a/.changes/unreleased/UX-20250617-192650.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: UX -body: Improve labeling of fields in person resource creation form -time: 2025-06-17T19:26:50.599703116+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/v4.0.0.md b/.changes/v4.0.0.md new file mode 100644 index 000000000..6063736a8 --- /dev/null +++ b/.changes/v4.0.0.md @@ -0,0 +1,74 @@ +## v4.0.0 - 2025-07-08 +### Feature +* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works +### Fixed +* ([#390](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/390)) Display the list of participant in the results, even if there is only one participant and that the search result display the requestor +* Fix admin entity edit actions for event admin entities and activity reason (category) entities +* Fix translations for social action fields in admin form: results, goals, evaluations +### DX +* Rewrite exports to run them asynchronously + + **Schema Change**: Add columns or tables +* Allow TranslatableMessage in flash messages +### UX +* Improve labeling of fields in person resource creation form + + +**Release notes** + +- Add new methods to serialize data using the rector rule +- Remove all references to the Request in filters, aggregators, filters. Actually, the most frequent occurence is `$security->getUser()`. +- Refactor manually the initializeQuery method +- Remove the injection of ExportManager into the constructor of each export element: + + ```diff + + - class MyFormatter implements FormatterInterface + + class MyFormatter implements FormatterInterface, \Chill\MainBundle\Export\ExportManagerAwareInterface + { + + use \Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; + + - public function __construct(private ExportManager $exportmanager) {} + + public function MyMethod(): void + { + - $this->exportManager->getFilter('alias'); + + $this->getExportManager()->getFilter('alias'); + } + } + ``` +- configure messenger to handle export in a queue: + +```diff +# config/packages/messenger.yaml +framework: + messenger: + routing: ++ 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority +``` + +- add missing methods to exports, aggregators, filters, formatter: + + ```php + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + ``` + + There are rector rules to generate those methods: + + - `Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector` + + See: + + ```php + // upgrade chill exports + $rectorConfig->rules([\Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class]); + ``` + + This rule will create most of the work necessary, but some manuals changes are still necessary: + + - we must set manually the correct repository for method `denormalizeDoctrineEntity`; + - when the form data contains some entities, and the form type is not one of EntityType::class, PickUserDynamicType::class, PickUserLocationType::class, PickThirdpartyDynamicType::class, Select2CountryType::class, then we must handle the normalization manually (using the `\Chill\MainBundle\Export\ExportDataNormalizerTrait`) + + diff --git a/CHANGELOG.md b/CHANGELOG.md index a00af6c44..834120aea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,81 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## v4.0.0 - 2025-07-08 +### Feature +* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works +### Fixed +* ([#390](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/390)) Display the list of participant in the results, even if there is only one participant and that the search result display the requestor +* Fix admin entity edit actions for event admin entities and activity reason (category) entities +* Fix translations for social action fields in admin form: results, goals, evaluations +### DX +* Rewrite exports to run them asynchronously + + **Schema Change**: Add columns or tables +* Allow TranslatableMessage in flash messages +### UX +* Improve labeling of fields in person resource creation form + + +**Release notes** + +- Add new methods to serialize data using the rector rule +- Remove all references to the Request in filters, aggregators, filters. Actually, the most frequent occurence is `$security->getUser()`. +- Refactor manually the initializeQuery method +- Remove the injection of ExportManager into the constructor of each export element: + + ```diff + + - class MyFormatter implements FormatterInterface + + class MyFormatter implements FormatterInterface, \Chill\MainBundle\Export\ExportManagerAwareInterface + { + + use \Chill\MainBundle\Export\Helper\ExportManagerAwareTrait; + + - public function __construct(private ExportManager $exportmanager) {} + + public function MyMethod(): void + { + - $this->exportManager->getFilter('alias'); + + $this->getExportManager()->getFilter('alias'); + } + } + ``` +- configure messenger to handle export in a queue: + +```diff +# config/packages/messenger.yaml +framework: + messenger: + routing: ++ 'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority +``` + +- add missing methods to exports, aggregators, filters, formatter: + + ```php + public function normalizeFormData(array $formData): array; + + public function denormalizeFormData(array $formData, int $fromVersion): array; + ``` + + There are rector rules to generate those methods: + + - `Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector` + + See: + + ```php + // upgrade chill exports + $rectorConfig->rules([\Chill\Utils\Rector\Rector\ChillBundleAddNormalizationMethodsOnExportRector::class]); + ``` + + This rule will create most of the work necessary, but some manuals changes are still necessary: + + - we must set manually the correct repository for method `denormalizeDoctrineEntity`; + - when the form data contains some entities, and the form type is not one of EntityType::class, PickUserDynamicType::class, PickUserLocationType::class, PickThirdpartyDynamicType::class, Select2CountryType::class, then we must handle the normalization manually (using the `\Chill\MainBundle\Export\ExportDataNormalizerTrait`) + + + ## v3.12.1 - 2025-06-30 ### Fixed * Fix loading of the list of documents From 4cfdcb2f02804edc64928ad3c00a892a1570f188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 8 Jul 2025 17:00:24 +0200 Subject: [PATCH 66/83] Release v4.0.1 with fix in package.json file --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 73fa14d86..fc50ba29c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "@hotwired/stimulus": "^3.0.0", "@luminateone/eslint-baseline": "^1.0.9", "@symfony/stimulus-bridge": "^3.2.0", - "@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets", "@symfony/webpack-encore": "^4.1.0", "@tsconfig/node20": "^20.1.4", "@types/dompurify": "^3.0.5", From a7e278204f687de0c472f7b4595b0604375ca0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Wed, 9 Jul 2025 09:22:28 +0200 Subject: [PATCH 67/83] fix changelog --- .changes/v4.0.1.md | 4 ++++ CHANGELOG.md | 5 +++++ 2 files changed, 9 insertions(+) create mode 100644 .changes/v4.0.1.md diff --git a/.changes/v4.0.1.md b/.changes/v4.0.1.md new file mode 100644 index 000000000..882b78709 --- /dev/null +++ b/.changes/v4.0.1.md @@ -0,0 +1,4 @@ +## v4.0.1 - 2025-07-08 +### Fixed +* Fix package.json for compilation + diff --git a/CHANGELOG.md b/CHANGELOG.md index 834120aea..78941a951 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## v4.0.1 - 2025-07-08 +### Fixed +* Fix package.json for compilation + + ## v4.0.0 - 2025-07-08 ### Feature * ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works From b870e71f77495c4ef99fdd96c538bdb1ebd4a20b Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 20 May 2025 14:05:02 +0200 Subject: [PATCH 68/83] Add translation for validation message in social action merger --- .changes/unreleased/Fixed-20250520-140433.yaml | 6 ++++++ src/Bundle/ChillPersonBundle/translations/messages.fr.yml | 1 + 2 files changed, 7 insertions(+) create mode 100644 .changes/unreleased/Fixed-20250520-140433.yaml diff --git a/.changes/unreleased/Fixed-20250520-140433.yaml b/.changes/unreleased/Fixed-20250520-140433.yaml new file mode 100644 index 000000000..2dafa1d0e --- /dev/null +++ b/.changes/unreleased/Fixed-20250520-140433.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Fix add missing translation +time: 2025-05-20T14:04:33.612140549+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 4739bac0f..c9f1af282 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -1513,6 +1513,7 @@ acpw_duplicate: to keep: Action d'accompagnement à conserver to delete: Action d'accompagnement à supprimer Successfully merged: Action d'accompagnement fusionnée avec succès. + You cannot merge a accompanying period work with itself. Please choose a different one: Vous ne pouvez pas fusionner un action d'accompagnement avec lui-même. Veuillez en choisir un autre. my_parcours_filters: referrer_parcours_and_acpw: Agent traitant ou réferent From 703f5dc32d8bd2c79bf841017371537ce9670e92 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 20 May 2025 16:43:39 +0200 Subject: [PATCH 69/83] Transfer evaluations (and related documents) during merge --- .../unreleased/Fixed-20250520-164429.yaml | 6 ++++ .../AccompanyingPeriodWorkMergeService.php | 34 +++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 .changes/unreleased/Fixed-20250520-164429.yaml diff --git a/.changes/unreleased/Fixed-20250520-164429.yaml b/.changes/unreleased/Fixed-20250520-164429.yaml new file mode 100644 index 000000000..453094763 --- /dev/null +++ b/.changes/unreleased/Fixed-20250520-164429.yaml @@ -0,0 +1,6 @@ +kind: Fixed +body: Fix the transfer of evaluations and documents during of accompanyingperiodwork +time: 2025-05-20T16:44:29.093304653+02:00 +custom: + Issue: "" + SchemaChange: No schema change diff --git a/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php b/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php index ba0f93ce3..490798171 100644 --- a/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php +++ b/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php @@ -17,9 +17,9 @@ use Doctrine\ORM\EntityManagerInterface; /** * Service for merging two AccompanyingPeriodWork entities into a single entity. */ -class AccompanyingPeriodWorkMergeService +readonly class AccompanyingPeriodWorkMergeService { - public function __construct(private readonly EntityManagerInterface $em) {} + public function __construct(private EntityManagerInterface $em) {} /** * Merges two AccompanyingPeriodWork entities into one by transferring relevant data and removing the obsolete entity. @@ -35,8 +35,9 @@ class AccompanyingPeriodWorkMergeService $this->alterStartDate($toKeep, $toDelete); $this->alterEndDate($toKeep, $toDelete); $this->concatenateComments($toKeep, $toDelete); + $this->transferEvaluationsSQL($toKeep, $toDelete); $this->transferWorkflowsSQL($toKeep, $toDelete); - $this->updateReferencesSQL($toKeep, $toDelete); + $this->updateReferences($toKeep, $toDelete); $entityManager->remove($toDelete); }); @@ -54,6 +55,16 @@ class AccompanyingPeriodWorkMergeService ); } + private function transferEvaluationsSQL(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void + { + $this->em->getConnection()->executeQuery( + "UPDATE chill_person_accompanying_period_work_evaluation cpapwe + SET accompanyingperiodwork_id = :toKeepId + WHERE cpapwe.accompanyingperiodwork_id = :toDeleteId", + ['toKeepId' => $toKeep->getId(), 'toDeleteId' => $toDelete->getId()] + ); + } + private function alterStartDate(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void { $startDate = min($toKeep->getStartDate(), $toDelete->getStartDate()); @@ -74,16 +85,17 @@ class AccompanyingPeriodWorkMergeService private function concatenateComments(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void { - $toKeep->setNote($toKeep->getNote()."\n\n-----------------\n\n".$toDelete->getNote()); - $toKeep->getPrivateComment()->concatenateComments($toDelete->getPrivateComment()); - } - - private function updateReferencesSQL(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void - { - foreach ($toDelete->getAccompanyingPeriodWorkEvaluations() as $evaluation) { - $toKeep->addAccompanyingPeriodWorkEvaluation($evaluation); + if ($toDelete->getNote() !== '') { + $toKeep->setNote($toKeep->getNote()."\n\n-----------------\n\n".$toDelete->getNote()); } + if (count($toDelete->getPrivateComment()->getComments()) > 0) { + $toKeep->getPrivateComment()->concatenateComments($toDelete->getPrivateComment()); + } + } + + private function updateReferences(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void + { foreach ($toDelete->getReferrers() as $referrer) { // we only keep the current referrer $toKeep->addReferrer($referrer); From f3cc4a89afd28888aeed594e5d0134249c95eb5f Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 9 Jul 2025 15:23:59 +0200 Subject: [PATCH 70/83] Update chill bundles to v4.0.2 --- .changes/unreleased/Fixed-20250520-140433.yaml | 6 ------ .changes/unreleased/Fixed-20250520-164429.yaml | 6 ------ .changes/v4.0.2.md | 4 ++++ CHANGELOG.md | 5 +++++ 4 files changed, 9 insertions(+), 12 deletions(-) delete mode 100644 .changes/unreleased/Fixed-20250520-140433.yaml delete mode 100644 .changes/unreleased/Fixed-20250520-164429.yaml create mode 100644 .changes/v4.0.2.md diff --git a/.changes/unreleased/Fixed-20250520-140433.yaml b/.changes/unreleased/Fixed-20250520-140433.yaml deleted file mode 100644 index 2dafa1d0e..000000000 --- a/.changes/unreleased/Fixed-20250520-140433.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Fixed -body: Fix add missing translation -time: 2025-05-20T14:04:33.612140549+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/unreleased/Fixed-20250520-164429.yaml b/.changes/unreleased/Fixed-20250520-164429.yaml deleted file mode 100644 index 453094763..000000000 --- a/.changes/unreleased/Fixed-20250520-164429.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: Fixed -body: Fix the transfer of evaluations and documents during of accompanyingperiodwork -time: 2025-05-20T16:44:29.093304653+02:00 -custom: - Issue: "" - SchemaChange: No schema change diff --git a/.changes/v4.0.2.md b/.changes/v4.0.2.md new file mode 100644 index 000000000..8761b48f2 --- /dev/null +++ b/.changes/v4.0.2.md @@ -0,0 +1,4 @@ +## v4.0.2 - 2025-07-09 +### Fixed +* Fix add missing translation +* Fix the transfer of evaluations and documents during of accompanyingperiodwork diff --git a/CHANGELOG.md b/CHANGELOG.md index 78941a951..332db3e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), and is generated by [Changie](https://github.com/miniscruff/changie). +## v4.0.2 - 2025-07-09 +### Fixed +* Fix add missing translation +* Fix the transfer of evaluations and documents during of accompanyingperiodwork + ## v4.0.1 - 2025-07-08 ### Fixed * Fix package.json for compilation From f383fab57871a12c9655a1d2ea7d1a1a12f50942 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 21 May 2025 09:29:25 +0200 Subject: [PATCH 71/83] Fix styling --- .../AccompanyingPeriodWorkMergeService.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php b/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php index 490798171..83793fcd2 100644 --- a/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php +++ b/src/Bundle/ChillPersonBundle/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeService.php @@ -58,9 +58,9 @@ readonly class AccompanyingPeriodWorkMergeService private function transferEvaluationsSQL(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void { $this->em->getConnection()->executeQuery( - "UPDATE chill_person_accompanying_period_work_evaluation cpapwe + 'UPDATE chill_person_accompanying_period_work_evaluation cpapwe SET accompanyingperiodwork_id = :toKeepId - WHERE cpapwe.accompanyingperiodwork_id = :toDeleteId", + WHERE cpapwe.accompanyingperiodwork_id = :toDeleteId', ['toKeepId' => $toKeep->getId(), 'toDeleteId' => $toDelete->getId()] ); } @@ -85,7 +85,7 @@ readonly class AccompanyingPeriodWorkMergeService private function concatenateComments(AccompanyingPeriodWork $toKeep, AccompanyingPeriodWork $toDelete): void { - if ($toDelete->getNote() !== '') { + if ('' !== $toDelete->getNote()) { $toKeep->setNote($toKeep->getNote()."\n\n-----------------\n\n".$toDelete->getNote()); } From 837089ff5d6b44158472eab83247394ef261ddac Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Thu, 10 Jul 2025 11:33:23 +0200 Subject: [PATCH 72/83] Fix testMerge method in AccompanyingPeriodWorkMergeServiceTest.php --- ...AccompanyingPeriodWorkMergeServiceTest.php | 84 +++++++++++-------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Tests/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeServiceTest.php b/src/Bundle/ChillPersonBundle/Tests/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeServiceTest.php index e69c8beee..0ce941f01 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeServiceTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Service/AccompanyingPeriodWork/AccompanyingPeriodWorkMergeServiceTest.php @@ -14,22 +14,20 @@ namespace Chill\PersonBundle\Tests\Service\AccompanyingPeriodWork; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; -use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkGoal; -use Chill\PersonBundle\Entity\SocialWork\Result; use Chill\PersonBundle\Service\AccompanyingPeriodWork\AccompanyingPeriodWorkMergeService; use Chill\ThirdPartyBundle\Entity\ThirdParty; use Doctrine\DBAL\Connection; use Doctrine\ORM\EntityManagerInterface; -use Monolog\Test\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; +use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; /** * @internal * * @coversNothing */ -class AccompanyingPeriodWorkMergeServiceTest extends TestCase +class AccompanyingPeriodWorkMergeServiceTest extends KernelTestCase { use ProphecyTrait; @@ -160,46 +158,62 @@ class AccompanyingPeriodWorkMergeServiceTest extends TestCase ]; } - public function testMerge(): void + public function testMergeAccompanyingPeriodWorks(): void { - $accompanyingPeriodWork = new AccompanyingPeriodWork(); - $accompanyingPeriodWork->setStartDate(new \DateTime('2022-01-01')); - $accompanyingPeriodWork->addReferrer($userA = new User()); - $accompanyingPeriodWork->addReferrer($userC = new User()); - $accompanyingPeriodWork->addAccompanyingPeriodWorkEvaluation($evaluationA = new AccompanyingPeriodWorkEvaluation()); - $accompanyingPeriodWork->setNote('blabla'); - $accompanyingPeriodWork->addThirdParty($thirdPartyA = new ThirdParty()); + $em = self::getContainer()->get(EntityManagerInterface::class); + + $userA = new User(); + $userA->setUsername('someUser'); + $userA->setEmail('someUser@example.com'); + $em->persist($userA); + + $toKeep = new AccompanyingPeriodWork(); + $toKeep->setStartDate(new \DateTime('2022-01-02')); + $toKeep->setNote('Keep note'); + $toKeep->setCreatedBy($userA); + $toKeep->setUpdatedBy($userA); + $toKeep->addReferrer($userA); + $em->persist($toKeep); + + $userB = new User(); + $userB->setUsername('anotherUser'); + $userB->setEmail('anotherUser@example.com'); + $em->persist($userB); $toDelete = new AccompanyingPeriodWork(); $toDelete->setStartDate(new \DateTime('2022-01-01')); - $toDelete->addReferrer($userB = new User()); - $toDelete->addReferrer($userC); - $toDelete->addAccompanyingPeriodWorkEvaluation($evaluationB = new AccompanyingPeriodWorkEvaluation()); - $toDelete->setNote('boum'); - $toDelete->addThirdParty($thirdPartyB = new ThirdParty()); - $toDelete->addGoal($goalA = new AccompanyingPeriodWorkGoal()); - $toDelete->addResult($resultA = new Result()); + $toDelete->setNote('Delete note'); + $toDelete->setCreatedBy($userB); + $toDelete->setUpdatedBy($userB); + $toDelete->addReferrer($userB); + $em->persist($toDelete); - $service = $this->buildMergeService($toDelete); - $service->merge($accompanyingPeriodWork, $toDelete); + $evaluation = new AccompanyingPeriodWorkEvaluation(); + $evaluation->setAccompanyingPeriodWork($toDelete); + $em->persist($evaluation); - self::assertTrue($accompanyingPeriodWork->getReferrers()->contains($userA)); - self::assertTrue($accompanyingPeriodWork->getReferrers()->contains($userB)); - self::assertTrue($accompanyingPeriodWork->getReferrers()->contains($userC)); + $em->flush(); - self::assertTrue($accompanyingPeriodWork->getAccompanyingPeriodWorkEvaluations()->contains($evaluationA)); - self::assertTrue($accompanyingPeriodWork->getAccompanyingPeriodWorkEvaluations()->contains($evaluationB)); - foreach ($accompanyingPeriodWork->getAccompanyingPeriodWorkEvaluations() as $evaluation) { - self::assertSame($accompanyingPeriodWork, $evaluation->getAccompanyingPeriodWork()); - } + $service = new AccompanyingPeriodWorkMergeService($em); + $merged = $service->merge($toKeep, $toDelete); - self::assertStringContainsString('blabla', $accompanyingPeriodWork->getNote()); - self::assertStringContainsString('boum', $toDelete->getNote()); + $em->refresh($merged); - self::assertTrue($accompanyingPeriodWork->getThirdParties()->contains($thirdPartyA)); - self::assertTrue($accompanyingPeriodWork->getThirdParties()->contains($thirdPartyB)); + // Assertions - self::assertTrue($accompanyingPeriodWork->getGoals()->contains($goalA)); - self::assertTrue($accompanyingPeriodWork->getResults()->contains($resultA)); + $this->assertEquals(new \DateTime('2022-01-01'), $merged->getStartDate()); + + $this->assertStringContainsString('Keep note', $merged->getNote()); + $this->assertStringContainsString('Delete note', $merged->getNote()); + + $em->refresh($evaluation); + $this->assertEquals($toKeep->getId(), $evaluation->getAccompanyingPeriodWork()->getId()); + + $em->remove($evaluation); + $em->remove($toKeep); + $em->remove($toDelete); + $em->remove($userA); + $em->remove($userB); + $em->flush(); } } From 63d0a52ea1f2a56f688146b4a5aafb712b3e5657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Fri, 11 Jul 2025 14:06:40 +0000 Subject: [PATCH 73/83] =?UTF-8?q?Ajout=20de=20commentaires=20suppl=C3=A9me?= =?UTF-8?q?ntaires=20aux=20motifs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unreleased/Feature-20240530-160003.yaml | 6 + .../unreleased/Feature-20240531-190242.yaml | 6 + .editorconfig | 8 +- .eslint-baseline.json | 902 +++----- .prettierrc | 4 + .vscode/launch.json | 30 + .vscode/tasks.json | 23 + CONVENTIONS-fr.md | 2 +- composer.json | 1 + config/bundles.php | 1 + config/packages/chill_ticket.yaml | 4 + .../packages/doctrine_migrations_chill.yaml | 1 + config/routes/chill_ticket.yaml | 2 + .../development/create-a-new-bundle.rst | 98 +- package.json | 4 +- phpunit.xml.dist | 4 + .../Resources/public/vuejs/Activity/App.vue | 20 +- .../Activity/components/ConcernedGroups.vue | 461 ++-- .../ConcernedGroups/PersonBadge.vue | 38 +- .../ConcernedGroups/PersonsBloc.vue | 54 +- .../vuejs/Activity/components/Location.vue | 154 +- .../components/Location/NewLocation.vue | 664 +++--- .../Activity/components/SocialIssuesAcc.vue | 462 ++-- .../SocialIssuesAcc/CheckSocialAction.vue | 74 +- .../SocialIssuesAcc/CheckSocialIssue.vue | 64 +- .../Resources/public/types.ts | 88 +- .../Resources/public/vuejs/Calendar/App.vue | 675 +++--- .../Calendar/Components/CalendarActive.vue | 186 +- .../Resources/public/vuejs/Calendar/api.ts | 42 +- .../Resources/public/vuejs/Calendar/const.ts | 26 +- .../public/vuejs/Calendar/store/utils.ts | 150 +- .../Resources/public/vuejs/Invite/Answer.vue | 208 +- .../public/vuejs/MyCalendarRange/App2.vue | 652 +++--- .../Components/EditLocation.vue | 86 +- .../public/vuejs/MyCalendarRange/i18n.ts | 46 +- .../public/vuejs/MyCalendarRange/index2.ts | 16 +- .../vuejs/MyCalendarRange/store/index.ts | 60 +- .../store/modules/calendarLocals.ts | 172 +- .../store/modules/calendarRanges.ts | 667 +++--- .../store/modules/calendarRemotes.ts | 175 +- .../store/modules/fullcalendar.ts | 132 +- .../MyCalendarRange/store/modules/location.ts | 98 +- .../vuejs/MyCalendarRange/store/modules/me.ts | 26 +- .../CalendarUserSelector.vue | 451 ++-- .../public/vuejs/_components/PickTemplate.vue | 239 ++- .../public/js/async-upload/uploader.ts | 104 +- .../Resources/public/js/generic-doc-api.ts | 8 +- .../public/module/async_upload/index.ts | 187 +- .../public/module/button_download/index.ts | 42 +- .../document_action_buttons_group/index.ts | 118 +- .../Resources/public/types/generic_doc.ts | 54 +- .../Resources/public/types/index.ts | 172 +- .../vuejs/DocumentActionButtonsGroup.vue | 334 ++- .../public/vuejs/DocumentSignature/App.vue | 1472 +++++++------ .../public/vuejs/DocumentSignature/index.ts | 54 +- .../public/vuejs/DropFileWidget/DropFile.vue | 274 ++- .../vuejs/DropFileWidget/DropFileModal.vue | 108 +- .../vuejs/DropFileWidget/DropFileWidget.vue | 122 +- .../Resources/public/vuejs/FileIcon.vue | 70 +- .../StoredObjectButton/ConvertButton.vue | 56 +- .../StoredObjectButton/DesktopEditButton.vue | 116 +- .../StoredObjectButton/DownloadButton.vue | 175 +- .../StoredObjectButton/HistoryButton.vue | 64 +- .../HistoryButton/HistoryButtonList.vue | 70 +- .../HistoryButton/HistoryButtonListItem.vue | 270 ++- .../HistoryButton/HistoryButtonModal.vue | 50 +- .../HistoryButton/RestoreVersionButton.vue | 28 +- .../StoredObjectButton/HistoryButton/api.ts | 40 +- .../StoredObjectButton/WopiEditButton.vue | 56 +- .../vuejs/StoredObjectButton/helpers.ts | 414 ++-- src/Bundle/ChillMainBundle/Entity/User.php | 10 + .../Formatter/SpreadsheetListFormatter.php | 2 + .../Phonenumber/PhonenumberHelper.php | 18 + .../Resources/public/chill/js/date.ts | 208 +- .../Resources/public/lib/api/address.ts | 64 +- .../Resources/public/lib/api/apiMethods.ts | 388 ++-- .../Resources/public/lib/api/export.ts | 20 +- .../Resources/public/lib/api/locations.ts | 4 +- .../Resources/public/lib/api/return_path.ts | 2 +- .../Resources/public/lib/api/user.ts | 30 +- .../public/lib/entity-notification/api.ts | 28 +- .../public/lib/entity-workflow/api.ts | 22 +- .../localizationHelper/localizationHelper.ts | 54 +- .../public/lib/workflow/attachments.ts | 20 +- .../public/module/address-details/index.ts | 76 +- .../public/module/ckeditor5/editor_config.ts | 96 +- .../public/module/ckeditor5/index.ts | 46 +- .../public/module/collection/index.ts | 236 +-- .../module/notification/toggle_read_all.ts | 68 +- .../public/module/pick-entity/index.js | 7 +- .../ChillMainBundle/Resources/public/types.ts | 331 +-- .../Resources/public/vuejs/Address/App.vue | 230 +- .../Address/components/ActionButtons.vue | 48 +- .../vuejs/Address/components/AddAddress.vue | 1722 ++++++++------- .../components/AddAddress/AddressMap.vue | 191 +- .../components/AddAddress/AddressMore.vue | 280 +-- .../AddAddress/AddressSelection.vue | 414 ++-- .../components/AddAddress/CitySelection.vue | 459 ++-- .../AddAddress/CountrySelection.vue | 140 +- .../vuejs/Address/components/DatePane.vue | 332 ++- .../vuejs/Address/components/EditPane.vue | 343 ++- .../vuejs/Address/components/ShowPane.vue | 359 ++-- .../vuejs/Address/components/SuggestPane.vue | 155 +- .../public/vuejs/DownloadExport/App.vue | 157 +- .../public/vuejs/DownloadExport/index.ts | 4 +- .../public/vuejs/HomepageWidget/App.vue | 252 ++- .../HomepageWidget/DashboardWidgets/News.vue | 42 +- .../DashboardWidgets/NewsItem.vue | 250 ++- .../HomepageWidget/MyAccompanyingCourses.vue | 175 +- .../public/vuejs/HomepageWidget/MyCustoms.vue | 268 ++- .../vuejs/HomepageWidget/MyEvaluations.vue | 227 +- .../vuejs/HomepageWidget/MyNotifications.vue | 196 +- .../public/vuejs/HomepageWidget/MyTasks.vue | 203 +- .../vuejs/HomepageWidget/MyWorkflows.vue | 30 +- .../vuejs/HomepageWidget/MyWorkflowsTable.vue | 156 +- .../public/vuejs/HomepageWidget/MyWorks.vue | 202 +- .../vuejs/HomepageWidget/TabCounter.vue | 18 +- .../public/vuejs/HomepageWidget/TabTable.vue | 24 +- .../Resources/public/vuejs/OnTheFly/App.vue | 60 +- .../vuejs/OnTheFly/components/Create.vue | 200 +- .../vuejs/OnTheFly/components/OnTheFly.vue | 638 +++--- .../public/vuejs/PickEntity/PickEntity.vue | 391 +++- .../vuejs/PickPostalCode/PickPostalCode.vue | 174 +- .../public/vuejs/SavedExportButtons/App.vue | 8 +- .../Component/GenerateButton.vue | 208 +- .../public/vuejs/SavedExportButtons/index.ts | 6 +- .../public/vuejs/WorkflowAttachment/App.vue | 72 +- .../Component/AttachmentList.vue | 66 +- .../Component/GenericDocItemBox.vue | 10 +- .../Component/PickGenericDoc.vue | 420 ++-- .../Component/PickGenericDocItem.vue | 132 +- .../Component/PickGenericDocModal.vue | 128 +- .../public/vuejs/WorkflowAttachment/index.ts | 177 +- .../AddressDetails/AddressDetailsButton.vue | 82 +- .../AddressDetails/AddressDetailsContent.vue | 28 +- .../AddressDetails/AddressModal.vue | 40 +- .../AddressDetailsGeographicalLayers.vue | 60 +- .../Parts/AddressDetailsMap.vue | 127 +- .../Parts/AddressDetailsRefMatching.vue | 262 ++- .../public/vuejs/_components/BadgeEntity.vue | 99 +- .../CommentEditor/CommentEditor.vue | 136 +- .../public/vuejs/_components/Confidential.vue | 52 +- .../_components/Entity/AddressRenderBox.vue | 218 +- .../Entity/GenderIconRenderBox.vue | 23 +- .../_components/Entity/UserGroupRenderBox.vue | 16 +- .../_components/Entity/UserRenderBoxBadge.vue | 42 +- .../EntityWorkflowVueSubscriber.vue | 126 +- .../EntityWorkflow/ListWorkflow.vue | 209 +- .../EntityWorkflow/ListWorkflowModal.vue | 142 +- .../EntityWorkflow/PickWorkflow.vue | 277 ++- .../public/vuejs/_components/Modal.vue | 128 +- .../NotificationReadAllToggle.vue | 70 +- .../Notification/NotificationReadToggle.vue | 184 +- .../Resources/public/vuejs/_js/i18n.ts | 145 +- .../public/vuejs/i18n/datetimeFormats.ts | 48 +- .../Resources/views/layout.html.twig | 52 +- .../Phonenumber/PhonenumberHelperTest.php | 45 + .../Validator/UserGroupDoNotExcludeTest.php | 91 + .../ChillMainBundle/chill.api.specs.yaml | 25 + .../translations/messages+intl-icu.fr.yaml | 55 + .../translations/messages.fr.yml | 31 + .../Repository/PersonACLAwareRepository.php | 25 + .../PersonACLAwareRepositoryInterface.php | 10 + .../Repository/PersonRepository.php | 39 + .../AccompanyingPeriodWorkSelector.ts | 74 +- .../Resources/public/types.ts | 478 +++-- .../public/vuejs/AccompanyingCourse/App.vue | 194 +- .../components/AdminLocation.vue | 170 +- .../AccompanyingCourse/components/Banner.vue | 257 +-- .../components/Banner/PersonsAssociated.vue | 210 +- .../components/Banner/SocialIssue.vue | 14 +- .../components/Banner/ToggleFlags.vue | 261 ++- .../components/ButtonLocation.vue | 151 +- .../AccompanyingCourse/components/Comment.vue | 302 ++- .../AccompanyingCourse/components/Confirm.vue | 431 ++-- .../components/CourseLocation.vue | 482 +++-- .../components/OriginDemand.vue | 143 +- .../components/PersonsAssociated.vue | 525 +++-- .../PersonsAssociated/ParticipationItem.vue | 431 ++-- .../components/Referrer.vue | 439 ++-- .../components/Requestor.vue | 869 ++++---- .../components/Resources.vue | 321 ++- .../components/Resources/ResourceItem.vue | 444 ++-- .../components/Resources/WriteComment.vue | 182 +- .../AccompanyingCourse/components/Scopes.vue | 93 +- .../components/SocialIssue.vue | 167 +- .../components/StartDate.vue | 137 +- .../components/StickyNav.vue | 350 ++-- .../components/StickyNav/Item.vue | 32 +- .../AccompanyingCourse/components/Test.vue | 277 ++- .../AccompanyingCourseWorkCreate/App.vue | 551 +++-- .../vuejs/AccompanyingCourseWorkEdit/App.vue | 1859 ++++++++--------- .../components/AddEvaluation.vue | 309 ++- .../components/AddResult.vue | 290 ++- .../components/FormEvaluation.vue | 1292 ++++++------ .../vuejs/ExportFormActionGoalResult/App.vue | 834 ++++---- .../vuejs/HouseholdMembersEditor/App.vue | 320 +-- .../components/Concerned.vue | 239 +-- .../components/Confirmation.vue | 46 +- .../components/CurrentHousehold.vue | 80 +- .../components/Dates.vue | 277 ++- .../components/Household.vue | 482 +++-- .../components/HouseholdAddress.vue | 131 +- .../components/MemberDetails.vue | 242 ++- .../components/PersonComment.vue | 38 +- .../components/Positioning.vue | 190 +- .../Resources/public/vuejs/VisGraph/App.vue | 1287 ++++++------ .../_api/{AddPersons.js => AddPersons.ts} | 37 +- ...ccompanyingCourseWorkEvaluationDocument.ts | 10 +- .../AccompanyingPeriod/SetReferrer.vue | 79 +- .../AccompanyingPeriodWorkItem.vue | 92 +- .../AccompanyingPeriodWorkList.vue | 42 +- .../AccompanyingPeriodWorkSelectorModal.vue | 140 +- .../public/vuejs/_components/AddPersons.vue | 954 ++++----- .../AddPersons/PersonSuggestion.vue | 228 +- .../_components/AddPersons/TypeHousehold.vue | 35 +- .../_components/AddPersons/TypePerson.vue | 73 +- .../_components/AddPersons/TypeThirdParty.vue | 223 +- .../vuejs/_components/AddPersons/TypeUser.vue | 52 +- .../_components/AddPersons/TypeUserGroup.vue | 36 +- .../_components/Entity/HouseholdRenderBox.vue | 295 ++- .../_components/Entity/PersonRenderBox.vue | 873 ++++---- .../vuejs/_components/Entity/PersonText.vue | 116 +- .../vuejs/_components/OnTheFly/Person.vue | 960 ++++----- .../Resources/public/vuejs/_js/i18n.ts | 117 +- .../PersonACLAwareRepositoryTest.php | 66 + .../translations/messages+intl-icu.fr.yaml | 56 + .../translations/messages.fr.yml | 45 + .../Repository/ThirdPartyRepository.php | 42 +- .../Resources/public/types.ts | 74 +- .../Entity/ThirdPartyRenderBox.vue | 382 ++-- .../_components/Entity/ThirdPartyText.vue | 38 +- .../vuejs/_components/OnTheFly/ThirdParty.vue | 841 ++++---- .../translations/messages.fr.yml | 13 + .../ChillTicketBundle/chill.api.specs.yaml | 521 +++++ .../ChillTicketBundle/chill.webpack.config.js | 4 + .../UpdateCommentContentCommandHandler.php | 26 + ...eCommentContentCommandHandlerInterface.php | 20 + ...dateCommentDeletedStatusCommandHandler.php | 26 + ...ntDeletedStatusCommandHandlerInterface.php | 20 + .../Comment/UpdateCommentContentCommand.php | 30 + .../UpdateCommentDeletedStatusCommand.php | 27 + .../src/Action/Ticket/AddAddresseeCommand.php | 29 + .../src/Action/Ticket/AddCommentCommand.php | 25 + .../Ticket/AssociateByPhonenumberCommand.php | 19 + .../Ticket/ChangeEmergencyStateCommand.php | 24 + .../src/Action/Ticket/ChangeStateCommand.php | 24 + .../src/Action/Ticket/CreateTicketCommand.php | 19 + .../Handler/AddCommentCommandHandler.php | 33 + .../AssociateByPhonenumberCommandHandler.php | 46 + .../ChangeEmergencyStateCommandHandler.php | 49 + .../Handler/ChangeStateCommandHandler.php | 49 + .../Handler/CreateTicketCommandHandler.php | 37 + .../Handler/ReplaceMotiveCommandHandler.php | 63 + .../Handler/SetAddresseesCommandHandler.php | 56 + .../Handler/SetCallerCommandHandler.php | 50 + .../Handler/SetPersonsCommandHandler.php | 56 + .../Action/Ticket/ReplaceMotiveCommand.php | 25 + .../Action/Ticket/SetAddresseesCommand.php | 40 + .../src/Action/Ticket/SetCallerCommand.php | 29 + .../src/Action/Ticket/SetPersonsCommand.php | 30 + .../src/ChillTicketBundle.php | 16 + .../src/Controller/AddCommentController.php | 73 + .../ChangeEmergencyStateApiController.php | 73 + .../Controller/ChangeStateApiController.php | 73 + .../src/Controller/CreateTicketController.php | 75 + .../src/Controller/EditTicketController.php | 45 + .../src/Controller/FindCallerController.php | 58 + .../src/Controller/MotiveApiController.php | 25 + .../Controller/ReplaceMotiveController.php | 69 + .../Controller/SetAddresseesController.php | 85 + .../src/Controller/SetCallerApiController.php | 66 + .../src/Controller/SetPersonsController.php | 72 + .../SuggestPersonForTicketApiController.php | 45 + .../src/Controller/TicketControllerApi.php | 31 + .../Controller/TicketListApiController.php | 81 + .../src/Controller/TicketListController.php | 84 + .../Controller/UpdateCommentController.php | 69 + .../UpdateCommentDeletedStatusController.php | 66 + .../src/DataFixtures/ORM/LoadMotives.php | 98 + .../ChillTicketExtension.php | 69 + .../src/DependencyInjection/Configuration.php | 42 + .../src/Entity/AddresseeHistory.php | 137 ++ .../src/Entity/CallerHistory.php | 148 ++ .../ChillTicketBundle/src/Entity/Comment.php | 85 + .../src/Entity/EmergencyStatusEnum.php | 21 + .../src/Entity/EmergencyStatusHistory.php | 85 + .../src/Entity/InputHistory.php | 96 + .../ChillTicketBundle/src/Entity/Motive.php | 101 + .../src/Entity/MotiveHistory.php | 86 + .../src/Entity/PersonHistory.php | 103 + .../src/Entity/StateEnum.php | 30 + .../src/Entity/StateHistory.php | 85 + .../ChillTicketBundle/src/Entity/Ticket.php | 336 +++ .../src/Menu/SectionMenuBuilder.php | 29 + .../src/Repository/MotiveRepository.php | 27 + .../PersonTicketACLAwareRepository.php | 60 + ...ersonTicketACLAwareRepositoryInterface.php | 29 + .../Repository/TicketACLAwareRepository.php | 79 + .../TicketACLAwareRepositoryInterface.php | 38 + .../src/Repository/TicketRepository.php | 64 + .../Repository/TicketRepositoryInterface.php | 28 + .../Resources/public/page/ticket/banner.scss | 24 + .../src/Resources/public/page/ticket/index.ts | 1 + .../src/Resources/public/types.ts | 157 ++ .../Resources/public/vuejs/TicketApp/App.vue | 44 + .../components/ActionToolbarComponent.vue | 356 ++++ .../components/AddCommentComponent.vue | 27 + .../components/AddresseeComponent.vue | 68 + .../components/AddresseeSelectorComponent.vue | 87 + .../TicketApp/components/BannerComponent.vue | 190 ++ .../components/CallerSelectorComponent.vue | 85 + .../components/MotiveSelectorComponent.vue | 71 + .../components/PersonsSelectorComponent.vue | 90 + .../TicketHistoryAddresseeComponent.vue | 18 + .../TicketHistoryCommentComponent.vue | 46 + .../TicketHistoryCreateComponent.vue | 16 + .../TicketHistoryEmergencyComponent.vue | 18 + .../components/TicketHistoryListComponent.vue | 151 ++ .../TicketHistoryMotiveComponent.vue | 14 + .../TicketHistoryPersonComponent.vue | 37 + .../TicketHistoryStateComponent.vue | 56 + .../components/TicketSelectorComponent.vue | 36 + .../TicketApp/components/ToggleFlags.vue | 67 + .../Resources/public/vuejs/TicketApp/index.ts | 25 + .../public/vuejs/TicketApp/store/index.ts | 24 + .../TicketApp/store/modules/addressee.ts | 86 + .../vuejs/TicketApp/store/modules/comment.ts | 38 + .../vuejs/TicketApp/store/modules/motive.ts | 66 + .../vuejs/TicketApp/store/modules/persons.ts | 86 + .../vuejs/TicketApp/store/modules/ticket.ts | 91 + .../Resources/views/Banner/banner.html.twig | 8 + .../src/Resources/views/Ticket/edit.html.twig | 19 + .../src/Resources/views/Ticket/list.html.twig | 99 + .../src/Resources/views/layout.html.twig | 17 + .../src/Security/Voter/CommentVoter.php | 50 + .../src/Security/Voter/TicketVoter.php | 37 + .../SetAddresseesCommandDenormalizer.php | 56 + .../SetPersonsCommandDenormalizer.php | 51 + .../Normalizer/TicketNormalizer.php | 275 +++ .../Service/Ticket/SuggestPersonForTicket.php | 30 + .../SuggestPersonForTicketInterface.php | 26 + .../Validator/SetPersonCommandConstraint.php | 25 + .../SetPersonCommandConstraintValidator.php | 47 + .../ChillTicketBundle/src/config/routes.yaml | 3 + .../src/config/services.yaml | 36 + .../src/migrations/Version20240416145919.php | 139 ++ .../src/migrations/Version20240423212824.php | 40 + .../src/migrations/Version20250603085035.php | 89 + .../src/migrations/Version20250620145414.php | 82 + .../src/migrations/Version20250620164517.php | 37 + .../src/migrations/Version20250624105842.php | 93 + .../src/migrations/Version20250711115128.php | 33 + .../src/migrations/Version20250711131126.php | 33 + .../src/translations/messages+intl-icu.fr.yml | 86 + ...UpdateCommentContentCommandHandlerTest.php | 50 + ...CommentDeletedStatusCommandHandlerTest.php | 55 + .../Handler/AddCommentCommandHandlerTest.php | 52 + ...sociateByPhonenumberCommandHandlerTest.php | 96 + ...ChangeEmergencyStateCommandHandlerTest.php | 118 ++ .../Handler/ChangeStateCommandHandlerTest.php | 118 ++ .../CreateTicketCommandHandlerTest.php | 55 + .../ReplaceMotiveCommandHandlerTest.php | 181 ++ .../SetAddressesCommandHandlerTest.php | 112 + .../Handler/SetCallerCommandHandlerTest.php | 178 ++ .../Handler/SetPersonsCommandHandlerTest.php | 112 + .../Controller/AddCommentControllerTest.php | 106 + .../ChangeEmergencyStateApiControllerTest.php | 144 ++ .../ChangeStateApiControllerTest.php | 144 ++ .../Controller/FindCallerControllerTest.php | 128 ++ .../ReplaceMotiveControllerTest.php | 119 ++ .../SetAddresseesControllerTest.php | 212 ++ .../Controller/SetCallerApiControllerTest.php | 200 ++ .../TicketListApiControllerTest.php | 302 +++ .../Controller/TicketListControllerTest.php | 34 + .../UpdateCommentControllerTest.php | 134 ++ ...dateCommentDeletedStatusControllerTest.php | 114 + .../tests/Entity/CallerHistoryTest.php | 63 + .../tests/Entity/TicketCallerTest.php | 82 + .../tests/Entity/TicketTest.php | 161 ++ .../PersonTicketACLAwareRepositoryTest.php | 64 + .../TicketACLAwareRepositoryTest.php | 81 + .../SetAddresseesCommandDenormalizerTest.php | 66 + .../Normalizer/TicketNormalizerTest.php | 408 ++++ .../Ticket/SuggestPersonForTicketTest.php | 70 + ...etPersonCommandConstraintValidatorTest.php | 90 + .../Resources/public/module/pending/index.ts | 44 +- src/shims-vue.d.ts | 6 +- src/vue-multiselect.d.ts | 18 +- src/vuex.d.ts | 8 +- tests/app/config/routes/chill_ticket.yaml | 3 + tsconfig.json | 3 +- 392 files changed, 35466 insertions(+), 24054 deletions(-) create mode 100644 .changes/unreleased/Feature-20240530-160003.yaml create mode 100644 .changes/unreleased/Feature-20240531-190242.yaml create mode 100644 .prettierrc create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 config/packages/chill_ticket.yaml create mode 100644 config/routes/chill_ticket.yaml create mode 100644 src/Bundle/ChillMainBundle/Tests/Validation/Validator/UserGroupDoNotExcludeTest.php rename src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/{AddPersons.js => AddPersons.ts} (57%) create mode 100644 src/Bundle/ChillTicketBundle/chill.api.specs.yaml create mode 100644 src/Bundle/ChillTicketBundle/chill.webpack.config.js create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Comment/Handler/UpdateCommentContentCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Comment/Handler/UpdateCommentContentCommandHandlerInterface.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Comment/Handler/UpdateCommentDeletedStatusCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Comment/Handler/UpdateCommentDeletedStatusCommandHandlerInterface.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Comment/UpdateCommentContentCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Comment/UpdateCommentDeletedStatusCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/AddAddresseeCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/AddCommentCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/AssociateByPhonenumberCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/ChangeEmergencyStateCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/ChangeStateCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/CreateTicketCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/AddCommentCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/AssociateByPhonenumberCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/ChangeEmergencyStateCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/ChangeStateCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/CreateTicketCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/ReplaceMotiveCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/SetAddresseesCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/SetCallerCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/Handler/SetPersonsCommandHandler.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/ReplaceMotiveCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/SetAddresseesCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/SetCallerCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/Action/Ticket/SetPersonsCommand.php create mode 100644 src/Bundle/ChillTicketBundle/src/ChillTicketBundle.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/AddCommentController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/ChangeEmergencyStateApiController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/ChangeStateApiController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/CreateTicketController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/EditTicketController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/FindCallerController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/MotiveApiController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/ReplaceMotiveController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/SetAddresseesController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/SetCallerApiController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/SetPersonsController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/SuggestPersonForTicketApiController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/TicketControllerApi.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/TicketListApiController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/TicketListController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/UpdateCommentController.php create mode 100644 src/Bundle/ChillTicketBundle/src/Controller/UpdateCommentDeletedStatusController.php create mode 100644 src/Bundle/ChillTicketBundle/src/DataFixtures/ORM/LoadMotives.php create mode 100644 src/Bundle/ChillTicketBundle/src/DependencyInjection/ChillTicketExtension.php create mode 100644 src/Bundle/ChillTicketBundle/src/DependencyInjection/Configuration.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/AddresseeHistory.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/CallerHistory.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/Comment.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/EmergencyStatusEnum.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/EmergencyStatusHistory.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/InputHistory.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/Motive.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/MotiveHistory.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/PersonHistory.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/StateEnum.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/StateHistory.php create mode 100644 src/Bundle/ChillTicketBundle/src/Entity/Ticket.php create mode 100644 src/Bundle/ChillTicketBundle/src/Menu/SectionMenuBuilder.php create mode 100644 src/Bundle/ChillTicketBundle/src/Repository/MotiveRepository.php create mode 100644 src/Bundle/ChillTicketBundle/src/Repository/PersonTicketACLAwareRepository.php create mode 100644 src/Bundle/ChillTicketBundle/src/Repository/PersonTicketACLAwareRepositoryInterface.php create mode 100644 src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepository.php create mode 100644 src/Bundle/ChillTicketBundle/src/Repository/TicketACLAwareRepositoryInterface.php create mode 100644 src/Bundle/ChillTicketBundle/src/Repository/TicketRepository.php create mode 100644 src/Bundle/ChillTicketBundle/src/Repository/TicketRepositoryInterface.php create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/page/ticket/banner.scss create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/page/ticket/index.ts create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/types.ts create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/App.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/ActionToolbarComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/AddCommentComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/AddresseeComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/AddresseeSelectorComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/BannerComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/CallerSelectorComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/MotiveSelectorComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/PersonsSelectorComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryAddresseeComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryCommentComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryCreateComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryEmergencyComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryListComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryMotiveComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryPersonComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketHistoryStateComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/TicketSelectorComponent.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/components/ToggleFlags.vue create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/index.ts create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/index.ts create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/addressee.ts create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/comment.ts create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/motive.ts create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/persons.ts create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/public/vuejs/TicketApp/store/modules/ticket.ts create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/views/Banner/banner.html.twig create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/views/Ticket/edit.html.twig create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/views/Ticket/list.html.twig create mode 100644 src/Bundle/ChillTicketBundle/src/Resources/views/layout.html.twig create mode 100644 src/Bundle/ChillTicketBundle/src/Security/Voter/CommentVoter.php create mode 100644 src/Bundle/ChillTicketBundle/src/Security/Voter/TicketVoter.php create mode 100644 src/Bundle/ChillTicketBundle/src/Serializer/Normalizer/SetAddresseesCommandDenormalizer.php create mode 100644 src/Bundle/ChillTicketBundle/src/Serializer/Normalizer/SetPersonsCommandDenormalizer.php create mode 100644 src/Bundle/ChillTicketBundle/src/Serializer/Normalizer/TicketNormalizer.php create mode 100644 src/Bundle/ChillTicketBundle/src/Service/Ticket/SuggestPersonForTicket.php create mode 100644 src/Bundle/ChillTicketBundle/src/Service/Ticket/SuggestPersonForTicketInterface.php create mode 100644 src/Bundle/ChillTicketBundle/src/Validation/Validator/SetPersonCommandConstraint.php create mode 100644 src/Bundle/ChillTicketBundle/src/Validation/Validator/SetPersonCommandConstraintValidator.php create mode 100644 src/Bundle/ChillTicketBundle/src/config/routes.yaml create mode 100644 src/Bundle/ChillTicketBundle/src/config/services.yaml create mode 100644 src/Bundle/ChillTicketBundle/src/migrations/Version20240416145919.php create mode 100644 src/Bundle/ChillTicketBundle/src/migrations/Version20240423212824.php create mode 100644 src/Bundle/ChillTicketBundle/src/migrations/Version20250603085035.php create mode 100644 src/Bundle/ChillTicketBundle/src/migrations/Version20250620145414.php create mode 100644 src/Bundle/ChillTicketBundle/src/migrations/Version20250620164517.php create mode 100644 src/Bundle/ChillTicketBundle/src/migrations/Version20250624105842.php create mode 100644 src/Bundle/ChillTicketBundle/src/migrations/Version20250711115128.php create mode 100644 src/Bundle/ChillTicketBundle/src/migrations/Version20250711131126.php create mode 100644 src/Bundle/ChillTicketBundle/src/translations/messages+intl-icu.fr.yml create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Comment/Handler/UpdateCommentContentCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Comment/Handler/UpdateCommentDeletedStatusCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/AddCommentCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/AssociateByPhonenumberCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/ChangeEmergencyStateCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/ChangeStateCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/CreateTicketCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/ReplaceMotiveCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/SetAddressesCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/SetCallerCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Action/Ticket/Handler/SetPersonsCommandHandlerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/AddCommentControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/ChangeEmergencyStateApiControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/ChangeStateApiControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/FindCallerControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/ReplaceMotiveControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/SetAddresseesControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/SetCallerApiControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListApiControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/TicketListControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/UpdateCommentControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Controller/UpdateCommentDeletedStatusControllerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Entity/CallerHistoryTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Entity/TicketCallerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Entity/TicketTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Repository/PersonTicketACLAwareRepositoryTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Repository/TicketACLAwareRepositoryTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Serializer/Normalizer/SetAddresseesCommandDenormalizerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Serializer/Normalizer/TicketNormalizerTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Service/Ticket/SuggestPersonForTicketTest.php create mode 100644 src/Bundle/ChillTicketBundle/tests/Validation/Validator/SetPersonCommandConstraintValidatorTest.php create mode 100644 tests/app/config/routes/chill_ticket.yaml diff --git a/.changes/unreleased/Feature-20240530-160003.yaml b/.changes/unreleased/Feature-20240530-160003.yaml new file mode 100644 index 000000000..6b6baedc5 --- /dev/null +++ b/.changes/unreleased/Feature-20240530-160003.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: | + Upgrade import of address list to the last version of compiled addresses of belgian-best-address +time: 2024-05-30T16:00:03.440767606+02:00 +custom: + Issue: "" diff --git a/.changes/unreleased/Feature-20240531-190242.yaml b/.changes/unreleased/Feature-20240531-190242.yaml new file mode 100644 index 000000000..083298a26 --- /dev/null +++ b/.changes/unreleased/Feature-20240531-190242.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: | + Upgrade CKEditor and refactor configuration with use of typescript +time: 2024-05-31T19:02:42.776662753+02:00 +custom: + Issue: "" diff --git a/.editorconfig b/.editorconfig index bede621e3..49ea12528 100644 --- a/.editorconfig +++ b/.editorconfig @@ -19,11 +19,11 @@ max_line_length = 80 [COMMIT_EDITMSG] max_line_length = 0 -[*.{js, vue, ts}] +[*.{js,vue,ts}] indent_size = 2 indent_style = space -[.rst] -ident_size = 3 -ident_style = space +[*.rst] +indent_size = 3 +indent_style = space diff --git a/.eslint-baseline.json b/.eslint-baseline.json index 6483e47b3..7b5d29c67 100644 --- a/.eslint-baseline.json +++ b/.eslint-baseline.json @@ -7,14 +7,6 @@ "message": "'app' is assigned a value but never used.", "hash": "f8c2979921289906e3baabae31ba101ead91504f" }, - { - "path": "src/Bundle/ChillActivityBundle/Resources/public/vuejs/Activity/index.js", - "line": 57, - "column": 23, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'event' is defined but never used.", - "hash": "cf0cf378f71403f62a6425f384ccbbdec433d1f2" - }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/module/Invite/answer.js", "line": 7, @@ -25,19 +17,19 @@ }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/Components/CalendarActive.vue", - "line": 19, - "column": 30, + "line": 10, + "column": 22, "ruleId": "vue/valid-v-else", "message": "'v-else' directives require no attribute value.", - "hash": "505f99b24500a684eec3c6bf870426fcd841c20b" + "hash": "c4d34a0df38baaf72092179b2e066c91b6f6733a" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/Components/CalendarActive.vue", - "line": 54, + "line": 45, "column": 10, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'mapGetters' is defined but never used.", - "hash": "01e3928f7d9be0accb6db3894b158dd683a685ff" + "hash": "8e80aff377fdd5af9686df6a89a4ba650911f02a" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/index.js", @@ -90,90 +82,50 @@ { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/utils.ts", "line": 4, - "column": 5, + "column": 3, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'DateTime' is defined but never used.", - "hash": "fdf38ad15813c4a931b93f4a85db985ca71105fc" + "hash": "88be21db222be347a0cf7f8fbfaba531d5cb0418" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/utils.ts", "line": 14, - "column": 27, + "column": 25, "ruleId": "@typescript-eslint/no-empty-object-type", "message": "The `{}` (\"empty object\") type allows any non-nullish value, including literals like `0` and `\"\"`.\n- If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.\n- If you want a type meaning \"any object\", you probably want `object` instead.\n- If you want a type meaning \"any value\", you probably want `unknown` instead.", - "hash": "9602ec11eda8f46ff5712d69dcd87419babcd6f0" + "hash": "6c1437cbb501093bde9c9f7eeda4e4853e558c1a" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/utils.ts", "line": 16, - "column": 20, + "column": 18, "ruleId": "@typescript-eslint/no-empty-object-type", "message": "The `{}` (\"empty object\") type allows any non-nullish value, including literals like `0` and `\"\"`.\n- If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.\n- If you want a type meaning \"any object\", you probably want `object` instead.\n- If you want a type meaning \"any value\", you probably want `unknown` instead.", - "hash": "23f7a8a1c3cba955077ddbd604bad6805ce30ad2" + "hash": "f03d8114bfe562d84f8160f5d122562e484b5de1" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Calendar/store/utils.ts", "line": 18, - "column": 19, + "column": 17, "ruleId": "@typescript-eslint/no-empty-object-type", "message": "The `{}` (\"empty object\") type allows any non-nullish value, including literals like `0` and `\"\"`.\n- If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option.\n- If you want a type meaning \"any object\", you probably want `object` instead.\n- If you want a type meaning \"any value\", you probably want `unknown` instead.", - "hash": "abbc89e186c74d0f71f45cfda31c490339e39078" + "hash": "de706baa154ead33e72ce520c50cdd16a85d1265" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/Invite/Answer.vue", - "line": 95, - "column": 13, + "line": 86, + "column": 19, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'payload' is defined but never used.", - "hash": "66c545917093ba30f1d6ca10ddaa676140e749bd" - }, - { - "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue", - "line": 224, - "column": 10, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'reactive' is defined but never used.", - "hash": "96ed76a9828138fb125fc36c4b55e900bbfe87c2" - }, - { - "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue", - "line": 230, - "column": 5, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'DropArg' is defined but never used.", - "hash": "bd405399a4091d65e8391404bfb0c4611816c8e0" - }, - { - "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue", - "line": 251, - "column": 9, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'t' is assigned a value but never used.", - "hash": "bc09207a496405f7a71c178e522b89aeb1f7ebd3" - }, - { - "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue", - "line": 356, - "column": 32, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'arg' is defined but never used.", - "hash": "aeae152f0669b946a1ad681dd52b0ef03393ae79" - }, - { - "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/App2.vue", - "line": 434, - "column": 11, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'changedEvent' is assigned a value but never used.", - "hash": "a7a81a6bf09d00c0364e3aa8207ffad853f0547b" + "hash": "8c189da3c258c22dfdd1e0a72d0f0ef5464c26eb" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue", "line": 77, - "column": 16, + "column": 12, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'_' is defined but never used.", - "hash": "d29fb2fc9299c48082167b2fa4c427c569716bd6" + "hash": "8040b05fb0422e91d049dd0d7fb4604b4455bf5e" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue", @@ -202,50 +154,50 @@ { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/index2.ts", "line": 12, - "column": 11, + "column": 9, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'app' is assigned a value but never used.", - "hash": "6422c0876b992a3c5174faa99ef06d8339b74b4e" + "hash": "3ebdc6637bc62a230176a4dff37a5900061e6eed" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/index.ts", - "line": 46, - "column": 20, + "line": 44, + "column": 74, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'_' is defined but never used.", - "hash": "5bf7ba0b0200fb6788d50df669e6b7701f8a87bb" + "hash": "7ac3276b20b1029e510bcbbbc8572fc34400e204" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/fullcalendar.ts", "line": 57, - "column": 32, + "column": 20, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'_' is defined but never used.", - "hash": "a733a5edfe4c434ed63682bcbd5fe65f175a1b85" + "hash": "d25bbc0a04099c361d99f34b0c731892c7b008a7" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/fullcalendar.ts", "line": 64, - "column": 32, + "column": 20, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'_' is defined but never used.", - "hash": "d83b1879b5fc6179c045ad1981421f46a7cbbd56" + "hash": "7c53ad6034cebccfe119a1ee7dd011984a720676" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/fullcalendar.ts", "line": 71, - "column": 32, + "column": 20, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'_' is defined but never used.", - "hash": "222eed84495212735a60fa05e0c7f298c7bc33e1" + "hash": "110e12e976ae0f8fe15cd70b974d99ee381bb6fb" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/fullcalendar.ts", "line": 72, - "column": 26, + "column": 18, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'_' is defined but never used.", - "hash": "0cf85eccce08b65a8590f7318cace58cbb21ea08" + "hash": "7919b6b2becd817e0fad8c51602b0f8c2fc0f5f2" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/store/modules/me.ts", @@ -258,90 +210,90 @@ { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", "line": 108, - "column": 47, + "column": 35, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "1a771f9f65375cac2002ed7d706184355685d378" + "hash": "e57ca97296d09e44a8e19573138a4f39f7777778" + }, + { + "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", + "line": 145, + "column": 15, + "ruleId": "vue/no-mutating-props", + "message": "Unexpected mutation of \"users\" prop.", + "hash": "26378799855b04bc15aa3e030b9ede95f3139c4d" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", "line": 148, - "column": 29, + "column": 15, "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"users\" prop.", - "hash": "d9f7e517892b6588be17516fd077c3149ccbefd9" + "message": "Unexpected mutation of \"calendarEvents\" prop.", + "hash": "279aee22cdc99740903c9fee736897a65074a8bb" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", "line": 151, - "column": 29, - "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"calendarEvents\" prop.", - "hash": "47c270d8c0bcda67ea04a83ba267eca3e70fa7fb" - }, - { - "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", - "line": 154, - "column": 59, + "column": 41, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "7cf30f0d2dc567ea0dfb44dadfc9ecf4977e7b95" + "hash": "2c597422f93f4b397bfe96365007c602d349ad0c" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", - "line": 155, - "column": 41, + "line": 152, + "column": 21, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"users\" prop.", - "hash": "7876348a3eee16be7effd3e992a6aaa2334cfd24" + "hash": "75af3ee3d935ce4d62c037461e552cef3ab0f291" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", - "line": 164, - "column": 63, + "line": 158, + "column": 47, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "0f308d7417bfc294b28eea6890666a2d91e31ecd" + "hash": "6ffe789bbd1d731daaf5c2bff4582372fd342dc1" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", - "line": 185, - "column": 57, + "line": 170, + "column": 27, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"calendarEvents\" prop.", - "hash": "5a021a581a33d3aeea21c65e023902f2123bb562" + "hash": "ef1795af6c50719c19ec3bffd8e511d06a9bfac2" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", - "line": 218, - "column": 17, + "line": 197, + "column": 9, "ruleId": "@typescript-eslint/prefer-for-of", "message": "Expected a `for-of` loop instead of a `for` loop with this simple iteration.", - "hash": "655811d4dad8de23b3d37dfbf02f7fca18b6d8f2" + "hash": "bc4a3fd50f5afd095d8e938885291d948c4ba689" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", - "line": 235, - "column": 13, + "line": 214, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"calendarEvents\" prop.", - "hash": "19317d9a564b8b10355cac1eeb9308c7d49c0dd1" + "hash": "6a27b96c77fc2747b4fd0f82ccfedc44138b0aa5" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", - "line": 240, - "column": 13, + "line": 219, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"users\" prop.", - "hash": "95e478eca53a2acfa8edd0aa5747e73b0da01d0c" + "hash": "61c0ca5de675766483584b7363b048356f9c55f8" }, { "path": "src/Bundle/ChillCalendarBundle/Resources/public/vuejs/_components/CalendarUserSelector/CalendarUserSelector.vue", - "line": 246, - "column": 13, + "line": 225, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"users\" prop.", - "hash": "0b66e38f0c0bbe17aa7c548dc127be033133ce06" + "hash": "d87ce5e7fbcefc884fb7be907d3c3fef53921ab4" }, { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/js/async-upload/uploader.ts", @@ -399,14 +351,6 @@ "message": "'error' is defined but never used.", "hash": "e26e5e101e90d2b7ee84d6f5de8c819e52129c17" }, - { - "path": "src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts", - "line": 29, - "column": 14, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'vm' is defined but never used.", - "hash": "8e7f5e89dd72c54459cf82156389b88988f97d63" - }, { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/uploader.js", "line": 39, @@ -545,11 +489,11 @@ }, { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue", - "line": 80, - "column": 5, + "line": 78, + "column": 3, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'StoredObjectVersion' is defined but never used.", - "hash": "2fadbe9e331a66f44b54782acc1420c4b487e1f6" + "hash": "da2ec95bcd2bb80819ef2032e40b0d7237acba31" }, { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/index.ts", @@ -578,10 +522,10 @@ { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue", "line": 11, - "column": 5, + "column": 3, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'download_and_decrypt_doc' is defined but never used.", - "hash": "9a803f1fe608ec60ab065aa8f76e50e2ef1fa4c2" + "hash": "ddc9ed7c384b9da6ed1398551cefe7d244cb0c9e" }, { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue", @@ -593,11 +537,11 @@ }, { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue", - "line": 50, - "column": 11, + "line": 48, + "column": 9, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reset_pending' is assigned a value but never used.", - "hash": "96fde3634a150f1253fac2f2f2bdcfe6e0daf82a" + "hash": "9919afcb6b2724f5b6be69caa1b487026b9260fe" }, { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton.vue", @@ -615,29 +559,21 @@ "message": "'ref' is defined but never used.", "hash": "2a27cd6d06a26e1326654c929068e3704137e24b" }, - { - "path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue", - "line": 57, - "column": 17, - "ruleId": "vue/valid-v-for", - "message": "Custom elements in iteration require 'v-bind:key' directives.", - "hash": "cce787939524e83dd135869e13738ef332d7156c" - }, { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue", - "line": 15, + "line": 13, "column": 8, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'WopiEditButton' is defined but never used.", - "hash": "80cb06a18ac4db47e8d120103704ec6988696b79" + "hash": "f197263cc1b8a5eeadd87e2e2c44eccc618375ec" }, { "path": "src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts", "line": 3, - "column": 5, + "column": 3, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'StoredObjectStatus' is defined but never used.", - "hash": "e868cee022cd1499bb54f7b5503a8a822773e097" + "hash": "ceb106361b5267900ff8852f6ff191698e065e13" }, { "path": "src/Bundle/ChillJobBundle/src/Resources/public/module/cv_edit/index.js", @@ -761,27 +697,27 @@ }, { "path": "src/Bundle/ChillMainBundle/Resources/public/chill/js/date.ts", - "line": 73, - "column": 5, + "line": 71, + "column": 3, "ruleId": "prefer-const", "message": "'cal' is never reassigned. Use 'const' instead.", - "hash": "53586ea7ec719f07750a8874db79f5e76583d5e0" + "hash": "f6bce9a9d62ca55fec76a414f36ad445ffbb5f17" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/chill/js/date.ts", - "line": 79, - "column": 5, + "line": 77, + "column": 3, "ruleId": "prefer-const", "message": "'time' is never reassigned. Use 'const' instead.", - "hash": "19821279c0b2d7e69418db6877c317f8c6e2dacc" + "hash": "7bb4fae8b43da52f82f65b50a87b22d0375b517f" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/chill/js/date.ts", - "line": 85, - "column": 5, + "line": 83, + "column": 3, "ruleId": "prefer-const", "message": "'offset' is never reassigned. Use 'const' instead.", - "hash": "244b65de0ab0791ec89219058c5cb4f2e11622c7" + "hash": "f91a4b071f6ad88a8408dfa241b3cec00c7d54db" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/lib/download-report/download-report.js", @@ -914,26 +850,26 @@ { "path": "src/Bundle/ChillMainBundle/Resources/public/module/collection/index.ts", "line": 141, - "column": 5, + "column": 3, "ruleId": "@typescript-eslint/prefer-for-of", "message": "Expected a `for-of` loop instead of a `for` loop with this simple iteration.", - "hash": "cd5fc994be294fde5fef27ba841c9af454b81dc0" + "hash": "c280bbe030fe9a793f531ce00c1a4a6c6fdcfdc0" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/module/collection/index.ts", "line": 153, - "column": 5, + "column": 3, "ruleId": "@typescript-eslint/prefer-for-of", "message": "Expected a `for-of` loop instead of a `for` loop with this simple iteration.", - "hash": "381830dd078845aeab5ea366757541cd7c77ca3f" + "hash": "98e7c2f525eddcfd8d0f839c9bf69b7c56c9d537" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/module/collection/index.ts", "line": 162, - "column": 9, + "column": 5, "ruleId": "@typescript-eslint/prefer-for-of", "message": "Expected a `for-of` loop instead of a `for` loop with this simple iteration.", - "hash": "768b1976fb935d4a4b0890f00be2299b7ff60170" + "hash": "66a4dba7d1d666cbac1ed22eb2a7916cf97ca5ec" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/module/disable-buttons/index.js", @@ -983,22 +919,6 @@ "message": "'_e' is defined but never used.", "hash": "1d6448401778e8c56554020fe5abd47851ed33f3" }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/module/wopi-link/index.js", - "line": 21, - "column": 55, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'e' is defined but never used.", - "hash": "eae499e4f6e9f43a9d17f9cd917cb6d3d97be25c" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/page/export/download-export.js", - "line": 3, - "column": 55, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'e' is defined but never used.", - "hash": "088fd383e7807e484aefc9825209bc7c8942bd22" - }, { "path": "src/Bundle/ChillMainBundle/Resources/public/page/homepage_widget/index.js", "line": 9, @@ -1074,514 +994,514 @@ { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue", "line": 79, - "column": 55, + "column": 39, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "29ea9ef3f1b2cb970e8103383bcde12a4ab66c25" + "hash": "3014fecdefbab379cc54e6abe7532c892cc788f6" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue", "line": 101, - "column": 55, + "column": 39, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "b81ee6947049c6876f81410cfbb499dc43da4374" + "hash": "03336dded33c9831e1f8c4b950591b0a24eac678" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 247, - "column": 5, + "line": 244, + "column": 3, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'postAddressToPerson' is defined but never used.", - "hash": "8a41c437cf2b5554cbbe1704cd51f3102b3d5994" + "hash": "0a60babcbc24fd0a410901c25994e93f1cd88acc" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 248, - "column": 5, + "line": 245, + "column": 3, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'postAddressToHousehold' is defined but never used.", - "hash": "66dec84b2ece299daf21308e5e60d497ba442b27" + "hash": "6022bfb8f8d94a3ce0fb2aca7a846c084c2170af" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 490, - "column": 21, + "line": 486, + "column": 11, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"context\" prop.", - "hash": "0d3f40c47974a4371072b3b9ee04b197c830162d" + "hash": "26b7b1a24fd66b1763e131ad372ea9e9246ed2ca" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 491, - "column": 21, + "line": 487, + "column": 11, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"context\" prop.", - "hash": "8e877b7e588c30e182f7b572bdb9685360f9cf99" + "hash": "1c8c8b1de3e43ec62a8193b0fa6efdfeade74677" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 508, - "column": 47, + "line": 501, + "column": 35, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "5a3e3401bc3c765d91faaf4cfde57697af1262b7" + "hash": "eca7f49f652654fcffdbe10ac410f108ff3fb28e" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 525, - "column": 47, + "line": 518, + "column": 35, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "35a741d90379574b9323279f5802193d0c98a9dc" + "hash": "31097431dfeb6ebcaa0893d89aa5309de9ee774e" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 553, - "column": 47, + "line": 546, + "column": 35, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "c23d1ddf6c0d10ae97948e74aee9c14b9320b86c" + "hash": "142f4ce9e45cf10df3d74345496c2a8919a138bc" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 572, - "column": 47, + "line": 565, + "column": 35, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "4322e81c6ea9d9734c680633a724d5bd4fabacb2" + "hash": "e9973c9eb1f151f71f6386d2a0ce83f322ff2cf5" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 803, - "column": 47, + "line": 787, + "column": 35, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "7928a6461b9d394c7d97f048933553936f7d8963" + "hash": "78f1d0e8d3b2fa2489f4e311db2d1215d77b1c46" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue", - "line": 852, - "column": 47, + "line": 836, + "column": 35, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "e5afdb8efccb5470a08dde48f755b1268fa947b5" + "hash": "cbabc9b1434c9a5690bc269aabd001c8841aeb1b" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue", "line": 93, - "column": 17, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "68f5e1cf5c03f9ada59c9e0afca0b74c7f3fca4b" + "hash": "9c66b7ec84cc786856badd8498414379ef4056ed" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue", "line": 101, - "column": 17, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "50d730f6109092baff2db66adc44dc1315e2bda2" + "hash": "92d482ada1514212e710c366a7700111eda96f1e" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue", "line": 109, - "column": 17, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "573e4c041ce663f28b933d7a675c2a525aba644c" + "hash": "f5c902853071084dfe7fd9a1c50e1c653e4c6a19" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue", "line": 117, - "column": 17, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "293f845eeab515b1df4649d136c2d8219ed59c4d" + "hash": "409c69e33cdf563b7bc2d0c074f413f8d12c0307" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue", "line": 125, - "column": 17, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "792310bc5def2c7b45f50da97cd8818d82862e8a" + "hash": "ea78eb9d3d49445fc131568291b4d777e2e8d271" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue", "line": 133, - "column": 17, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "a69e5335393f67a83d89720af34a4385a7d7e665" + "hash": "db792e379fcff91ef9388fbf88492552885efb66" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue", "line": 141, - "column": 17, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "2d5a5e680ff207ad97c7e7b7d999064b561dfd8a" + "hash": "dbba8af7379ffc65a6d80b4fffd00ca658251fbe" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 106, - "column": 17, + "line": 104, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "d52356f2af31d0167c02330ec22d09fbfa6b2b9f" + "hash": "7c28ce533e2011e4f052d5ca0c7d57610b0461da" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 114, - "column": 17, + "line": 112, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "c8e8e06f370f93bf05867e93b5f037dfa46937b1" + "hash": "c40ccbcab1e9ceccd5a350ddd88ff7dd5c468ec3" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 128, - "column": 13, + "line": 126, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "9abaf71ca4b4f292b3b01e724d0a7733365e71f1" + "hash": "f407cd7f81bc5d33ed0831a592870074e6a4d6f1" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 129, - "column": 13, + "line": 127, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "0b0743959778a9e3d93089b132608816ee4e6646" + "hash": "5d974adadc72bf10bfa1a863bdc44fdc43c2207f" + }, + { + "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", + "line": 130, + "column": 7, + "ruleId": "vue/no-mutating-props", + "message": "Unexpected mutation of \"entity\" prop.", + "hash": "1fbda9c797af826dbadcfd16dc945b8d21e3ddf0" + }, + { + "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", + "line": 131, + "column": 7, + "ruleId": "vue/no-mutating-props", + "message": "Unexpected mutation of \"entity\" prop.", + "hash": "b0ff83b3f077f1a6143654e5914321e3cf29ac95" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", "line": 132, - "column": 13, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "9759da7b7859b8ee8efaf74876430658ac6b6fe2" + "hash": "19b7e3498b9e90d6a86b3be74338d040247465ab" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 133, - "column": 13, + "line": 137, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "dba8be9a27ab74ec743b7d9e07c05d857b407dd3" + "hash": "d7c45fd73ad1721055f23116102655bdd90a9528" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 134, - "column": 13, - "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"entity\" prop.", - "hash": "9b1f5bce779aafc46b19d7a5d266eaa29f8f9be9" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 139, - "column": 13, - "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"entity\" prop.", - "hash": "fe6fc4aea0994ba9da15b7c09d308842b67958cb" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 153, - "column": 55, + "line": 151, + "column": 39, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'reject' is defined but never used.", - "hash": "bd0e024fcad2e3f4566f15293e3c25c840f6dd3e" + "hash": "bb2767a02ef0c4cd4598702d06ee95d7e79fced3" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 154, - "column": 37, + "line": 152, + "column": 19, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "596c4b180b926b7829f987384328bf5636cd367a" + "hash": "ce6c91b98e94605fe9c5b6c4e358a42da79a3150" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 171, - "column": 59, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'reject' is defined but never used.", - "hash": "5b41d5f9b45da074fb7bbbbd45e0da501da72071" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 172, + "line": 168, "column": 41, - "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"entity\" prop.", - "hash": "d92b92a25043244cca809bd129633b7e024e26b4" + "ruleId": "@typescript-eslint/no-unused-vars", + "message": "'reject' is defined but never used.", + "hash": "28a03efbb5ade5a575393d46f4ec87fd3ab3af57" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 190, - "column": 17, + "line": 169, + "column": 21, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "dd9a85ea740742d620e864796f67c5bff834486d" + "hash": "7eede2e283dcaeeeba01fba092100c0839eacb37" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 191, - "column": 17, + "line": 186, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "e3e59960d0d50709a57b336f66b586710b774892" + "hash": "a7ff3bc5466588198108718a166980b73e87ee40" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 192, - "column": 17, + "line": 187, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "fe11b0e54396511e7b3b08615a78d22fc27e2fad" + "hash": "c87deccee6f42a977abbf54b25c12af2543c4ba6" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", - "line": 222, - "column": 13, + "line": 188, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "63c14c2150c33ec701bc4a0ff94efde69537d490" + "hash": "83029bfbb4c6e6e76f097dedad5c95b7efbf1d54" + }, + { + "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue", + "line": 218, + "column": 7, + "ruleId": "vue/no-mutating-props", + "message": "Unexpected mutation of \"entity\" prop.", + "hash": "f8cc21c434f3a41e97c1e659f3e3e41bc8ea3015" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 96, - "column": 20, + "column": 14, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "d2a9fdaeef0e2810f480022d4c6f99e4f76a818e" + "hash": "9f4994768baeedde467d8a6a594eddb96c8d4f35" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 96, - "column": 20, + "column": 14, "ruleId": "vue/no-side-effects-in-computed-properties", "message": "Unexpected side effect in \"cities\" computed property.", - "hash": "dd92a60a9b1ebefeb9a90941d45326fbfa483733" + "hash": "efdf2f0fa3127580834a1a71c05cc412cdffc17a" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 102, - "column": 17, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "04be01ab638ce01f568fb0216929e65e1175ca23" + "hash": "331da5bdcdaca996b5a86bbf9d074172d9a97de5" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 110, - "column": 17, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "8619c8e0b63e87d09268832f90e4fba06b87e41f" + "hash": "1503a521d5aac26de71708193d5efe381fce1a91" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 124, - "column": 13, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "281f918da00635079501418b1e6b2c05b62eb4a7" + "hash": "b7099b57a857083cd18ffca20e489fcc894675f0" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 125, - "column": 13, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "c131b09fa67ab1d069f1d04a54582d6b0f206153" + "hash": "577790a8bd8c6081fe75e4d4ebbb99d251e7cf5a" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 126, - "column": 13, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "3d3a2a4add64c291b8f5f1cddd90a173cd6a819d" + "hash": "744ba1d220a69ca2c63c9b7dc1478ef71af0ec8e" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 131, - "column": 21, + "column": 11, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "ed48f4988914d7897018a2e06830a97e6740b3e8" + "hash": "e8134808053567607222487fcb4144183e0ffe86" + }, + { + "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", + "line": 143, + "column": 7, + "ruleId": "vue/no-mutating-props", + "message": "Unexpected mutation of \"entity\" prop.", + "hash": "fab57266818588096de456916ebe3fa88e89a4e4" + }, + { + "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", + "line": 144, + "column": 7, + "ruleId": "vue/no-mutating-props", + "message": "Unexpected mutation of \"entity\" prop.", + "hash": "f3d04b5c0a3a29bfa65e7eeeb1c50e989ec4821e" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 145, - "column": 13, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "744f3a7610d4d6015e50e25149bceffd6c6e2763" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 146, - "column": 13, - "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"entity\" prop.", - "hash": "eaaaaee5fb2e324ffe0a68eefe340dabdf162324" + "hash": "90b3e9a1ba5be62bd6894796ecc2e33e2f29329d" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 147, - "column": 13, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "2de47b4a4ddbe546b3fce9898af48b72853364bf" + "hash": "8a90e844f524ba01cc1c64806eeda052ab88957d" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", "line": 149, - "column": 17, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "1e7b1ad55866f708baaca72dfa4ff26d6f8e5d21" + "hash": "e83c3118bc20776c00437d7edc3deaf24915bd66" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 152, - "column": 13, + "line": 158, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "84779331536ffceec8d4a8c5ca4307310b882549" + "hash": "378ccd9fa7cf74e41c905f1a21bdbf1f74c4015f" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 161, - "column": 13, - "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"entity\" prop.", - "hash": "0789999841be671a4d8ab080d6fdb679f843eb52" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 170, - "column": 51, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'reject' is defined but never used.", - "hash": "bbb17afa114f016e2058d90aa32d2a625804f0d1" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 171, - "column": 33, - "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"entity\" prop.", - "hash": "5fbe407ceceb37bff2ac800ceddd7942540132f1" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 190, - "column": 55, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'reject' is defined but never used.", - "hash": "e2af91def877befbabef8e93deba4c58a3ee2ded" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 191, + "line": 167, "column": 37, - "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"entity\" prop.", - "hash": "ee8544ee45681a650ed7d4918ae979685cdd8f0f" + "ruleId": "@typescript-eslint/no-unused-vars", + "message": "'reject' is defined but never used.", + "hash": "5bbe7140e1370b411abbdfc0554d58d62472d0d7" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 210, + "line": 168, "column": 17, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "5d9d2217c8c7e6571bc9f72a98ea5b370edb4968" + "hash": "b712ccec63c177608e199b9bc63f30bba6e6ecfe" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 211, - "column": 17, - "ruleId": "vue/no-mutating-props", - "message": "Unexpected mutation of \"entity\" prop.", - "hash": "6e04619b373c23c91f6c36c2aad314ac16cdb697" + "line": 186, + "column": 39, + "ruleId": "@typescript-eslint/no-unused-vars", + "message": "'reject' is defined but never used.", + "hash": "9a5b009d8e7f30e61c1991c97d390f0735d19968" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 212, - "column": 17, + "line": 187, + "column": 19, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "39df045639a62f64ccdb03a80e286bc3ad772587" + "hash": "831be7f044c74b2c1a4244ffe3a065dd21e6247d" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 213, - "column": 17, + "line": 205, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "c399a43fa797a8ce61c9d96a644a39cc84a387b7" + "hash": "6c0fc8e55e3132c27cbaeabd2e84ca2838008247" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", - "line": 245, - "column": 13, + "line": 206, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "04337a07944caaa4819cfebcf29e1a7cbfdf248b" + "hash": "4b66a3e1cf5476e4a0e2f1b3f3546222572abf8f" + }, + { + "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", + "line": 207, + "column": 9, + "ruleId": "vue/no-mutating-props", + "message": "Unexpected mutation of \"entity\" prop.", + "hash": "c685acbb0d48bab6c55b0af2dcc701f5edf993fe" + }, + { + "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", + "line": 208, + "column": 9, + "ruleId": "vue/no-mutating-props", + "message": "Unexpected mutation of \"entity\" prop.", + "hash": "0059a00f9459b29cb4a54eb2b273d9f59d7890af" + }, + { + "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue", + "line": 240, + "column": 7, + "ruleId": "vue/no-mutating-props", + "message": "Unexpected mutation of \"entity\" prop.", + "hash": "e66f10f1d6116821b3f0fb066bcbf50772ba5374" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue", - "line": 76, - "column": 13, + "line": 72, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "373a2e31f110d138c66d77f1faf5dc61545c55af" + "hash": "037d3140ebf15fc52a0f58f7844a9a2af44a3d9c" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue", - "line": 81, - "column": 13, + "line": 77, + "column": 7, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "421eb6a63224b4b1d81b216677a710c5c99ddee3" + "hash": "936e5eed1f826a51d129d97580bfb5084a6a4a48" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue", - "line": 95, - "column": 17, + "line": 92, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "84e13d1fdc79f4568634a78df281adbe81739cbd" + "hash": "2058a778c66e3d4005cdfb4354ede1d93e8e1ab6" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue", - "line": 103, - "column": 17, + "line": 100, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "1eed832462e52537402a2825655733f0f2d391d9" + "hash": "20b24f03b3260f915df456ce4d54775f2dda68b6" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue", - "line": 155, - "column": 17, + "line": 152, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "b3a822914fcb5e2fcf28efc331a45b9205002eeb" + "hash": "849bea4394a838c6f9c75cb93a29049d9c7a3c9e" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue", - "line": 164, - "column": 17, + "line": 161, + "column": 9, "ruleId": "vue/no-mutating-props", "message": "Unexpected mutation of \"entity\" prop.", - "hash": "72c7d850f6cdeaf65b373a33234222f9766ee30b" + "hash": "56abd4aa323bb35e94cbd0264603b6d1848298d1" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/index.js", @@ -1609,19 +1529,11 @@ }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/NewsItem.vue", - "line": 142, - "column": 57, + "line": 136, + "column": 55, "ruleId": "@typescript-eslint/no-explicit-any", "message": "Unexpected any. Specify a different type.", - "hash": "a68eeba7b2e1e603d83da0946c94cd221134aa99" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/OnTheFly/components/OnTheFly.vue", - "line": 204, - "column": 22, - "ruleId": "vue/return-in-computed-property", - "message": "Expected to return a value in \"buttonMessage\" computed property.", - "hash": "b101c861dc11bc7024857fa2977118cb9f99c02c" + "hash": "2ca5e2fd1fccea428a772ad7d2dd0d001bdc413b" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/OnTheFly/index.js", @@ -1631,77 +1543,53 @@ "message": "'app' is assigned a value but never used.", "hash": "9e6125f4fc387dc362c69cc6e3ce360eb2851f1b" }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/PickEntity/PickEntity.vue", - "line": 60, - "column": 22, - "ruleId": "vue/require-valid-default-prop", - "message": "Type of the default value for 'suggested' prop must be a function.", - "hash": "d30212820bc2e97fa02d75dbc3a014558693f169" - }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/AddressDetails/Parts/AddressDetailsMap.vue", - "line": 24, + "line": 21, "column": 13, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'LatLngExpression' is defined but never used.", - "hash": "78f5a83dddf05b38aa9472ab93871e976719ef30" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/GenderIconRenderBox.vue", - "line": 6, - "column": 7, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'props' is assigned a value but never used.", - "hash": "29fe0a5d52e46c479aa2e7bb23fb55c53df7b22e" + "hash": "96b45c0371542f6e2a5b6f1b2d4f598698faff68" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue", - "line": 114, - "column": 34, + "line": 107, + "column": 32, "ruleId": "vue/require-valid-default-prop", "message": "Type of the default value for 'goToGenerateWorkflowPayload' prop must be a function.", - "hash": "d686fa87cfdc801aaaa08b24e827e503e81e86be" + "hash": "d86b61c318c09e12544ded19f252f6e281e8f985" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue", - "line": 170, + "line": 163, "column": 7, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'goToDuplicateRelatedEntity' is assigned a value but never used.", - "hash": "224ddf3abcff96e3e20a0facc7493883958d5a80" + "hash": "a6b7b632e663f282e0f4951d95a6987fa70f4046" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue", - "line": 171, - "column": 5, + "line": 164, + "column": 3, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'event' is defined but never used.", - "hash": "aa87fd5511528b5a45713fe1eaeda9ae0a8c0975" + "hash": "dd3f0de245d7a2107ad5526965d1a0c29df0ef26" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue", - "line": 172, - "column": 5, + "line": 165, + "column": 3, "ruleId": "@typescript-eslint/no-unused-vars", "message": "'workflowName' is defined but never used.", - "hash": "e34bbcf245552e9329efdf7bd64ea3a56f0d4538" + "hash": "d12891a4cc2df02d4b15f30b474edf8b01fd9766" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/PickWorkflow.vue", - "line": 173, + "line": 166, "column": 12, "ruleId": "@typescript-eslint/no-empty-function", "message": "Unexpected empty arrow function.", - "hash": "8bdff7a5b3a7ac1506966a6066a1deb556d30efe" - }, - { - "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue", - "line": 3, - "column": 9, - "ruleId": "vue/require-toggle-inside-transition", - "message": "The element inside `` is expected to have a `v-if` or `v-show` directive.", - "hash": "0594fb9d0984f4dd1612671aca21b571087ab8ee" + "hash": "3977a54eb58bc5c558dfb1ce043b14377a746441" }, { "path": "src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.ts", @@ -1727,14 +1615,6 @@ "message": "'tags' is assigned a value but never used.", "hash": "ae9bb2e0651c118ed9efd227e88b86cc83f5d80d" }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue", - "line": 116, - "column": 18, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'event' is defined but never used.", - "hash": "201f182769c6dfb87148b841e7d9b592be429669" - }, { "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/index.js", "line": 19, @@ -1767,14 +1647,6 @@ "message": "'app' is assigned a value but never used.", "hash": "aaaaa63e7a60443b8cbf8191feb9142852ebdf1c" }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue", - "line": 79, - "column": 13, - "ruleId": "vue/require-v-for-key", - "message": "Elements in iteration expect to have 'v-bind:key' directives.", - "hash": "422f53925922e59655d0f71624c19af75d41628c" - }, { "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/index.js", "line": 12, @@ -1815,22 +1687,6 @@ "message": "'evalFQDN' is assigned a value but never used.", "hash": "7fc32caafa23addddf44f3acbc5045b4523a0271" }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/store.js", - "line": 611, - "column": 9, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'errors' is assigned a value but never used.", - "hash": "c41cf979fc1626c38328dbf1028800c3395496bd" - }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue", - "line": 282, - "column": 7, - "ruleId": "@typescript-eslint/no-unused-expressions", - "message": "Expected an assignment or function call and instead saw an expression.", - "hash": "de3a6e2bb10a80a2bacba665be74266c7efc7d64" - }, { "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/index.js", "line": 16, @@ -1847,38 +1703,6 @@ "message": "'app' is assigned a value but never used.", "hash": "2f161e663689e3e4dfe2c53b0d64c91a4d2b1a60" }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue", - "line": 263, - "column": 19, - "ruleId": "vue/return-in-computed-property", - "message": "Expected to return a value in \"refreshNetwork\" computed property.", - "hash": "2c1b08a49098c83b09058cedc0a962126e91e544" - }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue", - "line": 270, - "column": 7, - "ruleId": "vue/no-side-effects-in-computed-properties", - "message": "Unexpected side effect in \"legendLayers\" computed property.", - "hash": "760948d2187c853f17ac9a1bd7107e883092d4f4" - }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue", - "line": 281, - "column": 5, - "ruleId": "vue/no-dupe-keys", - "message": "Duplicate key 'checkedLayers'. May cause name collision in script or template tag.", - "hash": "447edb461e15e3ff5c60c8ecba88131e442539aa" - }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue", - "line": 353, - "column": 7, - "ruleId": "@typescript-eslint/no-unused-expressions", - "message": "Expected an assignment or function call and instead saw an expression.", - "hash": "9cf656cbf1eb3d7cc0082e63adcd320b6093d14f" - }, { "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/index.js", "line": 20, @@ -1886,77 +1710,5 @@ "ruleId": "@typescript-eslint/no-unused-vars", "message": "'app' is assigned a value but never used.", "hash": "9e94e6412b8a44e47bfe8e66218cad09cff5bed4" - }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue", - "line": 42, - "column": 16, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'response' is defined but never used.", - "hash": "62de07b13c662e32332bb062038acee23978ea70" - }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue", - "line": 356, - "column": 28, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'_response' is defined but never used.", - "hash": "097e7788a2b5dea500b80b8a3cf968e57063a66a" - }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue", - "line": 6, - "column": 8, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'BadgeEntity' is defined but never used.", - "hash": "951a1b012bdec10c4b859af8b34dd894f63add23" - }, - { - "path": "src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue", - "line": 7, - "column": 8, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'UserRenderBoxBadge' is defined but never used.", - "hash": "99eba0d8633b2c9497417f4f61ec4194dbb2a96b" - }, - { - "path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts", - "line": 4, - "column": 3, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'StoredObjectStatus' is defined but never used.", - "hash": "63f8c4572293916850d6165647774b27d4b732c6" - }, - { - "path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts", - "line": 5, - "column": 3, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'StoredObjectStatusChange' is defined but never used.", - "hash": "a87c178e3eb5999bf0f46b3fa1c6da77e1be08b9" - }, - { - "path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts", - "line": 30, - "column": 61, - "ruleId": "@typescript-eslint/no-unused-vars", - "message": "'e' is defined but never used.", - "hash": "02953121583f4f73742a19adab099ab63df9076e" - }, - { - "path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts", - "line": 31, - "column": 32, - "ruleId": "@typescript-eslint/no-explicit-any", - "message": "Unexpected any. Specify a different type.", - "hash": "af48e21a1651b6017ede882dab249c00a818a44d" - }, - { - "path": "src/Bundle/ChillWopiBundle/src/Resources/public/module/pending/index.ts", - "line": 37, - "column": 16, - "ruleId": "@typescript-eslint/no-explicit-any", - "message": "Unexpected any. Specify a different type.", - "hash": "7513ea552a0a649ce4ab93b6cf9d40bfef4f68d9" } -] \ No newline at end of file +] diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..222861c34 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..d7a227605 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,30 @@ +{ + // Use IntelliSense to learn about possible attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Chill Debug", + "type": "php", + "request": "launch", + "port": 9000, + "pathMappings": { + "/var/www/html": "${workspaceFolder}" + }, + "preLaunchTask": "symfony" + }, + { + "name": "Yarn Encore Dev (Watch)", + "type": "node-terminal", + "request": "launch", + "command": "yarn encore dev --watch", + "cwd": "${workspaceFolder}" + } + ], + "compounds": [ + { + "name": "Chill Debug + Yarn Encore Dev (Watch)", + "configurations": ["Chill Debug", "Yarn Encore Dev (Watch)"] + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..a652cfe03 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "tasks": [ + { + "type": "shell", + "command": "symfony", + "args": [ + "server:start", + "--allow-http", + "--no-tls", + "--port=8000", + "--allow-all-ip", + "-d" + ], + "label": "symfony" + }, + { + "type": "shell", + "command": "yarn", + "args": ["encore", "dev", "--watch"], + "label": "webpack" + } + ] +} diff --git a/CONVENTIONS-fr.md b/CONVENTIONS-fr.md index 028971ef2..4462f4e08 100644 --- a/CONVENTIONS-fr.md +++ b/CONVENTIONS-fr.md @@ -54,7 +54,7 @@ Arborescence: - person - personvendee - household_edit_metadata - - index.js + - index.ts ``` ## Organisation des feuilles de styles diff --git a/composer.json b/composer.json index 8d36d4d07..94338ce42 100644 --- a/composer.json +++ b/composer.json @@ -133,6 +133,7 @@ "Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle", "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle", "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src", + "Chill\\TicketBundle\\": "src/Bundle/ChillTicketBundle/src", "Chill\\Utils\\Rector\\": "utils/rector/src" } }, diff --git a/config/bundles.php b/config/bundles.php index ec11bc0b6..2eb99868b 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -35,6 +35,7 @@ return [ Chill\ThirdPartyBundle\ChillThirdPartyBundle::class => ['all' => true], Chill\BudgetBundle\ChillBudgetBundle::class => ['all' => true], Chill\WopiBundle\ChillWopiBundle::class => ['all' => true], + Chill\TicketBundle\ChillTicketBundle::class => ['all' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true], ]; diff --git a/config/packages/chill_ticket.yaml b/config/packages/chill_ticket.yaml new file mode 100644 index 000000000..acb5571dc --- /dev/null +++ b/config/packages/chill_ticket.yaml @@ -0,0 +1,4 @@ +chill_ticket: + ticket: + person_per_ticket: one # One of "one"; "many" + diff --git a/config/packages/doctrine_migrations_chill.yaml b/config/packages/doctrine_migrations_chill.yaml index 8b8bf539b..29acb8a49 100644 --- a/config/packages/doctrine_migrations_chill.yaml +++ b/config/packages/doctrine_migrations_chill.yaml @@ -14,6 +14,7 @@ doctrine_migrations: 'Chill\Migrations\Calendar': '@ChillCalendarBundle/migrations' 'Chill\Migrations\Budget': '@ChillBudgetBundle/migrations' 'Chill\Migrations\Report': '@ChillReportBundle/migrations' + 'Chill\Migrations\Ticket': '@ChillTicketBundle/migrations' all_or_nothing: true diff --git a/config/routes/chill_ticket.yaml b/config/routes/chill_ticket.yaml new file mode 100644 index 000000000..311a51992 --- /dev/null +++ b/config/routes/chill_ticket.yaml @@ -0,0 +1,2 @@ +chill_ticket_bundle: + resource: '@ChillTicketBundle/config/routes.yaml' diff --git a/docs/source/development/create-a-new-bundle.rst b/docs/source/development/create-a-new-bundle.rst index 087487ff0..8206acee8 100644 --- a/docs/source/development/create-a-new-bundle.rst +++ b/docs/source/development/create-a-new-bundle.rst @@ -11,24 +11,94 @@ Create a new bundle ******************* -Create your own bundle is not a trivial task. - -The easiest way to achieve this is seems to be : - -1. Prepare a fresh installation of the chill project, in a new directory -2. Create a new bundle in this project, in the src directory -3. Initialize a git repository **at the root bundle**, and create your initial commit. -4. Register the bundle with composer/packagist. If you do not plan to distribute your bundle with packagist, you may use a custom repository for achieve this [#f1]_ -5. Move to a development installation, made as described in the :ref:`installation-for-development` section, and add your new repository to the composer.json file -6. Work as :ref:`usual ` - .. warning:: This part of the doc is not yet tested -TODO +Create a new directory with Bundle class +---------------------------------------- + +.. code-block:: bash + + mkdir -p src/Bundle/ChillSomeBundle/src/config + mkdir -p src/Bundle/ChillSomeBundle/src/Controller + +Add a bundle file + +.. code-block:: php + + ['all' => true], + +And import routes in :code:`config/routes/chill_some_bundle.yaml`: + +.. code-block:: yaml + + chill_ticket_bundle: + resource: '@ChillSomeBundle/config/routes.yaml' + +Add the doctrine_migration namespace +------------------------------------ + +Add the namespace to :code:`config/packages/doctrine_migrations_chill.yaml` + +.. code-block:: diff + + doctrine_migrations: + migrations_paths: + + 'Chill\Some\Ticket': '@ChillSomeBundle/migrations' + +Dump autoloading +---------------- + +.. code-block:: bash + + symfony composer dump-autoload -.. [#f1] Be aware that we use the Affero GPL Licence, which ensure that all users must have access to derivative works done with this software. diff --git a/package.json b/package.json index fc50ba29c..ccabc5c25 100644 --- a/package.json +++ b/package.json @@ -79,12 +79,12 @@ "dev": "encore dev", "watch": "encore dev --watch", "build": "encore production --progress", - "specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml> templates/api/specs.yaml", + "specs-build": "yaml-merge src/Bundle/ChillMainBundle/chill.api.specs.yaml src/Bundle/ChillPersonBundle/chill.api.specs.yaml src/Bundle/ChillCalendarBundle/chill.api.specs.yaml src/Bundle/ChillThirdPartyBundle/chill.api.specs.yaml src/Bundle/ChillDocStoreBundle/chill.api.specs.yaml src/Bundle/ChillTicketBundle/chill.api.specs.yaml> templates/api/specs.yaml", "specs-validate": "swagger-cli validate templates/api/specs.yaml", "specs-create-dir": "mkdir -p templates/api", "specs": "yarn run specs-create-dir && yarn run specs-build && yarn run specs-validate", "version": "node --version", - "eslint": "npx eslint-baseline --fix \"src/**/*.{js,ts,vue}\"" + "eslint": "eslint-baseline --fix \"src/**/*.{js,ts,vue}\"" }, "private": true } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cdc7468ec..d122aeb4c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -58,6 +58,10 @@ src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php + + + src/Bundle/ChillTicketBundle/tests/ + - + + diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue index 86d4a6c5a..5f434052e 100644 --- a/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue +++ b/src/Bundle/ChillCalendarBundle/Resources/public/vuejs/MyCalendarRange/Components/EditLocation.vue @@ -1,28 +1,28 @@ diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue b/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue index 1bcc95fd0..3a2f4fd59 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/public/vuejs/_components/PickTemplate.vue @@ -1,59 +1,54 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/js/async-upload/uploader.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/js/async-upload/uploader.ts index a09c4a5f6..367ab0829 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/js/async-upload/uploader.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/js/async-upload/uploader.ts @@ -6,20 +6,20 @@ const algo = "AES-CBC"; const URL_POST = "/asyncupload/temp_url/generate/post"; const keyDefinition = { - name: algo, - length: 256, + name: algo, + length: 256, }; const createFilename = (): string => { - let text = ""; - const possible = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let text = ""; + const possible = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - for (let i = 0; i < 7; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } + for (let i = 0; i < 7; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } - return text; + return text; }; /** @@ -30,59 +30,59 @@ const createFilename = (): string => { * @returns {Promise} A Promise that resolves to the newly created StoredObject. */ export const fetchNewStoredObject = async (): Promise => { - return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null); + return makeFetch("POST", "/api/1.0/doc-store/stored-object/create", null); }; export const uploadVersion = async ( - uploadFile: ArrayBuffer, - storedObject: StoredObject, + uploadFile: ArrayBuffer, + storedObject: StoredObject, ): Promise => { - const params = new URLSearchParams(); - params.append("expires_delay", "180"); - params.append("submit_delay", "180"); - const asyncData: PostStoreObjectSignature = await makeFetch( - "GET", - `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` + - "?" + - params.toString(), - ); - const suffix = createFilename(); - const filename = asyncData.prefix + suffix; - const formData = new FormData(); - formData.append("redirect", asyncData.redirect); - formData.append("max_file_size", asyncData.max_file_size.toString()); - formData.append("max_file_count", asyncData.max_file_count.toString()); - formData.append("expires", asyncData.expires.toString()); - formData.append("signature", asyncData.signature); - formData.append(filename, new Blob([uploadFile]), suffix); + const params = new URLSearchParams(); + params.append("expires_delay", "180"); + params.append("submit_delay", "180"); + const asyncData: PostStoreObjectSignature = await makeFetch( + "GET", + `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/post` + + "?" + + params.toString(), + ); + const suffix = createFilename(); + const filename = asyncData.prefix + suffix; + const formData = new FormData(); + formData.append("redirect", asyncData.redirect); + formData.append("max_file_size", asyncData.max_file_size.toString()); + formData.append("max_file_count", asyncData.max_file_count.toString()); + formData.append("expires", asyncData.expires.toString()); + formData.append("signature", asyncData.signature); + formData.append(filename, new Blob([uploadFile]), suffix); - const response = await window.fetch(asyncData.url, { - method: "POST", - body: formData, - }); + const response = await window.fetch(asyncData.url, { + method: "POST", + body: formData, + }); - if (!response.ok) { - console.error("Error while sending file to store", response); - throw new Error(response.statusText); - } + if (!response.ok) { + console.error("Error while sending file to store", response); + throw new Error(response.statusText); + } - return Promise.resolve(filename); + return Promise.resolve(filename); }; export const encryptFile = async ( - originalFile: ArrayBuffer, + originalFile: ArrayBuffer, ): Promise<[ArrayBuffer, Uint8Array, JsonWebKey]> => { - const iv = crypto.getRandomValues(new Uint8Array(16)); - const key = await window.crypto.subtle.generateKey(keyDefinition, true, [ - "encrypt", - "decrypt", - ]); - const exportedKey = await window.crypto.subtle.exportKey("jwk", key); - const encrypted = await window.crypto.subtle.encrypt( - { name: algo, iv: iv }, - key, - originalFile, - ); + const iv = crypto.getRandomValues(new Uint8Array(16)); + const key = await window.crypto.subtle.generateKey(keyDefinition, true, [ + "encrypt", + "decrypt", + ]); + const exportedKey = await window.crypto.subtle.exportKey("jwk", key); + const encrypted = await window.crypto.subtle.encrypt( + { name: algo, iv: iv }, + key, + originalFile, + ); - return Promise.resolve([encrypted, iv, exportedKey]); + return Promise.resolve([encrypted, iv, exportedKey]); }; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/js/generic-doc-api.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/js/generic-doc-api.ts index c15eff711..aaff2a331 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/js/generic-doc-api.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/js/generic-doc-api.ts @@ -2,9 +2,9 @@ import { fetchResults } from "ChillMainAssets/lib/api/apiMethods"; import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc"; export function fetch_generic_docs_by_accompanying_period( - periodId: number, + periodId: number, ): Promise { - return fetchResults( - `/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`, - ); + return fetchResults( + `/api/1.0/doc-store/generic-doc/by-period/${periodId}/index`, + ); } diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts index 30fe2b26a..97e0d3aa3 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/module/async_upload/index.ts @@ -6,117 +6,116 @@ import { _createI18n } from "../../../../../ChillMainBundle/Resources/public/vue const i18n = _createI18n({}); const startApp = ( - divElement: HTMLDivElement, - collectionEntry: null | HTMLLIElement, + divElement: HTMLDivElement, + collectionEntry: null | HTMLLIElement, ): void => { - console.log("app started", divElement); + console.log("app started", divElement); - const inputTitle = collectionEntry?.querySelector("input[type='text']"); + const inputTitle = collectionEntry?.querySelector("input[type='text']"); - const input_stored_object: HTMLInputElement | null = - divElement.querySelector("input[data-stored-object]"); - if (null === input_stored_object) { - throw new Error("input to stored object not found"); - } + const input_stored_object: HTMLInputElement | null = divElement.querySelector( + "input[data-stored-object]", + ); + if (null === input_stored_object) { + throw new Error("input to stored object not found"); + } - let existingDoc: StoredObject | null = null; - if (input_stored_object.value !== "") { - existingDoc = JSON.parse(input_stored_object.value); - } - const app_container = document.createElement("div"); - divElement.appendChild(app_container); + let existingDoc: StoredObject | null = null; + if (input_stored_object.value !== "") { + existingDoc = JSON.parse(input_stored_object.value); + } + const app_container = document.createElement("div"); + divElement.appendChild(app_container); - const app = createApp({ - template: - '', - data() { - return { - existingDoc: existingDoc, - inputTitle: inputTitle, - }; - }, - components: { - DropFileWidget, - }, - methods: { - addDocument: function ({ - stored_object, - stored_object_version, - file_name, - }: { - stored_object: StoredObject; - stored_object_version: StoredObjectVersion; - file_name: string; - }): void { - stored_object.title = file_name; - console.log("object added", stored_object); - console.log("version added", stored_object_version); - this.$data.existingDoc = stored_object; - this.$data.existingDoc.currentVersion = stored_object_version; - input_stored_object.value = JSON.stringify( - this.$data.existingDoc, - ); - if (this.$data.inputTitle) { - if (!this.$data.inputTitle?.value) { - this.$data.inputTitle.value = file_name; - } - } - }, - removeDocument: function (object: StoredObject): void { - console.log("catch remove document", object); - input_stored_object.value = ""; - this.$data.existingDoc = undefined; - console.log("collectionEntry", collectionEntry); + const app = createApp({ + template: + '', + data() { + return { + existingDoc: existingDoc, + inputTitle: inputTitle, + }; + }, + components: { + DropFileWidget, + }, + methods: { + addDocument: function ({ + stored_object, + stored_object_version, + file_name, + }: { + stored_object: StoredObject; + stored_object_version: StoredObjectVersion; + file_name: string; + }): void { + stored_object.title = file_name; + console.log("object added", stored_object); + console.log("version added", stored_object_version); + this.$data.existingDoc = stored_object; + this.$data.existingDoc.currentVersion = stored_object_version; + input_stored_object.value = JSON.stringify(this.$data.existingDoc); + if (this.$data.inputTitle) { + if (!this.$data.inputTitle?.value) { + this.$data.inputTitle.value = file_name; + } + } + }, + removeDocument: function (object: StoredObject): void { + console.log("catch remove document", object); + input_stored_object.value = ""; + this.$data.existingDoc = undefined; + console.log("collectionEntry", collectionEntry); - if (null !== collectionEntry) { - console.log("will remove collection"); - collectionEntry.remove(); - } - }, - }, - }); + if (null !== collectionEntry) { + console.log("will remove collection"); + collectionEntry.remove(); + } + }, + }, + }); - app.use(i18n).mount(app_container); + app.use(i18n).mount(app_container); }; window.addEventListener("collection-add-entry", (( - e: CustomEvent, + e: CustomEvent, ) => { - const detail = e.detail; - const divElement: null | HTMLDivElement = detail.entry.querySelector( - "div[data-stored-object]", - ); + const detail = e.detail; + const divElement: null | HTMLDivElement = detail.entry.querySelector( + "div[data-stored-object]", + ); - if (null === divElement) { - throw new Error("div[data-stored-object] not found"); - } + if (null === divElement) { + throw new Error("div[data-stored-object] not found"); + } - startApp(divElement, detail.entry); + startApp(divElement, detail.entry); }) as EventListener); window.addEventListener("DOMContentLoaded", () => { - const upload_inputs: NodeListOf = document.querySelectorAll( - "div[data-stored-object]", - ); + const upload_inputs: NodeListOf = document.querySelectorAll( + "div[data-stored-object]", + ); - upload_inputs.forEach((input: HTMLDivElement): void => { - // test for a parent to check if this is a collection entry - let collectionEntry: null | HTMLLIElement = null; - const parent = input.parentElement; - console.log("parent", parent); - if (null !== parent) { - const grandParent = parent.parentElement; - console.log("grandParent", grandParent); - if (null !== grandParent) { - if ( - grandParent.tagName.toLowerCase() === "li" && - grandParent.classList.contains("entry") - ) { - collectionEntry = grandParent as HTMLLIElement; - } - } + upload_inputs.forEach((input: HTMLDivElement): void => { + // test for a parent to check if this is a collection entry + let collectionEntry: null | HTMLLIElement = null; + const parent = input.parentElement; + console.log("parent", parent); + if (null !== parent) { + const grandParent = parent.parentElement; + console.log("grandParent", grandParent); + if (null !== grandParent) { + if ( + grandParent.tagName.toLowerCase() === "li" && + grandParent.classList.contains("entry") + ) { + collectionEntry = grandParent as HTMLLIElement; } - startApp(input, collectionEntry); - }); + } + } + startApp(input, collectionEntry); + }); }); export {}; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/module/button_download/index.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/module/button_download/index.ts index 30b9e29d1..4ab819ddf 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/module/button_download/index.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/module/button_download/index.ts @@ -9,26 +9,26 @@ import ToastPlugin from "vue-toast-notification"; const i18n = _createI18n({}); window.addEventListener("DOMContentLoaded", function (e) { - document - .querySelectorAll("div[data-download-button-single]") - .forEach((el) => { - const storedObject = JSON.parse( - el.dataset.storedObject as string, - ) as StoredObject; - const title = el.dataset.title as string; - const app = createApp({ - components: { DownloadButton }, - data() { - return { - storedObject, - title, - classes: { btn: true, "btn-outline-primary": true }, - }; - }, - template: - '', - }); + document + .querySelectorAll("div[data-download-button-single]") + .forEach((el) => { + const storedObject = JSON.parse( + el.dataset.storedObject as string, + ) as StoredObject; + const title = el.dataset.title as string; + const app = createApp({ + components: { DownloadButton }, + data() { + return { + storedObject, + title, + classes: { btn: true, "btn-outline-primary": true }, + }; + }, + template: + '', + }); - app.use(i18n).use(ToastPlugin).mount(el); - }); + app.use(i18n).use(ToastPlugin).mount(el); + }); }); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts index 5874e88fe..080f63c66 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts @@ -8,66 +8,66 @@ import ToastPlugin from "vue-toast-notification"; const i18n = _createI18n({}); window.addEventListener("DOMContentLoaded", function (e) { - document - .querySelectorAll("div[data-download-buttons]") - .forEach((el) => { - const app = createApp({ - components: { DocumentActionButtonsGroup }, - data() { - const datasets = el.dataset as { - filename: string; - canEdit: string; - storedObject: string; - buttonSmall: string; - davLink: string; - davLinkExpiration: string; - }; + document + .querySelectorAll("div[data-download-buttons]") + .forEach((el) => { + const app = createApp({ + components: { DocumentActionButtonsGroup }, + data() { + const datasets = el.dataset as { + filename: string; + canEdit: string; + storedObject: string; + buttonSmall: string; + davLink: string; + davLinkExpiration: string; + }; - const storedObject = JSON.parse( - datasets.storedObject, - ) as StoredObject, - filename = datasets.filename, - canEdit = datasets.canEdit === "1", - small = datasets.buttonSmall === "1", - davLink = - "davLink" in datasets && datasets.davLink !== "" - ? datasets.davLink - : null, - davLinkExpiration = - "davLinkExpiration" in datasets - ? Number.parseInt(datasets.davLinkExpiration) - : null; - return { - storedObject, - filename, - canEdit, - small, - davLink, - davLinkExpiration, - }; - }, - template: - '', - methods: { - onStoredObjectStatusChange: function ( - newStatus: StoredObjectStatusChange, - ): void { - this.$data.storedObject.status = newStatus.status; - this.$data.storedObject.filename = newStatus.filename; - this.$data.storedObject.type = newStatus.type; + const storedObject = JSON.parse( + datasets.storedObject, + ) as StoredObject, + filename = datasets.filename, + canEdit = datasets.canEdit === "1", + small = datasets.buttonSmall === "1", + davLink = + "davLink" in datasets && datasets.davLink !== "" + ? datasets.davLink + : null, + davLinkExpiration = + "davLinkExpiration" in datasets + ? Number.parseInt(datasets.davLinkExpiration) + : null; + return { + storedObject, + filename, + canEdit, + small, + davLink, + davLinkExpiration, + }; + }, + template: + '', + methods: { + onStoredObjectStatusChange: function ( + newStatus: StoredObjectStatusChange, + ): void { + this.$data.storedObject.status = newStatus.status; + this.$data.storedObject.filename = newStatus.filename; + this.$data.storedObject.type = newStatus.type; - // remove eventual div which inform pending status - document - .querySelectorAll( - `[data-docgen-is-pending="${this.$data.storedObject.id}"]`, - ) - .forEach(function (el) { - el.remove(); - }); - }, - }, - }); + // remove eventual div which inform pending status + document + .querySelectorAll( + `[data-docgen-is-pending="${this.$data.storedObject.id}"]`, + ) + .forEach(function (el) { + el.remove(); + }); + }, + }, + }); - app.use(i18n).use(ToastPlugin).mount(el); - }); + app.use(i18n).use(ToastPlugin).mount(el); + }); }); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/types/generic_doc.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/types/generic_doc.ts index facc55e58..9cda25cad 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/types/generic_doc.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/types/generic_doc.ts @@ -2,7 +2,7 @@ import { DateTime } from "ChillMainAssets/types"; import { StoredObject } from "ChillDocStoreAssets/types/index"; export interface GenericDocMetadata { - isPresent: boolean; + isPresent: boolean; } /** @@ -15,57 +15,57 @@ export interface EmptyMetadata extends GenericDocMetadata {} * Minimal Metadata for a GenericDoc with a normalizer */ export interface BaseMetadata extends GenericDocMetadata { - title: string; + title: string; } /** * A generic doc is a document attached to a Person or an AccompanyingPeriod. */ export interface GenericDoc { - type: "doc_store_generic_doc"; - uniqueKey: string; - key: string; - identifiers: object; - context: "person" | "accompanying-period"; - doc_date: DateTime; - metadata: GenericDocMetadata; - storedObject: StoredObject | null; + type: "doc_store_generic_doc"; + uniqueKey: string; + key: string; + identifiers: object; + context: "person" | "accompanying-period"; + doc_date: DateTime; + metadata: GenericDocMetadata; + storedObject: StoredObject | null; } export interface GenericDocForAccompanyingPeriod extends GenericDoc { - context: "accompanying-period"; + context: "accompanying-period"; } interface BaseMetadataWithHtml extends BaseMetadata { - html: string; + html: string; } export interface GenericDocForAccompanyingCourseDocument - extends GenericDocForAccompanyingPeriod { - key: "accompanying_course_document"; - metadata: BaseMetadataWithHtml; + extends GenericDocForAccompanyingPeriod { + key: "accompanying_course_document"; + metadata: BaseMetadataWithHtml; } export interface GenericDocForAccompanyingCourseActivityDocument - extends GenericDocForAccompanyingPeriod { - key: "accompanying_course_activity_document"; - metadata: BaseMetadataWithHtml; + extends GenericDocForAccompanyingPeriod { + key: "accompanying_course_activity_document"; + metadata: BaseMetadataWithHtml; } export interface GenericDocForAccompanyingCourseCalendarDocument - extends GenericDocForAccompanyingPeriod { - key: "accompanying_course_calendar_document"; - metadata: BaseMetadataWithHtml; + extends GenericDocForAccompanyingPeriod { + key: "accompanying_course_calendar_document"; + metadata: BaseMetadataWithHtml; } export interface GenericDocForAccompanyingCoursePersonDocument - extends GenericDocForAccompanyingPeriod { - key: "person_document"; - metadata: BaseMetadataWithHtml; + extends GenericDocForAccompanyingPeriod { + key: "person_document"; + metadata: BaseMetadataWithHtml; } export interface GenericDocForAccompanyingCourseWorkEvaluationDocument - extends GenericDocForAccompanyingPeriod { - key: "accompanying_period_work_evaluation_document"; - metadata: BaseMetadataWithHtml; + extends GenericDocForAccompanyingPeriod { + key: "accompanying_period_work_evaluation_document"; + metadata: BaseMetadataWithHtml; } diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/types/index.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/types/index.ts index 5b34b8f73..f71ed0350 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/types/index.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/types/index.ts @@ -4,73 +4,73 @@ import { SignedUrlGet } from "ChillDocStoreAssets/vuejs/StoredObjectButton/helpe export type StoredObjectStatus = "empty" | "ready" | "failure" | "pending"; export interface StoredObject { - id: number; - title: string | null; - uuid: string; - prefix: string; - status: StoredObjectStatus; - currentVersion: - | null - | StoredObjectVersionCreated - | StoredObjectVersionPersisted; - totalVersions: number; - datas: object; - /** @deprecated */ - creationDate: DateTime; - createdAt: DateTime | null; - createdBy: User | null; - _permissions: { - canEdit: boolean; - canSee: boolean; - }; - _links?: { - dav_link?: { - href: string; - expiration: number; - }; - downloadLink?: SignedUrlGet; + id: number; + title: string | null; + uuid: string; + prefix: string; + status: StoredObjectStatus; + currentVersion: + | null + | StoredObjectVersionCreated + | StoredObjectVersionPersisted; + totalVersions: number; + datas: object; + /** @deprecated */ + creationDate: DateTime; + createdAt: DateTime | null; + createdBy: User | null; + _permissions: { + canEdit: boolean; + canSee: boolean; + }; + _links?: { + dav_link?: { + href: string; + expiration: number; }; + downloadLink?: SignedUrlGet; + }; } export interface StoredObjectVersion { - /** - * filename of the object in the object storage - */ - filename: string; - iv: number[]; - keyInfos: JsonWebKey; - type: string; + /** + * filename of the object in the object storage + */ + filename: string; + iv: number[]; + keyInfos: JsonWebKey; + type: string; } export interface StoredObjectVersionCreated extends StoredObjectVersion { - persisted: false; + persisted: false; } export interface StoredObjectVersionPersisted - extends StoredObjectVersionCreated { - version: number; - id: number; - createdAt: DateTime | null; - createdBy: User | null; + extends StoredObjectVersionCreated { + version: number; + id: number; + createdAt: DateTime | null; + createdBy: User | null; } export interface StoredObjectStatusChange { - id: number; - filename: string; - status: StoredObjectStatus; - type: string; + id: number; + filename: string; + status: StoredObjectStatus; + type: string; } export interface StoredObjectVersionWithPointInTime - extends StoredObjectVersionPersisted { - "point-in-times": StoredObjectPointInTime[]; - "from-restored": StoredObjectVersionPersisted | null; + extends StoredObjectVersionPersisted { + "point-in-times": StoredObjectPointInTime[]; + "from-restored": StoredObjectVersionPersisted | null; } export interface StoredObjectPointInTime { - id: number; - byUser: User | null; - reason: "keep-before-conversion" | "keep-by-user"; + id: number; + byUser: User | null; + reason: "keep-before-conversion" | "keep-by-user"; } /** @@ -82,63 +82,63 @@ export type WopiEditButtonExecutableBeforeLeaveFunction = () => Promise; * Object containing information for performering a POST request to a swift object store */ export interface PostStoreObjectSignature { - method: "POST"; - max_file_size: number; - max_file_count: 1; - expires: number; - submit_delay: 180; - redirect: string; - prefix: string; - url: string; - signature: string; + method: "POST"; + max_file_size: number; + max_file_count: 1; + expires: number; + submit_delay: 180; + redirect: string; + prefix: string; + url: string; + signature: string; } export interface PDFPage { - index: number; - width: number; - height: number; + index: number; + width: number; + height: number; } export interface SignatureZone { - index: number | null; - x: number; - y: number; - width: number; - height: number; - PDFPage: PDFPage; + index: number | null; + x: number; + y: number; + width: number; + height: number; + PDFPage: PDFPage; } export interface Signature { - id: number; - storedObject: StoredObject; - zones: SignatureZone[]; + id: number; + storedObject: StoredObject; + zones: SignatureZone[]; } export type SignedState = - | "pending" - | "signed" - | "rejected" - | "canceled" - | "error"; + | "pending" + | "signed" + | "rejected" + | "canceled" + | "error"; export interface CheckSignature { - state: SignedState; - storedObject: StoredObject; + state: SignedState; + storedObject: StoredObject; } export type CanvasEvent = "select" | "add"; export interface ZoomLevel { - id: number; - zoom: number; - label: { - fr?: string; - nl?: string; - }; + id: number; + zoom: number; + label: { + fr?: string; + nl?: string; + }; } export interface GenericDoc { - type: "doc_store_generic_doc"; - key: string; - context: "person" | "accompanying-period"; - doc_date: DateTime; + type: "doc_store_generic_doc"; + key: string; + context: "person" | "accompanying-period"; + doc_date: DateTime; } diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue index 85548f6d3..8fd348807 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue @@ -1,67 +1,65 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/App.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/App.vue index ea195da4b..3021de36e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/App.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/App.vue @@ -1,355 +1,338 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/index.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/index.ts index a46257b5e..e3ace5b40 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/index.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentSignature/index.ts @@ -4,36 +4,36 @@ import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n"; import App from "./App.vue"; const appMessages = { - fr: { - yes: "Oui", - are_you_sure: "Êtes-vous sûr·e?", - you_are_going_to_sign: "Vous allez signer le document", - signature_confirmation: "Confirmation de la signature", - sign: "Signer", - choose_another_signature: "Choisir une autre zone", - cancel: "Annuler", - last_sign_zone: "Zone de signature précédente", - next_sign_zone: "Zone de signature suivante", - add_sign_zone: "Ajouter une zone de signature", - click_on_document: "Cliquer sur le document", - last_zone: "Zone précédente", - next_zone: "Zone suivante", - add_zone: "Ajouter une zone", - another_zone: "Autre zone", - electronic_signature_in_progress: "Signature électronique en cours...", - loading: "Chargement...", - remove_sign_zone: "Enlever la zone", - return: "Retour", - see_all_pages: "Voir toutes les pages", - all_pages: "Toutes les pages", - }, + fr: { + yes: "Oui", + are_you_sure: "Êtes-vous sûr·e?", + you_are_going_to_sign: "Vous allez signer le document", + signature_confirmation: "Confirmation de la signature", + sign: "Signer", + choose_another_signature: "Choisir une autre zone", + cancel: "Annuler", + last_sign_zone: "Zone de signature précédente", + next_sign_zone: "Zone de signature suivante", + add_sign_zone: "Ajouter une zone de signature", + click_on_document: "Cliquer sur le document", + last_zone: "Zone précédente", + next_zone: "Zone suivante", + add_zone: "Ajouter une zone", + another_zone: "Autre zone", + electronic_signature_in_progress: "Signature électronique en cours...", + loading: "Chargement...", + remove_sign_zone: "Enlever la zone", + return: "Retour", + see_all_pages: "Voir toutes les pages", + all_pages: "Toutes les pages", + }, }; const i18n = _createI18n(appMessages); const app = createApp({ - template: ``, + template: ``, }) - .use(i18n) - .component("app", App) - .mount("#document-signature"); + .use(i18n) + .component("app", App) + .mount("#document-signature"); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFile.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFile.vue index 017629ee4..c731ef19f 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFile.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFile.vue @@ -1,208 +1,206 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFileModal.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFileModal.vue index 956b3e859..bde094487 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFileModal.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFileModal.vue @@ -6,24 +6,24 @@ import { computed, reactive } from "vue"; import { useToast } from "vue-toast-notification"; interface DropFileConfig { - allowRemove: boolean; - existingDoc?: StoredObject; + allowRemove: boolean; + existingDoc?: StoredObject; } const props = withDefaults(defineProps(), { - allowRemove: false, + allowRemove: false, }); const emit = defineEmits<{ - ( - e: "addDocument", - { - stored_object: StoredObject, - stored_object_version: StoredObjectVersion, - file_name: string, - }, - ): void; - (e: "removeDocument"): void; + ( + e: "addDocument", + { + stored_object: StoredObject, + stored_object_version: StoredObjectVersion, + file_name: string, + }, + ): void; + (e: "removeDocument"): void; }>(); const $toast = useToast(); @@ -33,67 +33,67 @@ const state = reactive({ showModal: false }); const modalClasses = { "modal-dialog-centered": true, "modal-md": true }; const buttonState = computed<"add" | "replace">(() => { - if (props.existingDoc === undefined || props.existingDoc === null) { - return "add"; - } + if (props.existingDoc === undefined || props.existingDoc === null) { + return "add"; + } - return "replace"; + return "replace"; }); function onAddDocument({ - stored_object, - stored_object_version, - file_name, + stored_object, + stored_object_version, + file_name, }: { - stored_object: StoredObject; - stored_object_version: StoredObjectVersion; - file_name: string; + stored_object: StoredObject; + stored_object_version: StoredObjectVersion; + file_name: string; }): void { - const message = - buttonState.value === "add" ? "Document ajouté" : "Document remplacé"; - $toast.success(message); - emit("addDocument", { stored_object_version, stored_object, file_name }); - state.showModal = false; + const message = + buttonState.value === "add" ? "Document ajouté" : "Document remplacé"; + $toast.success(message); + emit("addDocument", { stored_object_version, stored_object, file_name }); + state.showModal = false; } function onRemoveDocument(): void { - emit("removeDocument"); + emit("removeDocument"); } function openModal(): void { - state.showModal = true; + state.showModal = true; } function closeModal(): void { - state.showModal = false; + state.showModal = false; } diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFileWidget.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFileWidget.vue index 54f8ddf0c..8ead08b45 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFileWidget.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DropFileWidget/DropFileWidget.vue @@ -5,97 +5,97 @@ import DropFile from "ChillDocStoreAssets/vuejs/DropFileWidget/DropFile.vue"; import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue"; interface DropFileConfig { - allowRemove: boolean; - existingDoc?: StoredObject; + allowRemove: boolean; + existingDoc?: StoredObject; } const props = withDefaults(defineProps(), { - allowRemove: false, + allowRemove: false, }); const emit = defineEmits<{ - ( - e: "addDocument", - { - stored_object: StoredObject, - stored_object_version: StoredObjectVersion, - file_name: string, - }, - ): void; - (e: "removeDocument"): void; + ( + e: "addDocument", + { + stored_object: StoredObject, + stored_object_version: StoredObjectVersion, + file_name: string, + }, + ): void; + (e: "removeDocument"): void; }>(); const has_existing_doc = computed(() => { - return props.existingDoc !== undefined && props.existingDoc !== null; + return props.existingDoc !== undefined && props.existingDoc !== null; }); const dav_link_expiration = computed(() => { - if (props.existingDoc === undefined || props.existingDoc === null) { - return undefined; - } - if (props.existingDoc.status !== "ready") { - return undefined; - } + if (props.existingDoc === undefined || props.existingDoc === null) { + return undefined; + } + if (props.existingDoc.status !== "ready") { + return undefined; + } - return props.existingDoc._links?.dav_link?.expiration; + return props.existingDoc._links?.dav_link?.expiration; }); const dav_link_href = computed(() => { - if (props.existingDoc === undefined || props.existingDoc === null) { - return undefined; - } - if (props.existingDoc.status !== "ready") { - return undefined; - } + if (props.existingDoc === undefined || props.existingDoc === null) { + return undefined; + } + if (props.existingDoc.status !== "ready") { + return undefined; + } - return props.existingDoc._links?.dav_link?.href; + return props.existingDoc._links?.dav_link?.href; }); const onAddDocument = ({ - stored_object, - stored_object_version, - file_name, + stored_object, + stored_object_version, + file_name, }: { - stored_object: StoredObject; - stored_object_version: StoredObjectVersion; - file_name: string; + stored_object: StoredObject; + stored_object_version: StoredObjectVersion; + file_name: string; }): void => { - emit("addDocument", { stored_object, stored_object_version, file_name }); + emit("addDocument", { stored_object, stored_object_version, file_name }); }; const onRemoveDocument = (e: Event): void => { - e.stopPropagation(); - e.preventDefault(); - emit("removeDocument"); + e.stopPropagation(); + e.preventDefault(); + emit("removeDocument"); }; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/FileIcon.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/FileIcon.vue index 5260095fa..ba2c44f6a 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/FileIcon.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/FileIcon.vue @@ -1,46 +1,46 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue index b6c6a496f..8cb60bc12 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue @@ -1,28 +1,28 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DesktopEditButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DesktopEditButton.vue index 2b74f76bd..f00889a8e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DesktopEditButton.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DesktopEditButton.vue @@ -3,13 +3,13 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue"; import { computed, reactive } from "vue"; export interface DesktopEditButtonConfig { - editLink: null; - classes: Record; - expirationLink: number | Date; + editLink: null; + classes: Record; + expirationLink: number | Date; } interface DesktopEditButtonState { - modalOpened: boolean; + modalOpened: boolean; } const state: DesktopEditButtonState = reactive({ modalOpened: false }); @@ -17,80 +17,76 @@ const state: DesktopEditButtonState = reactive({ modalOpened: false }); const props = defineProps(); const buildCommand = computed( - () => "vnd.libreoffice.command:ofe|u|" + props.editLink, + () => "vnd.libreoffice.command:ofe|u|" + props.editLink, ); const editionUntilFormatted = computed(() => { - let d; + let d; - if (props.expirationLink instanceof Date) { - d = props.expirationLink; - } else { - d = new Date(props.expirationLink * 1000); - } - console.log(props.expirationLink); + if (props.expirationLink instanceof Date) { + d = props.expirationLink; + } else { + d = new Date(props.expirationLink * 1000); + } + console.log(props.expirationLink); - return new Intl.DateTimeFormat(undefined, { - dateStyle: "long", - timeStyle: "medium", - }).format(d); + return new Intl.DateTimeFormat(undefined, { + dateStyle: "long", + timeStyle: "medium", + }).format(d); }); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue index d28b49cdf..8316229ce 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue @@ -1,26 +1,26 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton.vue index 71a8d42ea..f8c1c4ead 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton.vue @@ -1,20 +1,20 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue index ac4bf4591..cf70e3db3 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue @@ -1,26 +1,26 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue index bf5e3fffb..2a563d853 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonListItem.vue @@ -1,8 +1,8 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue index b9535ca2e..0d9a951e5 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/HistoryButtonModal.vue @@ -3,54 +3,54 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue"; import { reactive } from "vue"; import HistoryButtonList from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/HistoryButtonList.vue"; import { - StoredObject, - StoredObjectVersionWithPointInTime, + StoredObject, + StoredObjectVersionWithPointInTime, } from "./../../../types"; interface HistoryButtonListConfig { - versions: StoredObjectVersionWithPointInTime[]; - storedObject: StoredObject; - canEdit: boolean; + versions: StoredObjectVersionWithPointInTime[]; + storedObject: StoredObject; + canEdit: boolean; } const emit = defineEmits<{ - restoreVersion: [newVersion: StoredObjectVersionWithPointInTime]; + restoreVersion: [newVersion: StoredObjectVersionWithPointInTime]; }>(); interface HistoryButtonModalState { - opened: boolean; + opened: boolean; } const props = defineProps(); const state = reactive({ opened: false }); const open = () => { - state.opened = true; + state.opened = true; }; const onRestoreVersion = (payload: { - newVersion: StoredObjectVersionWithPointInTime; + newVersion: StoredObjectVersionWithPointInTime; }) => emit("restoreVersion", payload); defineExpose({ open }); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/RestoreVersionButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/RestoreVersionButton.vue index 41ae8e978..595d3781a 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/RestoreVersionButton.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/RestoreVersionButton.vue @@ -1,17 +1,17 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/api.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/api.ts index cb7581eab..b003e26b7 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/api.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/HistoryButton/api.ts @@ -1,33 +1,33 @@ import { - StoredObject, - StoredObjectVersionPersisted, - StoredObjectVersionWithPointInTime, + StoredObject, + StoredObjectVersionPersisted, + StoredObjectVersionWithPointInTime, } from "../../../types"; import { - fetchResults, - makeFetch, + fetchResults, + makeFetch, } from "../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; export const get_versions = async ( - storedObject: StoredObject, + storedObject: StoredObject, ): Promise => { - const versions = await fetchResults( - `/api/1.0/doc-store/stored-object/${storedObject.uuid}/versions`, - ); + const versions = await fetchResults( + `/api/1.0/doc-store/stored-object/${storedObject.uuid}/versions`, + ); - return versions.sort( - ( - a: StoredObjectVersionWithPointInTime, - b: StoredObjectVersionWithPointInTime, - ) => b.version - a.version, - ); + return versions.sort( + ( + a: StoredObjectVersionWithPointInTime, + b: StoredObjectVersionWithPointInTime, + ) => b.version - a.version, + ); }; export const restore_version = async ( - version: StoredObjectVersionPersisted, + version: StoredObjectVersionPersisted, ): Promise => { - return await makeFetch( - "POST", - `/api/1.0/doc-store/stored-object/restore-from-version/${version.id}`, - ); + return await makeFetch( + "POST", + `/api/1.0/doc-store/stored-object/restore-from-version/${version.id}`, + ); }; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue index b57c1f188..97073e4e1 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue @@ -1,29 +1,27 @@ diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts index ff1e6219d..aceaa038b 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts @@ -1,235 +1,230 @@ import { - StoredObject, - StoredObjectStatus, - StoredObjectStatusChange, - StoredObjectVersion, + StoredObject, + StoredObjectStatus, + StoredObjectStatusChange, + StoredObjectVersion, } from "../../types"; import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; const MIMES_EDIT = new Set([ - "application/vnd.ms-powerpoint", - "application/vnd.ms-excel", - "application/vnd.oasis.opendocument.text", - "application/vnd.oasis.opendocument.text-flat-xml", - "application/vnd.oasis.opendocument.spreadsheet", - "application/vnd.oasis.opendocument.spreadsheet-flat-xml", - "application/vnd.oasis.opendocument.presentation", - "application/vnd.oasis.opendocument.presentation-flat-xml", - "application/vnd.oasis.opendocument.graphics", - "application/vnd.oasis.opendocument.graphics-flat-xml", - "application/vnd.oasis.opendocument.chart", - "application/msword", - "application/vnd.ms-excel", - "application/vnd.ms-powerpoint", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "application/vnd.ms-word.document.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "application/vnd.ms-excel.sheet.binary.macroEnabled.12", - "application/vnd.ms-excel.sheet.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "application/vnd.ms-powerpoint.presentation.macroEnabled.12", - "application/x-dif-document", - "text/spreadsheet", - "text/csv", - "application/x-dbase", - "text/rtf", - "text/plain", - "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + "application/vnd.ms-powerpoint", + "application/vnd.ms-excel", + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.text-flat-xml", + "application/vnd.oasis.opendocument.spreadsheet", + "application/vnd.oasis.opendocument.spreadsheet-flat-xml", + "application/vnd.oasis.opendocument.presentation", + "application/vnd.oasis.opendocument.presentation-flat-xml", + "application/vnd.oasis.opendocument.graphics", + "application/vnd.oasis.opendocument.graphics-flat-xml", + "application/vnd.oasis.opendocument.chart", + "application/msword", + "application/vnd.ms-excel", + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.ms-word.document.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-excel.sheet.binary.macroEnabled.12", + "application/vnd.ms-excel.sheet.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.ms-powerpoint.presentation.macroEnabled.12", + "application/x-dif-document", + "text/spreadsheet", + "text/csv", + "application/x-dbase", + "text/rtf", + "text/plain", + "application/vnd.openxmlformats-officedocument.presentationml.slideshow", ]); const MIMES_VIEW = new Set([ - ...MIMES_EDIT, - [ - "image/svg+xml", - "application/vnd.sun.xml.writer", - "application/vnd.sun.xml.calc", - "application/vnd.sun.xml.impress", - "application/vnd.sun.xml.draw", - "application/vnd.sun.xml.writer.global", - "application/vnd.sun.xml.writer.template", - "application/vnd.sun.xml.calc.template", - "application/vnd.sun.xml.impress.template", - "application/vnd.sun.xml.draw.template", - "application/vnd.oasis.opendocument.text-master", - "application/vnd.oasis.opendocument.text-template", - "application/vnd.oasis.opendocument.text-master-template", - "application/vnd.oasis.opendocument.spreadsheet-template", - "application/vnd.oasis.opendocument.presentation-template", - "application/vnd.oasis.opendocument.graphics-template", - "application/vnd.ms-word.template.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.spreadsheetml.template", - "application/vnd.ms-excel.template.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.presentationml.template", - "application/vnd.ms-powerpoint.template.macroEnabled.12", - "application/vnd.wordperfect", - "application/x-aportisdoc", - "application/x-hwp", - "application/vnd.ms-works", - "application/x-mswrite", - "application/vnd.lotus-1-2-3", - "image/cgm", - "image/vnd.dxf", - "image/x-emf", - "image/x-wmf", - "application/coreldraw", - "application/vnd.visio2013", - "application/vnd.visio", - "application/vnd.ms-visio.drawing", - "application/x-mspublisher", - "application/x-sony-bbeb", - "application/x-gnumeric", - "application/macwriteii", - "application/x-iwork-numbers-sffnumbers", - "application/vnd.oasis.opendocument.text-web", - "application/x-pagemaker", - "application/x-fictionbook+xml", - "application/clarisworks", - "image/x-wpg", - "application/x-iwork-pages-sffpages", - "application/x-iwork-keynote-sffkey", - "application/x-abiword", - "image/x-freehand", - "application/vnd.sun.xml.chart", - "application/x-t602", - "image/bmp", - "image/png", - "image/gif", - "image/tiff", - "image/jpg", - "image/jpeg", - "application/pdf", - ], + ...MIMES_EDIT, + [ + "image/svg+xml", + "application/vnd.sun.xml.writer", + "application/vnd.sun.xml.calc", + "application/vnd.sun.xml.impress", + "application/vnd.sun.xml.draw", + "application/vnd.sun.xml.writer.global", + "application/vnd.sun.xml.writer.template", + "application/vnd.sun.xml.calc.template", + "application/vnd.sun.xml.impress.template", + "application/vnd.sun.xml.draw.template", + "application/vnd.oasis.opendocument.text-master", + "application/vnd.oasis.opendocument.text-template", + "application/vnd.oasis.opendocument.text-master-template", + "application/vnd.oasis.opendocument.spreadsheet-template", + "application/vnd.oasis.opendocument.presentation-template", + "application/vnd.oasis.opendocument.graphics-template", + "application/vnd.ms-word.template.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + "application/vnd.ms-excel.template.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.presentationml.template", + "application/vnd.ms-powerpoint.template.macroEnabled.12", + "application/vnd.wordperfect", + "application/x-aportisdoc", + "application/x-hwp", + "application/vnd.ms-works", + "application/x-mswrite", + "application/vnd.lotus-1-2-3", + "image/cgm", + "image/vnd.dxf", + "image/x-emf", + "image/x-wmf", + "application/coreldraw", + "application/vnd.visio2013", + "application/vnd.visio", + "application/vnd.ms-visio.drawing", + "application/x-mspublisher", + "application/x-sony-bbeb", + "application/x-gnumeric", + "application/macwriteii", + "application/x-iwork-numbers-sffnumbers", + "application/vnd.oasis.opendocument.text-web", + "application/x-pagemaker", + "application/x-fictionbook+xml", + "application/clarisworks", + "image/x-wpg", + "application/x-iwork-pages-sffpages", + "application/x-iwork-keynote-sffkey", + "application/x-abiword", + "image/x-freehand", + "application/vnd.sun.xml.chart", + "application/x-t602", + "image/bmp", + "image/png", + "image/gif", + "image/tiff", + "image/jpg", + "image/jpeg", + "application/pdf", + ], ]); export interface SignedUrlGet { - method: "GET" | "HEAD"; - url: string; - expires: number; - object_name: string; + method: "GET" | "HEAD"; + url: string; + expires: number; + object_name: string; } function is_extension_editable(mimeType: string): boolean { - return MIMES_EDIT.has(mimeType); + return MIMES_EDIT.has(mimeType); } function is_extension_viewable(mimeType: string): boolean { - return MIMES_VIEW.has(mimeType); + return MIMES_VIEW.has(mimeType); } function build_convert_link(uuid: string) { - return `/chill/wopi/convert/${uuid}`; + return `/chill/wopi/convert/${uuid}`; } function build_download_info_link( - storedObject: StoredObject, - atVersion: null | StoredObjectVersion, + storedObject: StoredObject, + atVersion: null | StoredObjectVersion, ): string { - const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`; + const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`; - if (null !== atVersion) { - const params = new URLSearchParams({ version: atVersion.filename }); + if (null !== atVersion) { + const params = new URLSearchParams({ version: atVersion.filename }); - return url + "?" + params.toString(); - } + return url + "?" + params.toString(); + } - return url; + return url; } async function download_info_link( - storedObject: StoredObject, - atVersion: null | StoredObjectVersion, + storedObject: StoredObject, + atVersion: null | StoredObjectVersion, ): Promise { - return makeFetch("GET", build_download_info_link(storedObject, atVersion)); + return makeFetch("GET", build_download_info_link(storedObject, atVersion)); } function build_wopi_editor_link(uuid: string, returnPath?: string) { - if (returnPath === undefined) { - returnPath = - window.location.pathname + - window.location.search + - window.location.hash; - } + if (returnPath === undefined) { + returnPath = + window.location.pathname + window.location.search + window.location.hash; + } - return ( - `/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath) - ); + return ( + `/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath) + ); } function download_doc(url: string): Promise { - return window.fetch(url).then((r) => { - if (r.ok) { - return r.blob(); - } + return window.fetch(url).then((r) => { + if (r.ok) { + return r.blob(); + } - throw new Error("Could not download document"); - }); + throw new Error("Could not download document"); + }); } async function download_and_decrypt_doc( - storedObject: StoredObject, - atVersion: null | StoredObjectVersion, + storedObject: StoredObject, + atVersion: null | StoredObjectVersion, ): Promise { - const algo = "AES-CBC"; + const algo = "AES-CBC"; - const atVersionToDownload = atVersion ?? storedObject.currentVersion; + const atVersionToDownload = atVersion ?? storedObject.currentVersion; - if (null === atVersionToDownload) { - throw new Error("no version associated to stored object"); - } + if (null === atVersionToDownload) { + throw new Error("no version associated to stored object"); + } - // sometimes, the downloadInfo may be embedded into the storedObject - console.log("storedObject", storedObject); - let downloadInfo; - if ( - typeof storedObject._links !== "undefined" && - typeof storedObject._links.downloadLink !== "undefined" - ) { - downloadInfo = storedObject._links.downloadLink; - } else { - downloadInfo = await download_info_link( - storedObject, - atVersionToDownload, - ); - } + // sometimes, the downloadInfo may be embedded into the storedObject + console.log("storedObject", storedObject); + let downloadInfo; + if ( + typeof storedObject._links !== "undefined" && + typeof storedObject._links.downloadLink !== "undefined" + ) { + downloadInfo = storedObject._links.downloadLink; + } else { + downloadInfo = await download_info_link(storedObject, atVersionToDownload); + } - const rawResponse = await window.fetch(downloadInfo.url); + const rawResponse = await window.fetch(downloadInfo.url); - if (!rawResponse.ok) { - throw new Error( - "error while downloading raw file " + - rawResponse.status + - " " + - rawResponse.statusText, - ); - } + if (!rawResponse.ok) { + throw new Error( + "error while downloading raw file " + + rawResponse.status + + " " + + rawResponse.statusText, + ); + } - if (atVersionToDownload.iv.length === 0) { - return rawResponse.blob(); - } + if (atVersionToDownload.iv.length === 0) { + return rawResponse.blob(); + } - const rawBuffer = await rawResponse.arrayBuffer(); - try { - const key = await window.crypto.subtle.importKey( - "jwk", - atVersionToDownload.keyInfos, - { name: algo }, - false, - ["decrypt"], - ); - const iv = Uint8Array.from(atVersionToDownload.iv); - const decrypted = await window.crypto.subtle.decrypt( - { name: algo, iv: iv }, - key, - rawBuffer, - ); + const rawBuffer = await rawResponse.arrayBuffer(); + try { + const key = await window.crypto.subtle.importKey( + "jwk", + atVersionToDownload.keyInfos, + { name: algo }, + false, + ["decrypt"], + ); + const iv = Uint8Array.from(atVersionToDownload.iv); + const decrypted = await window.crypto.subtle.decrypt( + { name: algo, iv: iv }, + key, + rawBuffer, + ); - return Promise.resolve(new Blob([decrypted])); - } catch (e) { - console.error("encounter error while keys and decrypt operations"); - console.error(e); + return Promise.resolve(new Blob([decrypted])); + } catch (e) { + console.error("encounter error while keys and decrypt operations"); + console.error(e); - throw e; - } + throw e; + } } /** @@ -239,48 +234,45 @@ async function download_and_decrypt_doc( * storage. */ async function download_doc_as_pdf(storedObject: StoredObject): Promise { - if (null === storedObject.currentVersion) { - throw new Error("the stored object does not count any version"); - } + if (null === storedObject.currentVersion) { + throw new Error("the stored object does not count any version"); + } - if (storedObject.currentVersion?.type === "application/pdf") { - return download_and_decrypt_doc( - storedObject, - storedObject.currentVersion, - ); - } + if (storedObject.currentVersion?.type === "application/pdf") { + return download_and_decrypt_doc(storedObject, storedObject.currentVersion); + } - const convertLink = build_convert_link(storedObject.uuid); - const response = await fetch(convertLink); + const convertLink = build_convert_link(storedObject.uuid); + const response = await fetch(convertLink); - if (!response.ok) { - throw new Error("Could not convert the document: " + response.status); - } + if (!response.ok) { + throw new Error("Could not convert the document: " + response.status); + } - return response.blob(); + return response.blob(); } async function is_object_ready( - storedObject: StoredObject, + storedObject: StoredObject, ): Promise { - const new_status_response = await window.fetch( - `/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`, - ); + const new_status_response = await window.fetch( + `/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`, + ); - if (!new_status_response.ok) { - throw new Error("could not fetch the new status"); - } + if (!new_status_response.ok) { + throw new Error("could not fetch the new status"); + } - return await new_status_response.json(); + return await new_status_response.json(); } export { - build_convert_link, - build_wopi_editor_link, - download_and_decrypt_doc, - download_doc, - download_doc_as_pdf, - is_extension_editable, - is_extension_viewable, - is_object_ready, + build_convert_link, + build_wopi_editor_link, + download_and_decrypt_doc, + download_doc, + download_doc_as_pdf, + is_extension_editable, + is_extension_viewable, + is_object_ready, }; diff --git a/src/Bundle/ChillMainBundle/Entity/User.php b/src/Bundle/ChillMainBundle/Entity/User.php index 8a31779f9..3325a9c83 100644 --- a/src/Bundle/ChillMainBundle/Entity/User.php +++ b/src/Bundle/ChillMainBundle/Entity/User.php @@ -613,4 +613,14 @@ class User implements UserInterface, \Stringable, PasswordAuthenticatedUserInter return $this; } + + /** + * Check if the current object is an instance of User. + * + * @return bool returns true if the current object is an instance of User, false otherwise + */ + public function isUser(): bool + { + return true; + } } diff --git a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php index 27ee7f44c..b8105fe5f 100644 --- a/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php +++ b/src/Bundle/ChillMainBundle/Export/Formatter/SpreadsheetListFormatter.php @@ -27,6 +27,8 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; +// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff + /** * Create a CSV List for the export. */ diff --git a/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php b/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php index a73c3c71c..8a7574ac0 100644 --- a/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php +++ b/src/Bundle/ChillMainBundle/Phonenumber/PhonenumberHelper.php @@ -76,6 +76,24 @@ final class PhonenumberHelper implements PhoneNumberHelperInterface ->formatOutOfCountryCallingNumber($phoneNumber, $this->config['default_carrier_code']); } + /** + * @throws NumberParseException + */ + public function parse(string $phoneNumber): PhoneNumber + { + $sanitizedPhoneNumber = $phoneNumber; + + if (str_starts_with($sanitizedPhoneNumber, '00')) { + $sanitizedPhoneNumber = '+'.substr($sanitizedPhoneNumber, 2, null); + } + + if (!str_starts_with($sanitizedPhoneNumber, '+') && !str_starts_with($sanitizedPhoneNumber, '0')) { + $sanitizedPhoneNumber = '+'.$sanitizedPhoneNumber; + } + + return $this->phoneNumberUtil->parse($sanitizedPhoneNumber, $this->config['default_carrier_code']); + } + /** * Get type (mobile, landline, ...) for phone number. */ diff --git a/src/Bundle/ChillMainBundle/Resources/public/chill/js/date.ts b/src/Bundle/ChillMainBundle/Resources/public/chill/js/date.ts index 8a0d7cce0..233d802a0 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/chill/js/date.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/chill/js/date.ts @@ -13,15 +13,15 @@ * */ export const dateToISO = (date: Date | null): string | null => { - if (null === date) { - return null; - } + if (null === date) { + return null; + } - return [ - date.getFullYear(), - (date.getMonth() + 1).toString().padStart(2, "0"), - date.getDate().toString().padStart(2, "0"), - ].join("-"); + return [ + date.getFullYear(), + (date.getMonth() + 1).toString().padStart(2, "0"), + date.getDate().toString().padStart(2, "0"), + ].join("-"); }; /** @@ -30,16 +30,16 @@ export const dateToISO = (date: Date | null): string | null => { * **Experimental** */ export const ISOToDate = (str: string | null): Date | null => { - if (null === str) { - return null; - } - if ("" === str.trim()) { - return null; - } + if (null === str) { + return null; + } + if ("" === str.trim()) { + return null; + } - const [year, month, day] = str.split("-").map((p) => parseInt(p)); + const [year, month, day] = str.split("-").map((p) => parseInt(p)); - return new Date(year, month - 1, day, 0, 0, 0, 0); + return new Date(year, month - 1, day, 0, 0, 0, 0); }; /** @@ -47,21 +47,19 @@ export const ISOToDate = (str: string | null): Date | null => { * */ export const ISOToDatetime = (str: string | null): Date | null => { - if (null === str) { - return null; - } + if (null === str) { + return null; + } - const [cal, times] = str.split("T"), - [year, month, date] = cal.split("-").map((s) => parseInt(s)), - [time, timezone] = times.split(times.charAt(8)), - [hours, minutes, seconds] = time.split(":").map((s) => parseInt(s)); - if ("0000" === timezone) { - return new Date( - Date.UTC(year, month - 1, date, hours, minutes, seconds), - ); - } + const [cal, times] = str.split("T"), + [year, month, date] = cal.split("-").map((s) => parseInt(s)), + [time, timezone] = times.split(times.charAt(8)), + [hours, minutes, seconds] = time.split(":").map((s) => parseInt(s)); + if ("0000" === timezone) { + return new Date(Date.UTC(year, month - 1, date, hours, minutes, seconds)); + } - return new Date(year, month - 1, date, hours, minutes, seconds); + return new Date(year, month - 1, date, hours, minutes, seconds); }; /** @@ -69,96 +67,94 @@ export const ISOToDatetime = (str: string | null): Date | null => { * */ export const datetimeToISO = (date: Date): string => { - let cal, time, offset; - cal = [ - date.getFullYear(), - (date.getMonth() + 1).toString().padStart(2, "0"), - date.getDate().toString().padStart(2, "0"), - ].join("-"); + let cal, time, offset; + cal = [ + date.getFullYear(), + (date.getMonth() + 1).toString().padStart(2, "0"), + date.getDate().toString().padStart(2, "0"), + ].join("-"); - time = [ - date.getHours().toString().padStart(2, "0"), - date.getMinutes().toString().padStart(2, "0"), - date.getSeconds().toString().padStart(2, "0"), - ].join(":"); + time = [ + date.getHours().toString().padStart(2, "0"), + date.getMinutes().toString().padStart(2, "0"), + date.getSeconds().toString().padStart(2, "0"), + ].join(":"); - offset = [ - date.getTimezoneOffset() <= 0 ? "+" : "-", - Math.abs(Math.floor(date.getTimezoneOffset() / 60)) - .toString() - .padStart(2, "0"), - ":", - Math.abs(date.getTimezoneOffset() % 60) - .toString() - .padStart(2, "0"), - ].join(""); + offset = [ + date.getTimezoneOffset() <= 0 ? "+" : "-", + Math.abs(Math.floor(date.getTimezoneOffset() / 60)) + .toString() + .padStart(2, "0"), + ":", + Math.abs(date.getTimezoneOffset() % 60) + .toString() + .padStart(2, "0"), + ].join(""); - const x = cal + "T" + time + offset; + const x = cal + "T" + time + offset; - return x; + return x; }; export const intervalDaysToISO = (days: number | string | null): string => { - if (null === days) { - return "P0D"; - } + if (null === days) { + return "P0D"; + } - return `P${days}D`; + return `P${days}D`; }; export const intervalISOToDays = (str: string | null): number | null => { - if (null === str) { - return null; - } + if (null === str) { + return null; + } - if ("" === str.trim()) { - return null; - } + if ("" === str.trim()) { + return null; + } - let days = 0; - let isDate = true; - let vstring = ""; - for (let i = 0; i < str.length; i = i + 1) { - if (!isDate) { - continue; - } - switch (str.charAt(i)) { - case "P": - isDate = true; - break; - case "T": - isDate = false; - break; - case "0": - case "1": - case "2": - case "3": - case "4": - case "5": - case "6": - case "7": - case "8": - case "9": - vstring = vstring + str.charAt(i); - break; - case "Y": - days = days + Number.parseInt(vstring) * 365; - vstring = ""; - break; - case "M": - days = days + Number.parseInt(vstring) * 30; - vstring = ""; - break; - case "D": - days = days + Number.parseInt(vstring); - vstring = ""; - break; - default: - throw Error( - "this character should not appears: " + str.charAt(i), - ); - } + let days = 0; + let isDate = true; + let vstring = ""; + for (let i = 0; i < str.length; i = i + 1) { + if (!isDate) { + continue; } + switch (str.charAt(i)) { + case "P": + isDate = true; + break; + case "T": + isDate = false; + break; + case "0": + case "1": + case "2": + case "3": + case "4": + case "5": + case "6": + case "7": + case "8": + case "9": + vstring = vstring + str.charAt(i); + break; + case "Y": + days = days + Number.parseInt(vstring) * 365; + vstring = ""; + break; + case "M": + days = days + Number.parseInt(vstring) * 30; + vstring = ""; + break; + case "D": + days = days + Number.parseInt(vstring); + vstring = ""; + break; + default: + throw Error("this character should not appears: " + str.charAt(i)); + } + } - return days; + return days; }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/address.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/address.ts index 896cbd323..864f6cd52 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/address.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/address.ts @@ -1,61 +1,61 @@ import { - Address, - GeographicalUnitLayer, - SimpleGeographicalUnit, + Address, + GeographicalUnitLayer, + SimpleGeographicalUnit, } from "../../types"; import { fetchResults, makeFetch } from "./apiMethods"; export const getAddressById = async (address_id: number): Promise
    => { - const url = `/api/1.0/main/address/${address_id}.json`; + const url = `/api/1.0/main/address/${address_id}.json`; - const response = await fetch(url); + const response = await fetch(url); - if (response.ok) { - return response.json(); - } + if (response.ok) { + return response.json(); + } - throw Error("Error with request resource response"); + throw Error("Error with request resource response"); }; export const getGeographicalUnitsByAddress = async ( - address: Address, + address: Address, ): Promise => { - return fetchResults( - `/api/1.0/main/geographical-unit/by-address/${address.address_id}.json`, - ); + return fetchResults( + `/api/1.0/main/geographical-unit/by-address/${address.address_id}.json`, + ); }; export const getAllGeographicalUnitLayers = async (): Promise< - GeographicalUnitLayer[] + GeographicalUnitLayer[] > => { - return fetchResults( - `/api/1.0/main/geographical-unit-layer.json`, - ); + return fetchResults( + `/api/1.0/main/geographical-unit-layer.json`, + ); }; export const syncAddressWithReference = async ( - address: Address, + address: Address, ): Promise
    => { - return makeFetch( - "POST", - `/api/1.0/main/address/reference-match/${address.address_id}/sync-with-reference`, - ); + return makeFetch( + "POST", + `/api/1.0/main/address/reference-match/${address.address_id}/sync-with-reference`, + ); }; export const markAddressReviewed = async ( - address: Address, + address: Address, ): Promise
    => { - return makeFetch( - "POST", - `/api/1.0/main/address/reference-match/${address.address_id}/set/reviewed`, - ); + return makeFetch( + "POST", + `/api/1.0/main/address/reference-match/${address.address_id}/set/reviewed`, + ); }; export const markAddressToReview = async ( - address: Address, + address: Address, ): Promise
    => { - return makeFetch( - "POST", - `/api/1.0/main/address/reference-match/${address.address_id}/set/to_review`, - ); + return makeFetch( + "POST", + `/api/1.0/main/address/reference-match/${address.address_id}/set/to_review`, + ); }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts index e8256b348..9bfb952b5 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/apiMethods.ts @@ -6,57 +6,57 @@ export type fetchOption = Record; export type Params = Record; export interface PaginationResponse { - pagination: { - more: boolean; - items_per_page: number; - }; - results: T[]; - count: number; + pagination: { + more: boolean; + items_per_page: number; + }; + results: T[]; + count: number; } export type FetchParams = Record; export interface TransportExceptionInterface { - name: string; + name: string; } export interface ValidationExceptionInterface - extends TransportExceptionInterface { - name: "ValidationException"; - error: object; - violations: string[]; - titles: string[]; - propertyPaths: string[]; + extends TransportExceptionInterface { + name: "ValidationException"; + error: object; + violations: string[]; + titles: string[]; + propertyPaths: string[]; } export interface ValidationErrorResponse extends TransportExceptionInterface { - violations: { - title: string; - propertyPath: string; - }[]; + violations: { + title: string; + propertyPath: string; + }[]; } export interface AccessExceptionInterface extends TransportExceptionInterface { - name: "AccessException"; - violations: string[]; + name: "AccessException"; + violations: string[]; } export interface NotFoundExceptionInterface - extends TransportExceptionInterface { - name: "NotFoundException"; + extends TransportExceptionInterface { + name: "NotFoundException"; } export interface ServerExceptionInterface extends TransportExceptionInterface { - name: "ServerException"; - message: string; - code: number; - body: string; + name: "ServerException"; + message: string; + code: number; + body: string; } export interface ConflictHttpExceptionInterface - extends TransportExceptionInterface { - name: "ConflictHttpException"; - violations: string[]; + extends TransportExceptionInterface { + name: "ConflictHttpException"; + violations: string[]; } /** @@ -66,223 +66,223 @@ export interface ConflictHttpExceptionInterface * and use of the @link{fetchResults} method. */ export const makeFetch = ( - method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE", - url: string, - body?: body | Input | null, - options?: FetchParams, + method: "POST" | "GET" | "PUT" | "PATCH" | "DELETE", + url: string, + body?: body | Input | null, + options?: FetchParams, ): Promise => { - let opts = { - method: method, - headers: { - "Content-Type": "application/json;charset=utf-8", - }, + let opts = { + method: method, + headers: { + "Content-Type": "application/json;charset=utf-8", + }, + }; + + if (body !== null && typeof body !== "undefined") { + Object.assign(opts, { body: JSON.stringify(body) }); + } + + if (typeof options !== "undefined") { + opts = Object.assign(opts, options); + } + return fetch(url, opts).then((response) => { + if (response.status === 204) { + return Promise.resolve(); + } + + if (response.ok) { + return response.json(); + } + + if (response.status === 422) { + return response.json().then((response) => { + throw ValidationException(response); + }); + } + + if (response.status === 403) { + throw AccessException(response); + } + + if (response.status === 409) { + throw ConflictHttpException(response); + } + + throw { + name: "Exception", + sta: response.status, + txt: response.statusText, + err: new Error(), + violations: response.body, }; - - if (body !== null && typeof body !== "undefined") { - Object.assign(opts, { body: JSON.stringify(body) }); - } - - if (typeof options !== "undefined") { - opts = Object.assign(opts, options); - } - return fetch(url, opts).then((response) => { - if (response.status === 204) { - return Promise.resolve(); - } - - if (response.ok) { - return response.json(); - } - - if (response.status === 422) { - return response.json().then((response) => { - throw ValidationException(response); - }); - } - - if (response.status === 403) { - throw AccessException(response); - } - - if (response.status === 409) { - throw ConflictHttpException(response); - } - - throw { - name: "Exception", - sta: response.status, - txt: response.statusText, - err: new Error(), - violations: response.body, - }; - }); + }); }; /** * Fetch results with certain parameters */ function _fetchAction( - page: number, - uri: string, - params?: FetchParams, + page: number, + uri: string, + params?: FetchParams, ): Promise> { - const item_per_page = 50; + const item_per_page = 50; - const searchParams = new URLSearchParams(); - searchParams.append("item_per_page", item_per_page.toString()); - searchParams.append("page", page.toString()); + const searchParams = new URLSearchParams(); + searchParams.append("item_per_page", item_per_page.toString()); + searchParams.append("page", page.toString()); - if (params !== undefined) { - Object.keys(params).forEach((key) => { - const v = params[key]; - if (typeof v === "string") { - searchParams.append(key, v); - } else if (typeof v === "number") { - searchParams.append(key, v.toString()); - } else if (v === null) { - searchParams.append(key, ""); - } + if (params !== undefined) { + Object.keys(params).forEach((key) => { + const v = params[key]; + if (typeof v === "string") { + searchParams.append(key, v); + } else if (typeof v === "number") { + searchParams.append(key, v.toString()); + } else if (v === null) { + searchParams.append(key, ""); + } + }); + } + + const url = uri + "?" + searchParams.toString(); + + return fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json;charset=utf-8", + }, + }) + .then((response) => { + if (response.ok) { + return response.json(); + } + + if (response.status === 404) { + throw NotFoundException(response); + } + + if (response.status === 422) { + return response.json().then((response) => { + throw ValidationException(response); }); - } + } - const url = uri + "?" + searchParams.toString(); + if (response.status === 403) { + throw AccessException(response); + } - return fetch(url, { - method: "GET", - headers: { - "Content-Type": "application/json;charset=utf-8", - }, + if (response.status >= 500) { + return response.text().then((body) => { + throw ServerException(response.status, body); + }); + } + + throw new Error("other network error"); }) - .then((response) => { - if (response.ok) { - return response.json(); - } - - if (response.status === 404) { - throw NotFoundException(response); - } - - if (response.status === 422) { - return response.json().then((response) => { - throw ValidationException(response); - }); - } - - if (response.status === 403) { - throw AccessException(response); - } - - if (response.status >= 500) { - return response.text().then((body) => { - throw ServerException(response.status, body); - }); - } - - throw new Error("other network error"); - }) - .catch( - ( - reason: - | NotFoundExceptionInterface - | ServerExceptionInterface - | ValidationExceptionInterface - | TransportExceptionInterface, - ) => { - console.error(reason); - throw reason; - }, - ); + .catch( + ( + reason: + | NotFoundExceptionInterface + | ServerExceptionInterface + | ValidationExceptionInterface + | TransportExceptionInterface, + ) => { + console.error(reason); + throw reason; + }, + ); } export const fetchResults = async ( - uri: string, - params?: FetchParams, + uri: string, + params?: FetchParams, ): Promise => { - const promises: Promise[] = []; - let page = 1; - const firstData: PaginationResponse = (await _fetchAction( - page, - uri, - params, - )) as PaginationResponse; + const promises: Promise[] = []; + let page = 1; + const firstData: PaginationResponse = (await _fetchAction( + page, + uri, + params, + )) as PaginationResponse; - promises.push(Promise.resolve(firstData.results)); + promises.push(Promise.resolve(firstData.results)); - if (firstData.pagination.more) { - do { - page = ++page; - promises.push( - _fetchAction(page, uri, params).then((r) => - Promise.resolve(r.results), - ), - ); - } while (page * firstData.pagination.items_per_page < firstData.count); - } + if (firstData.pagination.more) { + do { + page = ++page; + promises.push( + _fetchAction(page, uri, params).then((r) => + Promise.resolve(r.results), + ), + ); + } while (page * firstData.pagination.items_per_page < firstData.count); + } - return Promise.all(promises).then((values) => values.flat()); + return Promise.all(promises).then((values) => values.flat()); }; export const fetchScopes = (): Promise => { - return fetchResults("/api/1.0/main/scope.json"); + return fetchResults("/api/1.0/main/scope.json"); }; /** * Error objects to be thrown */ const ValidationException = ( - response: ValidationErrorResponse, + response: ValidationErrorResponse, ): ValidationExceptionInterface => { - const error = {} as ValidationExceptionInterface; - error.name = "ValidationException"; - error.violations = response.violations.map( - (violation) => `${violation.title}: ${violation.propertyPath}`, - ); - error.titles = response.violations.map((violation) => violation.title); - error.propertyPaths = response.violations.map( - (violation) => violation.propertyPath, - ); - return error; + const error = {} as ValidationExceptionInterface; + error.name = "ValidationException"; + error.violations = response.violations.map( + (violation) => `${violation.title}: ${violation.propertyPath}`, + ); + error.titles = response.violations.map((violation) => violation.title); + error.propertyPaths = response.violations.map( + (violation) => violation.propertyPath, + ); + return error; }; // eslint-disable-next-line @typescript-eslint/no-unused-vars const AccessException = (response: Response): AccessExceptionInterface => { - const error = {} as AccessExceptionInterface; - error.name = "AccessException"; - error.violations = ["You are not allowed to perform this action"]; + const error = {} as AccessExceptionInterface; + error.name = "AccessException"; + error.violations = ["You are not allowed to perform this action"]; - return error; + return error; }; // eslint-disable-next-line @typescript-eslint/no-unused-vars const NotFoundException = (response: Response): NotFoundExceptionInterface => { - const error = {} as NotFoundExceptionInterface; - error.name = "NotFoundException"; + const error = {} as NotFoundExceptionInterface; + error.name = "NotFoundException"; - return error; + return error; }; const ServerException = ( - code: number, - body: string, + code: number, + body: string, ): ServerExceptionInterface => { - const error = {} as ServerExceptionInterface; - error.name = "ServerException"; - error.code = code; - error.body = body; + const error = {} as ServerExceptionInterface; + error.name = "ServerException"; + error.code = code; + error.body = body; - return error; + return error; }; const ConflictHttpException = ( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - response: Response, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + response: Response, ): ConflictHttpExceptionInterface => { - const error = {} as ConflictHttpExceptionInterface; + const error = {} as ConflictHttpExceptionInterface; - error.name = "ConflictHttpException"; - error.violations = [ - "Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again", - ]; + error.name = "ConflictHttpException"; + error.violations = [ + "Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again", + ]; - return error; + return error; }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts index 7e350b102..6e66122e7 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/export.ts @@ -2,17 +2,17 @@ import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; import { ExportGeneration } from "ChillMainAssets/types"; export const fetchExportGenerationStatus = async ( - exportGenerationId: string, + exportGenerationId: string, ): Promise => - makeFetch( - "GET", - `/api/1.0/main/export-generation/${exportGenerationId}/object`, - ); + makeFetch( + "GET", + `/api/1.0/main/export-generation/${exportGenerationId}/object`, + ); export const generateFromSavedExport = async ( - savedExportUuid: string, + savedExportUuid: string, ): Promise => - makeFetch( - "POST", - `/api/1.0/main/export/export-generation/create-from-saved-export/${savedExportUuid}`, - ); + makeFetch( + "POST", + `/api/1.0/main/export/export-generation/create-from-saved-export/${savedExportUuid}`, + ); diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/locations.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/locations.ts index 3c326ba78..375881924 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/locations.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/locations.ts @@ -2,7 +2,7 @@ import { fetchResults } from "./apiMethods"; import { Location, LocationType } from "../../types"; export const getLocations = (): Promise => - fetchResults("/api/1.0/main/location.json"); + fetchResults("/api/1.0/main/location.json"); export const getLocationTypes = (): Promise => - fetchResults("/api/1.0/main/location-type.json"); + fetchResults("/api/1.0/main/location-type.json"); diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts index e444aa2e5..54a12c030 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/return_path.ts @@ -1,3 +1,3 @@ export function buildReturnPath(location: Location): string { - return location.pathname + location.search; + return location.pathname + location.search; } diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/api/user.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/api/user.ts index 62703b740..e51d09abb 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/api/user.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/api/user.ts @@ -2,23 +2,23 @@ import { User } from "../../types"; import { makeFetch } from "./apiMethods"; export const whoami = (): Promise => { - const url = `/api/1.0/main/whoami.json`; - return fetch(url).then((response) => { - if (response.ok) { - return response.json(); - } - throw { - msg: "Error while getting whoami.", - sta: response.status, - txt: response.statusText, - err: new Error(), - body: response.body, - }; - }); + const url = `/api/1.0/main/whoami.json`; + return fetch(url).then((response) => { + if (response.ok) { + return response.json(); + } + throw { + msg: "Error while getting whoami.", + sta: response.status, + txt: response.statusText, + err: new Error(), + body: response.body, + }; + }); }; export const whereami = (): Promise => { - const url = `/api/1.0/main/user-current-location.json`; + const url = `/api/1.0/main/user-current-location.json`; - return makeFetch("GET", url); + return makeFetch("GET", url); }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/entity-notification/api.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/entity-notification/api.ts index 47cf9b1e9..97ba98d75 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/entity-notification/api.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/entity-notification/api.ts @@ -1,22 +1,22 @@ const buildLinkCreate = function ( - relatedEntityClass: string, - relatedEntityId: number, - to: number | null, - returnPath: string | null, + relatedEntityClass: string, + relatedEntityId: number, + to: number | null, + returnPath: string | null, ): string { - const params = new URLSearchParams(); - params.append("entityClass", relatedEntityClass); - params.append("entityId", relatedEntityId.toString()); + const params = new URLSearchParams(); + params.append("entityClass", relatedEntityClass); + params.append("entityId", relatedEntityId.toString()); - if (null !== to) { - params.append("tos[0]", to.toString()); - } + if (null !== to) { + params.append("tos[0]", to.toString()); + } - if (null !== returnPath) { - params.append("returnPath", returnPath); - } + if (null !== returnPath) { + params.append("returnPath", returnPath); + } - return `/fr/notification/create?${params.toString()}`; + return `/fr/notification/create?${params.toString()}`; }; export { buildLinkCreate }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/entity-workflow/api.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/entity-workflow/api.ts index a9955f31b..4f9dea715 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/entity-workflow/api.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/entity-workflow/api.ts @@ -10,17 +10,17 @@ * @throws {Error} If the related entity ID is undefined. */ export const buildLinkCreate = ( - workflowName: string, - relatedEntityClass: string, - relatedEntityId: number | undefined, + workflowName: string, + relatedEntityClass: string, + relatedEntityId: number | undefined, ): string => { - if (typeof relatedEntityId === "undefined") { - throw new Error("the related entity id is not set"); - } - const params = new URLSearchParams(); - params.set("entityClass", relatedEntityClass); - params.set("entityId", relatedEntityId.toString(10)); - params.set("workflow", workflowName); + if (typeof relatedEntityId === "undefined") { + throw new Error("the related entity id is not set"); + } + const params = new URLSearchParams(); + params.set("entityClass", relatedEntityClass); + params.set("entityId", relatedEntityId.toString(10)); + params.set("workflow", workflowName); - return `/fr/main/workflow/create?` + params.toString(); + return `/fr/main/workflow/create?` + params.toString(); }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/localizationHelper/localizationHelper.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/localizationHelper/localizationHelper.ts index 9cd1dcb51..b2fa260d2 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/localizationHelper/localizationHelper.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/localizationHelper/localizationHelper.ts @@ -9,33 +9,33 @@ import { TranslatableString } from "ChillMainAssets/types"; * @returns The localized string or null if no translation is available */ export function localizeString( - translatableString: TranslatableString | null | undefined, - locale?: string, + translatableString: TranslatableString | null | undefined, + locale?: string, ): string { - if (!translatableString || Object.keys(translatableString).length === 0) { - return ""; - } - - const currentLocale = locale || navigator.language.split("-")[0] || "fr"; - - if (translatableString[currentLocale]) { - return translatableString[currentLocale]; - } - - // Define fallback locales - const fallbackLocales: string[] = ["fr", "en"]; - - for (const fallbackLocale of fallbackLocales) { - if (translatableString[fallbackLocale]) { - return translatableString[fallbackLocale]; - } - } - - // No fallback translation found, use the first available - const availableLocales = Object.keys(translatableString); - if (availableLocales.length > 0) { - return translatableString[availableLocales[0]]; - } - + if (!translatableString || Object.keys(translatableString).length === 0) { return ""; + } + + const currentLocale = locale || navigator.language.split("-")[0] || "fr"; + + if (translatableString[currentLocale]) { + return translatableString[currentLocale]; + } + + // Define fallback locales + const fallbackLocales: string[] = ["fr", "en"]; + + for (const fallbackLocale of fallbackLocales) { + if (translatableString[fallbackLocale]) { + return translatableString[fallbackLocale]; + } + } + + // No fallback translation found, use the first available + const availableLocales = Object.keys(translatableString); + if (availableLocales.length > 0) { + return translatableString[availableLocales[0]]; + } + + return ""; } diff --git a/src/Bundle/ChillMainBundle/Resources/public/lib/workflow/attachments.ts b/src/Bundle/ChillMainBundle/Resources/public/lib/workflow/attachments.ts index 7e2f23f57..05c745099 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/lib/workflow/attachments.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/lib/workflow/attachments.ts @@ -3,20 +3,20 @@ import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/gener import { makeFetch } from "ChillMainAssets/lib/api/apiMethods"; export const find_attachments_by_workflow = async ( - workflowId: number, + workflowId: number, ): Promise => - makeFetch("GET", `/api/1.0/main/workflow/${workflowId}/attachment`); + makeFetch("GET", `/api/1.0/main/workflow/${workflowId}/attachment`); export const create_attachment = async ( - workflowId: number, - genericDoc: GenericDocForAccompanyingPeriod, + workflowId: number, + genericDoc: GenericDocForAccompanyingPeriod, ): Promise => - makeFetch("POST", `/api/1.0/main/workflow/${workflowId}/attachment`, { - relatedGenericDocKey: genericDoc.key, - relatedGenericDocIdentifiers: genericDoc.identifiers, - }); + makeFetch("POST", `/api/1.0/main/workflow/${workflowId}/attachment`, { + relatedGenericDocKey: genericDoc.key, + relatedGenericDocIdentifiers: genericDoc.identifiers, + }); export const delete_attachment = async ( - attachment: WorkflowAttachment, + attachment: WorkflowAttachment, ): Promise => - makeFetch("DELETE", `/api/1.0/main/workflow/attachment/${attachment.id}`); + makeFetch("DELETE", `/api/1.0/main/workflow/attachment/${attachment.id}`); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/address-details/index.ts b/src/Bundle/ChillMainBundle/Resources/public/module/address-details/index.ts index abe516934..d41a70499 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/address-details/index.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/module/address-details/index.ts @@ -7,43 +7,43 @@ import { Address } from "../../types"; const i18n = _createI18n({}); document - .querySelectorAll("span[data-address-details]") - .forEach((el) => { - const dataset = el.dataset as { - addressId: string; - addressRefStatus: string; + .querySelectorAll("span[data-address-details]") + .forEach((el) => { + const dataset = el.dataset as { + addressId: string; + addressRefStatus: string; + }; + + const app = createApp({ + components: { AddressDetailsButton }, + data() { + return { + addressId: Number.parseInt(dataset.addressId), + addressRefStatus: dataset.addressRefStatus, }; - - const app = createApp({ - components: { AddressDetailsButton }, - data() { - return { - addressId: Number.parseInt(dataset.addressId), - addressRefStatus: dataset.addressRefStatus, - }; - }, - template: - '', - methods: { - onUpdateAddress: (address: Address): void => { - if ( - address.refStatus === "to_review" || - address.refStatus === "reviewed" - ) { - // in this two case, the address content do not change - return; - } - if ( - window.confirm( - "L'adresse a été modifiée. Vous pouvez continuer votre travail. Cependant, pour afficher les données immédiatement, veuillez recharger la page. \n\n Voulez-vous recharger la page immédiatement ?", - ) - ) { - window.location.reload(); - } - }, - }, - }); - - app.use(i18n); - app.mount(el); + }, + template: + '', + methods: { + onUpdateAddress: (address: Address): void => { + if ( + address.refStatus === "to_review" || + address.refStatus === "reviewed" + ) { + // in this two case, the address content do not change + return; + } + if ( + window.confirm( + "L'adresse a été modifiée. Vous pouvez continuer votre travail. Cependant, pour afficher les données immédiatement, veuillez recharger la page. \n\n Voulez-vous recharger la page immédiatement ?", + ) + ) { + window.location.reload(); + } + }, + }, }); + + app.use(i18n); + app.mount(el); + }); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts index a396da295..fbbbb5881 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/editor_config.ts @@ -1,16 +1,16 @@ import { - Essentials, - Bold, - Italic, - Paragraph, - Markdown, - BlockQuote, - Heading, - Link, - List, - Emoji, - Mention, - Fullscreen, + Essentials, + Bold, + Italic, + Paragraph, + Markdown, + BlockQuote, + Heading, + Link, + List, + Emoji, + Mention, + Fullscreen, } from "ckeditor5"; import coreTranslations from "ckeditor5/translations/fr.js"; @@ -19,41 +19,41 @@ import "ckeditor5/ckeditor5.css"; import "./index.scss"; export default { - plugins: [ - Essentials, - Markdown, - Bold, - Italic, - BlockQuote, - Heading, - Link, - List, - Paragraph, - // both Emoji and Mention are required for Emoji feature - Emoji, - Mention, - // to enable fullscreen - Fullscreen, + plugins: [ + Essentials, + Markdown, + Bold, + Italic, + BlockQuote, + Heading, + Link, + List, + Paragraph, + // both Emoji and Mention are required for Emoji feature + Emoji, + Mention, + // to enable fullscreen + Fullscreen, + ], + toolbar: { + items: [ + "heading", + "|", + "bold", + "italic", + "link", + "bulletedList", + "numberedList", + "blockQuote", + "|", + "emoji", + "|", + "undo", + "redo", + "|", + "fullscreen", ], - toolbar: { - items: [ - "heading", - "|", - "bold", - "italic", - "link", - "bulletedList", - "numberedList", - "blockQuote", - "|", - "emoji", - "|", - "undo", - "redo", - "|", - "fullscreen", - ], - }, - translations: [coreTranslations], - licenseKey: "GPL", + }, + translations: [coreTranslations], + licenseKey: "GPL", }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts index 783c0acfa..f8e0cb48b 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/module/ckeditor5/index.ts @@ -2,31 +2,31 @@ import { createApp } from "vue"; import CommentEditor from "ChillMainAssets/vuejs/_components/CommentEditor/CommentEditor.vue"; const ckeditorFields: NodeListOf = - document.querySelectorAll("textarea[ckeditor]"); + document.querySelectorAll("textarea[ckeditor]"); ckeditorFields.forEach((field: HTMLTextAreaElement): void => { - const content = field.value; - const div = document.createElement("div"); + const content = field.value; + const div = document.createElement("div"); - if (field.parentNode !== null) { - field.parentNode.insertBefore(div, field); - } else { - throw "parent is null"; - } + if (field.parentNode !== null) { + field.parentNode.insertBefore(div, field); + } else { + throw "parent is null"; + } - createApp({ - components: { CommentEditor }, - template: ``, - data() { - return { - content, - }; - }, - methods: { - handleInput() { - field.value = this.content; - }, - }, - }).mount(div); + createApp({ + components: { CommentEditor }, + template: ``, + data() { + return { + content, + }; + }, + methods: { + handleInput() { + field.value = this.content; + }, + }, + }).mount(div); - field.style.display = "none"; + field.style.display = "none"; }); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/collection/index.ts b/src/Bundle/ChillMainBundle/Resources/public/module/collection/index.ts index 77ff044ec..1c45f468d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/collection/index.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/module/collection/index.ts @@ -31,157 +31,157 @@ import "./collection.scss"; declare global { - interface GlobalEventHandlersEventMap { - "show-hide-show": CustomEvent<{ - id: number; - froms: HTMLElement[]; - container: HTMLElement; - }>; - } + interface GlobalEventHandlersEventMap { + "show-hide-show": CustomEvent<{ + id: number; + froms: HTMLElement[]; + container: HTMLElement; + }>; + } } export class CollectionEventPayload { - collection: HTMLUListElement; - entry: HTMLLIElement; + collection: HTMLUListElement; + entry: HTMLLIElement; - constructor(collection: HTMLUListElement, entry: HTMLLIElement) { - this.collection = collection; - this.entry = entry; - } + constructor(collection: HTMLUListElement, entry: HTMLLIElement) { + this.collection = collection; + this.entry = entry; + } } export const handleAdd = (button: any): void => { - const form_name = button.dataset.collectionAddTarget, - prototype = button.dataset.formPrototype, - collection: HTMLUListElement | null = document.querySelector( - 'ul[data-collection-name="' + form_name + '"]', - ); + const form_name = button.dataset.collectionAddTarget, + prototype = button.dataset.formPrototype, + collection: HTMLUListElement | null = document.querySelector( + 'ul[data-collection-name="' + form_name + '"]', + ); - if (collection === null) { - return; + if (collection === null) { + return; + } + + const empty_explain: HTMLLIElement | null = collection.querySelector( + "li[data-collection-empty-explain]", + ), + entry = document.createElement("li"), + counter = collection.querySelectorAll("li.entry").length, // Updated counter logic + content = prototype.replace(/__name__/g, counter.toString()), + event = new CustomEvent("collection-add-entry", { + detail: new CollectionEventPayload(collection, entry), + }); + + console.log(counter); + console.log(content); + + entry.innerHTML = content; + entry.classList.add("entry"); + + if ("collectionRegular" in collection.dataset) { + initializeRemove(collection, entry); + if (empty_explain !== null) { + empty_explain.remove(); } + } - const empty_explain: HTMLLIElement | null = collection.querySelector( - "li[data-collection-empty-explain]", - ), - entry = document.createElement("li"), - counter = collection.querySelectorAll("li.entry").length, // Updated counter logic - content = prototype.replace(/__name__/g, counter.toString()), - event = new CustomEvent("collection-add-entry", { - detail: new CollectionEventPayload(collection, entry), - }); - - console.log(counter); - console.log(content); - - entry.innerHTML = content; - entry.classList.add("entry"); - - if ("collectionRegular" in collection.dataset) { - initializeRemove(collection, entry); - if (empty_explain !== null) { - empty_explain.remove(); - } - } - - collection.appendChild(entry); - collection.dispatchEvent(event); - window.dispatchEvent(event); + collection.appendChild(entry); + collection.dispatchEvent(event); + window.dispatchEvent(event); }; const initializeRemove = ( - collection: HTMLUListElement, - entry: HTMLLIElement, + collection: HTMLUListElement, + entry: HTMLLIElement, ): void => { - const button = buildRemoveButton(collection, entry); - if (null === button) { - return; - } - entry.appendChild(button); + const button = buildRemoveButton(collection, entry); + if (null === button) { + return; + } + entry.appendChild(button); }; export const buildRemoveButton = ( - collection: HTMLUListElement, - entry: HTMLLIElement, + collection: HTMLUListElement, + entry: HTMLLIElement, ): HTMLButtonElement | null => { - const button = document.createElement("button"), - isPersisted = entry.dataset.collectionIsPersisted || "", - content = collection.dataset.collectionButtonRemoveLabel || "", - allowDelete = collection.dataset.collectionAllowDelete || "", - event = new CustomEvent("collection-remove-entry", { - detail: new CollectionEventPayload(collection, entry), - }); - - if (allowDelete === "0" && isPersisted === "1") { - return null; - } - button.classList.add("btn", "btn-delete", "remove-entry"); - button.textContent = content; - button.addEventListener("click", (e: Event) => { - e.preventDefault(); - entry.remove(); - collection.dispatchEvent(event); - window.dispatchEvent(event); + const button = document.createElement("button"), + isPersisted = entry.dataset.collectionIsPersisted || "", + content = collection.dataset.collectionButtonRemoveLabel || "", + allowDelete = collection.dataset.collectionAllowDelete || "", + event = new CustomEvent("collection-remove-entry", { + detail: new CollectionEventPayload(collection, entry), }); - return button; + if (allowDelete === "0" && isPersisted === "1") { + return null; + } + button.classList.add("btn", "btn-delete", "remove-entry"); + button.textContent = content; + button.addEventListener("click", (e: Event) => { + e.preventDefault(); + entry.remove(); + collection.dispatchEvent(event); + window.dispatchEvent(event); + }); + + return button; }; const collectionsInit = new Set(); const buttonsInit = new Set(); const initialize = function (target: Document | Element): void { - const addButtons: NodeListOf = document.querySelectorAll( - "button[data-collection-add-target]", - ), - collections: NodeListOf = document.querySelectorAll( - "ul[data-collection-regular]", - ); + const addButtons: NodeListOf = document.querySelectorAll( + "button[data-collection-add-target]", + ), + collections: NodeListOf = document.querySelectorAll( + "ul[data-collection-regular]", + ); - for (let i = 0; i < addButtons.length; i++) { - const addButton = addButtons[i]; - const uniqid = addButton.dataset.uniqid as string; - if (buttonsInit.has(uniqid)) { - continue; - } - buttonsInit.add(uniqid); - addButton.addEventListener("click", (e: Event) => { - e.preventDefault(); - handleAdd(e.target); - }); + for (let i = 0; i < addButtons.length; i++) { + const addButton = addButtons[i]; + const uniqid = addButton.dataset.uniqid as string; + if (buttonsInit.has(uniqid)) { + continue; } - for (let i = 0; i < collections.length; i++) { - const collection = collections[i]; - const uniqid = collection.dataset.uniqid as string; - if (collectionsInit.has(uniqid)) { - continue; - } - collectionsInit.add(uniqid); - const entries: NodeListOf = - collection.querySelectorAll(":scope > li"); - for (let j = 0; j < entries.length; j++) { - if (entries[j].dataset.collectionEmptyExplain === "1") { - continue; - } - initializeRemove(collections[i], entries[j]); - } + buttonsInit.add(uniqid); + addButton.addEventListener("click", (e: Event) => { + e.preventDefault(); + handleAdd(e.target); + }); + } + for (let i = 0; i < collections.length; i++) { + const collection = collections[i]; + const uniqid = collection.dataset.uniqid as string; + if (collectionsInit.has(uniqid)) { + continue; } + collectionsInit.add(uniqid); + const entries: NodeListOf = + collection.querySelectorAll(":scope > li"); + for (let j = 0; j < entries.length; j++) { + if (entries[j].dataset.collectionEmptyExplain === "1") { + continue; + } + initializeRemove(collections[i], entries[j]); + } + } }; window.addEventListener("DOMContentLoaded", () => { - initialize(document); + initialize(document); }); window.addEventListener( - "show-hide-show", - ( - event: CustomEvent<{ - id: number; - container: HTMLElement; - froms: HTMLElement[]; - }>, - ) => { - const container = event.detail.container as HTMLElement; - initialize(container); - }, + "show-hide-show", + ( + event: CustomEvent<{ + id: number; + container: HTMLElement; + froms: HTMLElement[]; + }>, + ) => { + const container = event.detail.container as HTMLElement; + initialize(container); + }, ); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read_all.ts b/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read_all.ts index 97697fca5..d07a7e815 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read_all.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/module/notification/toggle_read_all.ts @@ -5,39 +5,39 @@ import NotificationReadAllToggle from "../../vuejs/_components/Notification/Noti const i18n = _createI18n({}); document.addEventListener("DOMContentLoaded", function () { - const elements = document.querySelectorAll(".notification_all_read"); + const elements = document.querySelectorAll(".notification_all_read"); - elements.forEach((element) => { - console.log("launch"); - createApp({ - template: ``, - components: { - NotificationReadAllToggle, - }, - methods: { - markAsRead(id: number) { - const el = document.querySelector( - `div.notification-status[data-notification-id="${id}"]`, - ); - if (el === null) { - return; - } - el.classList.add("read"); - el.classList.remove("unread"); - }, - markAsUnread(id: number) { - const el = document.querySelector( - `div.notification-status[data-notification-id="${id}"]`, - ); - if (el === null) { - return; - } - el.classList.remove("read"); - el.classList.add("unread"); - }, - }, - }) - .use(i18n) - .mount(element); - }); + elements.forEach((element) => { + console.log("launch"); + createApp({ + template: ``, + components: { + NotificationReadAllToggle, + }, + methods: { + markAsRead(id: number) { + const el = document.querySelector( + `div.notification-status[data-notification-id="${id}"]`, + ); + if (el === null) { + return; + } + el.classList.add("read"); + el.classList.remove("unread"); + }, + markAsUnread(id: number) { + const el = document.querySelector( + `div.notification-status[data-notification-id="${id}"]`, + ); + if (el === null) { + return; + } + el.classList.remove("read"); + el.classList.add("unread"); + }, + }, + }) + .use(i18n) + .mount(element); + }); }); diff --git a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js index 9f6bb753e..8d9484bb1 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js +++ b/src/Bundle/ChillMainBundle/Resources/public/module/pick-entity/index.js @@ -124,9 +124,10 @@ function loadDynamicPicker(element) { removeEntity({ entity }) { if ( -1 === - this.suggested.findIndex( - (e) => e.type === entity.type && e.id === entity.id, - ) + this.suggested.findIndex( + (e) => e.type === entity.type && e.id === entity.id, + ) && + "me" !== entity ) { this.suggested.push(entity); } diff --git a/src/Bundle/ChillMainBundle/Resources/public/types.ts b/src/Bundle/ChillMainBundle/Resources/public/types.ts index 2cd83bc64..fecb9a578 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/types.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/types.ts @@ -2,216 +2,279 @@ import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc"; import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types"; export interface DateTime { - datetime: string; - datetime8601: string; + datetime: string; + datetime8601: string; } export interface Civility { - id: number; - // TODO + id: number; + // TODO +} + +export interface Household { + type: "household"; + id: number; } export interface Job { - id: number; - type: "user_job"; - label: { - fr: string; // could have other key. How to do that in ts ? - }; + id: number; + type: "user_job"; + label: { + fr: string; // could have other key. How to do that in ts ? + }; } export interface Center { - id: number; - type: "center"; - name: string; + id: number; + type: "center"; + name: string; } export interface Scope { - id: number; - type: "scope"; - name: { - fr: string; - }; + id: number; + type: "scope"; + name: { + fr: string; + }; } export interface ResultItem { - result: T; - relevance: number; + result: T; + relevance: number; } export interface User { - type: "user"; - id: number; - username: string; - text: string; - text_without_absence: string; - email: string; - user_job: Job; - label: string; - // todo: mainCenter; mainJob; etc.. + type: "user"; + id: number; + username: string; + text: string; + text_without_absence: string; + email: string; + user_job: Job; + label: string; + // todo: mainCenter; mainJob; etc.. } export interface UserGroup { - type: "user_group"; - id: number; - label: TranslatableString; - backgroundColor: string; - foregroundColor: string; - excludeKey: string; - text: string; + type: "user_group"; + id: number; + label: TranslatableString; + backgroundColor: string; + foregroundColor: string; + excludeKey: string; + text: string; } export type UserGroupOrUser = User | UserGroup; export interface UserAssociatedInterface { - type: "user"; - id: number; + type: "user"; + id: number; } export type TranslatableString = Record; export interface Postcode { - id: number; - name: string; - code: string; - center: Point; + id: number; + name: string; + code: string; + center: Point; } export interface Point { - type: "Point"; - coordinates: [lat: number, lon: number]; + type: "Point"; + coordinates: [lat: number, lon: number]; } export interface Country { - id: number; - name: TranslatableString; - code: string; + id: number; + name: TranslatableString; + code: string; } export type AddressRefStatus = "match" | "to_review" | "reviewed"; export interface Address { - type: "address"; - address_id: number; - text: string; - street: string; - streetNumber: string; - postcode: Postcode; - country: Country; - floor: string | null; - corridor: string | null; - steps: string | null; - flat: string | null; - buildingName: string | null; - distribution: string | null; - extra: string | null; - confidential: boolean; - lines: string[]; - addressReference: AddressReference | null; - validFrom: DateTime; - validTo: DateTime | null; - point: Point | null; - refStatus: AddressRefStatus; - isNoAddress: boolean; + type: "address"; + address_id: number; + text: string; + street: string; + streetNumber: string; + postcode: Postcode; + country: Country; + floor: string | null; + corridor: string | null; + steps: string | null; + flat: string | null; + buildingName: string | null; + distribution: string | null; + extra: string | null; + confidential: boolean; + lines: string[]; + addressReference: AddressReference | null; + validFrom: DateTime; + validTo: DateTime | null; + point: Point | null; + refStatus: AddressRefStatus; + isNoAddress: boolean; } export interface AddressWithPoint extends Address { - point: Point; + point: Point; } export interface AddressReference { - id: number; - createdAt: DateTime | null; - deletedAt: DateTime | null; - municipalityCode: string; - point: Point; - postcode: Postcode; - refId: string; - source: string; - street: string; - streetNumber: string; - updatedAt: DateTime | null; + id: number; + createdAt: DateTime | null; + deletedAt: DateTime | null; + municipalityCode: string; + point: Point; + postcode: Postcode; + refId: string; + source: string; + street: string; + streetNumber: string; + updatedAt: DateTime | null; } export interface SimpleGeographicalUnit { - id: number; - layerId: number; - unitName: string; - unitRefId: string; + id: number; + layerId: number; + unitName: string; + unitRefId: string; } export interface GeographicalUnitLayer { - id: number; - name: TranslatableString; - refId: string; + id: number; + name: TranslatableString; + refId: string; } export interface Location { - type: "location"; - id: number; - active: boolean; - address: Address | null; - availableForUsers: boolean; - createdAt: DateTime | null; - createdBy: User | null; - updatedAt: DateTime | null; - updatedBy: User | null; - email: string | null; - name: string; - phonenumber1: string | null; - phonenumber2: string | null; - locationType: LocationType; + type: "location"; + id: number; + active: boolean; + address: Address | null; + availableForUsers: boolean; + createdAt: DateTime | null; + createdBy: User | null; + updatedAt: DateTime | null; + updatedBy: User | null; + email: string | null; + name: string; + phonenumber1: string | null; + phonenumber2: string | null; + locationType: LocationType; } export interface LocationAssociated { - type: "location"; - id: number; + type: "location"; + id: number; } export interface LocationType { - type: "location-type"; - id: number; - active: boolean; - addressRequired: "optional" | "required"; - availableForUsers: boolean; - editableByUsers: boolean; - contactData: "optional" | "required"; - title: TranslatableString; + type: "location-type"; + id: number; + active: boolean; + addressRequired: "optional" | "required"; + availableForUsers: boolean; + editableByUsers: boolean; + contactData: "optional" | "required"; + title: TranslatableString; } export interface NewsItemType { - id: number; - title: string; - content: string; - startDate: DateTime; - endDate: DateTime | null; + id: number; + title: string; + content: string; + startDate: DateTime; + endDate: DateTime | null; } export interface WorkflowAvailable { - name: string; - text: string; + name: string; + text: string; } export interface WorkflowAttachment { - id: number; - relatedGenericDocKey: string; - relatedGenericDocIdentifiers: object; - createdAt: DateTime | null; - createdBy: User | null; - updatedAt: DateTime | null; - updatedBy: User | null; - genericDoc: null | GenericDoc; + id: number; + relatedGenericDocKey: string; + relatedGenericDocIdentifiers: object; + createdAt: DateTime | null; + createdBy: User | null; + updatedAt: DateTime | null; + updatedBy: User | null; + genericDoc: null | GenericDoc; } export interface ExportGeneration { - id: string; - type: "export_generation"; - exportAlias: string; - createdBy: User | null; - createdAt: DateTime | null; - status: StoredObjectStatus; - storedObject: StoredObject; + id: string; + type: "export_generation"; + exportAlias: string; + createdBy: User | null; + createdAt: DateTime | null; + status: StoredObjectStatus; + storedObject: StoredObject; } export interface PrivateCommentEmbeddable { - comments: Record; + comments: Record; +} + +// API Exception types +export interface TransportExceptionInterface { + name: string; +} + +export interface ValidationExceptionInterface + extends TransportExceptionInterface { + name: "ValidationException"; + error: object; + violations: string[]; + titles: string[]; + propertyPaths: string[]; +} + +export interface AccessExceptionInterface extends TransportExceptionInterface { + name: "AccessException"; + violations: string[]; +} + +export interface NotFoundExceptionInterface + extends TransportExceptionInterface { + name: "NotFoundException"; +} + +export interface ServerExceptionInterface extends TransportExceptionInterface { + name: "ServerException"; + message: string; + code: number; + body: string; +} + +export interface ConflictHttpExceptionInterface + extends TransportExceptionInterface { + name: "ConflictHttpException"; + violations: string[]; +} + +export type ApiException = + | ValidationExceptionInterface + | AccessExceptionInterface + | NotFoundExceptionInterface + | ServerExceptionInterface + | ConflictHttpExceptionInterface; + +export interface Modal { + showModal: boolean; + modalDialogClass: string; +} + +export interface Selected { + result: UserGroupOrUser; +} + +export interface addNewEntities { + selected: Selected[]; + modal: Modal; } diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue index 075bf6d65..d259d0cae 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/App.vue @@ -1,130 +1,130 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/ActionButtons.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/ActionButtons.vue index 5d90bf818..fda84c817 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/ActionButtons.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/ActionButtons.vue @@ -1,32 +1,32 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue index 103849e3a..ede1b3778 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress.vue @@ -1,251 +1,248 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMap.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMap.vue index 167b3b981..4aa44f4a6 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMap.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMap.vue @@ -1,5 +1,5 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue index 4b59efbc6..3e9da5da4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressMore.vue @@ -1,149 +1,149 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue index fd1740a40..6ad8965b4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/AddressSelection.vue @@ -1,227 +1,223 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue index 0d85c69f9..74520310e 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CitySelection.vue @@ -1,60 +1,60 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue index 37decf15e..47feab563 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/AddAddress/CountrySelection.vue @@ -1,23 +1,23 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue index 96e67f952..3e8e1f4bb 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/DatePane.vue @@ -1,69 +1,66 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue index df5ea56dc..cf808ed2a 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/EditPane.vue @@ -1,104 +1,101 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/ShowPane.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/ShowPane.vue index a17ce719d..d0796bc0d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/ShowPane.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/ShowPane.vue @@ -1,109 +1,98 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/SuggestPane.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/SuggestPane.vue index ddaed6219..2e62b5963 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/SuggestPane.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/Address/components/SuggestPane.vue @@ -1,67 +1,64 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue index 033eb6cac..d1389a748 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/App.vue @@ -1,10 +1,10 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts index e970e51b9..ae77fbb08 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/DownloadExport/index.ts @@ -4,8 +4,8 @@ import App from "./App.vue"; const el = document.getElementById("app"); if (null === el) { - console.error("div element app was not found"); - throw new Error("div element app was not found"); + console.error("div element app was not found"); + throw new Error("div element app was not found"); } const exportGenerationId = el?.dataset.exportGenerationId as string; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue index 8686b59a7..c550d1c0d 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/App.vue @@ -1,36 +1,36 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/News.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/News.vue index 5633d3133..4f664d6a5 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/News.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/News.vue @@ -1,13 +1,13 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/NewsItem.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/NewsItem.vue index 997eae58b..5cbd33712 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/NewsItem.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/DashboardWidgets/NewsItem.vue @@ -1,40 +1,38 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyAccompanyingCourses.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyAccompanyingCourses.vue index 9f7edf8e7..bd3b5cdf8 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyAccompanyingCourses.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyAccompanyingCourses.vue @@ -1,76 +1,65 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyCustoms.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyCustoms.vue index 1f76a5a0a..5b10dc89c 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyCustoms.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/HomepageWidget/MyCustoms.vue @@ -1,105 +1,103 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/BadgeEntity.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/BadgeEntity.vue index 1c5d2b6da..b74b64054 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/BadgeEntity.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/BadgeEntity.vue @@ -1,66 +1,63 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/CommentEditor/CommentEditor.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/CommentEditor/CommentEditor.vue index d6019fabe..97242ffa2 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/CommentEditor/CommentEditor.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/CommentEditor/CommentEditor.vue @@ -4,9 +4,9 @@ import { Ckeditor } from "@ckeditor/ckeditor5-vue"; import { ClassicEditor } from "ckeditor5"; import classicEditorConfig from "ChillMainAssets/module/ckeditor5/editor_config"; import { - trans, - EDITOR_SWITCH_TO_SIMPLE, - EDITOR_SWITCH_TO_COMPLEX, + trans, + EDITOR_SWITCH_TO_SIMPLE, + EDITOR_SWITCH_TO_COMPLEX, } from "translator"; const EDITOR_MODE_KEY = "editorMode"; @@ -16,107 +16,101 @@ const value = defineModel({ required: true }); const isSimple = computed(() => kind.value === "simple"); const toggleButtonClass = computed(() => { - return { - ["toggle-button"]: true, - onEditor: !isSimple.value, - onSimple: isSimple.value, - }; + return { + ["toggle-button"]: true, + onEditor: !isSimple.value, + onSimple: isSimple.value, + }; }); const toggleEditor = () => { - let newValue; + let newValue; - newValue = kind.value === "simple" ? "rich" : "simple"; - kind.value = "rich"; - window.localStorage.setItem(EDITOR_MODE_KEY, newValue); + newValue = kind.value === "simple" ? "rich" : "simple"; + kind.value = "rich"; + window.localStorage.setItem(EDITOR_MODE_KEY, newValue); - window.dispatchEvent(new Event("toggleEditorKind")); + window.dispatchEvent(new Event("toggleEditorKind")); }; const onKindChange = function (/* event: StorageEvent | Event */) { - const newValue = window.localStorage.getItem(EDITOR_MODE_KEY); + const newValue = window.localStorage.getItem(EDITOR_MODE_KEY); - if (null === newValue || !(newValue === "rich" || newValue === "simple")) { - throw "invalid new value: " + newValue; - } + if (null === newValue || !(newValue === "rich" || newValue === "simple")) { + throw "invalid new value: " + newValue; + } - if (kind.value !== newValue) { - kind.value = newValue; - } + if (kind.value !== newValue) { + kind.value = newValue; + } }; onMounted(function () { - const storage = window.localStorage; - const savedKind = storage.getItem(EDITOR_MODE_KEY); + const storage = window.localStorage; + const savedKind = storage.getItem(EDITOR_MODE_KEY); - if ( - null !== kind.value && - (savedKind === "simple" || savedKind === "rich") - ) { - kind.value = savedKind; - } + if (null !== kind.value && (savedKind === "simple" || savedKind === "rich")) { + kind.value = savedKind; + } - window.addEventListener("storage", onKindChange); - window.addEventListener("toggleEditorKind", onKindChange); + window.addEventListener("storage", onKindChange); + window.addEventListener("toggleEditorKind", onKindChange); }); onUnmounted(function () { - window.removeEventListener("storage", onKindChange); - window.removeEventListener("toggleEditorKind", onKindChange); + window.removeEventListener("storage", onKindChange); + window.removeEventListener("toggleEditorKind", onKindChange); }); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue index 13459b390..9c010e8d1 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue @@ -1,40 +1,40 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/AddressRenderBox.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/AddressRenderBox.vue index 86f4155b6..7085b3b18 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/AddressRenderBox.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/AddressRenderBox.vue @@ -1,83 +1,77 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/GenderIconRenderBox.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/GenderIconRenderBox.vue index d47b9b777..794771014 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/GenderIconRenderBox.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/GenderIconRenderBox.vue @@ -1,9 +1,28 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserGroupRenderBox.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserGroupRenderBox.vue index 7c37e98a9..81f836c62 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserGroupRenderBox.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserGroupRenderBox.vue @@ -4,23 +4,23 @@ import { computed } from "vue"; import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper"; interface UserGroupRenderBoxProps { - userGroup: UserGroup; + userGroup: UserGroup; } const props = defineProps(); const styles = computed<{ color: string; "background-color": string }>(() => { - return { - color: props.userGroup.foregroundColor, - "background-color": props.userGroup.backgroundColor, - }; + return { + color: props.userGroup.foregroundColor, + "background-color": props.userGroup.backgroundColor, + }; }); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue index 63c43c37f..4bae3b541 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue @@ -1,31 +1,31 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue index 332849ef5..4d817a2f9 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue @@ -1,83 +1,83 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue index 59dcd80f4..658c0f9e9 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue @@ -1,49 +1,45 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue index 06e1bfa1a..4808e2bda 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue @@ -1,42 +1,39 @@ @@ -73,17 +79,17 @@ const emits = defineEmits<{ * This is a mask behind the modal. */ .modal-mask { - position: fixed; - z-index: 9998; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.75); - transition: opacity 0.3s ease; + position: fixed; + z-index: 9998; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.75); + transition: opacity 0.3s ease; } .modal-header .close { - border-top-right-radius: 0.3rem; + border-top-right-radius: 0.3rem; } /* * The following styles are auto-applied to elements with @@ -94,23 +100,23 @@ const emits = defineEmits<{ * these styles. */ .modal-enter { - opacity: 0; + opacity: 0; } .modal-leave-active { - opacity: 0; + opacity: 0; } .modal-enter .modal-container, .modal-leave-active .modal-container { - -webkit-transform: scale(1.1); - transform: scale(1.1); + -webkit-transform: scale(1.1); + transform: scale(1.1); } h3.modal-title { - font-size: 1.5rem; - font-weight: bold; + font-size: 1.5rem; + font-weight: bold; } div.modal-footer { - button:first-child { - margin-right: auto; - } + button:first-child { + margin-right: auto; + } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadAllToggle.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadAllToggle.vue index 57f7fd7f8..bb7a43c92 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadAllToggle.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadAllToggle.vue @@ -1,17 +1,17 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue index cc64d4835..fb94af80f 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue @@ -1,96 +1,96 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.ts b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.ts index db9a37104..cbee6bf25 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.ts @@ -2,87 +2,86 @@ import { createI18n } from "vue-i18n"; import datetimeFormats from "../i18n/datetimeFormats"; const messages = { - fr: { - action: { - actions: "Actions", - show: "Voir", - edit: "Modifier", - create: "Créer", - remove: "Enlever", - delete: "Supprimer", - save: "Enregistrer", - valid: "Valider", - valid_and_see: "Valider et voir", - add: "Ajouter", - show_modal: "Ouvrir une modale", - ok: "OK", - cancel: "Annuler", - close: "Fermer", - back: "Retour", - check_all: "cocher tout", - reset: "réinitialiser", - redirect: { - person: "Quitter la page et ouvrir la fiche de l'usager", - thirdparty: "Quitter la page et voir le tiers", - }, - refresh: "Rafraîchir", - addContact: "Ajouter un contact", - }, - nav: { - next: "Suivant", - previous: "Précédent", - top: "Haut", - bottom: "Bas", - }, - renderbox: { - person: "Usager", - birthday: { - man: "Né le", - woman: "Née le", - neutral: "Né·e le", - unknown: "Né·e le", - }, - deathdate: "Date de décès", - household_without_address: "Le ménage de l'usager est sans adresse", - no_data: "Aucune information renseignée", - type: { - thirdparty: "Tiers", - person: "Usager", - }, - holder: "Titulaire", - years_old: "1 an | {n} an | {n} ans", - residential_address: "Adresse de résidence", - located_at: "réside chez", - }, + fr: { + action: { + actions: "Actions", + show: "Voir", + edit: "Modifier", + create: "Créer", + remove: "Enlever", + delete: "Supprimer", + save: "Enregistrer", + valid: "Valider", + valid_and_see: "Valider et voir", + add: "Ajouter", + show_modal: "Ouvrir une modale", + ok: "OK", + cancel: "Annuler", + close: "Fermer", + back: "Retour", + check_all: "cocher tout", + reset: "réinitialiser", + redirect: { + person: "Quitter la page et ouvrir la fiche de l'usager", + thirdparty: "Quitter la page et voir le tiers", + }, + refresh: "Rafraîchir", + addContact: "Ajouter un contact", }, + nav: { + next: "Suivant", + previous: "Précédent", + top: "Haut", + bottom: "Bas", + }, + renderbox: { + person: "Usager", + birthday: { + man: "Né le", + woman: "Née le", + neutral: "Né·e le", + unknown: "Né·e le", + }, + deathdate: "Date de décès", + household_without_address: "Le ménage de l'usager est sans adresse", + no_data: "Aucune information renseignée", + type: { + thirdparty: "Tiers", + person: "Usager", + }, + holder: "Titulaire", + years_old: "1 an | {n} an | {n} ans", + residential_address: "Adresse de résidence", + located_at: "réside chez", + }, + }, }; const _createI18n = (appMessages: any, legacy?: boolean) => { - Object.assign(messages.fr, appMessages.fr); - return createI18n({ - legacy: typeof legacy === undefined ? true : legacy, - locale: "fr", - fallbackLocale: "fr", - // @ts-ignore - datetimeFormats, - messages, - }); + Object.assign(messages.fr, appMessages.fr); + return createI18n({ + legacy: typeof legacy === undefined ? true : legacy, + locale: "fr", + fallbackLocale: "fr", + // @ts-ignore + datetimeFormats, + messages, + }); }; export { _createI18n }; export const multiSelectMessages = { - fr: { - multiselect: { - placeholder: "Choisir", - tag_placeholder: "Créer un nouvel élément", - select_label: '"Entrée" ou cliquez pour sélectionner', - deselect_label: '"Entrée" ou cliquez pour désélectionner', - select_group_label: - 'Appuyer sur "Entrée" pour sélectionner ce groupe', - deselect_group_label: - 'Appuyer sur "Entrée" pour désélectionner ce groupe', - selected_label: "Sélectionné", - }, + fr: { + multiselect: { + placeholder: "Choisir", + tag_placeholder: "Créer un nouvel élément", + select_label: '"Entrée" ou cliquez pour sélectionner', + deselect_label: '"Entrée" ou cliquez pour désélectionner', + select_group_label: 'Appuyer sur "Entrée" pour sélectionner ce groupe', + deselect_group_label: + 'Appuyer sur "Entrée" pour désélectionner ce groupe', + selected_label: "Sélectionné", }, + }, }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/i18n/datetimeFormats.ts b/src/Bundle/ChillMainBundle/Resources/public/vuejs/i18n/datetimeFormats.ts index fe20cb217..aca1328ac 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/i18n/datetimeFormats.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/i18n/datetimeFormats.ts @@ -1,27 +1,27 @@ export default { - fr: { - short: { - year: "numeric", - month: "numeric", - day: "numeric", - }, - text: { - year: "numeric", - month: "long", - day: "numeric", - }, - long: { - year: "numeric", - month: "numeric", - day: "numeric", - hour: "numeric", - minute: "numeric", - hour12: false, - }, - hoursOnly: { - hour: "numeric", - minute: "numeric", - hour12: false, - }, + fr: { + short: { + year: "numeric", + month: "numeric", + day: "numeric", }, + text: { + year: "numeric", + month: "long", + day: "numeric", + }, + long: { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + hour12: false, + }, + hoursOnly: { + hour: "numeric", + minute: "numeric", + hour12: false, + }, + }, }; diff --git a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig index 30f84c855..d92f2e260 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig @@ -69,35 +69,37 @@
    {% endif %} - {% block content %} - - {# DISABLED {{ chill_widget('homepage', {} ) }} #} + {# DISABLED {{ chill_widget('homepage', {} ) }} #} - {% include '@ChillMain/Homepage/index.html.twig' %} + {% include '@ChillMain/Homepage/index.html.twig' %} + {% endblock %} {% endblock %}
    diff --git a/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php index 182980dd0..cae0cdd48 100644 --- a/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php @@ -12,6 +12,7 @@ declare(strict_types=1); namespace Chill\MainBundle\Tests\Phonenumber; use Chill\MainBundle\Phonenumber\PhonenumberHelper; +use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberUtil; use Psr\Log\NullLogger; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -31,6 +32,7 @@ final class PhonenumberHelperTest extends KernelTestCase public function testFormatPhonenumbers(string $defaultCarrierCode, string $phoneNumber, string $expected) { $util = PhoneNumberUtil::getInstance(); + $subject = new PhonenumberHelper( new ArrayAdapter(), new ParameterBag([ @@ -70,4 +72,47 @@ final class PhonenumberHelperTest extends KernelTestCase '00 33 6 23 12 45 54', ]; } + + /** + * @dataProvider providePhoneNumbersToParse + */ + public function testParsePhonenumbers(string $defaultCarrierCode, string $phoneNumber, PhoneNumber $expected): void + { + $subject = new PhonenumberHelper( + new ArrayAdapter(), + new ParameterBag([ + 'chill_main.phone_helper' => [ + 'default_carrier_code' => $defaultCarrierCode, + ], + ]), + new NullLogger() + ); + + $actual = $subject->parse($phoneNumber); + + self::assertTrue($expected->equals($actual)); + } + + public static function providePhoneNumbersToParse(): iterable + { + $util = PhoneNumberUtil::getInstance(); + + yield [ + 'FR', + '+32486544999', + $util->parse('+32486544999', 'FR'), + ]; + + yield [ + 'FR', + '32486544999', + $util->parse('+32486544999', 'FR'), + ]; + + yield [ + 'FR', + '0228858040', + $util->parse('+33228858040', 'FR'), + ]; + } } diff --git a/src/Bundle/ChillMainBundle/Tests/Validation/Validator/UserGroupDoNotExcludeTest.php b/src/Bundle/ChillMainBundle/Tests/Validation/Validator/UserGroupDoNotExcludeTest.php new file mode 100644 index 000000000..d6d1c9887 --- /dev/null +++ b/src/Bundle/ChillMainBundle/Tests/Validation/Validator/UserGroupDoNotExcludeTest.php @@ -0,0 +1,91 @@ +validator->validate([], new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude()); + + $this->assertNoViolation(); + } + + public function testMixedUserGroupAndUsersIsValid(): void + { + $this->validator->validate( + [new User(), new UserGroup()], + new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude() + ); + + $this->assertNoViolation(); + } + + public function testDifferentExcludeKeysIsValid(): void + { + $this->validator->validate( + [(new UserGroup())->setExcludeKey('A'), (new UserGroup())->setExcludeKey('B')], + new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude() + ); + + $this->assertNoViolation(); + } + + public function testMultipleGroupsWithEmptyExcludeKeyIsValid(): void + { + $this->validator->validate( + [(new UserGroup())->setExcludeKey(''), (new UserGroup())->setExcludeKey('')], + new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude() + ); + + $this->assertNoViolation(); + } + + public function testSameExclusionKeyWillRaiseError(): void + { + $this->validator->validate( + [ + (new UserGroup())->setExcludeKey('A')->setLabel(['fr' => 'Group 1']), + (new UserGroup())->setExcludeKey('A')->setLabel(['fr' => 'Group 2']), + ], + new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude() + ); + + $this->buildViolation('The groups {{ excluded_groups }} do exclude themselves. Please choose one between them') + ->setParameter('excluded_groups', 'Group 1, Group 2') + ->setCode('e16c8226-0090-11ef-8560-f7239594db09') + ->assertRaised(); + } +} diff --git a/src/Bundle/ChillMainBundle/chill.api.specs.yaml b/src/Bundle/ChillMainBundle/chill.api.specs.yaml index d87a0eb71..047f69675 100644 --- a/src/Bundle/ChillMainBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillMainBundle/chill.api.specs.yaml @@ -10,6 +10,31 @@ servers: components: schemas: + Collection: + type: object + properties: + count: + type: number + format: u64 + pagination: + type: object + properties: + first: + type: number + format: u64 + items_per_page: + type: number + format: u64 + next: + type: string + format: uri + nullable: true + previous: + type: string + format: uri + nullable: true + more: + type: boolean EntityWorkflowAttachment: type: object properties: diff --git a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml index 2982d94db..0d1b30ef8 100644 --- a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml @@ -130,3 +130,58 @@ filter_order: Search: Chercher dans la liste By date: Filtrer par date search_box: Filtrer par contenu +renderbox: + person: "Usager" + birthday: + man: "Né le" + woman: "Née le" + neutral: "Né·e le" + unknown: "Né·e le" + deathdate: "Date de décès" + household_without_address: "Le ménage de l'usager est sans adresse" + no_data: "Aucune information renseignée" + type: + thirdparty: "Tiers" + person: "Usager" + holder: "Titulaire" + years_old: >- + {n, plural, + =0 {0 an} + one {1 an} + other {# ans} + } + residential_address: "Adresse de résidence" + located_at: "réside chez" + household_number: "Ménage n°{number}" + current_members: "Membres actuels" + no_current_address: "Sans adresse actuellement" + new_household: "Nouveau ménage" + no_members_yet: "Aucun membre actuellement" + +pick_entity: + add: "Ajouter" + modal_title: >- + {count, plural, + one {Indiquer un} + other {Ajouter des} + } + user: >- + {count, plural, + one {Utilisateur} + other {Utilisateurs} + } + user_group: >- + {count, plural, + one {Groupe d'utilisateur} + other {Groupes d'utilisateurs} + } + person: >- + {count, plural, + one {Usager} + other {Usagers} + } + thirdparty: >- + {count, plural, + one {Tiers} + other {Tiers} + } diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index d3498cba9..5e591fba8 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -941,3 +941,34 @@ multiselect: editor: switch_to_simple: Éditeur simple switch_to_complex: Éditeur riche +action: + actions: Actions + show: Voir + edit: Modifier + create: Créer + remove: Enlever + delete: Supprimer + save: Enregistrer + valid: Valider + valid_and_see: Valider et voir + add: Ajouter + show_modal: Ouvrir une modale + ok: OK + cancel: Annuler + close: Fermer + back: Retour + check_all: cocher tout + reset: réinitialiser + redirect: + person: Quitter la page et ouvrir la fiche de l'usager + thirdparty: Quitter la page et voir le tiers + refresh: Rafraîchir + addContact: Ajouter un contact + +nav: + next: "Suivant" + previous: "Précédent" + top: "Haut" + bottom: "Bas" + + diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php index 686022ca3..0d972914d 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php @@ -21,6 +21,8 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\Query; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberFormat; use Symfony\Component\Security\Core\Security; final readonly class PersonACLAwareRepository implements PersonACLAwareRepositoryInterface @@ -298,4 +300,27 @@ final readonly class PersonACLAwareRepository implements PersonACLAwareRepositor \array_map(static fn (Center $c) => $c->getId(), $authorizedCenters) ); } + + public function findByPhone(PhoneNumber $phoneNumber, int $start = 0, int $limit = 20): array + { + $authorizedCenters = $this->authorizationHelper + ->getReachableCenters($this->security->getUser(), PersonVoter::SEE); + + if ([] === $authorizedCenters) { + return []; + } + + $util = \libphonenumber\PhoneNumberUtil::getInstance(); + + return $this->em->createQuery( + 'SELECT p FROM '.Person::class.' p LEFT JOIN p.otherPhoneNumbers opn JOIN p.centerCurrent pcc '. + 'WHERE (p.phonenumber LIKE :phone OR p.mobilenumber LIKE :phone OR opn.phonenumber LIKE :phone) '. + 'AND pcc.center IN (:centers)' + ) + ->setMaxResults($limit) + ->setFirstResult($start) + ->setParameter('phone', $util->format($phoneNumber, PhoneNumberFormat::E164)) + ->setParameter('centers', $authorizedCenters) + ->getResult(); + } } diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php index 50fdcd4b3..aeaefecd4 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php @@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Repository; use Chill\MainBundle\Search\SearchApiQuery; use Chill\PersonBundle\Entity\Person; +use libphonenumber\PhoneNumber; interface PersonACLAwareRepositoryInterface { @@ -60,4 +61,13 @@ interface PersonACLAwareRepositoryInterface ?string $phonenumber = null, ?string $city = null, ): array; + + /** + * @return list + */ + public function findByPhone( + PhoneNumber $phoneNumber, + int $start = 0, + int $limit = 20, + ): array; } diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php index 6d6373999..8d5e244ea 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php @@ -12,10 +12,12 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Entity\PersonPhone; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; +use libphonenumber\PhoneNumber; class PersonRepository implements ObjectRepository { @@ -29,6 +31,8 @@ class PersonRepository implements ObjectRepository /** * @throws \Doctrine\ORM\NoResultException * @throws \Doctrine\ORM\NonUniqueResultException + * + * @deprecated */ public function countByPhone( string $phonenumber, @@ -71,6 +75,8 @@ class PersonRepository implements ObjectRepository /** * @throws \Exception + * + * @deprecated Use @see{self::findByPhoneNumber} or use a dedicated method in PersonACLAwareRepository */ public function findByPhone( string $phonenumber, @@ -91,6 +97,25 @@ class PersonRepository implements ObjectRepository return $qb->getQuery()->getResult(); } + /** + * Find a person which is associated to the given phonenumber, without restrictions + * on any. + * + * @return list + */ + public function findByPhoneNumber(PhoneNumber $phoneNumber, int $firstResult = 0, int $maxResults = 50): array + { + $qb = $this->repository->createQueryBuilder('p'); + $qb->select('p'); + + $this->searchByPhoneNumbers($qb, $phoneNumber); + + $qb->setFirstResult($firstResult) + ->setMaxResults($maxResults); + + return $qb->getQuery()->getResult(); + } + public function findOneBy(array $criteria) { return $this->repository->findOneBy($criteria); @@ -109,6 +134,20 @@ class PersonRepository implements ObjectRepository } } + private function searchByPhoneNumbers(QueryBuilder $qb, PhoneNumber $phoneNumber): void + { + $qb->setParameter('number', $phoneNumber, 'phone_number'); + + $orX = $qb->expr()->orX(); + $orX->add($qb->expr()->eq('p.mobilenumber', ':number')); + $orX->add($qb->expr()->eq('p.phonenumber', ':number')); + $orX->add( + $qb->expr()->exists('SELECT 1 FROM '.PersonPhone::class.' k WHERE k.phonenumber = :number AND k.person = p') + ); + + $qb->andWhere($orX); + } + /** * @throws \Exception */ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts b/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts index 8f117d6ef..6ceb64b98 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts @@ -3,49 +3,47 @@ import AccompanyingPeriodWorkSelectorModal from "../../vuejs/_components/Accompa import { AccompanyingPeriodWork } from "../../types"; document.addEventListener("DOMContentLoaded", () => { - const elements = document.querySelectorAll( - 'div[data-pick-entities-type="acpw"]', + const elements = document.querySelectorAll( + 'div[data-pick-entities-type="acpw"]', + ); + elements.forEach((el) => { + const uniqid = el.dataset.inputUniqid; + + if (undefined === uniqid) { + throw "Uniqid not found on this element"; + } + + const input = document.querySelector( + `input[data-input-uniqid="${uniqid}"]`, ); - elements.forEach((el) => { - const uniqid = el.dataset.inputUniqid; - if (undefined === uniqid) { - throw "Uniqid not found on this element"; - } + if (null === input) { + throw "Element with uniqid not found: " + uniqid; + } - const input = document.querySelector( - `input[data-input-uniqid="${uniqid}"]`, - ); + const accompanyingPeriodIdAsString = input.dataset.accompanyingPeriodId; - if (null === input) { - throw "Element with uniqid not found: " + uniqid; - } + if (undefined === accompanyingPeriodIdAsString) { + throw "accompanying period id not found"; + } - const accompanyingPeriodIdAsString = input.dataset.accompanyingPeriodId; + const accompanyingPeriodId = Number.parseInt(accompanyingPeriodIdAsString); - if (undefined === accompanyingPeriodIdAsString) { - throw "accompanying period id not found"; - } - - const accompanyingPeriodId = Number.parseInt( - accompanyingPeriodIdAsString, - ); - - const app = createApp({ - template: - '', - components: { AccompanyingPeriodWorkSelectorModal }, - data() { - return { accompanyingPeriodId }; - }, - methods: { - pickWork: function (payload: { work: AccompanyingPeriodWork }) { - console.log("payload", payload); - input.value = payload.work.id.toString(); - }, - }, - }); - - app.mount(el); + const app = createApp({ + template: + '', + components: { AccompanyingPeriodWorkSelectorModal }, + data() { + return { accompanyingPeriodId }; + }, + methods: { + pickWork: function (payload: { work: AccompanyingPeriodWork }) { + console.log("payload", payload); + input.value = payload.work.id.toString(); + }, + }, }); + + app.mount(el); + }); }); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/types.ts b/src/Bundle/ChillPersonBundle/Resources/public/types.ts index 7d641c859..32dc24f73 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/types.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/types.ts @@ -1,253 +1,329 @@ import { - Address, - Scope, - Center, - Civility, - DateTime, - User, - WorkflowAvailable, - Job, - PrivateCommentEmbeddable, + Address, + Center, + Civility, + DateTime, + User, + UserGroup, + Household, + WorkflowAvailable, + Scope, + Job, + PrivateCommentEmbeddable, } from "ChillMainAssets/types"; import { StoredObject } from "ChillDocStoreAssets/types"; import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types"; import { Calendar } from "../../../ChillCalendarBundle/Resources/public/types"; +export interface AltName { + label: string; + key: string; +} + export interface Person { - id: number; - type: "person"; - text: string; - textAge: string; - firstName: string; - lastName: string; - current_household_address: Address | null; - birthdate: DateTime | null; - deathdate: DateTime | null; - age: number; - phonenumber: string; - mobilenumber: string; - email: string; - gender: "woman" | "man" | "other"; - centers: Center[]; - civility: Civility | null; - current_household_id: number; - current_residential_addresses: Address[]; + id: number; + type: "person"; + text: string; + textAge: string; + firstName: string; + lastName: string; + altNames: AltName[]; + suffixText: string; + current_household_address: Address | null; + birthdate: DateTime | null; + deathdate: DateTime | null; + age: number; + phonenumber: string; + mobilenumber: string; + email: string; + gender: "woman" | "man" | "other"; + centers: Center[]; + civility: Civility | null; + current_household_id: number; + current_residential_addresses: Address[]; } export interface AccompanyingPeriod { - id: number; - addressLocation?: Address | null; - administrativeLocation?: Location | null; - calendars: Calendar[]; - closingDate?: Date | null; - closingMotive?: ClosingMotive | null; - comments: Comment[]; - confidential: boolean; - createdAt?: Date | null; - createdBy?: User | null; - emergency: boolean; - intensity?: "occasional" | "regular"; - job?: Job | null; - locationHistories: AccompanyingPeriodLocationHistory[]; - openingDate?: Date | null; - origin?: Origin | null; - participations: AccompanyingPeriodParticipation[]; - personLocation?: Person | null; - pinnedComment?: Comment | null; - preventUserIsChangedNotification: boolean; - remark: string; - requestorAnonymous: boolean; - requestorPerson?: Person | null; - requestorThirdParty?: Thirdparty | null; - resources: AccompanyingPeriodResource[]; - scopes: Scope[]; - socialIssues: SocialIssue[]; - step?: - | "CLOSED" - | "CONFIRMED" - | "CONFIRMED_INACTIVE_SHORT" - | "CONFIRMED_INACTIVE_LONG" - | "DRAFT"; -} - -export interface AccompanyingPeriodWorkEvaluationDocument { - id: number; - type: "accompanying_period_work_evaluation_document"; - storedObject: StoredObject; - title: string; - createdAt: DateTime | null; - createdBy: User | null; - updatedAt: DateTime | null; - updatedBy: User | null; - workflows_availables: WorkflowAvailable[]; - workflows: object[]; + id: number; + addressLocation?: Address | null; + administrativeLocation?: Location | null; + calendars: Calendar[]; + closingDate?: Date | null; + closingMotive?: ClosingMotive | null; + comments: Comment[]; + confidential: boolean; + createdAt?: Date | null; + createdBy?: User | null; + emergency: boolean; + intensity?: "occasional" | "regular"; + job?: Job | null; + locationHistories: AccompanyingPeriodLocationHistory[]; + openingDate?: Date | null; + origin?: Origin | null; + participations: AccompanyingPeriodParticipation[]; + personLocation?: Person | null; + pinnedComment?: Comment | null; + preventUserIsChangedNotification: boolean; + remark: string; + requestorAnonymous: boolean; + requestorPerson?: Person | null; + requestorThirdParty?: Thirdparty | null; + resources: AccompanyingPeriodResource[]; + scopes: Scope[]; + socialIssues: SocialIssue[]; + step?: + | "CLOSED" + | "CONFIRMED" + | "CONFIRMED_INACTIVE_SHORT" + | "CONFIRMED_INACTIVE_LONG" + | "DRAFT"; } export interface AccompanyingPeriodWork { - id: number; - accompanyingPeriod?: AccompanyingPeriod; - accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[]; - createdAt?: string; - createdAutomatically: boolean; - createdAutomaticallyReason: string; - createdBy: User; - endDate?: string; - goals: AccompanyingPeriodWorkGoal[]; - handlingThierParty?: Thirdparty; - note: string; - persons: Person[]; - privateComment: PrivateCommentEmbeddable; - referrersHistory: AccompanyingPeriodWorkReferrerHistory[]; - results: Result[]; - socialAction?: SocialAction; - startDate?: string; - thirdParties: Thirdparty[]; - updatedAt?: string; - updatedBy: User; - version: number; + id: number; + accompanyingPeriod?: AccompanyingPeriod; + accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[]; + createdAt?: string; + createdAutomatically: boolean; + createdAutomaticallyReason: string; + createdBy: User; + endDate?: string; + goals: AccompanyingPeriodWorkGoal[]; + handlingThierParty?: Thirdparty; + note: string; + persons: Person[]; + privateComment: PrivateCommentEmbeddable; + referrersHistory: AccompanyingPeriodWorkReferrerHistory[]; + results: Result[]; + socialAction?: SocialAction; + startDate?: string; + thirdParties: Thirdparty[]; + updatedAt?: string; + updatedBy: User; + version: number; } -interface SocialAction { - id: number; - parent?: SocialAction | null; - children: SocialAction[]; - issue?: SocialIssue | null; - ordering: number; - title: { - fr: string; - }; - defaultNotificationDelay?: string | null; - desactivationDate?: string | null; - evaluations: Evaluation[]; - goals: Goal[]; - results: Result[]; +export interface SocialAction { + id: number; + parent?: SocialAction | null; + children: SocialAction[]; + issue?: SocialIssue | null; + ordering: number; + title: { + fr: string; + }; + defaultNotificationDelay?: string | null; + desactivationDate?: string | null; + evaluations: Evaluation[]; + goals: Goal[]; + results: Result[]; } export interface AccompanyingPeriodResource { - id: number; - accompanyingPeriod: AccompanyingPeriod; - comment?: string | null; - person?: Person | null; - thirdParty?: Thirdparty | null; + id: number; + accompanyingPeriod: AccompanyingPeriod; + comment?: string | null; + person?: Person | null; + thirdParty?: Thirdparty | null; } export interface Origin { - id: number; - label: { - fr: string; - }; - noActiveAfter: DateTime; + id: number; + label: { + fr: string; + }; + noActiveAfter: DateTime; } export interface ClosingMotive { - id: number; - active: boolean; - name: { - fr: string; - }; - ordering: number; - isCanceledAccompanyingPeriod: boolean; - parent?: ClosingMotive | null; - children: ClosingMotive[]; + id: number; + active: boolean; + name: { + fr: string; + }; + ordering: number; + isCanceledAccompanyingPeriod: boolean; + parent?: ClosingMotive | null; + children: ClosingMotive[]; } export interface AccompanyingPeriodParticipation { - id: number; - startDate: DateTime; - endDate?: DateTime | null; - accompanyingPeriod: AccompanyingPeriod; - person: Person; + id: number; + startDate: DateTime; + endDate?: DateTime | null; + accompanyingPeriod: AccompanyingPeriod; + person: Person; } export interface AccompanyingPeriodLocationHistory { - id: number; - startDate: DateTime; - endDate?: DateTime | null; - addressLocation?: Address | null; - period: AccompanyingPeriod; - personLocation?: Person | null; + id: number; + startDate: DateTime; + endDate?: DateTime | null; + addressLocation?: Address | null; + period: AccompanyingPeriod; + personLocation?: Person | null; } export interface SocialIssue { - id: number; - parent?: SocialIssue | null; - children: SocialIssue[]; - socialActions?: SocialAction[] | null; - ordering: number; - title: { - fr: string; - }; - desactivationDate?: string | null; + id: number; + parent?: SocialIssue | null; + children: SocialIssue[]; + socialActions?: SocialAction[] | null; + ordering: number; + title: { + fr: string; + }; + desactivationDate?: string | null; } export interface Goal { - id: number; - results: Result[]; - socialActions?: SocialAction[] | null; - title: { - fr: string; - }; + id: number; + results: Result[]; + socialActions?: SocialAction[] | null; + title: { + fr: string; + }; } export interface Result { - id: number; - accompanyingPeriodWorks: AccompanyingPeriodWork[]; - accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[]; - goals: Goal[]; - socialActions: SocialAction[]; - title: { - fr: string; - }; - desactivationDate?: string | null; + id: number; + accompanyingPeriodWorks: AccompanyingPeriodWork[]; + accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[]; + goals: Goal[]; + socialActions: SocialAction[]; + title: { + fr: string; + }; + desactivationDate?: string | null; } export interface AccompanyingPeriodWorkGoal { - id: number; - accompanyingPeriodWork: AccompanyingPeriodWork; - goal: Goal; - note: string; - results: Result[]; + id: number; + accompanyingPeriodWork: AccompanyingPeriodWork; + goal: Goal; + note: string; + results: Result[]; } export interface AccompanyingPeriodWorkEvaluation { - accompanyingPeriodWork: AccompanyingPeriodWork | null; - comment: string; - createdAt: DateTime | null; - createdBy: User | null; - documents: AccompanyingPeriodWorkEvaluationDocument[]; - endDate: DateTime | null; - evaluation: Evaluation | null; - id: number | null; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - key: any; - maxDate: DateTime | null; - startDate: DateTime | null; - updatedAt: DateTime | null; - updatedBy: User | null; - warningInterval: string | null; - timeSpent: number | null; + accompanyingPeriodWork: AccompanyingPeriodWork | null; + comment: string; + createdAt: DateTime | null; + createdBy: User | null; + documents: AccompanyingPeriodWorkEvaluationDocument[]; + endDate: DateTime | null; + evaluation: Evaluation | null; + id: number | null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + key: any; + maxDate: DateTime | null; + startDate: DateTime | null; + updatedAt: DateTime | null; + updatedBy: User | null; + warningInterval: string | null; + timeSpent: number | null; } export interface Evaluation { - id: number; - url: string; - socialActions: SocialAction[]; - title: { - fr: string; - }; - active: boolean; - delay: string; - notificationDelay: string; + id: number; + url: string; + socialActions: SocialAction[]; + title: { + fr: string; + }; + active: boolean; + delay: string; + notificationDelay: string; } export interface AccompanyingPeriodWorkReferrerHistory { - id: number; - accompanyingPeriodWork: AccompanyingPeriodWork; - user: User; - startDate: DateTime; - endDate: DateTime | null; - createdAt: DateTime; - updatedAt: DateTime | null; - createdBy: User; - updatedBy: User | null; + id: number; + accompanyingPeriodWork: AccompanyingPeriodWork; + user: User; + startDate: DateTime; + endDate: DateTime | null; + createdAt: DateTime; + updatedAt: DateTime | null; + createdBy: User; + updatedBy: User | null; +} + +export interface AccompanyingPeriodWorkEvaluationDocument { + id: number; + type: "accompanying_period_work_evaluation_document"; + storedObject: StoredObject; + title: string; + createdAt: DateTime | null; + createdBy: User | null; + updatedAt: DateTime | null; + updatedBy: User | null; + workflows_availables: WorkflowAvailable[]; + workflows: object[]; +} + +export type EntityType = + | "user_group" + | "user" + | "person" + | "thirdparty" + | "household"; + +export type Entities = (UserGroup | User | Person | Thirdparty | Household) & { + address?: Address | null; + kind?: string; + text?: string; + profession?: string; +}; + +export type EntitiesOrMe = "me" | Entities; + +export type AddPersonResult = Entities & { + parent?: Entities | null; +}; + +export interface Suggestion { + key: string; + relevance: number; + result: AddPersonResult; +} + +export interface SearchPagination { + first: number; + items_per_page: number; + next: number | null; + previous: number | null; + more: boolean; +} + +export interface Search { + count: number; + pagination: SearchPagination; + results: Suggestion[]; +} + +export interface SearchOptions { + uniq: boolean; + type: string[]; + priority: number | null; + button: { + size: string; + class: string; + type: string; + display: string; + }; +} + +export class MakeFetchException extends Error { + sta: number; + txt: string; + violations: unknown | null; + + constructor(txt: string, sta: number, violations: unknown | null = null) { + super(txt); + this.name = "ValidationException"; + this.sta = sta; + this.txt = txt; + this.violations = violations; + Object.setPrototypeOf(this, MakeFetchException.prototype); + } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue index 85f031a64..e81949711 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue @@ -1,28 +1,28 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue index 408852711..3ce902daa 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue @@ -1,127 +1,120 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue index f781eca0b..af985e502 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue @@ -1,95 +1,85 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue index 641c9ef37..053eb058f 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue @@ -1,33 +1,33 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue index d6979928b..b4e0e70b8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue @@ -1,121 +1,106 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue index 61a9d2571..c2dbd6e0c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue @@ -1,98 +1,97 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue index dbb21fa40..963ff65b5 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue @@ -1,116 +1,113 @@ - + + + - +
    + - -
    + -
    -
      -
    • - -
    • -
    -
    + -
    - {{ $t("job.not_valid") }} -
    + + +
    + +
    +
      +
    • + +
    • +
    +
    + +
    + {{ $t("job.not_valid") }} +
    +
    diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue index c2b9acfc4..5858cdb00 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue @@ -1,263 +1,239 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue index 1ef9bfe45..fa600bfdd 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue @@ -1,57 +1,57 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue index 8a52748d4..87daacf47 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue @@ -1,107 +1,107 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue index 26b561c5d..416c6085c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue @@ -1,34 +1,32 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue index 1d05e0bbe..fee6a89ad 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue @@ -1,25 +1,25 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue index 02d3b0c84..21c290f48 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue @@ -1,30 +1,30 @@ @@ -96,20 +91,20 @@ export default { @import "ChillPersonAssets/chill/scss/mixins"; @import "ChillMainAssets/chill/scss/chill_variables"; div#accompanying-course { - span.multiselect__tag { - @include badge_social($social-issue-color); - background: $chill-l-gray; - color: $dark; + span.multiselect__tag { + @include badge_social($social-issue-color); + background: $chill-l-gray; + color: $dark; + } + span.multiselect__option--highlight { + &::after { + background: $green; } - span.multiselect__option--highlight { - &::after { - background: $green; - } - &.multiselect__option--selected { - &::after { - background: $red; - } - } + &.multiselect__option--selected { + &::after { + background: $red; + } } + } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue index 0bfd87b18..d3e9ae97a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue @@ -1,22 +1,22 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue index 890aa5153..3ff18f03a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue @@ -1,207 +1,195 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue index 8e175f72b..ecdf3026c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue @@ -1,26 +1,22 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue index 904d4ff42..929b4edd8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue @@ -1,155 +1,146 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue index 745317211..f8b5389f4 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue @@ -1,152 +1,135 @@ @@ -288,46 +267,46 @@ export default { @import "ChillPersonAssets/chill/scss/mixins"; @import "ChillMainAssets/chill/scss/chill_variables"; span.badge { - @include badge_social($social-issue-color); - font-size: 95%; - margin-bottom: 5px; - margin-right: 1em; - margin-left: 1em; + @include badge_social($social-issue-color); + font-size: 95%; + margin-bottom: 5px; + margin-right: 1em; + margin-left: 1em; } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index f3404e2de..bd87224b2 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -1,482 +1,453 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue index bb90557b7..4499b40c8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue @@ -1,136 +1,124 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue index 86a58737a..a1e33c4de 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue @@ -1,26 +1,25 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue index 259029f73..98119bd8e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue @@ -1,43 +1,48 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue index 8c97d2e29..28be7c119 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue @@ -1,126 +1,137 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue index 56b3afe17..74f80c321 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue @@ -1,40 +1,40 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue index 1daf921e1..ca3aa2cb8 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue @@ -1,32 +1,24 @@ + + - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue index ad996a375..e6f50708b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue @@ -1,168 +1,161 @@ +
    + + {{ trans(RENDERBOX_HOUSEHOLD_NUMBER, { number: household.id }) }} +
    +
    +
    +
      + +
    • + + + + + +
    • +
    • +

      + {{ trans(RENDERBOX_NO_MEMBERS_YET) }} +

      +
    • - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue index cdc143f76..0d9e3ec5f 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue @@ -1,493 +1,418 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue index f157fac6a..10302ee30 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue @@ -1,75 +1,51 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue index ff4b312f1..3a93a305d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue @@ -1,530 +1,466 @@ - - - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts index ba7637544..088ff11ea 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts @@ -1,64 +1,63 @@ const personMessages = { - fr: { - add_persons: { - title: "Ajouter des usagers", - suggested_counter: - "Pas de résultats | 1 résultat | {count} résultats", - selected_counter: " 1 sélectionné | {count} sélectionnés", - search_some_persons: "Rechercher des personnes..", - }, - item: { - type_person: "Usager", - type_user: "TMS", - type_thirdparty: "Tiers professionnel", - type_household: "Ménage", - }, - person: { - firstname: "Prénom", - lastname: "Nom", - born: (ctx: { gender: "man" | "woman" | "neutral" }) => { - if (ctx.gender === "man") { - return "Né le"; - } else if (ctx.gender === "woman") { - return "Née le"; - } else { - return "Né·e le"; - } - }, - center_id: "Identifiant du centre", - center_type: "Type de centre", - center_name: "Territoire", // vendée - phonenumber: "Téléphone", - mobilenumber: "Mobile", - altnames: "Autres noms", - email: "Courriel", - gender: { - title: "Genre", - placeholder: "Choisissez le genre de l'usager", - woman: "Féminin", - man: "Masculin", - neutral: "Neutre, non binaire", - unknown: "Non renseigné", - undefined: "Non renseigné", - }, - civility: { - title: "Civilité", - placeholder: "Choisissez la civilité", - }, - address: { - create_address: "Ajouter une adresse", - show_address_form: - "Ajouter une adresse pour un usager non suivi et seul dans un ménage", - warning: - "Un nouveau ménage va être créé. L'usager sera membre de ce ménage.", - }, - center: { - placeholder: "Choisissez un centre", - title: "Centre", - }, - }, - error_only_one_person: "Une seule personne peut être sélectionnée !", + fr: { + add_persons: { + title: "Ajouter des usagers", + suggested_counter: "Pas de résultats | 1 résultat | {count} résultats", + selected_counter: " 1 sélectionné | {count} sélectionnés", + search_some_persons: "Rechercher des personnes..", }, + item: { + type_person: "Usager", + type_user: "TMS", + type_thirdparty: "Tiers professionnel", + type_household: "Ménage", + }, + person: { + firstname: "Prénom", + lastname: "Nom", + born: (ctx: { gender: "man" | "woman" | "neutral" }) => { + if (ctx.gender === "man") { + return "Né le"; + } else if (ctx.gender === "woman") { + return "Née le"; + } else { + return "Né·e le"; + } + }, + center_id: "Identifiant du centre", + center_type: "Type de centre", + center_name: "Territoire", // vendée + phonenumber: "Téléphone", + mobilenumber: "Mobile", + altnames: "Autres noms", + email: "Courriel", + gender: { + title: "Genre", + placeholder: "Choisissez le genre de l'usager", + woman: "Féminin", + man: "Masculin", + neutral: "Neutre, non binaire", + unknown: "Non renseigné", + undefined: "Non renseigné", + }, + civility: { + title: "Civilité", + placeholder: "Choisissez la civilité", + }, + address: { + create_address: "Ajouter une adresse", + show_address_form: + "Ajouter une adresse pour un usager non suivi et seul dans un ménage", + warning: + "Un nouveau ménage va être créé. L'usager sera membre de ce ménage.", + }, + center: { + placeholder: "Choisissez un centre", + title: "Centre", + }, + }, + error_only_one_person: "Une seule personne peut être sélectionnée !", + }, }; export { personMessages }; diff --git a/src/Bundle/ChillPersonBundle/Tests/Repository/PersonACLAwareRepositoryTest.php b/src/Bundle/ChillPersonBundle/Tests/Repository/PersonACLAwareRepositoryTest.php index 94df35d88..667242111 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Repository/PersonACLAwareRepositoryTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Repository/PersonACLAwareRepositoryTest.php @@ -11,14 +11,17 @@ declare(strict_types=1); namespace Chill\PersonBundle\Tests\Repository; +use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\CountryRepository; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\PersonBundle\Entity\Person; +use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Repository\PersonACLAwareRepository; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\ORM\EntityManagerInterface; +use PHPUnit\Framework\Attributes\DataProvider; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -98,4 +101,67 @@ final class PersonACLAwareRepositoryTest extends KernelTestCase $this->assertStringContainsString('diallo', strtolower($person->getFirstName().' '.$person->getLastName())); } } + + /** + * @dataProvider providePersonsWithPhoneNumbers + */ + public function testFindByPhonenumber(\libphonenumber\PhoneNumber $phoneNumber, ?int $expectedId): void + { + $user = new User(); + + $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); + $authorizationHelper->getReachableCenters(Argument::exact($user), Argument::exact(PersonVoter::SEE)) + ->willReturn($this->centerRepository->findAll()); + + $security = $this->prophesize(Security::class); + $security->getUser()->willReturn($user); + + $repository = new PersonACLAwareRepository( + $security->reveal(), + $this->entityManager, + $this->countryRepository, + $authorizationHelper->reveal() + ); + + $actual = $repository->findByPhone($phoneNumber, 0, 10); + + if (null === $expectedId) { + self::assertCount(0, $actual); + } else { + $actualIds = array_map(fn (Person $person) => $person->getId(), $actual); + + self::assertContains($expectedId, $actualIds); + } + } + + public static function providePersonsWithPhoneNumbers(): iterable + { + self::bootKernel(); + $em = self::getContainer()->get(EntityManagerInterface::class); + $center = $em->createQuery('SELECT c FROM '.Center::class.' c ')->setMaxResults(1) + ->getSingleResult(); + $util = \libphonenumber\PhoneNumberUtil::getInstance(); + + $mobile = $util->parse('+32486123456'); + $fixed = $util->parse('+3281136917'); + $anotherMobile = $util->parse('+32486123478'); + $person = (new Person())->setFirstName('diallo')->setLastName('diallo')->setCenter($center); + $person->setMobilenumber($mobile)->setPhonenumber($fixed); + $otherPhone = new PersonPhone(); + $otherPhone->setPerson($person); + $otherPhone->setPhonenumber($anotherMobile); + $otherPhone->setType('mobile'); + + $em->persist($person); + $em->persist($otherPhone); + + $em->flush(); + + self::ensureKernelShutdown(); + + yield [$mobile, $person->getId()]; + yield [$anotherMobile, $person->getId()]; + yield [$fixed, $person->getId()]; + yield [$util->parse('+331234567890'), null]; + } } diff --git a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml index 6e42a362b..ae7ac6284 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml @@ -206,3 +206,59 @@ accompanying_course_evaluation_document: accompanying_period_work: title: Action d'accompagnement (n°{id}) - {action_title} + +add_persons: + title: "Ajouter des usagers" + suggested_counter: >- + {count, plural, + =0 {Pas de résultats} + =1 {1 résultat} + other {# résultats} + } + selected_counter: >- + {count, plural, + =1 {1 sélectionné} + other {# sélectionnés} + } + search_some_persons: "Rechercher des personnes.." + + item: + type_person: "Usager" + type_user: "TMS" + type_thirdparty: "Tiers professionnel" + type_household: "Ménage" + + person: + firstname: "Prénom" + lastname: "Nom" + born: + man: "Né le" + woman: "Née le" + neutral: "Né·e le" + center_id: "Identifiant du centre" + center_type: "Type de centre" + center_name: "Territoire" + phonenumber: "Téléphone" + mobilenumber: "Mobile" + altnames: "Autres noms" + email: "Courriel" + gender: + title: "Genre" + placeholder: "Choisissez le genre de l'usager" + woman: "Féminin" + man: "Masculin" + neutral: "Neutre, non binaire" + unknown: "Non renseigné" + undefined: "Non renseigné" + civility: + title: "Civilité" + placeholder: "Choisissez la civilité" + address: + create_address: "Ajouter une adresse" + show_address_form: "Ajouter une adresse pour un usager non suivi et seul dans un ménage" + warning: "Un nouveau ménage va être créé. L'usager sera membre de ce ménage." + center: + placeholder: "Choisissez un centre" + title: "Centre" + + error_only_one_person: "Une seule personne peut être sélectionnée !" diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index c9f1af282..6c271fd80 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -1522,3 +1522,48 @@ my_parcours_filters: parcours_intervening: Intervenant is_open: Parcours ouverts is_closed: Parcours clôturés + +person_messages: + add_persons: + title: "Ajouter des usagers" + suggested_counter: "Pas de résultats | 1 résultat | {count} résultats" + selected_counter: " 1 sélectionné | {count} sélectionnés" + search_some_persons: "Rechercher des personnes.." + item: + type_person: "Usager" + type_user: "TMS" + type_thirdparty: "Tiers professionnel" + type_household: "Ménage" + person: + firstname: "Prénom" + lastname: "Nom" + born: + man: "Né le" + woman: "Née le" + neutral: "Né·e le" + center_id: "Identifiant du centre" + center_type: "Type de centre" + center_name: "Territoire" + phonenumber: "Téléphone" + mobilenumber: "Mobile" + altnames: "Autres noms" + email: "Courriel" + gender: + title: "Genre" + placeholder: "Choisissez le genre de l'usager" + woman: "Féminin" + man: "Masculin" + neutral: "Neutre, non binaire" + unknown: "Non renseigné" + undefined: "Non renseigné" + civility: + title: "Civilité" + placeholder: "Choisissez la civilité" + address: + create_address: "Ajouter une adresse" + show_address_form: "Ajouter une adresse pour un usager non suivi et seul dans un ménage" + warning: "Un nouveau ménage va être créé. L'usager sera membre de ce ménage." + center: + placeholder: "Choisissez un centre" + title: "Centre" + error_only_one_person: "Une seule personne peut être sélectionnée !" diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php index 111be4089..3153713bb 100644 --- a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php +++ b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php @@ -18,12 +18,15 @@ use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; +use libphonenumber\PhoneNumber; +use libphonenumber\PhoneNumberFormat; +use libphonenumber\PhoneNumberUtil; class ThirdPartyRepository implements ObjectRepository { private readonly EntityRepository $repository; - public function __construct(EntityManagerInterface $em, private readonly Connection $connection) + public function __construct(EntityManagerInterface $em, private readonly Connection $connection, private readonly PhoneNumberUtil $phonenumberUtil) { $this->repository = $em->getRepository(ThirdParty::class); } @@ -122,6 +125,43 @@ class ThirdPartyRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } + /** + * Finds third-party records by phone number. + * + * The search is performed agains every phonenumber field (there are two phonenumber on a thirdParty). + * + * @param string|PhoneNumber $phonenumber The phone number to search for. Can be a string or a PhoneNumber object. + * @param int $firstResult the index of the first result to retrieve (pagination start) + * @param int $maxResults the maximum number of results to retrieve (pagination limit) + * + * @return list the result set containing matching third-party records + */ + public function findByPhonenumber(string|PhoneNumber $phonenumber, int $firstResult = 0, int $maxResults = 20): array + { + if ('' === $phonenumber) { + return []; + } + + $qb = $this->createQueryBuilder('tp'); + $qb->select('tp'); + + $qb->where( + $qb->expr()->orX( + $qb->expr()->eq('tp.telephone', ':phonenumber'), + $qb->expr()->eq('tp.telephone2', ':phonenumber') + ) + ); + + $qb->setParameter( + 'phonenumber', + is_string($phonenumber) ? $phonenumber : $this->phonenumberUtil->format($phonenumber, PhoneNumberFormat::E164) + ); + + $qb->setFirstResult($firstResult)->setMaxResults($maxResults); + + return $qb->getQuery()->getResult(); + } + /** * Search amongst parties associated to $centers, with $terms parameters. * diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts b/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts index 20ae3309b..2d2acc42b 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts @@ -1,47 +1,49 @@ import { - Address, - Center, - Civility, - DateTime, - User, + Address, + Center, + Civility, + DateTime, + User, } from "ChillMainAssets/types"; export interface Thirdparty { - acronym: string | null; - active: boolean; - address: Address | null; - canonicalized: string | null; - categories: ThirdpartyCategory[]; - centers: Center[]; - children: Thirdparty[]; - civility: Civility | null; - comment: string | null; - contactDataAnonymous: boolean; - createdAt: DateTime; - createdBy: User | null; - email: string | null; - firstname: string | null; - id: number | null; - kind: string; - name: string; - nameCompany: string | null; - parent: Thirdparty | null; - profession: string; - telephone: string | null; - thirdPartyTypes: ThirdpartyType[] | null; - updatedAt: DateTime | null; - updatedBy: User | null; + type: "thirdparty"; + text: string; + acronym: string | null; + active: boolean; + address: Address | null; + canonicalized: string | null; + categories: ThirdpartyCategory[]; + centers: Center[]; + children: Thirdparty[]; + civility: Civility | null; + comment: string | null; + contactDataAnonymous: boolean; + createdAt: DateTime; + createdBy: User | null; + email: string | null; + firstname: string | null; + id: number | null; + kind: string; + name: string; + nameCompany: string | null; + parent: Thirdparty | null; + profession: string; + telephone: string | null; + thirdPartyTypes: ThirdpartyType[] | null; + updatedAt: DateTime | null; + updatedBy: User | null; } interface ThirdpartyType { - key: string; - value: string; + key: string; + value: string; } export interface ThirdpartyCategory { - id: number; - active: boolean; - name: { - fr: string; - }; + id: number; + active: boolean; + name: { + fr: string; + }; } diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue index 0fee3dcdb..3413861ae 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue @@ -1,158 +1,131 @@ diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyText.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyText.vue index 2214605e2..dfd46efd9 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyText.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyText.vue @@ -1,28 +1,28 @@ diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue index 623926807..e3b2338bd 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue @@ -1,449 +1,450 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue index 9c010e8d1..13459b390 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Confidential.vue @@ -1,40 +1,40 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/AddressRenderBox.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/AddressRenderBox.vue index 7085b3b18..86f4155b6 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/AddressRenderBox.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/AddressRenderBox.vue @@ -1,77 +1,83 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/GenderIconRenderBox.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/GenderIconRenderBox.vue index 794771014..d47b9b777 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/GenderIconRenderBox.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/GenderIconRenderBox.vue @@ -1,28 +1,9 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserGroupRenderBox.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserGroupRenderBox.vue index 81f836c62..7c37e98a9 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserGroupRenderBox.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserGroupRenderBox.vue @@ -4,23 +4,23 @@ import { computed } from "vue"; import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper"; interface UserGroupRenderBoxProps { - userGroup: UserGroup; + userGroup: UserGroup; } const props = defineProps(); const styles = computed<{ color: string; "background-color": string }>(() => { - return { - color: props.userGroup.foregroundColor, - "background-color": props.userGroup.backgroundColor, - }; + return { + color: props.userGroup.foregroundColor, + "background-color": props.userGroup.backgroundColor, + }; }); diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue index 4bae3b541..63c43c37f 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Entity/UserRenderBoxBadge.vue @@ -1,31 +1,31 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue index 4d817a2f9..332849ef5 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/EntityWorkflowVueSubscriber.vue @@ -1,83 +1,83 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue index 658c0f9e9..59dcd80f4 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue @@ -1,45 +1,49 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue index 4808e2bda..06e1bfa1a 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Modal.vue @@ -1,39 +1,42 @@ @@ -79,17 +73,17 @@ const emits = defineEmits<{ * This is a mask behind the modal. */ .modal-mask { - position: fixed; - z-index: 9998; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.75); - transition: opacity 0.3s ease; + position: fixed; + z-index: 9998; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.75); + transition: opacity 0.3s ease; } .modal-header .close { - border-top-right-radius: 0.3rem; + border-top-right-radius: 0.3rem; } /* * The following styles are auto-applied to elements with @@ -100,23 +94,23 @@ const emits = defineEmits<{ * these styles. */ .modal-enter { - opacity: 0; + opacity: 0; } .modal-leave-active { - opacity: 0; + opacity: 0; } .modal-enter .modal-container, .modal-leave-active .modal-container { - -webkit-transform: scale(1.1); - transform: scale(1.1); + -webkit-transform: scale(1.1); + transform: scale(1.1); } h3.modal-title { - font-size: 1.5rem; - font-weight: bold; + font-size: 1.5rem; + font-weight: bold; } div.modal-footer { - button:first-child { - margin-right: auto; - } + button:first-child { + margin-right: auto; + } } diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadAllToggle.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadAllToggle.vue index bb7a43c92..57f7fd7f8 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadAllToggle.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadAllToggle.vue @@ -1,17 +1,17 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue index fb94af80f..cc64d4835 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_components/Notification/NotificationReadToggle.vue @@ -1,96 +1,96 @@ diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.ts b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.ts index cbee6bf25..db9a37104 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/_js/i18n.ts @@ -2,86 +2,87 @@ import { createI18n } from "vue-i18n"; import datetimeFormats from "../i18n/datetimeFormats"; const messages = { - fr: { - action: { - actions: "Actions", - show: "Voir", - edit: "Modifier", - create: "Créer", - remove: "Enlever", - delete: "Supprimer", - save: "Enregistrer", - valid: "Valider", - valid_and_see: "Valider et voir", - add: "Ajouter", - show_modal: "Ouvrir une modale", - ok: "OK", - cancel: "Annuler", - close: "Fermer", - back: "Retour", - check_all: "cocher tout", - reset: "réinitialiser", - redirect: { - person: "Quitter la page et ouvrir la fiche de l'usager", - thirdparty: "Quitter la page et voir le tiers", - }, - refresh: "Rafraîchir", - addContact: "Ajouter un contact", + fr: { + action: { + actions: "Actions", + show: "Voir", + edit: "Modifier", + create: "Créer", + remove: "Enlever", + delete: "Supprimer", + save: "Enregistrer", + valid: "Valider", + valid_and_see: "Valider et voir", + add: "Ajouter", + show_modal: "Ouvrir une modale", + ok: "OK", + cancel: "Annuler", + close: "Fermer", + back: "Retour", + check_all: "cocher tout", + reset: "réinitialiser", + redirect: { + person: "Quitter la page et ouvrir la fiche de l'usager", + thirdparty: "Quitter la page et voir le tiers", + }, + refresh: "Rafraîchir", + addContact: "Ajouter un contact", + }, + nav: { + next: "Suivant", + previous: "Précédent", + top: "Haut", + bottom: "Bas", + }, + renderbox: { + person: "Usager", + birthday: { + man: "Né le", + woman: "Née le", + neutral: "Né·e le", + unknown: "Né·e le", + }, + deathdate: "Date de décès", + household_without_address: "Le ménage de l'usager est sans adresse", + no_data: "Aucune information renseignée", + type: { + thirdparty: "Tiers", + person: "Usager", + }, + holder: "Titulaire", + years_old: "1 an | {n} an | {n} ans", + residential_address: "Adresse de résidence", + located_at: "réside chez", + }, }, - nav: { - next: "Suivant", - previous: "Précédent", - top: "Haut", - bottom: "Bas", - }, - renderbox: { - person: "Usager", - birthday: { - man: "Né le", - woman: "Née le", - neutral: "Né·e le", - unknown: "Né·e le", - }, - deathdate: "Date de décès", - household_without_address: "Le ménage de l'usager est sans adresse", - no_data: "Aucune information renseignée", - type: { - thirdparty: "Tiers", - person: "Usager", - }, - holder: "Titulaire", - years_old: "1 an | {n} an | {n} ans", - residential_address: "Adresse de résidence", - located_at: "réside chez", - }, - }, }; const _createI18n = (appMessages: any, legacy?: boolean) => { - Object.assign(messages.fr, appMessages.fr); - return createI18n({ - legacy: typeof legacy === undefined ? true : legacy, - locale: "fr", - fallbackLocale: "fr", - // @ts-ignore - datetimeFormats, - messages, - }); + Object.assign(messages.fr, appMessages.fr); + return createI18n({ + legacy: typeof legacy === undefined ? true : legacy, + locale: "fr", + fallbackLocale: "fr", + // @ts-ignore + datetimeFormats, + messages, + }); }; export { _createI18n }; export const multiSelectMessages = { - fr: { - multiselect: { - placeholder: "Choisir", - tag_placeholder: "Créer un nouvel élément", - select_label: '"Entrée" ou cliquez pour sélectionner', - deselect_label: '"Entrée" ou cliquez pour désélectionner', - select_group_label: 'Appuyer sur "Entrée" pour sélectionner ce groupe', - deselect_group_label: - 'Appuyer sur "Entrée" pour désélectionner ce groupe', - selected_label: "Sélectionné", + fr: { + multiselect: { + placeholder: "Choisir", + tag_placeholder: "Créer un nouvel élément", + select_label: '"Entrée" ou cliquez pour sélectionner', + deselect_label: '"Entrée" ou cliquez pour désélectionner', + select_group_label: + 'Appuyer sur "Entrée" pour sélectionner ce groupe', + deselect_group_label: + 'Appuyer sur "Entrée" pour désélectionner ce groupe', + selected_label: "Sélectionné", + }, }, - }, }; diff --git a/src/Bundle/ChillMainBundle/Resources/public/vuejs/i18n/datetimeFormats.ts b/src/Bundle/ChillMainBundle/Resources/public/vuejs/i18n/datetimeFormats.ts index aca1328ac..fe20cb217 100644 --- a/src/Bundle/ChillMainBundle/Resources/public/vuejs/i18n/datetimeFormats.ts +++ b/src/Bundle/ChillMainBundle/Resources/public/vuejs/i18n/datetimeFormats.ts @@ -1,27 +1,27 @@ export default { - fr: { - short: { - year: "numeric", - month: "numeric", - day: "numeric", + fr: { + short: { + year: "numeric", + month: "numeric", + day: "numeric", + }, + text: { + year: "numeric", + month: "long", + day: "numeric", + }, + long: { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "numeric", + hour12: false, + }, + hoursOnly: { + hour: "numeric", + minute: "numeric", + hour12: false, + }, }, - text: { - year: "numeric", - month: "long", - day: "numeric", - }, - long: { - year: "numeric", - month: "numeric", - day: "numeric", - hour: "numeric", - minute: "numeric", - hour12: false, - }, - hoursOnly: { - hour: "numeric", - minute: "numeric", - hour12: false, - }, - }, }; diff --git a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig index d92f2e260..30f84c855 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/layout.html.twig @@ -69,37 +69,35 @@
    {% endif %} - {% block wrapping_content %} - {% block content %} - - {# DISABLED {{ chill_widget('homepage', {} ) }} #} + {# DISABLED {{ chill_widget('homepage', {} ) }} #} - {% include '@ChillMain/Homepage/index.html.twig' %} + {% include '@ChillMain/Homepage/index.html.twig' %} - {% endblock %} {% endblock %}
    diff --git a/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php b/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php index cae0cdd48..182980dd0 100644 --- a/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php +++ b/src/Bundle/ChillMainBundle/Tests/Phonenumber/PhonenumberHelperTest.php @@ -12,7 +12,6 @@ declare(strict_types=1); namespace Chill\MainBundle\Tests\Phonenumber; use Chill\MainBundle\Phonenumber\PhonenumberHelper; -use libphonenumber\PhoneNumber; use libphonenumber\PhoneNumberUtil; use Psr\Log\NullLogger; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -32,7 +31,6 @@ final class PhonenumberHelperTest extends KernelTestCase public function testFormatPhonenumbers(string $defaultCarrierCode, string $phoneNumber, string $expected) { $util = PhoneNumberUtil::getInstance(); - $subject = new PhonenumberHelper( new ArrayAdapter(), new ParameterBag([ @@ -72,47 +70,4 @@ final class PhonenumberHelperTest extends KernelTestCase '00 33 6 23 12 45 54', ]; } - - /** - * @dataProvider providePhoneNumbersToParse - */ - public function testParsePhonenumbers(string $defaultCarrierCode, string $phoneNumber, PhoneNumber $expected): void - { - $subject = new PhonenumberHelper( - new ArrayAdapter(), - new ParameterBag([ - 'chill_main.phone_helper' => [ - 'default_carrier_code' => $defaultCarrierCode, - ], - ]), - new NullLogger() - ); - - $actual = $subject->parse($phoneNumber); - - self::assertTrue($expected->equals($actual)); - } - - public static function providePhoneNumbersToParse(): iterable - { - $util = PhoneNumberUtil::getInstance(); - - yield [ - 'FR', - '+32486544999', - $util->parse('+32486544999', 'FR'), - ]; - - yield [ - 'FR', - '32486544999', - $util->parse('+32486544999', 'FR'), - ]; - - yield [ - 'FR', - '0228858040', - $util->parse('+33228858040', 'FR'), - ]; - } } diff --git a/src/Bundle/ChillMainBundle/Tests/Validation/Validator/UserGroupDoNotExcludeTest.php b/src/Bundle/ChillMainBundle/Tests/Validation/Validator/UserGroupDoNotExcludeTest.php deleted file mode 100644 index d6d1c9887..000000000 --- a/src/Bundle/ChillMainBundle/Tests/Validation/Validator/UserGroupDoNotExcludeTest.php +++ /dev/null @@ -1,91 +0,0 @@ -validator->validate([], new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude()); - - $this->assertNoViolation(); - } - - public function testMixedUserGroupAndUsersIsValid(): void - { - $this->validator->validate( - [new User(), new UserGroup()], - new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude() - ); - - $this->assertNoViolation(); - } - - public function testDifferentExcludeKeysIsValid(): void - { - $this->validator->validate( - [(new UserGroup())->setExcludeKey('A'), (new UserGroup())->setExcludeKey('B')], - new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude() - ); - - $this->assertNoViolation(); - } - - public function testMultipleGroupsWithEmptyExcludeKeyIsValid(): void - { - $this->validator->validate( - [(new UserGroup())->setExcludeKey(''), (new UserGroup())->setExcludeKey('')], - new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude() - ); - - $this->assertNoViolation(); - } - - public function testSameExclusionKeyWillRaiseError(): void - { - $this->validator->validate( - [ - (new UserGroup())->setExcludeKey('A')->setLabel(['fr' => 'Group 1']), - (new UserGroup())->setExcludeKey('A')->setLabel(['fr' => 'Group 2']), - ], - new \Chill\MainBundle\Validation\Constraint\UserGroupDoNotExclude() - ); - - $this->buildViolation('The groups {{ excluded_groups }} do exclude themselves. Please choose one between them') - ->setParameter('excluded_groups', 'Group 1, Group 2') - ->setCode('e16c8226-0090-11ef-8560-f7239594db09') - ->assertRaised(); - } -} diff --git a/src/Bundle/ChillMainBundle/chill.api.specs.yaml b/src/Bundle/ChillMainBundle/chill.api.specs.yaml index 047f69675..d87a0eb71 100644 --- a/src/Bundle/ChillMainBundle/chill.api.specs.yaml +++ b/src/Bundle/ChillMainBundle/chill.api.specs.yaml @@ -10,31 +10,6 @@ servers: components: schemas: - Collection: - type: object - properties: - count: - type: number - format: u64 - pagination: - type: object - properties: - first: - type: number - format: u64 - items_per_page: - type: number - format: u64 - next: - type: string - format: uri - nullable: true - previous: - type: string - format: uri - nullable: true - more: - type: boolean EntityWorkflowAttachment: type: object properties: diff --git a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml index 0d1b30ef8..2982d94db 100644 --- a/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillMainBundle/translations/messages+intl-icu.fr.yaml @@ -130,58 +130,3 @@ filter_order: Search: Chercher dans la liste By date: Filtrer par date search_box: Filtrer par contenu -renderbox: - person: "Usager" - birthday: - man: "Né le" - woman: "Née le" - neutral: "Né·e le" - unknown: "Né·e le" - deathdate: "Date de décès" - household_without_address: "Le ménage de l'usager est sans adresse" - no_data: "Aucune information renseignée" - type: - thirdparty: "Tiers" - person: "Usager" - holder: "Titulaire" - years_old: >- - {n, plural, - =0 {0 an} - one {1 an} - other {# ans} - } - residential_address: "Adresse de résidence" - located_at: "réside chez" - household_number: "Ménage n°{number}" - current_members: "Membres actuels" - no_current_address: "Sans adresse actuellement" - new_household: "Nouveau ménage" - no_members_yet: "Aucun membre actuellement" - -pick_entity: - add: "Ajouter" - modal_title: >- - {count, plural, - one {Indiquer un} - other {Ajouter des} - } - user: >- - {count, plural, - one {Utilisateur} - other {Utilisateurs} - } - user_group: >- - {count, plural, - one {Groupe d'utilisateur} - other {Groupes d'utilisateurs} - } - person: >- - {count, plural, - one {Usager} - other {Usagers} - } - thirdparty: >- - {count, plural, - one {Tiers} - other {Tiers} - } diff --git a/src/Bundle/ChillMainBundle/translations/messages.fr.yml b/src/Bundle/ChillMainBundle/translations/messages.fr.yml index 5e591fba8..d3498cba9 100644 --- a/src/Bundle/ChillMainBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillMainBundle/translations/messages.fr.yml @@ -941,34 +941,3 @@ multiselect: editor: switch_to_simple: Éditeur simple switch_to_complex: Éditeur riche -action: - actions: Actions - show: Voir - edit: Modifier - create: Créer - remove: Enlever - delete: Supprimer - save: Enregistrer - valid: Valider - valid_and_see: Valider et voir - add: Ajouter - show_modal: Ouvrir une modale - ok: OK - cancel: Annuler - close: Fermer - back: Retour - check_all: cocher tout - reset: réinitialiser - redirect: - person: Quitter la page et ouvrir la fiche de l'usager - thirdparty: Quitter la page et voir le tiers - refresh: Rafraîchir - addContact: Ajouter un contact - -nav: - next: "Suivant" - previous: "Précédent" - top: "Haut" - bottom: "Bas" - - diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php index 0d972914d..686022ca3 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepository.php @@ -21,8 +21,6 @@ use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\Query; -use libphonenumber\PhoneNumber; -use libphonenumber\PhoneNumberFormat; use Symfony\Component\Security\Core\Security; final readonly class PersonACLAwareRepository implements PersonACLAwareRepositoryInterface @@ -300,27 +298,4 @@ final readonly class PersonACLAwareRepository implements PersonACLAwareRepositor \array_map(static fn (Center $c) => $c->getId(), $authorizedCenters) ); } - - public function findByPhone(PhoneNumber $phoneNumber, int $start = 0, int $limit = 20): array - { - $authorizedCenters = $this->authorizationHelper - ->getReachableCenters($this->security->getUser(), PersonVoter::SEE); - - if ([] === $authorizedCenters) { - return []; - } - - $util = \libphonenumber\PhoneNumberUtil::getInstance(); - - return $this->em->createQuery( - 'SELECT p FROM '.Person::class.' p LEFT JOIN p.otherPhoneNumbers opn JOIN p.centerCurrent pcc '. - 'WHERE (p.phonenumber LIKE :phone OR p.mobilenumber LIKE :phone OR opn.phonenumber LIKE :phone) '. - 'AND pcc.center IN (:centers)' - ) - ->setMaxResults($limit) - ->setFirstResult($start) - ->setParameter('phone', $util->format($phoneNumber, PhoneNumberFormat::E164)) - ->setParameter('centers', $authorizedCenters) - ->getResult(); - } } diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php index aeaefecd4..50fdcd4b3 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonACLAwareRepositoryInterface.php @@ -13,7 +13,6 @@ namespace Chill\PersonBundle\Repository; use Chill\MainBundle\Search\SearchApiQuery; use Chill\PersonBundle\Entity\Person; -use libphonenumber\PhoneNumber; interface PersonACLAwareRepositoryInterface { @@ -61,13 +60,4 @@ interface PersonACLAwareRepositoryInterface ?string $phonenumber = null, ?string $city = null, ): array; - - /** - * @return list - */ - public function findByPhone( - PhoneNumber $phoneNumber, - int $start = 0, - int $limit = 20, - ): array; } diff --git a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php index 8d5e244ea..6d6373999 100644 --- a/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/PersonRepository.php @@ -12,12 +12,10 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Entity\PersonPhone; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; -use libphonenumber\PhoneNumber; class PersonRepository implements ObjectRepository { @@ -31,8 +29,6 @@ class PersonRepository implements ObjectRepository /** * @throws \Doctrine\ORM\NoResultException * @throws \Doctrine\ORM\NonUniqueResultException - * - * @deprecated */ public function countByPhone( string $phonenumber, @@ -75,8 +71,6 @@ class PersonRepository implements ObjectRepository /** * @throws \Exception - * - * @deprecated Use @see{self::findByPhoneNumber} or use a dedicated method in PersonACLAwareRepository */ public function findByPhone( string $phonenumber, @@ -97,25 +91,6 @@ class PersonRepository implements ObjectRepository return $qb->getQuery()->getResult(); } - /** - * Find a person which is associated to the given phonenumber, without restrictions - * on any. - * - * @return list - */ - public function findByPhoneNumber(PhoneNumber $phoneNumber, int $firstResult = 0, int $maxResults = 50): array - { - $qb = $this->repository->createQueryBuilder('p'); - $qb->select('p'); - - $this->searchByPhoneNumbers($qb, $phoneNumber); - - $qb->setFirstResult($firstResult) - ->setMaxResults($maxResults); - - return $qb->getQuery()->getResult(); - } - public function findOneBy(array $criteria) { return $this->repository->findOneBy($criteria); @@ -134,20 +109,6 @@ class PersonRepository implements ObjectRepository } } - private function searchByPhoneNumbers(QueryBuilder $qb, PhoneNumber $phoneNumber): void - { - $qb->setParameter('number', $phoneNumber, 'phone_number'); - - $orX = $qb->expr()->orX(); - $orX->add($qb->expr()->eq('p.mobilenumber', ':number')); - $orX->add($qb->expr()->eq('p.phonenumber', ':number')); - $orX->add( - $qb->expr()->exists('SELECT 1 FROM '.PersonPhone::class.' k WHERE k.phonenumber = :number AND k.person = p') - ); - - $qb->andWhere($orX); - } - /** * @throws \Exception */ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts b/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts index 6ceb64b98..8f117d6ef 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/mod/DuplicateSelector/AccompanyingPeriodWorkSelector.ts @@ -3,47 +3,49 @@ import AccompanyingPeriodWorkSelectorModal from "../../vuejs/_components/Accompa import { AccompanyingPeriodWork } from "../../types"; document.addEventListener("DOMContentLoaded", () => { - const elements = document.querySelectorAll( - 'div[data-pick-entities-type="acpw"]', - ); - elements.forEach((el) => { - const uniqid = el.dataset.inputUniqid; - - if (undefined === uniqid) { - throw "Uniqid not found on this element"; - } - - const input = document.querySelector( - `input[data-input-uniqid="${uniqid}"]`, + const elements = document.querySelectorAll( + 'div[data-pick-entities-type="acpw"]', ); + elements.forEach((el) => { + const uniqid = el.dataset.inputUniqid; - if (null === input) { - throw "Element with uniqid not found: " + uniqid; - } + if (undefined === uniqid) { + throw "Uniqid not found on this element"; + } - const accompanyingPeriodIdAsString = input.dataset.accompanyingPeriodId; + const input = document.querySelector( + `input[data-input-uniqid="${uniqid}"]`, + ); - if (undefined === accompanyingPeriodIdAsString) { - throw "accompanying period id not found"; - } + if (null === input) { + throw "Element with uniqid not found: " + uniqid; + } - const accompanyingPeriodId = Number.parseInt(accompanyingPeriodIdAsString); + const accompanyingPeriodIdAsString = input.dataset.accompanyingPeriodId; - const app = createApp({ - template: - '', - components: { AccompanyingPeriodWorkSelectorModal }, - data() { - return { accompanyingPeriodId }; - }, - methods: { - pickWork: function (payload: { work: AccompanyingPeriodWork }) { - console.log("payload", payload); - input.value = payload.work.id.toString(); - }, - }, + if (undefined === accompanyingPeriodIdAsString) { + throw "accompanying period id not found"; + } + + const accompanyingPeriodId = Number.parseInt( + accompanyingPeriodIdAsString, + ); + + const app = createApp({ + template: + '', + components: { AccompanyingPeriodWorkSelectorModal }, + data() { + return { accompanyingPeriodId }; + }, + methods: { + pickWork: function (payload: { work: AccompanyingPeriodWork }) { + console.log("payload", payload); + input.value = payload.work.id.toString(); + }, + }, + }); + + app.mount(el); }); - - app.mount(el); - }); }); diff --git a/src/Bundle/ChillPersonBundle/Resources/public/types.ts b/src/Bundle/ChillPersonBundle/Resources/public/types.ts index 32dc24f73..7d641c859 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/types.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/types.ts @@ -1,329 +1,253 @@ import { - Address, - Center, - Civility, - DateTime, - User, - UserGroup, - Household, - WorkflowAvailable, - Scope, - Job, - PrivateCommentEmbeddable, + Address, + Scope, + Center, + Civility, + DateTime, + User, + WorkflowAvailable, + Job, + PrivateCommentEmbeddable, } from "ChillMainAssets/types"; import { StoredObject } from "ChillDocStoreAssets/types"; import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types"; import { Calendar } from "../../../ChillCalendarBundle/Resources/public/types"; -export interface AltName { - label: string; - key: string; -} - export interface Person { - id: number; - type: "person"; - text: string; - textAge: string; - firstName: string; - lastName: string; - altNames: AltName[]; - suffixText: string; - current_household_address: Address | null; - birthdate: DateTime | null; - deathdate: DateTime | null; - age: number; - phonenumber: string; - mobilenumber: string; - email: string; - gender: "woman" | "man" | "other"; - centers: Center[]; - civility: Civility | null; - current_household_id: number; - current_residential_addresses: Address[]; + id: number; + type: "person"; + text: string; + textAge: string; + firstName: string; + lastName: string; + current_household_address: Address | null; + birthdate: DateTime | null; + deathdate: DateTime | null; + age: number; + phonenumber: string; + mobilenumber: string; + email: string; + gender: "woman" | "man" | "other"; + centers: Center[]; + civility: Civility | null; + current_household_id: number; + current_residential_addresses: Address[]; } export interface AccompanyingPeriod { - id: number; - addressLocation?: Address | null; - administrativeLocation?: Location | null; - calendars: Calendar[]; - closingDate?: Date | null; - closingMotive?: ClosingMotive | null; - comments: Comment[]; - confidential: boolean; - createdAt?: Date | null; - createdBy?: User | null; - emergency: boolean; - intensity?: "occasional" | "regular"; - job?: Job | null; - locationHistories: AccompanyingPeriodLocationHistory[]; - openingDate?: Date | null; - origin?: Origin | null; - participations: AccompanyingPeriodParticipation[]; - personLocation?: Person | null; - pinnedComment?: Comment | null; - preventUserIsChangedNotification: boolean; - remark: string; - requestorAnonymous: boolean; - requestorPerson?: Person | null; - requestorThirdParty?: Thirdparty | null; - resources: AccompanyingPeriodResource[]; - scopes: Scope[]; - socialIssues: SocialIssue[]; - step?: - | "CLOSED" - | "CONFIRMED" - | "CONFIRMED_INACTIVE_SHORT" - | "CONFIRMED_INACTIVE_LONG" - | "DRAFT"; -} - -export interface AccompanyingPeriodWork { - id: number; - accompanyingPeriod?: AccompanyingPeriod; - accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[]; - createdAt?: string; - createdAutomatically: boolean; - createdAutomaticallyReason: string; - createdBy: User; - endDate?: string; - goals: AccompanyingPeriodWorkGoal[]; - handlingThierParty?: Thirdparty; - note: string; - persons: Person[]; - privateComment: PrivateCommentEmbeddable; - referrersHistory: AccompanyingPeriodWorkReferrerHistory[]; - results: Result[]; - socialAction?: SocialAction; - startDate?: string; - thirdParties: Thirdparty[]; - updatedAt?: string; - updatedBy: User; - version: number; -} - -export interface SocialAction { - id: number; - parent?: SocialAction | null; - children: SocialAction[]; - issue?: SocialIssue | null; - ordering: number; - title: { - fr: string; - }; - defaultNotificationDelay?: string | null; - desactivationDate?: string | null; - evaluations: Evaluation[]; - goals: Goal[]; - results: Result[]; -} - -export interface AccompanyingPeriodResource { - id: number; - accompanyingPeriod: AccompanyingPeriod; - comment?: string | null; - person?: Person | null; - thirdParty?: Thirdparty | null; -} - -export interface Origin { - id: number; - label: { - fr: string; - }; - noActiveAfter: DateTime; -} - -export interface ClosingMotive { - id: number; - active: boolean; - name: { - fr: string; - }; - ordering: number; - isCanceledAccompanyingPeriod: boolean; - parent?: ClosingMotive | null; - children: ClosingMotive[]; -} - -export interface AccompanyingPeriodParticipation { - id: number; - startDate: DateTime; - endDate?: DateTime | null; - accompanyingPeriod: AccompanyingPeriod; - person: Person; -} - -export interface AccompanyingPeriodLocationHistory { - id: number; - startDate: DateTime; - endDate?: DateTime | null; - addressLocation?: Address | null; - period: AccompanyingPeriod; - personLocation?: Person | null; -} - -export interface SocialIssue { - id: number; - parent?: SocialIssue | null; - children: SocialIssue[]; - socialActions?: SocialAction[] | null; - ordering: number; - title: { - fr: string; - }; - desactivationDate?: string | null; -} - -export interface Goal { - id: number; - results: Result[]; - socialActions?: SocialAction[] | null; - title: { - fr: string; - }; -} - -export interface Result { - id: number; - accompanyingPeriodWorks: AccompanyingPeriodWork[]; - accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[]; - goals: Goal[]; - socialActions: SocialAction[]; - title: { - fr: string; - }; - desactivationDate?: string | null; -} - -export interface AccompanyingPeriodWorkGoal { - id: number; - accompanyingPeriodWork: AccompanyingPeriodWork; - goal: Goal; - note: string; - results: Result[]; -} - -export interface AccompanyingPeriodWorkEvaluation { - accompanyingPeriodWork: AccompanyingPeriodWork | null; - comment: string; - createdAt: DateTime | null; - createdBy: User | null; - documents: AccompanyingPeriodWorkEvaluationDocument[]; - endDate: DateTime | null; - evaluation: Evaluation | null; - id: number | null; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - key: any; - maxDate: DateTime | null; - startDate: DateTime | null; - updatedAt: DateTime | null; - updatedBy: User | null; - warningInterval: string | null; - timeSpent: number | null; -} - -export interface Evaluation { - id: number; - url: string; - socialActions: SocialAction[]; - title: { - fr: string; - }; - active: boolean; - delay: string; - notificationDelay: string; -} - -export interface AccompanyingPeriodWorkReferrerHistory { - id: number; - accompanyingPeriodWork: AccompanyingPeriodWork; - user: User; - startDate: DateTime; - endDate: DateTime | null; - createdAt: DateTime; - updatedAt: DateTime | null; - createdBy: User; - updatedBy: User | null; + id: number; + addressLocation?: Address | null; + administrativeLocation?: Location | null; + calendars: Calendar[]; + closingDate?: Date | null; + closingMotive?: ClosingMotive | null; + comments: Comment[]; + confidential: boolean; + createdAt?: Date | null; + createdBy?: User | null; + emergency: boolean; + intensity?: "occasional" | "regular"; + job?: Job | null; + locationHistories: AccompanyingPeriodLocationHistory[]; + openingDate?: Date | null; + origin?: Origin | null; + participations: AccompanyingPeriodParticipation[]; + personLocation?: Person | null; + pinnedComment?: Comment | null; + preventUserIsChangedNotification: boolean; + remark: string; + requestorAnonymous: boolean; + requestorPerson?: Person | null; + requestorThirdParty?: Thirdparty | null; + resources: AccompanyingPeriodResource[]; + scopes: Scope[]; + socialIssues: SocialIssue[]; + step?: + | "CLOSED" + | "CONFIRMED" + | "CONFIRMED_INACTIVE_SHORT" + | "CONFIRMED_INACTIVE_LONG" + | "DRAFT"; } export interface AccompanyingPeriodWorkEvaluationDocument { - id: number; - type: "accompanying_period_work_evaluation_document"; - storedObject: StoredObject; - title: string; - createdAt: DateTime | null; - createdBy: User | null; - updatedAt: DateTime | null; - updatedBy: User | null; - workflows_availables: WorkflowAvailable[]; - workflows: object[]; + id: number; + type: "accompanying_period_work_evaluation_document"; + storedObject: StoredObject; + title: string; + createdAt: DateTime | null; + createdBy: User | null; + updatedAt: DateTime | null; + updatedBy: User | null; + workflows_availables: WorkflowAvailable[]; + workflows: object[]; } -export type EntityType = - | "user_group" - | "user" - | "person" - | "thirdparty" - | "household"; - -export type Entities = (UserGroup | User | Person | Thirdparty | Household) & { - address?: Address | null; - kind?: string; - text?: string; - profession?: string; -}; - -export type EntitiesOrMe = "me" | Entities; - -export type AddPersonResult = Entities & { - parent?: Entities | null; -}; - -export interface Suggestion { - key: string; - relevance: number; - result: AddPersonResult; +export interface AccompanyingPeriodWork { + id: number; + accompanyingPeriod?: AccompanyingPeriod; + accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[]; + createdAt?: string; + createdAutomatically: boolean; + createdAutomaticallyReason: string; + createdBy: User; + endDate?: string; + goals: AccompanyingPeriodWorkGoal[]; + handlingThierParty?: Thirdparty; + note: string; + persons: Person[]; + privateComment: PrivateCommentEmbeddable; + referrersHistory: AccompanyingPeriodWorkReferrerHistory[]; + results: Result[]; + socialAction?: SocialAction; + startDate?: string; + thirdParties: Thirdparty[]; + updatedAt?: string; + updatedBy: User; + version: number; } -export interface SearchPagination { - first: number; - items_per_page: number; - next: number | null; - previous: number | null; - more: boolean; +interface SocialAction { + id: number; + parent?: SocialAction | null; + children: SocialAction[]; + issue?: SocialIssue | null; + ordering: number; + title: { + fr: string; + }; + defaultNotificationDelay?: string | null; + desactivationDate?: string | null; + evaluations: Evaluation[]; + goals: Goal[]; + results: Result[]; } -export interface Search { - count: number; - pagination: SearchPagination; - results: Suggestion[]; +export interface AccompanyingPeriodResource { + id: number; + accompanyingPeriod: AccompanyingPeriod; + comment?: string | null; + person?: Person | null; + thirdParty?: Thirdparty | null; } -export interface SearchOptions { - uniq: boolean; - type: string[]; - priority: number | null; - button: { - size: string; - class: string; - type: string; - display: string; - }; +export interface Origin { + id: number; + label: { + fr: string; + }; + noActiveAfter: DateTime; } -export class MakeFetchException extends Error { - sta: number; - txt: string; - violations: unknown | null; - - constructor(txt: string, sta: number, violations: unknown | null = null) { - super(txt); - this.name = "ValidationException"; - this.sta = sta; - this.txt = txt; - this.violations = violations; - Object.setPrototypeOf(this, MakeFetchException.prototype); - } +export interface ClosingMotive { + id: number; + active: boolean; + name: { + fr: string; + }; + ordering: number; + isCanceledAccompanyingPeriod: boolean; + parent?: ClosingMotive | null; + children: ClosingMotive[]; +} + +export interface AccompanyingPeriodParticipation { + id: number; + startDate: DateTime; + endDate?: DateTime | null; + accompanyingPeriod: AccompanyingPeriod; + person: Person; +} + +export interface AccompanyingPeriodLocationHistory { + id: number; + startDate: DateTime; + endDate?: DateTime | null; + addressLocation?: Address | null; + period: AccompanyingPeriod; + personLocation?: Person | null; +} + +export interface SocialIssue { + id: number; + parent?: SocialIssue | null; + children: SocialIssue[]; + socialActions?: SocialAction[] | null; + ordering: number; + title: { + fr: string; + }; + desactivationDate?: string | null; +} + +export interface Goal { + id: number; + results: Result[]; + socialActions?: SocialAction[] | null; + title: { + fr: string; + }; +} + +export interface Result { + id: number; + accompanyingPeriodWorks: AccompanyingPeriodWork[]; + accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[]; + goals: Goal[]; + socialActions: SocialAction[]; + title: { + fr: string; + }; + desactivationDate?: string | null; +} + +export interface AccompanyingPeriodWorkGoal { + id: number; + accompanyingPeriodWork: AccompanyingPeriodWork; + goal: Goal; + note: string; + results: Result[]; +} + +export interface AccompanyingPeriodWorkEvaluation { + accompanyingPeriodWork: AccompanyingPeriodWork | null; + comment: string; + createdAt: DateTime | null; + createdBy: User | null; + documents: AccompanyingPeriodWorkEvaluationDocument[]; + endDate: DateTime | null; + evaluation: Evaluation | null; + id: number | null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + key: any; + maxDate: DateTime | null; + startDate: DateTime | null; + updatedAt: DateTime | null; + updatedBy: User | null; + warningInterval: string | null; + timeSpent: number | null; +} + +export interface Evaluation { + id: number; + url: string; + socialActions: SocialAction[]; + title: { + fr: string; + }; + active: boolean; + delay: string; + notificationDelay: string; +} + +export interface AccompanyingPeriodWorkReferrerHistory { + id: number; + accompanyingPeriodWork: AccompanyingPeriodWork; + user: User; + startDate: DateTime; + endDate: DateTime | null; + createdAt: DateTime; + updatedAt: DateTime | null; + createdBy: User; + updatedBy: User | null; } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue index e81949711..85f031a64 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/App.vue @@ -1,28 +1,28 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue index 3ce902daa..408852711 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Confirm.vue @@ -1,120 +1,127 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue index af985e502..f781eca0b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/CourseLocation.vue @@ -1,85 +1,95 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue index 053eb058f..641c9ef37 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/OriginDemand.vue @@ -1,33 +1,33 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue index b4e0e70b8..d6979928b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated.vue @@ -1,106 +1,121 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue index c2dbd6e0c..61a9d2571 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/PersonsAssociated/ParticipationItem.vue @@ -1,97 +1,98 @@ - - + + + + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue index 963ff65b5..dbb21fa40 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Referrer.vue @@ -1,113 +1,116 @@ - - - + -
    - + - + +
    - +
    +
      +
    • + +
    • +
    +
    - - - +
    + {{ $t("job.not_valid") }} +
    - -
    -
      -
    • - -
    • -
    -
    - -
    - {{ $t("job.not_valid") }} -
    - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue index 5858cdb00..c2b9acfc4 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Requestor.vue @@ -1,239 +1,263 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue index fa600bfdd..1ef9bfe45 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources.vue @@ -1,57 +1,57 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue index 87daacf47..8a52748d4 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/ResourceItem.vue @@ -1,107 +1,107 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue index 416c6085c..26b561c5d 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Resources/WriteComment.vue @@ -1,32 +1,34 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue index fee6a89ad..1d05e0bbe 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Scopes.vue @@ -1,25 +1,25 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue index 21c290f48..02d3b0c84 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/SocialIssue.vue @@ -1,30 +1,30 @@ @@ -91,20 +96,20 @@ export default { @import "ChillPersonAssets/chill/scss/mixins"; @import "ChillMainAssets/chill/scss/chill_variables"; div#accompanying-course { - span.multiselect__tag { - @include badge_social($social-issue-color); - background: $chill-l-gray; - color: $dark; - } - span.multiselect__option--highlight { - &::after { - background: $green; + span.multiselect__tag { + @include badge_social($social-issue-color); + background: $chill-l-gray; + color: $dark; } - &.multiselect__option--selected { - &::after { - background: $red; - } + span.multiselect__option--highlight { + &::after { + background: $green; + } + &.multiselect__option--selected { + &::after { + background: $red; + } + } } - } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue index d3e9ae97a..0bfd87b18 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StartDate.vue @@ -1,22 +1,22 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue index 3ff18f03a..890aa5153 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav.vue @@ -1,195 +1,207 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue index ecdf3026c..8e175f72b 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/StickyNav/Item.vue @@ -1,22 +1,26 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue index 929b4edd8..904d4ff42 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/Test.vue @@ -1,146 +1,155 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue index f8b5389f4..745317211 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkCreate/App.vue @@ -1,135 +1,152 @@ @@ -267,46 +288,46 @@ export default { @import "ChillPersonAssets/chill/scss/mixins"; @import "ChillMainAssets/chill/scss/chill_variables"; span.badge { - @include badge_social($social-issue-color); - font-size: 95%; - margin-bottom: 5px; - margin-right: 1em; - margin-left: 1em; + @include badge_social($social-issue-color); + font-size: 95%; + margin-bottom: 5px; + margin-right: 1em; + margin-left: 1em; } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue index bd87224b2..f3404e2de 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/App.vue @@ -1,453 +1,482 @@ @@ -806,172 +841,172 @@ export default { @import "~ChillMainAssets/chill/scss/mixins"; div#workEditor { - display: grid; - grid-template-columns: 50%; - column-gap: 0rem; - grid-template-areas: - "title title" - "startDate endDate" - "comment comment" - "privateComment privateComment" - "objectives objectives" - "evaluations evaluations" - "persons persons" - "referrers referrers" - "handling handling" - "tparties tparties" - "errors errors"; + display: grid; + grid-template-columns: 50%; + column-gap: 0rem; + grid-template-areas: + "title title" + "startDate endDate" + "comment comment" + "privateComment privateComment" + "objectives objectives" + "evaluations evaluations" + "persons persons" + "referrers referrers" + "handling handling" + "tparties tparties" + "errors errors"; - #title { - grid-area: title; - } - #startDate { - grid-area: startDate; - } - #endDate { - grid-area: endDate; - } - #comment { - grid-area: comment; - } - #privateComment { - grid-area: privateComment; - } - #objectives { - grid-area: objectives; - } - #evaluations { - grid-area: evaluations; - } - #persons { - grid-area: persons; - } - #handlingThirdParty { - grid-area: handling; - } - #thirdParties { - grid-area: tparties; - } - #referrers { - grid-area: referrers; - } - #errors { - grid-area: errors; - } - - div.action-row { - @include border-collapse; - padding: 1em; - - &#title { - label { - margin-bottom: 0; - } - p { - margin-top: 0; - font-weight: bold; - font-size: 1rem; - } + #title { + grid-area: title; + } + #startDate { + grid-area: startDate; + } + #endDate { + grid-area: endDate; + } + #comment { + grid-area: comment; + } + #privateComment { + grid-area: privateComment; + } + #objectives { + grid-area: objectives; + } + #evaluations { + grid-area: evaluations; + } + #persons { + grid-area: persons; + } + #handlingThirdParty { + grid-area: handling; + } + #thirdParties { + grid-area: tparties; + } + #referrers { + grid-area: referrers; + } + #errors { + grid-area: errors; } - &#objectives { - & > div { - display: grid; - grid-template-columns: 50%; - column-gap: 0rem; - grid-template-areas: "obj res"; - - & > div { - @include border-collapse; - padding: 1em; - - &:nth-child(1) { - grid-area: obj; - } - - &:nth-child(2) { - grid-area: res; - } - } - - & > div.results_without_objective { - background: repeating-linear-gradient( - 45deg, - $gray-200, - $gray-200 10px, - $gray-100 10px, - $gray-100 20px - ); - text-align: center; - font-weight: 700; - padding-top: 1.5rem; - } - } - } - - &#evaluations { - & > div { + div.action-row { @include border-collapse; padding: 1em; - } - } - &#objectives, - &#evaluations { - padding: 0; - - & > div.title { - background-color: $gray-200; - color: $gray-700; - - h3 { - text-align: center; + &#title { + label { + margin-bottom: 0; + } + p { + margin-top: 0; + font-weight: bold; + font-size: 1rem; + } } - } - .item-title { - font-weight: bold; - } - .item-details { - margin: 1em 2em; - font-size: 85%; - } + &#objectives { + & > div { + display: grid; + grid-template-columns: 50%; + column-gap: 0rem; + grid-template-areas: "obj res"; - i.fa { - padding: 0.25rem; - color: $white; + & > div { + @include border-collapse; + padding: 1em; - &.fa-times { - color: $red; + &:nth-child(1) { + grid-area: obj; + } + + &:nth-child(2) { + grid-area: res; + } + } + + & > div.results_without_objective { + background: repeating-linear-gradient( + 45deg, + $gray-200, + $gray-200 10px, + $gray-100 10px, + $gray-100 20px + ); + text-align: center; + font-weight: 700; + padding-top: 1.5rem; + } + } + } + + &#evaluations { + & > div { + @include border-collapse; + padding: 1em; + } + } + + &#objectives, + &#evaluations { + padding: 0; + + & > div.title { + background-color: $gray-200; + color: $gray-700; + + h3 { + text-align: center; + } + } + + .item-title { + font-weight: bold; + } + .item-details { + margin: 1em 2em; + font-size: 85%; + } + + i.fa { + padding: 0.25rem; + color: $white; + + &.fa-times { + color: $red; + } + } + } + + &#persons { + margin-top: 1.5em; + } + + ul.record_actions { + margin-bottom: 0; } - } } - &#persons { - margin-top: 1.5em; + div#errors { + &.alert { + margin-top: 2em; + } } - - ul.record_actions { - margin-bottom: 0; - } - } - - div#errors { - &.alert { - margin-top: 2em; - } - } } .accordion-item:first-of-type, .accordion-item:last-of-type { - border-radius: 0rem; - border: 0px; - .accordion-button { - padding: 0.25rem; - border: 1px solid rgba(17, 17, 17, 0.125); - margin-top: 20px; - margin-bottom: 20px; - } + border-radius: 0rem; + border: 0px; + .accordion-button { + padding: 0.25rem; + border: 1px solid rgba(17, 17, 17, 0.125); + margin-top: 20px; + margin-bottom: 20px; + } } diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue index ee8cca532..77b6810bb 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddEvaluation.vue @@ -1,67 +1,70 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddResult.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddResult.vue index f25de72c8..aa8948a4e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddResult.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/AddResult.vue @@ -1,167 +1,179 @@ 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 30bb2a2dd..b54baf09a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -1,11 +1,11 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue index 7922362ca..29b707842 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/ExportFormActionGoalResult/App.vue @@ -1,447 +1,445 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue index a58b8a98a..b8e8ee8c4 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/App.vue @@ -1,53 +1,53 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Concerned.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Concerned.vue index ae422f150..d4bacd855 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Concerned.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Concerned.vue @@ -1,74 +1,88 @@ @@ -78,61 +92,62 @@ import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue"; import PersonText from "ChillPersonAssets/vuejs/_components/Entity/PersonText.vue"; export default { - name: "Concerned", - components: { - AddPersons, - PersonText, - }, - computed: { - ...mapState(["concerned", "household"]), - ...mapGetters(["persons"]), - noPerson() { - return this.$store.getters.persons.length === 0; + name: "Concerned", + components: { + AddPersons, + PersonText, }, - concernedPersonsWithHouseholds() { - if (this.$store.state.household) { - return this.$store.state.concerned.filter( - (c) => - c.person.current_household_id !== null && - c.person.current_household_id !== this.$store.state.household.id, - ); - } else { - return []; - } - }, - }, - data() { - return { - addPersons: { - key: "household_members_editor_concerned", - options: { - type: ["person"], - priority: null, - uniq: false, + computed: { + ...mapState(["concerned", "household"]), + ...mapGetters(["persons"]), + noPerson() { + return this.$store.getters.persons.length === 0; + }, + concernedPersonsWithHouseholds() { + if (this.$store.state.household) { + return this.$store.state.concerned.filter( + (c) => + c.person.current_household_id !== null && + c.person.current_household_id !== + this.$store.state.household.id, + ); + } else { + return []; + } }, - }, - }; - }, - methods: { - addNewPersons({ selected, modal }) { - selected.forEach(function (item) { - this.$store.dispatch("addConcerned", item.result); - }, this); - this.$refs.addPersons.resetSearch(); // to cast child method - modal.showModal = false; }, - removeConcerned(concerned) { - console.log("removedconcerned", concerned); + data() { + return { + addPersons: { + key: "household_members_editor_concerned", + options: { + type: ["person"], + priority: null, + uniq: false, + }, + }, + }; + }, + methods: { + addNewPersons({ selected, modal }) { + selected.forEach(function (item) { + this.$store.dispatch("addConcerned", item.result); + }, this); + this.$refs.addPersons.resetSearch(); // to cast child method + modal.showModal = false; + }, + removeConcerned(concerned) { + console.log("removedconcerned", concerned); - if (!concerned.allowRemove) { - return; - } + if (!concerned.allowRemove) { + return; + } - this.$store.dispatch("removePerson", concerned.person); + this.$store.dispatch("removePerson", concerned.person); + }, + makeHouseholdLink(id) { + return `/fr/person/household/${id}/summary`; + }, }, - makeHouseholdLink(id) { - return `/fr/person/household/${id}/summary`; - }, - }, }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Confirmation.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Confirmation.vue index 5441bc1af..902e95648 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Confirmation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Confirmation.vue @@ -1,20 +1,20 @@ @@ -23,14 +23,14 @@ import { mapState } from "vuex"; export default { - name: "Confirmation", - computed: { - ...mapState({ - hasWarnings: (state) => - state.warnings.length > 0 || state.errors.length > 0, - warnings: (state) => state.warnings, - errors: (state) => state.errors, - }), - }, + name: "Confirmation", + computed: { + ...mapState({ + hasWarnings: (state) => + state.warnings.length > 0 || state.errors.length > 0, + warnings: (state) => state.warnings, + errors: (state) => state.errors, + }), + }, }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue index 3ae8c1ab6..34002972c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/CurrentHousehold.vue @@ -1,35 +1,37 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue index 1139d2fe9..8757f23bc 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Dates.vue @@ -1,83 +1,83 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue index 56798341a..9773d178e 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Household.vue @@ -1,116 +1,130 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/HouseholdAddress.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/HouseholdAddress.vue index c7d9d8aac..af6f00d84 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/HouseholdAddress.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/HouseholdAddress.vue @@ -1,26 +1,30 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue index 3bdfb9d03..13515efec 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/MemberDetails.vue @@ -1,102 +1,104 @@ @@ -106,44 +108,44 @@ import PersonRenderBox from "ChillPersonAssets/vuejs/_components/Entity/PersonRe import CommentEditor from "ChillMainAssets/vuejs/_components/CommentEditor/CommentEditor.vue"; export default { - name: "MemberDetails", - components: { - PersonRenderBox, - CommentEditor, - }, - props: ["conc"], - computed: { - ...mapGetters(["concByPersonId"]), - classicEditor: () => ClassicEditor, - editorConfig: () => classicEditorConfig, - isHolder() { - return this.conc.holder; + name: "MemberDetails", + components: { + PersonRenderBox, + CommentEditor, }, - comment: { - get() { - return this.conc.comment; - }, - set(text) { - console.log("set comment"); - console.log("comment", text); + props: ["conc"], + computed: { + ...mapGetters(["concByPersonId"]), + classicEditor: () => ClassicEditor, + editorConfig: () => classicEditorConfig, + isHolder() { + return this.conc.holder; + }, + comment: { + get() { + return this.conc.comment; + }, + set(text) { + console.log("set comment"); + console.log("comment", text); - this.$store.dispatch("setComment", { - conc: this.conc, - comment: text, - }); - }, + this.$store.dispatch("setComment", { + conc: this.conc, + comment: text, + }); + }, + }, }, - }, - methods: { - toggleHolder() { - this.$store.dispatch("toggleHolder", this.conc); + methods: { + toggleHolder() { + this.$store.dispatch("toggleHolder", this.conc); + }, + removePosition() { + this.$store.dispatch("removePosition", this.conc); + }, + removeConcerned() { + this.$store.dispatch("removeConcerned", this.conc); + }, }, - removePosition() { - this.$store.dispatch("removePosition", this.conc); - }, - removeConcerned() { - this.$store.dispatch("removeConcerned", this.conc); - }, - }, }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue index 99fb3ebfa..6630a72f1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/PersonComment.vue @@ -1,30 +1,30 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue index 26ab0058e..a19d80c7a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/HouseholdMembersEditor/components/Positioning.vue @@ -1,57 +1,57 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue index e9ee85164..401f41be5 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/VisGraph/App.vue @@ -1,181 +1,211 @@ + + + + +
      +
    • + - {{ layer.label }} - - - - - - - - - - - - - -
        -
      • - -
      • -
      +
    • +
    @@ -645,21 +694,21 @@ export default { diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.ts b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js similarity index 57% rename from src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.ts rename to src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js index 519a3d3e7..1d0a607ed 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/AddPersons.js @@ -1,15 +1,7 @@ -import { Search, SearchOptions } from "ChillPersonAssets/types"; - /* * Build query string with query and options */ -const parametersToString = ({ - query, - options, -}: { - query: string; - options: SearchOptions; -}) => { +const parametersToString = ({ query, options }) => { let types = ""; options.type.forEach(function (type) { types += "&type[]=" + type; @@ -24,13 +16,11 @@ const parametersToString = ({ * @query string - the query to search for * @deprecated */ -export function searchPersons( - { query, options }: { query: string; options: SearchOptions }, - signal: AbortSignal, -): Promise { - const queryStr = parametersToString({ query, options }); - const url = `/fr/search.json?name=person_regular&${queryStr}`; - const fetchOpts = { +const searchPersons = ({ query, options }, signal) => { + console.err("deprecated"); + let queryStr = parametersToString({ query, options }); + let url = `/fr/search.json?name=person_regular&${queryStr}`; + let fetchOpts = { method: "GET", headers: { "Content-Type": "application/json;charset=utf-8", @@ -44,7 +34,7 @@ export function searchPersons( } throw Error("Error with request resource response"); }); -} +}; /* * Endpoint v.2 chill_main_search_global @@ -53,16 +43,15 @@ export function searchPersons( * @param query string - the query to search for * */ -export function searchEntities( - { query, options }: { query: string; options: SearchOptions }, - signal: AbortSignal, -): Promise { - const queryStr = parametersToString({ query, options }); - const url = `/api/1.0/search.json?${queryStr}`; +const searchEntities = ({ query, options }, signal) => { + let queryStr = parametersToString({ query, options }); + let url = `/api/1.0/search.json?${queryStr}`; return fetch(url, { signal }).then((response) => { if (response.ok) { return response.json(); } throw Error("Error with request resource response"); }); -} +}; + +export { searchPersons, searchEntities }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/accompanyingCourseWorkEvaluationDocument.ts b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/accompanyingCourseWorkEvaluationDocument.ts index 577c4b52b..ddb37fab1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/accompanyingCourseWorkEvaluationDocument.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_api/accompanyingCourseWorkEvaluationDocument.ts @@ -2,10 +2,10 @@ import { AccompanyingPeriodWorkEvaluationDocument } from "../../types"; import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods"; export const duplicate = async ( - id: number, + id: number, ): Promise => { - return makeFetch( - "POST", - `/api/1.0/person/accompanying-course-work-evaluation-document/${id}/duplicate`, - ); + return makeFetch( + "POST", + `/api/1.0/person/accompanying-course-work-evaluation-document/${id}/duplicate`, + ); }; diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue index 0a8d07f30..8391fbbb6 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriod/SetReferrer.vue @@ -1,44 +1,53 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkItem.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkItem.vue index 98668a65b..330423a20 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkItem.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkItem.vue @@ -1,45 +1,51 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue index c3615c959..b2ebc5f1c 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkList.vue @@ -1,24 +1,24 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue index ee2c7c66c..4b4f3ddba 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AccompanyingPeriodWorkSelector/AccompanyingPeriodWorkSelectorModal.vue @@ -1,52 +1,62 @@ diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue index 24cdca24e..5e81b8241 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue @@ -1,489 +1,517 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue index 4499b40c8..bb90557b7 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/PersonSuggestion.vue @@ -1,124 +1,136 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue index a1e33c4de..86a58737a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeHousehold.vue @@ -1,25 +1,26 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue index 98119bd8e..259029f73 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypePerson.vue @@ -1,48 +1,43 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue index 28be7c119..8c97d2e29 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeThirdParty.vue @@ -1,137 +1,126 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue index 74f80c321..56b3afe17 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUser.vue @@ -1,40 +1,40 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue index ca3aa2cb8..1daf921e1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/AddPersons/TypeUserGroup.vue @@ -1,24 +1,32 @@ - - + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue index e6f50708b..ad996a375 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/HouseholdRenderBox.vue @@ -1,161 +1,168 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue index 0d9e3ec5f..cdc143f76 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue @@ -1,418 +1,493 @@ - + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue index 10302ee30..f157fac6a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonText.vue @@ -1,51 +1,75 @@ - diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue index 3a93a305d..ff4b312f1 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_components/OnTheFly/Person.vue @@ -1,466 +1,530 @@ - + + diff --git a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts index 088ff11ea..ba7637544 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/_js/i18n.ts @@ -1,63 +1,64 @@ const personMessages = { - fr: { - add_persons: { - title: "Ajouter des usagers", - suggested_counter: "Pas de résultats | 1 résultat | {count} résultats", - selected_counter: " 1 sélectionné | {count} sélectionnés", - search_some_persons: "Rechercher des personnes..", + fr: { + add_persons: { + title: "Ajouter des usagers", + suggested_counter: + "Pas de résultats | 1 résultat | {count} résultats", + selected_counter: " 1 sélectionné | {count} sélectionnés", + search_some_persons: "Rechercher des personnes..", + }, + item: { + type_person: "Usager", + type_user: "TMS", + type_thirdparty: "Tiers professionnel", + type_household: "Ménage", + }, + person: { + firstname: "Prénom", + lastname: "Nom", + born: (ctx: { gender: "man" | "woman" | "neutral" }) => { + if (ctx.gender === "man") { + return "Né le"; + } else if (ctx.gender === "woman") { + return "Née le"; + } else { + return "Né·e le"; + } + }, + center_id: "Identifiant du centre", + center_type: "Type de centre", + center_name: "Territoire", // vendée + phonenumber: "Téléphone", + mobilenumber: "Mobile", + altnames: "Autres noms", + email: "Courriel", + gender: { + title: "Genre", + placeholder: "Choisissez le genre de l'usager", + woman: "Féminin", + man: "Masculin", + neutral: "Neutre, non binaire", + unknown: "Non renseigné", + undefined: "Non renseigné", + }, + civility: { + title: "Civilité", + placeholder: "Choisissez la civilité", + }, + address: { + create_address: "Ajouter une adresse", + show_address_form: + "Ajouter une adresse pour un usager non suivi et seul dans un ménage", + warning: + "Un nouveau ménage va être créé. L'usager sera membre de ce ménage.", + }, + center: { + placeholder: "Choisissez un centre", + title: "Centre", + }, + }, + error_only_one_person: "Une seule personne peut être sélectionnée !", }, - item: { - type_person: "Usager", - type_user: "TMS", - type_thirdparty: "Tiers professionnel", - type_household: "Ménage", - }, - person: { - firstname: "Prénom", - lastname: "Nom", - born: (ctx: { gender: "man" | "woman" | "neutral" }) => { - if (ctx.gender === "man") { - return "Né le"; - } else if (ctx.gender === "woman") { - return "Née le"; - } else { - return "Né·e le"; - } - }, - center_id: "Identifiant du centre", - center_type: "Type de centre", - center_name: "Territoire", // vendée - phonenumber: "Téléphone", - mobilenumber: "Mobile", - altnames: "Autres noms", - email: "Courriel", - gender: { - title: "Genre", - placeholder: "Choisissez le genre de l'usager", - woman: "Féminin", - man: "Masculin", - neutral: "Neutre, non binaire", - unknown: "Non renseigné", - undefined: "Non renseigné", - }, - civility: { - title: "Civilité", - placeholder: "Choisissez la civilité", - }, - address: { - create_address: "Ajouter une adresse", - show_address_form: - "Ajouter une adresse pour un usager non suivi et seul dans un ménage", - warning: - "Un nouveau ménage va être créé. L'usager sera membre de ce ménage.", - }, - center: { - placeholder: "Choisissez un centre", - title: "Centre", - }, - }, - error_only_one_person: "Une seule personne peut être sélectionnée !", - }, }; export { personMessages }; diff --git a/src/Bundle/ChillPersonBundle/Tests/Repository/PersonACLAwareRepositoryTest.php b/src/Bundle/ChillPersonBundle/Tests/Repository/PersonACLAwareRepositoryTest.php index 667242111..94df35d88 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Repository/PersonACLAwareRepositoryTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Repository/PersonACLAwareRepositoryTest.php @@ -11,17 +11,14 @@ declare(strict_types=1); namespace Chill\PersonBundle\Tests\Repository; -use Chill\MainBundle\Entity\Center; use Chill\MainBundle\Entity\User; use Chill\MainBundle\Repository\CenterRepositoryInterface; use Chill\MainBundle\Repository\CountryRepository; use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface; use Chill\PersonBundle\Entity\Person; -use Chill\PersonBundle\Entity\PersonPhone; use Chill\PersonBundle\Repository\PersonACLAwareRepository; use Chill\PersonBundle\Security\Authorization\PersonVoter; use Doctrine\ORM\EntityManagerInterface; -use PHPUnit\Framework\Attributes\DataProvider; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -101,67 +98,4 @@ final class PersonACLAwareRepositoryTest extends KernelTestCase $this->assertStringContainsString('diallo', strtolower($person->getFirstName().' '.$person->getLastName())); } } - - /** - * @dataProvider providePersonsWithPhoneNumbers - */ - public function testFindByPhonenumber(\libphonenumber\PhoneNumber $phoneNumber, ?int $expectedId): void - { - $user = new User(); - - $authorizationHelper = $this->prophesize(AuthorizationHelperInterface::class); - $authorizationHelper->getReachableCenters(Argument::exact($user), Argument::exact(PersonVoter::SEE)) - ->willReturn($this->centerRepository->findAll()); - - $security = $this->prophesize(Security::class); - $security->getUser()->willReturn($user); - - $repository = new PersonACLAwareRepository( - $security->reveal(), - $this->entityManager, - $this->countryRepository, - $authorizationHelper->reveal() - ); - - $actual = $repository->findByPhone($phoneNumber, 0, 10); - - if (null === $expectedId) { - self::assertCount(0, $actual); - } else { - $actualIds = array_map(fn (Person $person) => $person->getId(), $actual); - - self::assertContains($expectedId, $actualIds); - } - } - - public static function providePersonsWithPhoneNumbers(): iterable - { - self::bootKernel(); - $em = self::getContainer()->get(EntityManagerInterface::class); - $center = $em->createQuery('SELECT c FROM '.Center::class.' c ')->setMaxResults(1) - ->getSingleResult(); - $util = \libphonenumber\PhoneNumberUtil::getInstance(); - - $mobile = $util->parse('+32486123456'); - $fixed = $util->parse('+3281136917'); - $anotherMobile = $util->parse('+32486123478'); - $person = (new Person())->setFirstName('diallo')->setLastName('diallo')->setCenter($center); - $person->setMobilenumber($mobile)->setPhonenumber($fixed); - $otherPhone = new PersonPhone(); - $otherPhone->setPerson($person); - $otherPhone->setPhonenumber($anotherMobile); - $otherPhone->setType('mobile'); - - $em->persist($person); - $em->persist($otherPhone); - - $em->flush(); - - self::ensureKernelShutdown(); - - yield [$mobile, $person->getId()]; - yield [$anotherMobile, $person->getId()]; - yield [$fixed, $person->getId()]; - yield [$util->parse('+331234567890'), null]; - } } diff --git a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml index ae7ac6284..6e42a362b 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml +++ b/src/Bundle/ChillPersonBundle/translations/messages+intl-icu.fr.yaml @@ -206,59 +206,3 @@ accompanying_course_evaluation_document: accompanying_period_work: title: Action d'accompagnement (n°{id}) - {action_title} - -add_persons: - title: "Ajouter des usagers" - suggested_counter: >- - {count, plural, - =0 {Pas de résultats} - =1 {1 résultat} - other {# résultats} - } - selected_counter: >- - {count, plural, - =1 {1 sélectionné} - other {# sélectionnés} - } - search_some_persons: "Rechercher des personnes.." - - item: - type_person: "Usager" - type_user: "TMS" - type_thirdparty: "Tiers professionnel" - type_household: "Ménage" - - person: - firstname: "Prénom" - lastname: "Nom" - born: - man: "Né le" - woman: "Née le" - neutral: "Né·e le" - center_id: "Identifiant du centre" - center_type: "Type de centre" - center_name: "Territoire" - phonenumber: "Téléphone" - mobilenumber: "Mobile" - altnames: "Autres noms" - email: "Courriel" - gender: - title: "Genre" - placeholder: "Choisissez le genre de l'usager" - woman: "Féminin" - man: "Masculin" - neutral: "Neutre, non binaire" - unknown: "Non renseigné" - undefined: "Non renseigné" - civility: - title: "Civilité" - placeholder: "Choisissez la civilité" - address: - create_address: "Ajouter une adresse" - show_address_form: "Ajouter une adresse pour un usager non suivi et seul dans un ménage" - warning: "Un nouveau ménage va être créé. L'usager sera membre de ce ménage." - center: - placeholder: "Choisissez un centre" - title: "Centre" - - error_only_one_person: "Une seule personne peut être sélectionnée !" diff --git a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml index 6c271fd80..c9f1af282 100644 --- a/src/Bundle/ChillPersonBundle/translations/messages.fr.yml +++ b/src/Bundle/ChillPersonBundle/translations/messages.fr.yml @@ -1522,48 +1522,3 @@ my_parcours_filters: parcours_intervening: Intervenant is_open: Parcours ouverts is_closed: Parcours clôturés - -person_messages: - add_persons: - title: "Ajouter des usagers" - suggested_counter: "Pas de résultats | 1 résultat | {count} résultats" - selected_counter: " 1 sélectionné | {count} sélectionnés" - search_some_persons: "Rechercher des personnes.." - item: - type_person: "Usager" - type_user: "TMS" - type_thirdparty: "Tiers professionnel" - type_household: "Ménage" - person: - firstname: "Prénom" - lastname: "Nom" - born: - man: "Né le" - woman: "Née le" - neutral: "Né·e le" - center_id: "Identifiant du centre" - center_type: "Type de centre" - center_name: "Territoire" - phonenumber: "Téléphone" - mobilenumber: "Mobile" - altnames: "Autres noms" - email: "Courriel" - gender: - title: "Genre" - placeholder: "Choisissez le genre de l'usager" - woman: "Féminin" - man: "Masculin" - neutral: "Neutre, non binaire" - unknown: "Non renseigné" - undefined: "Non renseigné" - civility: - title: "Civilité" - placeholder: "Choisissez la civilité" - address: - create_address: "Ajouter une adresse" - show_address_form: "Ajouter une adresse pour un usager non suivi et seul dans un ménage" - warning: "Un nouveau ménage va être créé. L'usager sera membre de ce ménage." - center: - placeholder: "Choisissez un centre" - title: "Centre" - error_only_one_person: "Une seule personne peut être sélectionnée !" diff --git a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php index 3153713bb..111be4089 100644 --- a/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php +++ b/src/Bundle/ChillThirdPartyBundle/Repository/ThirdPartyRepository.php @@ -18,15 +18,12 @@ use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ObjectRepository; -use libphonenumber\PhoneNumber; -use libphonenumber\PhoneNumberFormat; -use libphonenumber\PhoneNumberUtil; class ThirdPartyRepository implements ObjectRepository { private readonly EntityRepository $repository; - public function __construct(EntityManagerInterface $em, private readonly Connection $connection, private readonly PhoneNumberUtil $phonenumberUtil) + public function __construct(EntityManagerInterface $em, private readonly Connection $connection) { $this->repository = $em->getRepository(ThirdParty::class); } @@ -125,43 +122,6 @@ class ThirdPartyRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } - /** - * Finds third-party records by phone number. - * - * The search is performed agains every phonenumber field (there are two phonenumber on a thirdParty). - * - * @param string|PhoneNumber $phonenumber The phone number to search for. Can be a string or a PhoneNumber object. - * @param int $firstResult the index of the first result to retrieve (pagination start) - * @param int $maxResults the maximum number of results to retrieve (pagination limit) - * - * @return list the result set containing matching third-party records - */ - public function findByPhonenumber(string|PhoneNumber $phonenumber, int $firstResult = 0, int $maxResults = 20): array - { - if ('' === $phonenumber) { - return []; - } - - $qb = $this->createQueryBuilder('tp'); - $qb->select('tp'); - - $qb->where( - $qb->expr()->orX( - $qb->expr()->eq('tp.telephone', ':phonenumber'), - $qb->expr()->eq('tp.telephone2', ':phonenumber') - ) - ); - - $qb->setParameter( - 'phonenumber', - is_string($phonenumber) ? $phonenumber : $this->phonenumberUtil->format($phonenumber, PhoneNumberFormat::E164) - ); - - $qb->setFirstResult($firstResult)->setMaxResults($maxResults); - - return $qb->getQuery()->getResult(); - } - /** * Search amongst parties associated to $centers, with $terms parameters. * diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts b/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts index 2d2acc42b..20ae3309b 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/types.ts @@ -1,49 +1,47 @@ import { - Address, - Center, - Civility, - DateTime, - User, + Address, + Center, + Civility, + DateTime, + User, } from "ChillMainAssets/types"; export interface Thirdparty { - type: "thirdparty"; - text: string; - acronym: string | null; - active: boolean; - address: Address | null; - canonicalized: string | null; - categories: ThirdpartyCategory[]; - centers: Center[]; - children: Thirdparty[]; - civility: Civility | null; - comment: string | null; - contactDataAnonymous: boolean; - createdAt: DateTime; - createdBy: User | null; - email: string | null; - firstname: string | null; - id: number | null; - kind: string; - name: string; - nameCompany: string | null; - parent: Thirdparty | null; - profession: string; - telephone: string | null; - thirdPartyTypes: ThirdpartyType[] | null; - updatedAt: DateTime | null; - updatedBy: User | null; + acronym: string | null; + active: boolean; + address: Address | null; + canonicalized: string | null; + categories: ThirdpartyCategory[]; + centers: Center[]; + children: Thirdparty[]; + civility: Civility | null; + comment: string | null; + contactDataAnonymous: boolean; + createdAt: DateTime; + createdBy: User | null; + email: string | null; + firstname: string | null; + id: number | null; + kind: string; + name: string; + nameCompany: string | null; + parent: Thirdparty | null; + profession: string; + telephone: string | null; + thirdPartyTypes: ThirdpartyType[] | null; + updatedAt: DateTime | null; + updatedBy: User | null; } interface ThirdpartyType { - key: string; - value: string; + key: string; + value: string; } export interface ThirdpartyCategory { - id: number; - active: boolean; - name: { - fr: string; - }; + id: number; + active: boolean; + name: { + fr: string; + }; } diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue index 3413861ae..0fee3dcdb 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyRenderBox.vue @@ -1,131 +1,158 @@ diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyText.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyText.vue index dfd46efd9..2214605e2 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyText.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/Entity/ThirdPartyText.vue @@ -1,28 +1,28 @@ diff --git a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue index e3b2338bd..623926807 100644 --- a/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue +++ b/src/Bundle/ChillThirdPartyBundle/Resources/public/vuejs/_components/OnTheFly/ThirdParty.vue @@ -1,450 +1,449 @@