From 050a4feab586c8b0624c017692d267202aeedfaf Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Wed, 25 Jan 2023 11:55:13 +0100 Subject: [PATCH 01/45] Fix: [export][aggregator] group by user job, not by job --- .../AccompanyingCourseAggregators/JobAggregator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobAggregator.php index 5019d3a8b..1278bc21c 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobAggregator.php @@ -40,11 +40,11 @@ final class JobAggregator implements AggregatorInterface public function alterQuery(QueryBuilder $qb, $data) { - if (!in_array('acpjob', $qb->getAllAliases(), true)) { - $qb->leftJoin('acp.job', 'acpjob'); + if (!in_array('acpuser', $qb->getAllAliases(), true)) { + $qb->leftJoin('acp.user', 'acpuser'); } - $qb->addSelect('IDENTITY(acp.job) AS job_aggregator'); + $qb->addSelect('IDENTITY(acpuser.userJob) AS job_aggregator'); $qb->addGroupBy('job_aggregator'); } From 8e3a83de850d6798925512b70c85f69385d741be Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Wed, 25 Jan 2023 12:03:39 +0100 Subject: [PATCH 02/45] DX: [export] Rename JobAggregator to more logical UserJobAggregator --- .../{JobAggregator.php => UserJobAggregator.php} | 2 +- .../{JobAggregatorTest.php => UserJobAggregatorTest.php} | 6 +++--- .../config/services/exports_accompanying_course.yaml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/{JobAggregator.php => UserJobAggregator.php} (97%) rename src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/{JobAggregatorTest.php => UserJobAggregatorTest.php} (90%) diff --git a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobAggregator.php b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php similarity index 97% rename from src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobAggregator.php rename to src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php index 1278bc21c..a8acdcf12 100644 --- a/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/JobAggregator.php +++ b/src/Bundle/ChillPersonBundle/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregator.php @@ -19,7 +19,7 @@ use Doctrine\ORM\QueryBuilder; use Symfony\Component\Form\FormBuilderInterface; use function in_array; -final class JobAggregator implements AggregatorInterface +final class UserJobAggregator implements AggregatorInterface { private UserJobRepository $jobRepository; diff --git a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/JobAggregatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregatorTest.php similarity index 90% rename from src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/JobAggregatorTest.php rename to src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregatorTest.php index 18ef84c15..1ed748e32 100644 --- a/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/JobAggregatorTest.php +++ b/src/Bundle/ChillPersonBundle/Tests/Export/Aggregator/AccompanyingCourseAggregators/UserJobAggregatorTest.php @@ -13,16 +13,16 @@ namespace Chill\PersonBundle\Tests\Export\Aggregator\AccompanyingCourseAggregato use Chill\MainBundle\Test\Export\AbstractAggregatorTest; use Chill\PersonBundle\Entity\AccompanyingPeriod; -use Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\JobAggregator; +use Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\UserJobAggregator; use Doctrine\ORM\EntityManagerInterface; /** * @internal * @coversNothing */ -final class JobAggregatorTest extends AbstractAggregatorTest +final class UserJobAggregatorTest extends AbstractAggregatorTest { - private JobAggregator $aggregator; + private UserJobAggregator $aggregator; protected function setUp(): void { diff --git a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml index 6d0e160fd..bb2cfa556 100644 --- a/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml +++ b/src/Bundle/ChillPersonBundle/config/services/exports_accompanying_course.yaml @@ -136,7 +136,7 @@ services: - { name: chill.export_aggregator, alias: accompanyingcourse_scope_aggregator } chill.person.export.aggregator_referrer_job: - class: Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\JobAggregator + class: Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\UserJobAggregator tags: - { name: chill.export_aggregator, alias: accompanyingcourse_referrer_job_aggregator } From 2f091a639b57121d1c3bef4607f32a953d8a285b Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Thu, 26 Jan 2023 11:52:45 +0100 Subject: [PATCH 03/45] remove unused private function --- .../Filter/AccompanyingCourseFilters/UserJobFilter.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php index b97f97dda..2b69c528b 100644 --- a/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php +++ b/src/Bundle/ChillPersonBundle/Export/Filter/AccompanyingCourseFilters/UserJobFilter.php @@ -126,12 +126,4 @@ class UserJobFilter implements FilterInterface { return 'Filter by user job'; } - - private function getUserJob(): UserJob - { - /** @var User $user */ - $user = $this->security->getUser(); - - return $user->getUserJob(); - } } From 9ffe1ff8a8c0a0279445b5f60b236442a9126f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Mon, 30 Jan 2023 12:14:40 +0100 Subject: [PATCH 04/45] Fixed: fix loading of AddAddress A conflict was not resolved correctly during a merge. --- .../vuejs/Address/components/AddAddress/AddressSelection.vue | 5 ----- 1 file changed, 5 deletions(-) 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 f1cac5254..2c8e17687 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 @@ -98,11 +98,6 @@ export default { } }, }, - mounted() { - if (typeof this.value.point !== 'undefined') { - this.updateMapCenter(this.value.point); - } - }, methods: { transName(value) { return value.streetNumber === undefined ? value.street : `${value.streetNumber}, ${value.street}` From 56a17a0bcd56b6ac5fabda68bede2bcf81a78581 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 31 Jan 2023 14:38:51 +0100 Subject: [PATCH 05/45] FIX [parcours][repo] if user is not within scope of the parcours, but (s)he is the referrer the parcours will appear in the list of parcours of a person --- .../Repository/AccompanyingPeriodACLAwareRepository.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php index 7de509f1f..e28832bf9 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodACLAwareRepository.php @@ -187,8 +187,12 @@ final class AccompanyingPeriodACLAwareRepository implements AccompanyingPeriodAC ); foreach ($scopes as $key => $scope) { - $orx->add($qb->expr()->isMemberOf(':scope_' . $key, 'ap.scopes')); + $orx->add($qb->expr()->orX( + $qb->expr()->isMemberOf(':scope_' . $key, 'ap.scopes'), + $qb->expr()->eq('ap.user', ':user') + )); $qb->setParameter('scope_' . $key, $scope); + $qb->setParameter('user', $this->security->getUser()); } $qb->andWhere($orx); From 5c0d89a88b91e87eeab9249300bfb50c74a60d2e Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 31 Jan 2023 14:40:21 +0100 Subject: [PATCH 06/45] [phpcsfixer] --- src/Bundle/ChillPersonBundle/Search/PersonSearch.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Search/PersonSearch.php b/src/Bundle/ChillPersonBundle/Search/PersonSearch.php index c85f1dabd..8a70c1100 100644 --- a/src/Bundle/ChillPersonBundle/Search/PersonSearch.php +++ b/src/Bundle/ChillPersonBundle/Search/PersonSearch.php @@ -26,7 +26,6 @@ use Chill\PersonBundle\Repository\PersonACLAwareRepositoryInterface; use DateTime; use Exception; use libphonenumber\PhoneNumber; -use Symfony\Component\Form\Extension\Core\Type\TelType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Templating\EngineInterface; @@ -123,7 +122,6 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf // add quote if contains spaces (strpos($data[$key], ' ') !== false ? '"' . $data[$key] . '"' : $data[$key]) . ' '; - } foreach (['birthdate', 'birthdate-before', 'birthdate-after'] as $key) { From e5bc74d11db74c794aa41de2e7f5a6f99c76e65b Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Tue, 31 Jan 2023 16:10:12 +0100 Subject: [PATCH 07/45] FIX [duplicates][birthdate] also verify duplication based on birthdate if available. Modified precision from .15 to .30 --- .../Search/SimilarPersonMatcher.php | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php index afbaee07e..b8209eaeb 100644 --- a/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php +++ b/src/Bundle/ChillPersonBundle/Search/SimilarPersonMatcher.php @@ -62,7 +62,7 @@ class SimilarPersonMatcher public function matchPerson( Person $person, - float $precision = 0.15, + float $precision = 0.30, string $orderBy = self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY, bool $addYearComparison = false ) { @@ -72,41 +72,50 @@ class SimilarPersonMatcher ); $query = $this->em->createQuery(); - $dql = 'SELECT p from ChillPersonBundle:Person p ' - . ' WHERE (' - . ' SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) >= :precision ' - . ' ) ' - . ' AND p.center IN (:centers)'; + $qb = $this->em->createQueryBuilder(); + + $qb->select('p') + ->from(Person::class, 'p') + ->where('SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) >= :precision') + ->andWhere($qb->expr()->in('p.center', ':centers')); + + if (null !== $person->getBirthdate()) { + $qb->andWhere($qb->expr()->orX( + $qb->expr()->eq('p.birthdate', ':personBirthdate'), + $qb->expr()->isNull('p.birthdate') + )); + + $qb->setParameter('personBirthdate', $person->getBirthdate()); + } if ($person->getId() !== null) { - $dql .= ' AND p.id != :personId '; - $notDuplicatePersons = $this->personNotDuplicateRepository->findNotDuplicatePerson($person); - + $qb->andWhere($qb->expr()->neq('p.id', ':personId')); $query->setParameter('personId', $person->getId()); + $notDuplicatePersons = $this->personNotDuplicateRepository->findNotDuplicatePerson($person); + if (count($notDuplicatePersons)) { - $dql .= ' AND p.id not in (:notDuplicatePersons)'; + $qb->andWhere($qb->expr()->notIn('p.id', ':notDuplicatePersons')); $query->setParameter('notDuplicatePersons', $notDuplicatePersons); } } switch ($orderBy) { case self::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL: - $dql .= ' ORDER BY p.fullnameCanonical ASC '; + $qb->orderBy('p.fullnameCanonical', 'ASC'); break; case self::SIMILAR_SEARCH_ORDER_BY_SIMILARITY: default: - $dql .= ' ORDER BY SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName))) DESC '; + $qb->orderBy('SIMILARITY(p.fullnameCanonical, UNACCENT(LOWER(:fullName)))', 'DESC'); } - $query = $query - ->setDQL($dql) + $qb ->setParameter('fullName', $this->personRender->renderString($person, [])) ->setParameter('centers', $centers) ->setParameter('precision', $precision); - return $query->getResult(); + return $qb->getQuery()->getResult(); } } From 9f5b11e6cc87605381dcf72c5e2fbe65bd1f4130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 31 Jan 2023 16:30:19 +0000 Subject: [PATCH 08/45] Feature: allow to convert to PDF from Chill and group action button on document BREAKING CHANGE: avoid using the macro for download button. To keep the UI clean, use always the new "group of action buttons". --- .../Resources/views/Activity/show.html.twig | 2 +- .../Activity/showAccompanyingCourse.html.twig | 2 + .../views/Activity/showPerson.html.twig | 4 +- .../document_action_buttons_group/index.ts | 35 ++++ .../Resources/public/types.ts | 25 +++ .../vuejs/DocumentActionButtonsGroup.vue | 63 +++++++ .../Resources/public/vuejs/README.md | 5 + .../StoredObjectButton/ConvertButton.vue | 46 +++++ .../StoredObjectButton/DownloadButton.vue | 53 ++++++ .../StoredObjectButton/WopiEditButton.vue | 44 +++++ .../vuejs/StoredObjectButton/helpers.ts | 164 ++++++++++++++++++ .../_workflow.html.twig | 15 +- .../index.html.twig | 4 +- .../AccompanyingCourseDocument/show.html.twig | 9 +- .../views/Button/button_group.html.twig | 7 + .../Resources/views/List/list_item.html.twig | 14 +- .../Resources/views/Macro/macro.html.twig | 33 +++- .../views/PersonDocument/index.html.twig | 4 +- .../views/PersonDocument/show.html.twig | 22 +-- .../Templating/WopiEditTwigExtension.php | 4 + .../WopiEditTwigExtensionRuntime.php | 37 +++- .../chill.webpack.config.js | 1 + .../Resources/views/Workflow/index.html.twig | 2 + .../components/FormEvaluation.vue | 111 +++--------- .../_objectifs_results_evaluations.html.twig | 2 +- .../AccompanyingCourseWork/show.html.twig | 10 +- .../Workflow/_evaluation_document.html.twig | 9 +- .../src/Controller/Convert.php | 108 ++++++++++++ .../src/Resources/config/routes/routes.php | 4 + .../tests/Controller/ConverTest.php | 92 ++++++++++ 30 files changed, 770 insertions(+), 161 deletions(-) create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/public/types.ts create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts create mode 100644 src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig create mode 100644 src/Bundle/ChillWopiBundle/src/Controller/Convert.php create mode 100644 src/Bundle/ChillWopiBundle/tests/Controller/ConverTest.php diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig index 53bd3e8a7..1ec0ab274 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/show.html.twig @@ -168,7 +168,7 @@ {% if entity.documents|length > 0 %}
    {% for d in entity.documents %} -
  • {{ d.title }}{{ m.download_button(d) }}
  • +
  • {{ d.title }} {{ d|chill_document_button_group() }}
  • {% endfor %}
{% else %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig index fbd7b20b4..3486f47bc 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showAccompanyingCourse.html.twig @@ -8,12 +8,14 @@ {{ parent() }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }} {{ encore_entry_script_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }} {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} diff --git a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig index 5776eddb5..43a8eb86b 100644 --- a/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig +++ b/src/Bundle/ChillActivityBundle/Resources/views/Activity/showPerson.html.twig @@ -7,13 +7,13 @@ {% block js %} {{ parent() }} {{ encore_entry_script_tags('mod_notification_toggle_read_status') }} - {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} {{ encore_entry_link_tags('mod_notification_toggle_read_status') }} - {{ encore_entry_link_tags('mod_async_upload') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %} 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 new file mode 100644 index 000000000..ec1d50a86 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/module/document_action_buttons_group/index.ts @@ -0,0 +1,35 @@ +import {_createI18n} from "../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n"; +import DocumentActionButtonsGroup from "../../vuejs/DocumentActionButtonsGroup.vue"; +import {createApp} from "vue"; +import {StoredObject} from "../../types"; + +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, + small: string, + }; + + const + storedObject = JSON.parse(datasets.storedObject), + filename = datasets.filename, + canEdit = datasets.canEdit === '1', + small = datasets.small === '1' + ; + + return { storedObject, filename, canEdit, small }; + }, + template: '', + }); + + app.use(i18n).mount(el); + }) +}); diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts new file mode 100644 index 000000000..918526117 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/types.ts @@ -0,0 +1,25 @@ +import {DateTime} from "../../../ChillMainBundle/Resources/public/types"; + +export interface StoredObject { + id: number, + + /** + * filename of the object in the object storage + */ + filename: string, + creationDate: DateTime, + datas: object, + iv: number[], + keyInfos: object, + title: string, + type: string, + uuid: string +} + +/** + * Function executed by the WopiEditButton component. + */ +export type WopiEditButtonExecutableBeforeLeaveFunction = { + (): Promise +} + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue new file mode 100644 index 000000000..cb72fa5cd --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue @@ -0,0 +1,63 @@ + + + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md new file mode 100644 index 000000000..2d10dace8 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/README.md @@ -0,0 +1,5 @@ +# About buttons and components available + +## DocumentActionButtonsGroup + +This is an component to use to render a group of button with actions linked to a document. diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue new file mode 100644 index 000000000..aa99a223f --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue new file mode 100644 index 000000000..ca6a0f618 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue @@ -0,0 +1,53 @@ + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue new file mode 100644 index 000000000..d68f60f86 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue @@ -0,0 +1,44 @@ + + + + + + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts new file mode 100644 index 000000000..dc746ace7 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts @@ -0,0 +1,164 @@ + +const SUPPORTED_MIMES = new Set([ + 'image/svg+xml', + 'application/vnd.ms-powerpoint', + 'application/vnd.ms-excel', + 'application/vnd.sun.xml.writer', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.text-flat-xml', + 'application/vnd.sun.xml.calc', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.spreadsheet-flat-xml', + 'application/vnd.sun.xml.impress', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.oasis.opendocument.presentation-flat-xml', + 'application/vnd.sun.xml.draw', + 'application/vnd.oasis.opendocument.graphics', + 'application/vnd.oasis.opendocument.graphics-flat-xml', + 'application/vnd.oasis.opendocument.chart', + 'application/vnd.sun.xml.writer.global', + 'application/vnd.oasis.opendocument.text-master', + 'application/vnd.sun.xml.writer.template', + 'application/vnd.oasis.opendocument.text-template', + 'application/vnd.oasis.opendocument.text-master-template', + 'application/vnd.sun.xml.calc.template', + 'application/vnd.oasis.opendocument.spreadsheet-template', + 'application/vnd.sun.xml.impress.template', + 'application/vnd.oasis.opendocument.presentation-template', + 'application/vnd.sun.xml.draw.template', + 'application/vnd.oasis.opendocument.graphics-template', + 'application/msword', + '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.wordprocessingml.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.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/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/x-dif-document', + 'text/spreadsheet', + 'text/csv', + 'application/x-dbase', + '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', + 'text/rtf', + 'text/plain', + 'application/x-fictionbook+xml', + 'application/clarisworks', + 'image/x-wpg', + 'application/x-iwork-pages-sffpages', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + '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', +]); + +function is_extension_editable(mimeType: string): boolean { + return SUPPORTED_MIMES.has(mimeType); +} + +function build_convert_link(uuid: string) { + return `/chill/wopi/convert/${uuid}`; +} + +function build_download_info_link(object_name: string) { + return `/asyncupload/temp_url/generate/GET?object_name=${object_name}`; +} + +function build_wopi_editor_link(uuid: string, returnPath?: string) { + if (returnPath === undefined) { + returnPath = window.location.pathname + window.location.search + window.location.hash; + } + + 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() + } + + throw new Error('Could not download document'); + }); +} + +async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKey, iv: Uint8Array): Promise +{ + const algo = 'AES-CBC'; + // get an url to download the object + const downloadInfoResponse = await window.fetch(urlGenerator); + + if (!downloadInfoResponse.ok) { + throw new Error("error while downloading url " + downloadInfoResponse.status + " " + downloadInfoResponse.statusText); + } + + const downloadInfo = await downloadInfoResponse.json() as {url: string}; + const rawResponse = await window.fetch(downloadInfo.url); + + if (!rawResponse.ok) { + throw new Error("error while downloading raw file " + rawResponse.status + " " + rawResponse.statusText); + } + + const rawBuffer = await rawResponse.arrayBuffer(); + + try { + const key = await window.crypto.subtle + .importKey('jwk', keyData, { name: algo }, false, ['decrypt']); + const decrypted = await window.crypto.subtle + .decrypt({ name: algo, iv: iv }, key, rawBuffer); + + return Promise.resolve(new Blob([decrypted])); + } catch (e) { + console.error('get error while keys and decrypt operations'); + console.error(e); + + throw e; + } +} + +export { + build_convert_link, + build_download_info_link, + build_wopi_editor_link, + download_and_decrypt_doc, + download_doc, + is_extension_editable, +}; diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig index f914bd7f5..58d4903b4 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/_workflow.html.twig @@ -46,21 +46,8 @@ {% endif %}
  • - {{ m.download_button(document.object, document.title) }} + {{ document.object|chill_document_button_group(document.title, not freezed) }}
  • - {% if chill_document_is_editable(document.object) %} - {% if not freezed %} -
  • - {{ document.object|chill_document_edit_button({'title': document.title|e('html') }) }} -
  • - {% else %} -
  • - - {{ 'Update document'|trans }} - -
  • - {% endif %} - {% endif %} {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE', document) and document.course != null %}
  • diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig index df18efa71..7a013260c 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig @@ -8,16 +8,16 @@ {% block js %} {{ parent() }} - {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_docgen_picktemplate') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} - {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_docgen_picktemplate') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig index 45ed3988b..3c62451a9 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/show.html.twig @@ -14,6 +14,7 @@ {{ parent() }} {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} @@ -61,13 +62,8 @@
  • {% endif %}
  • - {{ m.download_button(document.object, document.title) }} + {{ document.object|chill_document_button_group(document.title) }}
  • - {% if chill_document_is_editable(document.object) %} -
  • - {{ document.object|chill_document_edit_button }} -
  • - {% endif %} {% set workflows_frame = chill_entity_workflow_list('Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument', document.id) %} {% if workflows_frame is not empty %}
  • @@ -86,4 +82,5 @@ {{ parent() }} {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig new file mode 100644 index 000000000..f83cafd51 --- /dev/null +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Button/button_group.html.twig @@ -0,0 +1,7 @@ +{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%} +
    diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig index 3963b0715..4cbcb7bef 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/List/list_item.html.twig @@ -53,15 +53,10 @@
  • - {% if chill_document_is_editable(document.object) %} -
  • - {{ document.object|chill_document_edit_button }} -
  • - {% endif %} {% endif %} {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_SEE_DETAILS', document) %}
  • - {{ m.download_button(document.object, document.title) }} + {{ document.object|chill_document_button_group(document.title, is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_UPDATE', document)) }}
  • @@ -80,15 +75,10 @@
  • - {% if chill_document_is_editable(document.object) %} -
  • - {{ document.object|chill_document_edit_button }} -
  • - {% endif %} {% endif %} {% if is_granted('CHILL_PERSON_DOCUMENT_SEE_DETAILS', document) %}
  • - {{ m.download_button(document.object, document.title) }} + {{ document.object|chill_document_button_group(document.title, is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document)) }}
  • diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig index 0e739d94b..199a86c15 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/Macro/macro.html.twig @@ -2,13 +2,13 @@ {% if storedObject is null %} {% else %} - {{ 'Download'|trans }} {% endif %} @@ -28,4 +28,21 @@ data-mime-type="{{ storedObject.type|escape('html_attr') }}" {% if filename is not null %}data-filename="{{ filename|escape('html_attr') }}"{% endif %}> {{ 'Download'|trans }} {% endif %} -{% endmacro %} \ No newline at end of file +{% endmacro %} + +{% macro download_button_group(storedObject, canEdit = true, filename = null, options = {}) %} + {% if storedObject is null %} + + {% else %} +
    + {% endif %} +{% endmacro %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig index 5dc359fa8..8d201605e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/index.html.twig @@ -27,16 +27,16 @@ {% block js %} {{ parent() }} - {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_docgen_picktemplate') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block css %} {{ parent() }} - {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_docgen_picktemplate') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig index b26aa4b8b..c276e067e 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig +++ b/src/Bundle/ChillDocStoreBundle/Resources/views/PersonDocument/show.html.twig @@ -24,7 +24,11 @@ {% block title %}{{ 'Detail of document of %name%'|trans({ '%name%': person|chill_entity_render_string } ) }}{% endblock %} {% block js %} - {{ encore_entry_script_tags('mod_async_upload') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} +{% endblock %} + +{% block css %} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %} @@ -70,6 +74,10 @@
  • {% endif %} +
  • + {{ document.object|chill_document_button_group(document.title, is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document)) }} +
  • + {% if is_granted('CHILL_PERSON_DOCUMENT_UPDATE', document) %}
  • @@ -77,16 +85,4 @@
  • {% endif %} - -
  • - {{ m.download_button(document.object, document.title) }} -
  • - - {% if chill_document_is_editable(document.object) %} -
  • - {{ document.object|chill_document_edit_button }} -
  • - {% endif %} - - {# {{ include('ChillDocStoreBundle:PersonDocument:_delete_form.html.twig') }} #} {% endblock %} diff --git a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtension.php b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtension.php index 43dc28f15..754c3fb3c 100644 --- a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtension.php +++ b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtension.php @@ -24,6 +24,10 @@ class WopiEditTwigExtension extends AbstractExtension 'needs_environment' => true, 'is_safe' => ['html'], ]), + new TwigFilter('chill_document_button_group', [WopiEditTwigExtensionRuntime::class, 'renderButtonGroup'], [ + 'needs_environment' => true, + 'is_safe' => ['html'], + ]), ]; } diff --git a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php index f53d6336b..bae436b0a 100644 --- a/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php +++ b/src/Bundle/ChillDocStoreBundle/Templating/WopiEditTwigExtensionRuntime.php @@ -13,6 +13,8 @@ namespace Chill\DocStoreBundle\Templating; use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface; use Chill\DocStoreBundle\Entity\StoredObject; +use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Twig\Environment; use Twig\Extension\RuntimeExtensionInterface; @@ -112,20 +114,53 @@ final class WopiEditTwigExtensionRuntime implements RuntimeExtensionInterface 'application/pdf', ]; + private const DEFAULT_OPTIONS_TEMPLATE_BUTTON_GROUP = [ + 'small' => false, + ]; + private const TEMPLATE = '@ChillDocStore/Button/wopi_edit_document.html.twig'; + private const TEMPLATE_BUTTON_GROUP = '@ChillDocStore/Button/button_group.html.twig'; + private DiscoveryInterface $discovery; - public function __construct(DiscoveryInterface $discovery) + private NormalizerInterface $normalizer; + + public function __construct(DiscoveryInterface $discovery, NormalizerInterface $normalizer) { $this->discovery = $discovery; + $this->normalizer = $normalizer; } + /** + * return true if the document is editable. + * + * **NOTE**: as the Vue button does have similar test, this is not required if in use with + * the dedicated Vue component (GroupDownloadButton.vue, WopiEditButton.vue) + */ public function isEditable(StoredObject $document): bool { return in_array($document->getType(), self::SUPPORTED_MIMES, true); } + /** + * @param array{small: boolean} $options + * + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError + */ + public function renderButtonGroup(Environment $environment, StoredObject $document, ?string $title = null, bool $canEdit = true, array $options = []): string + { + return $environment->render(self::TEMPLATE_BUTTON_GROUP, [ + 'document' => $document, + 'document_json' => $this->normalizer->normalize($document, 'json', [AbstractNormalizer::GROUPS => ['read']]), + 'title' => $title, + 'can_edit' => $canEdit, + 'options' => array_merge($options, self::DEFAULT_OPTIONS_TEMPLATE_BUTTON_GROUP), + ]); + } + public function renderEditButton(Environment $environment, StoredObject $document, ?array $options = null): string { return $environment->render(self::TEMPLATE, [ diff --git a/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js b/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js index c9b2b8877..3499fcf55 100644 --- a/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js +++ b/src/Bundle/ChillDocStoreBundle/chill.webpack.config.js @@ -4,4 +4,5 @@ module.exports = function(encore) ChillDocStoreAssets: __dirname + '/Resources/public' }); encore.addEntry('mod_async_upload', __dirname + '/Resources/public/module/async_upload/index.js'); + encore.addEntry('mod_document_action_buttons_group', __dirname + '/Resources/public/module/document_action_buttons_group/index'); }; diff --git a/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig b/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig index 1a6f3103b..782eae136 100644 --- a/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig +++ b/src/Bundle/ChillMainBundle/Resources/views/Workflow/index.html.twig @@ -11,6 +11,7 @@ {{ 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') }} {% endblock %} {% block css %} @@ -19,6 +20,7 @@ {{ 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') }} {% endblock %} {% import '@ChillMain/Workflow/macro_breadcrumb.html.twig' as macro %} 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 66918ad71..4bf9e1a8a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourseWorkEdit/components/FormEvaluation.vue @@ -111,14 +111,12 @@
  • - - -
  • -
  • - +
  • @@ -174,6 +172,7 @@ import AddAsyncUpload from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUpload import AddAsyncUploadDownloader from 'ChillDocStoreAssets/vuejs/_components/AddAsyncUploadDownloader.vue'; import ListWorkflowModal from 'ChillMainAssets/vuejs/_components/EntityWorkflow/ListWorkflowModal.vue'; import {buildLinkCreate} from 'ChillMainAssets/lib/entity-workflow/api.js'; +import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue"; const i18n = { messages: { @@ -212,6 +211,7 @@ export default { AddAsyncUpload, AddAsyncUploadDownloader, ListWorkflowModal, + DocumentActionButtonsGroup, }, i18n, data() { @@ -223,78 +223,6 @@ export default { maxPostSize: 15000000, required: false, }, - mime: [ - // TODO temporary hardcoded. to be replaced by twig extension or a collabora server query - 'application/clarisworks', - 'application/coreldraw', - 'application/macwriteii', - 'application/msword', - 'application/vnd.lotus-1-2-3', - 'application/vnd.ms-excel', - 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', - 'application/vnd.ms-excel.sheet.macroEnabled.12', - 'application/vnd.ms-excel.template.macroEnabled.12', - 'application/vnd.ms-powerpoint', - 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', - 'application/vnd.ms-powerpoint.template.macroEnabled.12', - 'application/vnd.ms-visio.drawing', - 'application/vnd.ms-word.document.macroEnabled.12', - 'application/vnd.ms-word.template.macroEnabled.12', - 'application/vnd.ms-works', - 'application/vnd.oasis.opendocument.chart', - 'application/vnd.oasis.opendocument.formula', - 'application/vnd.oasis.opendocument.graphics', - 'application/vnd.oasis.opendocument.graphics-flat-xml', - 'application/vnd.oasis.opendocument.graphics-template', - 'application/vnd.oasis.opendocument.presentation', - 'application/vnd.oasis.opendocument.presentation-flat-xml', - 'application/vnd.oasis.opendocument.presentation-template', - 'application/vnd.oasis.opendocument.spreadsheet', - 'application/vnd.oasis.opendocument.spreadsheet-flat-xml', - 'application/vnd.oasis.opendocument.spreadsheet-template', - 'application/vnd.oasis.opendocument.text', - 'application/vnd.oasis.opendocument.text-flat-xml', - 'application/vnd.oasis.opendocument.text-master', - 'application/vnd.oasis.opendocument.text-master-template', - 'application/vnd.oasis.opendocument.text-template', - 'application/vnd.oasis.opendocument.text-web', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 'application/vnd.openxmlformats-officedocument.presentationml.template', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'application/vnd.sun.xml.calc', - 'application/vnd.sun.xml.calc.template', - 'application/vnd.sun.xml.chart', - 'application/vnd.sun.xml.draw', - 'application/vnd.sun.xml.draw.template', - 'application/vnd.sun.xml.impress', - 'application/vnd.sun.xml.impress.template', - 'application/vnd.sun.xml.math', - 'application/vnd.sun.xml.writer', - 'application/vnd.sun.xml.writer.global', - 'application/vnd.sun.xml.writer.template', - 'application/vnd.visio', - 'application/vnd.visio2013', - 'application/vnd.wordperfect', - 'application/x-abiword', - 'application/x-aportisdoc', - 'application/x-dbase', - 'application/x-dif-document', - 'application/x-fictionbook+xml', - 'application/x-gnumeric', - 'application/x-hwp', - 'application/x-iwork-keynote-sffkey', - 'application/x-iwork-numbers-sffnumbers', - 'application/x-iwork-pages-sffpages', - 'application/x-mspublisher', - 'application/x-mswrite', - 'application/x-pagemaker', - 'application/x-sony-bbeb', - 'application/x-t602', - ] } }, computed: { @@ -343,10 +271,6 @@ export default { }, methods: { ISOToDatetime, - canEditDocument(document) { - return 'storedObject' in document ? - this.mime.includes(document.storedObject.type) : false; - }, listAllStatus() { console.log('load all status'); let url = `/api/`; @@ -359,10 +283,25 @@ export default { }) ; }, - buildEditLink(storedObject) { - return `/chill/wopi/edit/${storedObject.uuid}?returnPath=` + encodeURIComponent( + buildEditLink(document) { + return `/chill/wopi/edit/${document.storedObject.uuid}?returnPath=` + encodeURIComponent( window.location.pathname + window.location.search + window.location.hash); }, + submitBeforeLeaveToEditor() { + console.log('submit beore edit 2'); + // empty callback + const callback = () => null; + return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); + }, + submitBeforeEdit(storedObject) { + const callback = (data) => { + let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key); + let document = evaluation.documents.find(d => d.storedObject.id === storedObject.id); + //console.log('=> document', document); + window.location.assign(this.buildEditLink(document)); + }; + return this.$store.dispatch('submit', callback).catch(e => { console.log(e); throw e; }); + }, submitBeforeGenerate({template}) { const callback = (data) => { let evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key).id; diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig index aa3838348..63e4ff638 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig @@ -142,7 +142,7 @@ {{ mm.mimeIcon(d.storedObject.type) }}
    - {{ m.download_button_small(d.storedObject, d.title) }} + {{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}
    {% endfor %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig index 9e47b61d1..bddffebdf 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/AccompanyingCourseWork/show.html.twig @@ -6,20 +6,20 @@ {% block css %} {{ parent() }} - {{ encore_entry_link_tags('mod_async_upload') }} {{ encore_entry_link_tags('mod_entity_workflow_pick') }} + {{ encore_entry_link_tags('mod_document_action_buttons_group') }} {% endblock %} {% block js %} {{ parent() }} - {{ encore_entry_script_tags('mod_async_upload') }} {{ encore_entry_script_tags('mod_entity_workflow_pick') }} + {{ encore_entry_script_tags('mod_document_action_buttons_group') }} {% endblock %} {% block content %}
    {% endblock %} diff --git a/src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation_document.html.twig b/src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation_document.html.twig index 5ab93896a..e5d8f9c9a 100644 --- a/src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation_document.html.twig +++ b/src/Bundle/ChillPersonBundle/Resources/views/Workflow/_evaluation_document.html.twig @@ -120,20 +120,13 @@ {% if display_action is defined and display_action == true %} - {% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork) %} - {% endif %} {% endif %} {% endif %} diff --git a/src/Bundle/ChillWopiBundle/src/Controller/Convert.php b/src/Bundle/ChillWopiBundle/src/Controller/Convert.php new file mode 100644 index 000000000..0a4dc8762 --- /dev/null +++ b/src/Bundle/ChillWopiBundle/src/Controller/Convert.php @@ -0,0 +1,108 @@ +httpClient = $httpClient; + $this->security = $security; + $this->storedObjectManager = $storedObjectManager; + $this->logger = $logger; + $this->collaboraDomain = $parameters->get('wopi')['server']; + } + + public function __invoke(StoredObject $storedObject): Response + { + if (!$this->security->getUser() instanceof User) { + throw new AccessDeniedHttpException('User must be authenticated'); + } + + $content = $this->storedObjectManager->read($storedObject); + + try { + $url = sprintf('%s/cool/convert-to/pdf', $this->collaboraDomain); + $form = new FormDataPart([ + 'data' => new DataPart($content, $storedObject->getUuid()->toString(), $storedObject->getType()), + ]); + $response = $this->httpClient->request('POST', $url, [ + 'headers' => $form->getPreparedHeaders()->toArray(), + 'body' => $form->bodyToString(), + 'timeout' => 10, + ]); + + return new Response($response->getContent(), Response::HTTP_OK, [ + 'Content-Type' => 'application/pdf', + ]); + } catch (ClientExceptionInterface $exception) { + return $this->onConversionFailed($url, $response); + } catch (RedirectionExceptionInterface $e) { + return $this->onConversionFailed($url, $response); + } catch (ServerExceptionInterface $e) { + return $this->onConversionFailed($url, $response); + } catch (TransportExceptionInterface $e) { + return $this->onConversionFailed($url, $response); + } + } + + private function onConversionFailed(string $url, ResponseInterface $response): JsonResponse + { + $this->logger->error(self::LOG_PREFIX . ' could not convert document', [ + 'response_status' => $response->getStatusCode(), + 'message' => $response->getContent(false), + 'server' => $this->collaboraDomain, + 'url' => $url, + ]); + + return new JsonResponse(['message' => 'conversion failed : ' . $response->getContent(false)], Response::HTTP_SERVICE_UNAVAILABLE); + } +} diff --git a/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php b/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php index ddf4d5531..141799207 100644 --- a/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php +++ b/src/Bundle/ChillWopiBundle/src/Resources/config/routes/routes.php @@ -16,4 +16,8 @@ return static function (RoutingConfigurator $routes) { $routes ->add('chill_wopi_file_edit', '/edit/{fileId}') ->controller(Editor::class); + + $routes + ->add('chill_wopi_object_convert', '/convert/{uuid}') + ->controller(\Chill\WopiBundle\Controller\Convert::class); }; diff --git a/src/Bundle/ChillWopiBundle/tests/Controller/ConverTest.php b/src/Bundle/ChillWopiBundle/tests/Controller/ConverTest.php new file mode 100644 index 000000000..27d162a34 --- /dev/null +++ b/src/Bundle/ChillWopiBundle/tests/Controller/ConverTest.php @@ -0,0 +1,92 @@ +setType('application/vnd.oasis.opendocument.text'); + + $httpClient = new MockHttpClient([ + new MockResponse('not authorized', ['http_code' => 401]), + ], 'http://collabora:9980'); + + $security = $this->prophesize(Security::class); + $security->getUser()->willReturn(new User()); + + $storeManager = $this->prophesize(StoredObjectManagerInterface::class); + $storeManager->read($storedObject)->willReturn('content'); + + $parameterBag = new ParameterBag(['wopi' => ['server' => 'http://collabora:9980']]); + + $convert = new Convert( + $httpClient, + $security->reveal(), + $storeManager->reveal(), + new NullLogger(), + $parameterBag + ); + + $response = $convert($storedObject); + + $this->assertNotEquals(200, $response->getStatusCode()); + } + + public function testEverythingWentFine(): void + { + $storedObject = (new StoredObject())->setType('application/vnd.oasis.opendocument.text'); + + $httpClient = new MockHttpClient([ + new MockResponse('1234', ['http_code' => 200]), + ], 'http://collabora:9980'); + + $security = $this->prophesize(Security::class); + $security->getUser()->willReturn(new User()); + + $storeManager = $this->prophesize(StoredObjectManagerInterface::class); + $storeManager->read($storedObject)->willReturn('content'); + + $parameterBag = new ParameterBag(['wopi' => ['server' => 'http://collabora:9980']]); + + $convert = new Convert( + $httpClient, + $security->reveal(), + $storeManager->reveal(), + new NullLogger(), + $parameterBag + ); + + $response = $convert($storedObject); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('1234', $response->getContent()); + } +} From 885256ac0dc6c633dedfb6945ec5d767d0f3ee85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 31 Jan 2023 18:22:14 +0100 Subject: [PATCH 09/45] Fixed: default value for type --- src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php b/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php index 73060f40b..f12ba3d72 100644 --- a/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php +++ b/src/Bundle/ChillBudgetBundle/Entity/AbstractElement.php @@ -75,7 +75,7 @@ abstract class AbstractElement /** * @ORM\Column(name="type", type="string", length=255) */ - private string $type; + private string $type = ''; /*Getters and Setters */ From de55ff920f1ffee00b0d5ea47aa5028a23dff5a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 31 Jan 2023 19:41:43 +0100 Subject: [PATCH 10/45] Fixed: [budget] remove deprecated config repository and fix summary budget when generating document --- .../Config/ConfigRepository.php | 102 ------------------ .../ChillBudgetExtension.php | 1 - .../ChillBudgetBundle/Form/ChargeType.php | 22 ---- .../ChillBudgetBundle/Form/ResourceType.php | 21 ---- .../Service/Summary/SummaryBudget.php | 94 +++++++++------- .../ChillBudgetBundle/Templating/Twig.php | 65 ----------- .../config/services/config.yaml | 5 - .../vuejs/StoredObjectButton/helpers.ts | 4 + 8 files changed, 61 insertions(+), 253 deletions(-) delete mode 100644 src/Bundle/ChillBudgetBundle/Config/ConfigRepository.php delete mode 100644 src/Bundle/ChillBudgetBundle/Templating/Twig.php delete mode 100644 src/Bundle/ChillBudgetBundle/config/services/config.yaml diff --git a/src/Bundle/ChillBudgetBundle/Config/ConfigRepository.php b/src/Bundle/ChillBudgetBundle/Config/ConfigRepository.php deleted file mode 100644 index f28a83a4d..000000000 --- a/src/Bundle/ChillBudgetBundle/Config/ConfigRepository.php +++ /dev/null @@ -1,102 +0,0 @@ -resources = $resources; - $this->charges = $charges; - } - - public function getChargesKeys(bool $onlyActive = false): array - { - return array_map(static function ($element) { - return $element['key']; - }, $this->getCharges($onlyActive)); - } - - /** - * @return array where keys are the resource'key and label the ressource label - */ - public function getChargesLabels(bool $onlyActive = false) - { - $charges = []; - - foreach ($this->getCharges($onlyActive) as $definition) { - $charges[$definition['key']] = $this->normalizeLabel($definition['labels']); - } - - return $charges; - } - - public function getResourcesKeys(bool $onlyActive = false): array - { - return array_map(static function ($element) { - return $element['key']; - }, $this->getResources($onlyActive)); - } - - /** - * @return array where keys are the resource'key and label the ressource label - */ - public function getResourcesLabels(bool $onlyActive = false) - { - $resources = []; - - foreach ($this->getResources($onlyActive) as $definition) { - $resources[$definition['key']] = $this->normalizeLabel($definition['labels']); - } - - return $resources; - } - - private function getCharges(bool $onlyActive = false): array - { - return $onlyActive ? - array_filter($this->charges, static function ($el) { - return $el['active']; - }) - : $this->charges; - } - - private function getResources(bool $onlyActive = false): array - { - return $onlyActive ? - array_filter($this->resources, static function ($el) { - return $el['active']; - }) - : $this->resources; - } - - private function normalizeLabel($labels) - { - $normalizedLabels = []; - - foreach ($labels as $labelDefinition) { - $normalizedLabels[$labelDefinition['lang']] = $labelDefinition['label']; - } - - return $normalizedLabels; - } -} diff --git a/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php b/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php index ed7fd1ae9..57976f846 100644 --- a/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php +++ b/src/Bundle/ChillBudgetBundle/DependencyInjection/ChillBudgetExtension.php @@ -35,7 +35,6 @@ class ChillBudgetExtension extends Extension implements PrependExtensionInterfac $config = $this->processConfiguration($configuration, $configs); $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config')); - $loader->load('services/config.yaml'); $loader->load('services/form.yaml'); $loader->load('services/repository.yaml'); $loader->load('services/security.yaml'); diff --git a/src/Bundle/ChillBudgetBundle/Form/ChargeType.php b/src/Bundle/ChillBudgetBundle/Form/ChargeType.php index 3dc2e230c..3356057cf 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ChargeType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ChargeType.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\BudgetBundle\Form; -use Chill\BudgetBundle\Config\ConfigRepository; use Chill\BudgetBundle\Entity\Charge; use Chill\BudgetBundle\Entity\ChargeKind; use Chill\BudgetBundle\Repository\ChargeKindRepository; @@ -25,13 +24,9 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\Translation\TranslatorInterface; -use function array_flip; -use function asort; class ChargeType extends AbstractType { - protected ConfigRepository $configRepository; - protected TranslatableStringHelperInterface $translatableStringHelper; private ChargeKindRepository $repository; @@ -39,12 +34,10 @@ class ChargeType extends AbstractType private TranslatorInterface $translator; public function __construct( - ConfigRepository $configRepository, TranslatableStringHelperInterface $translatableStringHelper, ChargeKindRepository $repository, TranslatorInterface $translator ) { - $this->configRepository = $configRepository; $this->translatableStringHelper = $translatableStringHelper; $this->repository = $repository; $this->translator = $translator; @@ -116,19 +109,4 @@ class ChargeType extends AbstractType { return 'chill_budgetbundle_charge'; } - - private function getTypes() - { - $charges = $this->configRepository - ->getChargesLabels(true); - - // rewrite labels to filter in language - foreach ($charges as $key => $labels) { - $charges[$key] = $this->translatableStringHelper->localize($labels); - } - - asort($charges); - - return array_flip($charges); - } } diff --git a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php index 106f50ad1..fd859217a 100644 --- a/src/Bundle/ChillBudgetBundle/Form/ResourceType.php +++ b/src/Bundle/ChillBudgetBundle/Form/ResourceType.php @@ -11,7 +11,6 @@ declare(strict_types=1); namespace Chill\BudgetBundle\Form; -use Chill\BudgetBundle\Config\ConfigRepository; use Chill\BudgetBundle\Entity\Resource; use Chill\BudgetBundle\Entity\ResourceKind; use Chill\BudgetBundle\Repository\ResourceKindRepository; @@ -24,12 +23,9 @@ use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Contracts\Translation\TranslatorInterface; -use function array_flip; class ResourceType extends AbstractType { - protected ConfigRepository $configRepository; - protected TranslatableStringHelperInterface $translatableStringHelper; private ResourceKindRepository $repository; @@ -37,12 +33,10 @@ class ResourceType extends AbstractType private TranslatorInterface $translator; public function __construct( - ConfigRepository $configRepository, TranslatableStringHelperInterface $translatableStringHelper, ResourceKindRepository $repository, TranslatorInterface $translator ) { - $this->configRepository = $configRepository; $this->translatableStringHelper = $translatableStringHelper; $this->repository = $repository; $this->translator = $translator; @@ -98,19 +92,4 @@ class ResourceType extends AbstractType { return 'chill_budgetbundle_resource'; } - - private function getTypes() - { - $resources = $this->configRepository - ->getResourcesLabels(true); - - // rewrite labels to filter in language - foreach ($resources as $key => $labels) { - $resources[$key] = $this->translatableStringHelper->localize($labels); - } - - asort($resources); - - return array_flip($resources); - } } diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index f02d2e64a..096531c52 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -11,13 +11,17 @@ declare(strict_types=1); namespace Chill\BudgetBundle\Service\Summary; -use Chill\BudgetBundle\Config\ConfigRepository; +use Chill\BudgetBundle\Entity\ChargeKind; +use Chill\BudgetBundle\Entity\ResourceKind; +use Chill\BudgetBundle\Repository\ChargeKindRepository; +use Chill\BudgetBundle\Repository\ResourceKindRepository; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Query\ResultSetMapping; use LogicException; +use RuntimeException; use function count; /** @@ -25,31 +29,36 @@ use function count; */ class SummaryBudget implements SummaryBudgetInterface { - private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, charge_id AS kind_id FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY charge_id'; - private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_CHARGE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, charge_id AS kind_id FROM chill_budget.charge WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY charge_id'; - private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_RESOURCE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, resource_id AS kind_id FROM chill_budget.resource WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY resource_id'; - private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, type FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY type'; + private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, resource_id AS kind_id FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY resource_id'; + + private ChargeKindRepository $chargeKindRepository; private array $chargeLabels; - private ConfigRepository $configRepository; - private EntityManagerInterface $em; + private ResourceKindRepository $resourceKindRepository; + private array $resourcesLabels; private TranslatableStringHelperInterface $translatableStringHelper; - public function __construct(EntityManagerInterface $em, ConfigRepository $configRepository, TranslatableStringHelperInterface $translatableStringHelper) - { + public function __construct( + EntityManagerInterface $em, + TranslatableStringHelperInterface $translatableStringHelper, + ResourceKindRepository $resourceKindRepository, + ChargeKindRepository $chargeKindRepository + ) { $this->em = $em; - $this->configRepository = $configRepository; - $this->chargeLabels = $configRepository->getChargesLabels(); - $this->resourcesLabels = $configRepository->getResourcesLabels(); $this->translatableStringHelper = $translatableStringHelper; + $this->resourceKindRepository = $resourceKindRepository; + $this->chargeKindRepository = $chargeKindRepository; } public function getSummaryForHousehold(?Household $household): array @@ -112,7 +121,7 @@ class SummaryBudget implements SummaryBudgetInterface $rsm = new ResultSetMapping(); $rsm ->addScalarResult('sum', 'sum') - ->addScalarResult('type', 'type') + ->addScalarResult('kind_id', 'kind_id') ->addScalarResult('comment', 'comment'); return $rsm; @@ -120,51 +129,62 @@ class SummaryBudget implements SummaryBudgetInterface private function getEmptyChargeArray(): array { - $keys = $this->configRepository->getChargesKeys(); - $labels = $this->chargeLabels; + $keys = array_map(static fn (ChargeKind $kind) => $kind->getId(), $this->chargeKindRepository->findAll()); - return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => '']; + return array_combine($keys, array_map(function ($id) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->chargeKindRepository->find($id)->getName()), 'comment' => '']; }, $keys)); } private function getEmptyResourceArray(): array { - $keys = $this->configRepository->getResourcesKeys(); - $labels = $this->resourcesLabels; + $keys = array_map(static fn (ResourceKind $kind) => $kind->getId(), $this->resourceKindRepository->findAll()); - return array_combine($keys, array_map(function ($i) use ($labels) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($labels[$i]), 'comment' => '']; + return array_combine($keys, array_map(function ($id) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->resourceKindRepository->find($id)->getName()), 'comment' => '']; }, $keys)); } private function rowToArray(array $rows, string $kind): array { + $result = []; + switch ($kind) { case 'charge': - $label = $this->chargeLabels; + foreach ($rows as $row) { + $chargeKind = $this->chargeKindRepository->find($row['kind_id']); - break; + if (null === $chargeKind) { + throw new RuntimeException('charge kind not found'); + } + $result[$chargeKind->getKind()] = [ + 'sum' => (float) $row['sum'], + 'label' => $this->translatableStringHelper->localize($chargeKind->getName()), + 'comment' => (string) $row['comment'], + ]; + } + + return $result; case 'resource': - $label = $this->resourcesLabels; + foreach ($rows as $row) { + $resourceKind = $this->resourceKindRepository->find($row['kind_id']); - break; + if (null === $resourceKind) { + throw new RuntimeException('charge kind not found'); + } + + $result[$resourceKind->getKind()] = [ + 'sum' => (float) $row['sum'], + 'label' => $this->translatableStringHelper->localize($resourceKind->getName()), + 'comment' => (string) $row['comment'], + ]; + } + + return $result; default: throw new LogicException(); } - - $result = []; - - foreach ($rows as $row) { - $result[$row['type']] = [ - 'sum' => (float) $row['sum'], - 'label' => $this->translatableStringHelper->localize($label[$row['type']]), - 'comment' => (string) $row['comment'], - ]; - } - - return $result; } } diff --git a/src/Bundle/ChillBudgetBundle/Templating/Twig.php b/src/Bundle/ChillBudgetBundle/Templating/Twig.php deleted file mode 100644 index b4395f375..000000000 --- a/src/Bundle/ChillBudgetBundle/Templating/Twig.php +++ /dev/null @@ -1,65 +0,0 @@ -configRepository = $configRepository; - $this->translatableStringHelper = $translatableStringHelper; - } - - public function displayLink($link, $family) - { - switch ($family) { - case 'resource': - return $this->translatableStringHelper->localize( - $this->configRepository->getResourcesLabels()[$link] - ); - - case 'charge': - return $this->translatableStringHelper->localize( - $this->configRepository->getChargesLabels()[$link] - ); - - default: - throw new UnexpectedValueException("This family of element: {$family} is not " - . "supported. Supported families are 'resource', 'charge'"); - } - } - - public function getFilters() - { - return [ - new TwigFilter('budget_element_type_display', [$this, 'displayLink'], ['is_safe' => ['html']]), - ]; - } -} diff --git a/src/Bundle/ChillBudgetBundle/config/services/config.yaml b/src/Bundle/ChillBudgetBundle/config/services/config.yaml deleted file mode 100644 index 21136db2f..000000000 --- a/src/Bundle/ChillBudgetBundle/config/services/config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -services: - Chill\BudgetBundle\Config\ConfigRepository: - arguments: - $resources: '%chill_budget.resources%' - $charges: '%chill_budget.charges%' diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts index dc746ace7..58d2ebc71 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts @@ -137,6 +137,10 @@ async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKe throw new Error("error while downloading raw file " + rawResponse.status + " " + rawResponse.statusText); } + if (iv.length === 0) { + return rawResponse.blob(); + } + const rawBuffer = await rawResponse.arrayBuffer(); try { From 16ec858ee8954ebc53247e6833d2f773988506b3 Mon Sep 17 00:00:00 2001 From: Julie Lenaerts Date: Wed, 1 Feb 2023 14:13:29 +0100 Subject: [PATCH 11/45] FIX [migration][budget] migration to change the comment on tags column from jsonb to json --- .../migrations/Version20230201131008.php | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/Bundle/ChillBudgetBundle/migrations/Version20230201131008.php diff --git a/src/Bundle/ChillBudgetBundle/migrations/Version20230201131008.php b/src/Bundle/ChillBudgetBundle/migrations/Version20230201131008.php new file mode 100644 index 000000000..7131c31f2 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/migrations/Version20230201131008.php @@ -0,0 +1,37 @@ +addSql('COMMENT ON COLUMN chill_budget.resource_type.tags IS \'(DC2Type:json)\''); + $this->addSql('COMMENT ON COLUMN chill_budget.charge_type.tags IS \'(DC2Type:json)\''); + } +} From 88eefa698bab8993708e7585c2624b2c0a3f5231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 7 Feb 2023 15:09:40 +0100 Subject: [PATCH 12/45] Fixed: add string key for summary of charges and resources into doc generation --- .../Repository/ChargeKindRepository.php | 11 +- .../ChargeKindRepositoryInterface.php | 49 ++++++ .../Repository/ResourceKindRepository.php | 11 +- .../ResourceKindRepositoryInterface.php | 49 ++++++ .../Repository/ResourceRepository.php | 2 +- .../Service/Summary/SummaryBudget.php | 36 ++-- .../Service/Summary/SummaryBudgetTest.php | 155 ++++++++++++++++++ .../ParticipationOverlapValidatorTest.php | 62 +++++++ 8 files changed, 349 insertions(+), 26 deletions(-) create mode 100644 src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php create mode 100644 src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php create mode 100644 src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php create mode 100644 src/Bundle/ChillPersonBundle/Tests/Validator/AccompanyingPeriod/ParticipationOverlapValidatorTest.php diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php index e170a362a..10d02749a 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php @@ -14,9 +14,8 @@ namespace Chill\BudgetBundle\Repository; use Chill\BudgetBundle\Entity\ChargeKind; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; -use Doctrine\Persistence\ObjectRepository; -class ChargeKindRepository implements ObjectRepository +final class ChargeKindRepository implements ChargeKindRepositoryInterface { private EntityRepository $repository; @@ -50,7 +49,8 @@ class ChargeKindRepository implements ObjectRepository ->where($qb->expr()->eq('c.isActive', 'true')) ->orderBy('c.ordering', 'ASC') ->getQuery() - ->getResult(); + ->getResult() + ; } /** @@ -77,6 +77,11 @@ class ChargeKindRepository implements ObjectRepository return $this->repository->findOneBy($criteria); } + public function findOneByKind(string $kind): ?ChargeKind + { + return $this->repository->findOneBy(['kind' => $kind]); + } + public function getClassName(): string { return ChargeKind::class; diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php new file mode 100644 index 000000000..5099a5674 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php @@ -0,0 +1,49 @@ +where($qb->expr()->eq('r.isActive', 'true')) ->orderBy('r.ordering', 'ASC') ->getQuery() - ->getResult(); + ->getResult() + ; } /** @@ -77,6 +77,11 @@ class ResourceKindRepository implements ObjectRepository return $this->repository->findOneBy($criteria); } + public function findOneByKind(string $kind): ?ResourceKind + { + return $this->repository->findOneBy(['kind' => $kind]); + } + public function getClassName(): string { return ResourceKind::class; diff --git a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php new file mode 100644 index 000000000..658a87a3d --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php @@ -0,0 +1,49 @@ +andWhere('c.startDate < :date') // TODO: there is a misconception here, the end date must be lower or null. startDate are never null //->andWhere('c.startDate < :date OR c.startDate IS NULL'); -; + ; if (null !== $sort) { $qb->orderBy($sort); diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index 096531c52..b7a88bb9d 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -14,7 +14,9 @@ namespace Chill\BudgetBundle\Service\Summary; use Chill\BudgetBundle\Entity\ChargeKind; use Chill\BudgetBundle\Entity\ResourceKind; use Chill\BudgetBundle\Repository\ChargeKindRepository; +use Chill\BudgetBundle\Repository\ChargeKindRepositoryInterface; use Chill\BudgetBundle\Repository\ResourceKindRepository; +use Chill\BudgetBundle\Repository\ResourceKindRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; @@ -27,7 +29,7 @@ use function count; /** * Helps to find a summary of the budget: the sum of resources and charges. */ -class SummaryBudget implements SummaryBudgetInterface +final class SummaryBudget implements SummaryBudgetInterface { private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, charge_id AS kind_id FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY charge_id'; @@ -37,23 +39,19 @@ class SummaryBudget implements SummaryBudgetInterface private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, resource_id AS kind_id FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY resource_id'; - private ChargeKindRepository $chargeKindRepository; - - private array $chargeLabels; + private ChargeKindRepositoryInterface $chargeKindRepository; private EntityManagerInterface $em; - private ResourceKindRepository $resourceKindRepository; - - private array $resourcesLabels; + private ResourceKindRepositoryInterface $resourceKindRepository; private TranslatableStringHelperInterface $translatableStringHelper; public function __construct( EntityManagerInterface $em, TranslatableStringHelperInterface $translatableStringHelper, - ResourceKindRepository $resourceKindRepository, - ChargeKindRepository $chargeKindRepository + ResourceKindRepositoryInterface $resourceKindRepository, + ChargeKindRepositoryInterface $chargeKindRepository ) { $this->em = $em; $this->translatableStringHelper = $translatableStringHelper; @@ -129,19 +127,19 @@ class SummaryBudget implements SummaryBudgetInterface private function getEmptyChargeArray(): array { - $keys = array_map(static fn (ChargeKind $kind) => $kind->getId(), $this->chargeKindRepository->findAll()); + $keys = array_map(static fn (ChargeKind $kind) => $kind->getKind(), $this->chargeKindRepository->findAll()); - return array_combine($keys, array_map(function ($id) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->chargeKindRepository->find($id)->getName()), 'comment' => '']; + return array_combine($keys, array_map(function ($kind) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->chargeKindRepository->findOneByKind($kind)->getName()), 'comment' => '']; }, $keys)); } private function getEmptyResourceArray(): array { - $keys = array_map(static fn (ResourceKind $kind) => $kind->getId(), $this->resourceKindRepository->findAll()); + $keys = array_map(static fn (ResourceKind $kind) => $kind->getKind(), $this->resourceKindRepository->findAll()); - return array_combine($keys, array_map(function ($id) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->resourceKindRepository->find($id)->getName()), 'comment' => '']; + return array_combine($keys, array_map(function ($kind) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->resourceKindRepository->findOneByKind($kind)->getName()), 'comment' => '']; }, $keys)); } @@ -152,10 +150,10 @@ class SummaryBudget implements SummaryBudgetInterface switch ($kind) { case 'charge': foreach ($rows as $row) { - $chargeKind = $this->chargeKindRepository->find($row['kind_id']); + $chargeKind = $this->chargeKindRepository->findOneByKind($row['kind_id']); if (null === $chargeKind) { - throw new RuntimeException('charge kind not found'); + throw new RuntimeException('charge kind not found: ' . $row['kind_id']); } $result[$chargeKind->getKind()] = [ 'sum' => (float) $row['sum'], @@ -168,10 +166,10 @@ class SummaryBudget implements SummaryBudgetInterface case 'resource': foreach ($rows as $row) { - $resourceKind = $this->resourceKindRepository->find($row['kind_id']); + $resourceKind = $this->resourceKindRepository->findOneByKind($row['kind_id']); if (null === $resourceKind) { - throw new RuntimeException('charge kind not found'); + throw new RuntimeException('charge kind not found: ' . $row['kind_id']); } $result[$resourceKind->getKind()] = [ diff --git a/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php b/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php new file mode 100644 index 000000000..9f90c60bd --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php @@ -0,0 +1,155 @@ +prophesize(AbstractQuery::class); + $queryCharges->getResult()->willReturn([ + [ + 'sum' => 250.0, + 'comment' => '', + 'kind_id' => 'rental', + ], + ]); + $queryCharges->setParameters(Argument::type('array')) + ->will(function ($args, $query) { + return $query; + }) + ; + + $queryResources = $this->prophesize(AbstractQuery::class); + $queryResources->getResult()->willReturn([ + [ + 'sum' => 1500.0, + 'comment' => '', + 'kind_id' => 'salary', + ], + ]); + $queryResources->setParameters(Argument::type('array')) + ->will(function ($args, $query) { + return $query; + }) + ; + + $em = $this->prophesize(EntityManagerInterface::class); + $em->createNativeQuery(Argument::type('string'), Argument::type(Query\ResultSetMapping::class)) + ->will(function ($args) use ($queryResources, $queryCharges) { + if (false !== strpos($args[0], 'chill_budget.resource')) { + return $queryResources->reveal(); + } + if (false !== strpos($args[0], 'chill_budget.charge')) { + return $queryCharges->reveal(); + } + throw new \RuntimeException('this query does not have a stub counterpart: '.$args[0]); + }) + ; + + $chargeRepository = $this->prophesize(ChargeKindRepositoryInterface::class); + $chargeRepository->findAll()->willReturn([ + $rental = (new ChargeKind())->setKind('rental')->setName(['fr' => 'Rental']), + $other = (new ChargeKind())->setKind('other')->setName(['fr' => 'Other']), + ]); + $chargeRepository->findOneByKind('rental')->willReturn($rental); + $chargeRepository->findOneByKind('other')->willReturn($other); + + $resourceRepository = $this->prophesize(ResourceKindRepositoryInterface::class); + $resourceRepository->findAll()->willReturn([ + $salary = (new ResourceKind())->setKind('salary')->setName(['fr' => 'Salary']), + $misc = (new ResourceKind())->setKind('misc')->setName(['fr' => 'Misc']), + ]); + $resourceRepository->findOneByKind('salary')->willReturn($salary); + $resourceRepository->findOneByKind('misc')->willReturn($misc); + + $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class); + $translatableStringHelper->localize(Argument::type('array'))->will(function ($arg) { + return $arg[0]['fr']; + }); + + $person = new Person(); + $personReflection = new \ReflectionClass($person); + $personIdReflection = $personReflection->getProperty('id'); + $personIdReflection->setAccessible(true); + $personIdReflection->setValue($person, 1); + + $household = new Household(); + $householdReflection = new \ReflectionClass($household); + $householdReflection->getProperty('id')->setAccessible(true); + $householdReflection->getProperty('id')->setValue($household, 1); + $householdMember = (new HouseholdMember())->setPerson($person) + ->setStartDate(new \DateTimeImmutable('1 month ago')) + ; + $household->addMember($householdMember); + + $summaryBudget = new SummaryBudget( + $em->reveal(), + $translatableStringHelper->reveal(), + $resourceRepository->reveal(), + $chargeRepository->reveal() + ); + + $summary = $summaryBudget->getSummaryForPerson($person); + $summaryForHousehold = $summaryBudget->getSummaryForHousehold($household); + + // we check the structure for the summary. The structure is the same for household + // and persons + + $expected = [ + 'charges' => [ + 'rental' => ['sum' => 250.0, 'comment' => '', 'label' => 'Rental'], + 'other' => ['sum' => 0.0, 'comment' => '', 'label' => 'Other'], + ], + 'resources' => [ + 'salary' => ['sum' => 1500.0, 'comment' => '', 'label' => 'Salary'], + 'misc' => ['sum' => 0.0, 'comment' => '', 'label' => 'Misc'], + ], + ]; + + foreach ([$summaryForHousehold, $summary] as $summary) { + $this->assertIsArray($summary); + $this->assertEqualsCanonicalizing(['charges', 'resources'], array_keys($summary)); + $this->assertEqualsCanonicalizing(['rental', 'other'], array_keys($summary['charges'])); + $this->assertEqualsCanonicalizing(['salary', 'misc'], array_keys($summary['resources'])); + + foreach ($expected as $resCha => $contains) { + foreach ($contains as $kind => $row) { + $this->assertEqualsCanonicalizing($row, $summary[$resCha][$kind]); + } + } + } + } +} diff --git a/src/Bundle/ChillPersonBundle/Tests/Validator/AccompanyingPeriod/ParticipationOverlapValidatorTest.php b/src/Bundle/ChillPersonBundle/Tests/Validator/AccompanyingPeriod/ParticipationOverlapValidatorTest.php new file mode 100644 index 000000000..181834b6a --- /dev/null +++ b/src/Bundle/ChillPersonBundle/Tests/Validator/AccompanyingPeriod/ParticipationOverlapValidatorTest.php @@ -0,0 +1,62 @@ +prophesize(PersonRenderInterface::class); + $personRender->renderString(Argument::is(Person::class), [])->willReturn('person'); + $thirdPartyRender = $this->prophesize(ThirdPartyRender::class); + $thirdPartyRender->renderString(Argument::is(ThirdParty::class), [])->willReturn('thirdparty'); + + return new ParticipationOverlapValidator($personRender->reveal(), $thirdPartyRender->reveal()); + } + + public function testOneParticipation() + { + $period = new AccompanyingPeriod(); + $person = new Person(); + + $collection = new ArrayCollection([ + new AccompanyingPeriodParticipation($period, $person) + ]); + + $this->validator->validate($collection, $this->getConstraint()); + + $this->assertNoViolation(); + } + + /** + * @return mixed + */ + public function getConstraint() + { + return new ParticipationOverlap(); + } +} From 5830c3e177b19af923a4e470239336545ae517f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 7 Feb 2023 15:49:57 +0100 Subject: [PATCH 13/45] Feature: [doc generation] show all the deps of the tree for debug information --- .../Controller/DocGeneratorTemplateController.php | 5 +++-- .../Resources/views/Generator/debug_value.html.twig | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php index e54c4ae34..d618f758a 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php +++ b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php @@ -272,8 +272,9 @@ final class DocGeneratorTemplateController extends AbstractController } if ($isTest && isset($form) && $form['show_data']->getData()) { - // very ugly hack... - dd($context->getData($template, $entity, $contextGenerationData)); + return $this->render('@ChillDocGenerator/Generator/debug_value.html.twig', [ + 'datas' => json_encode($context->getData($template, $entity, $contextGenerationData), JSON_PRETTY_PRINT) + ]); } try { diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig new file mode 100644 index 000000000..c08a05c96 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig @@ -0,0 +1,8 @@ + + + {{ 'Doc generator debug'|trans }} + + +
    {{ datas }}
    + + From d8af7d455e75d1c382575cb9cf45efa1abd9177b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 7 Feb 2023 16:22:26 +0100 Subject: [PATCH 14/45] Fixed: [doc generation] fix summary budget --- .../Service/Summary/SummaryBudget.php | 4 ++-- .../Tests/Service/Summary/SummaryBudgetTest.php | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index b7a88bb9d..2f401f1ec 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -150,7 +150,7 @@ final class SummaryBudget implements SummaryBudgetInterface switch ($kind) { case 'charge': foreach ($rows as $row) { - $chargeKind = $this->chargeKindRepository->findOneByKind($row['kind_id']); + $chargeKind = $this->chargeKindRepository->find($row['kind_id']); if (null === $chargeKind) { throw new RuntimeException('charge kind not found: ' . $row['kind_id']); @@ -166,7 +166,7 @@ final class SummaryBudget implements SummaryBudgetInterface case 'resource': foreach ($rows as $row) { - $resourceKind = $this->resourceKindRepository->findOneByKind($row['kind_id']); + $resourceKind = $this->resourceKindRepository->find($row['kind_id']); if (null === $resourceKind) { throw new RuntimeException('charge kind not found: ' . $row['kind_id']); diff --git a/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php b/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php index 9f90c60bd..cf8c00efe 100644 --- a/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php +++ b/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php @@ -43,7 +43,7 @@ final class SummaryBudgetTest extends TestCase [ 'sum' => 250.0, 'comment' => '', - 'kind_id' => 'rental', + 'kind_id' => 1, // kind: rental ], ]); $queryCharges->setParameters(Argument::type('array')) @@ -57,7 +57,7 @@ final class SummaryBudgetTest extends TestCase [ 'sum' => 1500.0, 'comment' => '', - 'kind_id' => 'salary', + 'kind_id' => 2, // kind: 'salary', ], ]); $queryResources->setParameters(Argument::type('array')) @@ -84,6 +84,7 @@ final class SummaryBudgetTest extends TestCase $rental = (new ChargeKind())->setKind('rental')->setName(['fr' => 'Rental']), $other = (new ChargeKind())->setKind('other')->setName(['fr' => 'Other']), ]); + $chargeRepository->find(1)->willReturn($rental); $chargeRepository->findOneByKind('rental')->willReturn($rental); $chargeRepository->findOneByKind('other')->willReturn($other); @@ -92,6 +93,7 @@ final class SummaryBudgetTest extends TestCase $salary = (new ResourceKind())->setKind('salary')->setName(['fr' => 'Salary']), $misc = (new ResourceKind())->setKind('misc')->setName(['fr' => 'Misc']), ]); + $resourceRepository->find(2)->willReturn($salary); $resourceRepository->findOneByKind('salary')->willReturn($salary); $resourceRepository->findOneByKind('misc')->willReturn($misc); @@ -108,8 +110,9 @@ final class SummaryBudgetTest extends TestCase $household = new Household(); $householdReflection = new \ReflectionClass($household); - $householdReflection->getProperty('id')->setAccessible(true); - $householdReflection->getProperty('id')->setValue($household, 1); + $householdId = $householdReflection->getProperty('id'); + $householdId->setAccessible(true); + $householdId->setValue($household, 1); $householdMember = (new HouseholdMember())->setPerson($person) ->setStartDate(new \DateTimeImmutable('1 month ago')) ; From b0ab591cbdd3381b1b3f36af21e12cdfb7f9bc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Fastr=C3=A9?= Date: Tue, 7 Feb 2023 16:50:07 +0100 Subject: [PATCH 15/45] Feature: [Document action buttons] do now show "Editer en ligne" for document which are not editable --- .../vuejs/DocumentActionButtonsGroup.vue | 4 +- .../vuejs/StoredObjectButton/helpers.ts | 135 ++++++++++-------- 2 files changed, 75 insertions(+), 64 deletions(-) diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue index cb72fa5cd..60b368cd3 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue @@ -7,7 +7,7 @@
  • -
  • +
  • @@ -23,7 +23,7 @@ import ConvertButton from "./StoredObjectButton/ConvertButton.vue"; import DownloadButton from "./StoredObjectButton/DownloadButton.vue"; import WopiEditButton from "./StoredObjectButton/WopiEditButton.vue"; -import {is_extension_editable} from "./StoredObjectButton/helpers"; +import {is_extension_editable, is_extension_viewable} from "./StoredObjectButton/helpers"; import {StoredObject, WopiEditButtonExecutableBeforeLeaveFunction} from "../types"; interface DocumentActionButtonsGroupConfig { diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts index 58d2ebc71..d82efb111 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts @@ -1,97 +1,107 @@ -const SUPPORTED_MIMES = new Set([ - 'image/svg+xml', +const MIMES_EDIT = new Set([ 'application/vnd.ms-powerpoint', 'application/vnd.ms-excel', - 'application/vnd.sun.xml.writer', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.text-flat-xml', - 'application/vnd.sun.xml.calc', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.spreadsheet-flat-xml', - 'application/vnd.sun.xml.impress', 'application/vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.presentation-flat-xml', - 'application/vnd.sun.xml.draw', 'application/vnd.oasis.opendocument.graphics', 'application/vnd.oasis.opendocument.graphics-flat-xml', 'application/vnd.oasis.opendocument.chart', - 'application/vnd.sun.xml.writer.global', - 'application/vnd.oasis.opendocument.text-master', - 'application/vnd.sun.xml.writer.template', - 'application/vnd.oasis.opendocument.text-template', - 'application/vnd.oasis.opendocument.text-master-template', - 'application/vnd.sun.xml.calc.template', - 'application/vnd.oasis.opendocument.spreadsheet-template', - 'application/vnd.sun.xml.impress.template', - 'application/vnd.oasis.opendocument.presentation-template', - 'application/vnd.sun.xml.draw.template', - 'application/vnd.oasis.opendocument.graphics-template', - 'application/msword', '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.wordprocessingml.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.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/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/x-dif-document', 'text/spreadsheet', 'text/csv', 'application/x-dbase', - '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', 'text/rtf', 'text/plain', - 'application/x-fictionbook+xml', - 'application/clarisworks', - 'image/x-wpg', - 'application/x-iwork-pages-sffpages', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - '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', ]); + + +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', + ] +]) + function is_extension_editable(mimeType: string): boolean { - return SUPPORTED_MIMES.has(mimeType); + return MIMES_EDIT.has(mimeType); +} + +function is_extension_viewable(mimeType: string): boolean { + return MIMES_VIEW.has(mimeType); } function build_convert_link(uuid: string) { @@ -165,4 +175,5 @@ export { download_and_decrypt_doc, download_doc, is_extension_editable, + is_extension_viewable, }; From 5e58d36e798d9fd99e1086e0d6982d69758a7f87 Mon Sep 17 00:00:00 2001 From: LenaertsJ Date: Wed, 8 Feb 2023 14:28:21 +0000 Subject: [PATCH 16/45] Feature: exports for aside activities --- .../Aggregator/ByActivityTypeAggregator.php | 6 +- .../Export/Aggregator/ByUserJobAggregator.php | 89 +++++++ .../Aggregator/ByUserScopeAggregator.php | 89 +++++++ .../Export/AvgAsideActivityDuration.php | 102 ++++++++ .../src/Export/Export/CountAsideActivity.php | 6 +- .../src/Export/Export/ListAsideActivity.php | 236 ++++++++++++++++++ .../Export/SumAsideActivityDuration.php | 102 ++++++++ .../Export/Filter/ByActivityTypeFilter.php | 4 +- .../src/Export/Filter/ByDateFilter.php | 11 +- .../src/Export/Filter/ByUserFilter.php | 75 ++++++ .../src/Export/Filter/ByUserJobFilter.php | 81 ++++++ .../src/Export/Filter/ByUserScopeFilter.php | 88 +++++++ .../src/config/services.yaml | 30 --- .../src/config/services/export.yaml | 39 ++- .../src/translations/messages.fr.yml | 70 ++++-- .../Export/ExportInterface.php | 4 +- .../Export/Helper/DateTimeHelper.php | 4 + 17 files changed, 962 insertions(+), 74 deletions(-) create mode 100644 src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php create mode 100644 src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php create mode 100644 src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php create mode 100644 src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php create mode 100644 src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php create mode 100644 src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php create mode 100644 src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php create mode 100644 src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php index 6c91c9336..32418e3c3 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php @@ -53,19 +53,15 @@ class ByActivityTypeAggregator implements AggregatorInterface public function getLabels($key, array $values, $data) { - $this->asideActivityCategoryRepository->findBy(['id' => $values]); - return function ($value): string { if ('_header' === $value) { return 'export.aggregator.Aside activity type'; } - if (null === $value) { + if (null === $value || null === $t = $this->asideActivityCategoryRepository->find($value)) { return ''; } - $t = $this->asideActivityCategoryRepository->find($value); - return $this->translatableStringHelper->localize($t->getTitle()); }; } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php new file mode 100644 index 000000000..d2fe7c2f2 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php @@ -0,0 +1,89 @@ +userJobRepository = $userJobRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('aside_user', $qb->getAllAliases(), true)) { + $qb->leftJoin('aside.agent', 'aside_user'); + } + + $qb + ->addSelect('IDENTITY(aside_user.userJob) AS aside_activity_user_job_aggregator') + ->addGroupBy('aside_activity_user_job_aggregator'); + } + + public function applyOn() + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // nothing to add in the form + } + + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ('_header' === $value) { + return 'Users \'s job'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $j = $this->userJobRepository->find($value); + + return $this->translatableStringHelper->localize( + $j->getLabel() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['aside_activity_user_job_aggregator']; + } + + public function getTitle() + { + return 'export.aggregator.Aggregate by user job'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php new file mode 100644 index 000000000..6c06ee756 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php @@ -0,0 +1,89 @@ +scopeRepository = $scopeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + if (!in_array('aside_user', $qb->getAllAliases(), true)) { + $qb->leftJoin('aside.agent', 'aside_user'); + } + + $qb + ->addSelect('IDENTITY(aside_user.mainScope) AS aside_activity_user_scope_aggregator') + ->addGroupBy('aside_activity_user_scope_aggregator'); + } + + public function applyOn() + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + // nothing to add in the form + } + + public function getLabels($key, array $values, $data) + { + return function ($value): string { + if ('_header' === $value) { + return 'Users \'s scope'; + } + + if (null === $value || '' === $value) { + return ''; + } + + $s = $this->scopeRepository->find($value); + + return $this->translatableStringHelper->localize( + $s->getName() + ); + }; + } + + public function getQueryKeys($data): array + { + return ['aside_activity_user_scope_aggregator']; + } + + public function getTitle() + { + return 'export.aggregator.Aggregate by user scope'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php new file mode 100644 index 000000000..f3db629cb --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php @@ -0,0 +1,102 @@ +repository = $repository; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'export.Average aside activities duration'; + } + + public function getGroup(): string + { + return 'export.Exports of aside activities'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_avg_aside_activity_duration' !== $key) { + throw new LogicException("the key {$key} is not used by this export"); + } + + return static fn ($value) => '_header' === $value ? 'Average duration aside activities' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_avg_aside_activity_duration']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'export.Average aside activities duration'; + } + + public function getType(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $qb = $this->repository->createQueryBuilder('aside'); + + $qb + ->select('AVG(aside.duration) as export_avg_aside_activity_duration') + ->andWhere($qb->expr()->isNotNull('aside.duration')); + + return $qb; + } + + public function requiredRole(): string + { + return AsideActivityVoter::STATS; + } + + public function supportsModifiers(): array + { + return [Declarations::ASIDE_ACTIVITY_TYPE]; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php index c3e99f129..87aad1659 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php @@ -11,12 +11,12 @@ declare(strict_types=1); namespace Chill\AsideActivityBundle\Export\Export; +use Chill\AsideActivityBundle\Export\Declarations; use Chill\AsideActivityBundle\Repository\AsideActivityRepository; use Chill\AsideActivityBundle\Security\AsideActivityVoter; use Chill\MainBundle\Export\ExportInterface; use Chill\MainBundle\Export\FormatterInterface; use Chill\MainBundle\Export\GroupedExportInterface; -use ChillAsideActivityBundle\Export\Declarations; use Doctrine\ORM\Query; use LogicException; use Symfony\Component\Form\FormBuilderInterface; @@ -100,6 +100,8 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface public function supportsModifiers(): array { - return []; + return [ + Declarations::ASIDE_ACTIVITY_TYPE, + ]; } } diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php new file mode 100644 index 000000000..bf370f71b --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php @@ -0,0 +1,236 @@ +em = $em; + $this->dateTimeHelper = $dateTimeHelper; + $this->userHelper = $userHelper; + $this->scopeRepository = $scopeRepository; + $this->centerRepository = $centerRepository; + $this->asideActivityCategoryRepository = $asideActivityCategoryRepository; + $this->categoryRender = $categoryRender; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes() + { + return [FormatterInterface::TYPE_LIST]; + } + + public function getDescription() + { + return 'export.aside_activity.List of aside activities'; + } + + public function getTitle() + { + return 'export.aside_activity.List of aside activities'; + } + + public function getGroup(): string + { + return 'export.Exports of aside activities'; + } + + public function getLabels($key, array $values, $data) + { + switch ($key) { + case 'id': + case 'note': + return function ($value) use ($key) { + if ('_header' === $value) { + return 'export.aside_activity.' . $key; + } + + return $value ?? ''; + }; + case 'duration': + return function ($value) use ($key) { + if ('_header' === $value) { + return 'export.aside_activity.' . $key; + } + + if (null === $value) { + return ''; + } + + if ($value instanceof \DateTimeInterface) { + return $value->format('H:i:s'); + } + + return $value; + }; + + case 'createdAt': + case 'updatedAt': + case 'date': + return $this->dateTimeHelper->getLabel('export.aside_activity.'.$key); + + case 'agent_id': + case 'creator_id': + return $this->userHelper->getLabel($key, $values, 'export.aside_activity.' . $key); + + case 'aside_activity_type': + return function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.aside_activity_type'; + } + + if (null === $value || '' === $value || null === $c = $this->asideActivityCategoryRepository->find($value)) { + return ''; + } + + return $this->categoryRender->renderString($c, []); + }; + + case 'main_scope': + return function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.main_scope'; + } + + if (null === $value || '' === $value || null === $c = $this->scopeRepository->find($value)) { + return ''; + } + + return $this->translatableStringHelper->localize($c->getName()); + }; + + case 'main_center': + return function ($value) { + if ('_header' === $value) { + return 'export.aside_activity.main_center'; + } + + /** @var Center $c */ + if (null === $value || '' === $value || null === $c = $this->centerRepository->find($value)) { + return ''; + } + + return $c->getName(); + }; + + default: + throw new \LogicException('this key is not supported : ' . $key); + } + } + + public function getQueryKeys($data) + { + return [ + 'id', + 'createdAt', + 'updatedAt', + 'agent_id', + 'creator_id', + 'main_scope', + 'main_center', + 'aside_activity_type', + 'date', + 'duration', + 'note' + ]; + } + + /** + * @param QueryBuilder $query + * @param array $data + */ + public function getResult($query, $data): array + { + return $query->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY); + } + + public function getType(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $qb = $this->em->createQueryBuilder() + ->from(AsideActivity::class, 'aside') + ->leftJoin('aside.agent', 'agent') + ; + + $qb + ->addSelect('aside.id AS id') + ->addSelect('aside.createdAt AS createdAt') + ->addSelect('aside.updatedAt AS updatedAt') + ->addSelect('IDENTITY(aside.agent) AS agent_id') + ->addSelect('IDENTITY(aside.createdBy) AS creator_id') + ->addSelect('IDENTITY(agent.mainScope) AS main_scope') + ->addSelect('IDENTITY(agent.mainCenter) AS main_center') + ->addSelect('IDENTITY(aside.type) AS aside_activity_type') + ->addSelect('aside.date') + ->addSelect('aside.duration') + ->addSelect('aside.note') + ; + + return $qb; + } + + public function requiredRole(): string + { + return AsideActivityVoter::STATS; + } + + public function supportsModifiers() + { + return [Declarations::ASIDE_ACTIVITY_TYPE]; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php new file mode 100644 index 000000000..af17a2591 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php @@ -0,0 +1,102 @@ +repository = $repository; + } + + public function buildForm(FormBuilderInterface $builder) + { + } + + public function getAllowedFormattersTypes(): array + { + return [FormatterInterface::TYPE_TABULAR]; + } + + public function getDescription(): string + { + return 'export.Sum aside activities duration'; + } + + public function getGroup(): string + { + return 'export.Exports of aside activities'; + } + + public function getLabels($key, array $values, $data) + { + if ('export_sum_aside_activity_duration' !== $key) { + throw new LogicException("the key {$key} is not used by this export"); + } + + return static fn ($value) => '_header' === $value ? 'Sum duration aside activities' : $value; + } + + public function getQueryKeys($data): array + { + return ['export_sum_aside_activity_duration']; + } + + public function getResult($query, $data) + { + return $query->getQuery()->getResult(Query::HYDRATE_SCALAR); + } + + public function getTitle(): string + { + return 'export.Sum aside activities duration'; + } + + public function getType(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function initiateQuery(array $requiredModifiers, array $acl, array $data = []) + { + $qb = $this->repository + ->createQueryBuilder('aside'); + + $qb->select('SUM(aside.duration) as export_sum_aside_activity_duration') + ->andWhere($qb->expr()->isNotNull('aside.duration')); + + return $qb; + } + + public function requiredRole(): string + { + return AsideActivityVoter::STATS; + } + + public function supportsModifiers(): array + { + return [Declarations::ASIDE_ACTIVITY_TYPE]; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php index 85b327795..3ad8e0e93 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php @@ -80,8 +80,8 @@ class ByActivityTypeFilter implements FilterInterface public function describeAction($data, $format = 'string'): array { $types = array_map( - fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getName()), - $this->asideActivityTypeRepository->findBy(['id' => $data['types']->toArray()]) + fn (AsideActivityCategory $t): string => $this->translatableStringHelper->localize($t->getTitle()), + $data['types']->toArray() ); return ['export.filter.Filtered by aside activity type: only %type%', [ diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php index 099c87fc0..2d49b3d57 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php @@ -46,25 +46,18 @@ class ByDateFilter implements FilterInterface public function alterQuery(QueryBuilder $qb, $data) { - $where = $qb->getDQLPart('where'); $clause = $qb->expr()->between( 'aside.date', ':date_from', ':date_to' ); - if ($where instanceof Andx) { - $where->add($clause); - } else { - $where = $qb->expr()->andX($clause); - } + $qb->andWhere($clause); - $qb->add('where', $where); $qb->setParameter( 'date_from', $this->rollingDateConverter->convert($data['date_from']) - ); - $qb->setParameter( + )->setParameter( 'date_to', $this->rollingDateConverter->convert($data['date_to']) ); diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php new file mode 100644 index 000000000..c2a3b4c54 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php @@ -0,0 +1,75 @@ +userRender = $userRender; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $clause = $qb->expr()->in('aside.agent', ':users'); + + $qb + ->andWhere($clause) + ->setParameter('users', $data['accepted_users']); + } + + public function applyOn(): string + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('accepted_users', PickUserDynamicType::class, [ + 'multiple' => true, + 'label' => 'Creators', + ]); + } + + public function describeAction($data, $format = 'string'): array + { + $users = []; + + foreach ($data['accepted_users'] as $u) { + $users[] = $this->userRender->renderString($u, []); + } + + return ['export.filter.Filtered aside activity by user: only %users%', [ + '%users%' => implode(', ', $users), + ]]; + } + + public function getTitle(): string + { + return 'export.filter.Filter aside activity by user'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php new file mode 100644 index 000000000..86194b123 --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php @@ -0,0 +1,81 @@ +translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . AsideActivity::class . ' aside_activity_user_job_filter_act + JOIN aside_activity_user_job_filter_act.agent aside_activity_user_job_filter_user WHERE aside_activity_user_job_filter_user.userJob IN (:aside_activity_user_job_filter_jobs) AND aside_activity_user_job_filter_act = aside' + ) + ) + ->setParameter('aside_activity_user_job_filter_jobs', $data['jobs']); + } + + public function applyOn() + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('jobs', EntityType::class, [ + 'class' => UserJob::class, + 'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string') + { + return ['export.filter.Filtered aside activities by user jobs: only %jobs%', [ + '%jobs%' => implode( + ', ', + array_map( + fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()), + $data['jobs']->toArray() + ) + ), + ]]; + } + + public function getTitle() + { + return 'export.filter.Filter by user jobs'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php new file mode 100644 index 000000000..4342e11eb --- /dev/null +++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php @@ -0,0 +1,88 @@ +scopeRepository = $scopeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + public function addRole(): ?string + { + return null; + } + + public function alterQuery(QueryBuilder $qb, $data) + { + $qb + ->andWhere( + $qb->expr()->exists( + 'SELECT 1 FROM ' . AsideActivity::class . ' aside_activity_user_scope_filter_act + JOIN aside_activity_user_scope_filter_act.agent aside_activity_user_scope_filter_user WHERE aside_activity_user_scope_filter_user.mainScope IN (:aside_activity_user_scope_filter_scopes) AND aside_activity_user_scope_filter_act = aside ' + ) + ) + ->setParameter('aside_activity_user_scope_filter_scopes', $data['scopes']); + } + + public function applyOn() + { + return Declarations::ASIDE_ACTIVITY_TYPE; + } + + public function buildForm(FormBuilderInterface $builder) + { + $builder->add('scopes', EntityType::class, [ + 'class' => Scope::class, + 'choices' => $this->scopeRepository->findAllActive(), + 'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), + 'multiple' => true, + 'expanded' => true, + ]); + } + + public function describeAction($data, $format = 'string') + { + return ['export.filter.Filtered aside activities by user scope: only %scopes%', [ + '%scopes%' => implode( + ', ', + array_map( + fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), + $data['scopes']->toArray() + ) + ), + ]]; + } + + public function getTitle() + { + return 'export.filter.Filter by user scope'; + } +} diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml index 2a7c30d7c..34bb6da33 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/services.yaml @@ -20,33 +20,3 @@ services: resource: "../Controller" autowire: true autoconfigure: true - - - ## Exports - - # indicators - Chill\AsideActivityBundle\Export\Export\CountAsideActivity: - autowire: true - autoconfigure: true - tags: - - { name: chill.export, alias: count_asideactivity } - - # filters - Chill\AsideActivityBundle\Export\Filter\ByDateFilter: - autowire: true - autoconfigure: true - tags: - - { name: chill.export_filter, alias: asideactivity_bydate_filter } - - Chill\AsideActivityBundle\Export\Filter\ByActivityTypeFilter: - autowire: true - autoconfigure: true - tags: - - { name: chill.export_filter, alias: asideactivity_activitytype_filter } - - # aggregators - Chill\AsideActivityBundle\Export\Aggregator\ByActivityTypeAggregator: - autowire: true - autoconfigure: true - tags: - - { name: chill.export_aggregator, alias: asideactivity_activitytype_aggregator } diff --git a/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml b/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml index 1b6b05e1c..a29413e15 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml +++ b/src/Bundle/ChillAsideActivityBundle/src/config/services/export.yaml @@ -3,11 +3,23 @@ services: autowire: true autoconfigure: true + Chill\AsideActivityBundle\Export\Export\ListAsideActivity: + tags: + - { name: chill.export, alias: 'list_aside_activity' } + ## Indicators Chill\AsideActivityBundle\Export\Export\CountAsideActivity: tags: - { name: chill.export, alias: 'count_aside_activity' } + Chill\AsideActivityBundle\Export\Export\SumAsideActivityDuration: + tags: + - { name: chill.export, alias: 'sum_aside_activity_duration' } + + Chill\AsideActivityBundle\Export\Export\AvgAsideActivityDuration: + tags: + - { name: chill.export, alias: 'avg_aside_activity_duration' } + ## Filters chill.aside_activity.export.date_filter: class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter @@ -19,9 +31,34 @@ services: tags: - { name: chill.export_filter, alias: 'aside_activity_type_filter' } + chill.aside_activity.export.user_job_filter: + class: Chill\AsideActivityBundle\Export\Filter\ByUserJobFilter + tags: + - { name: chill.export_filter, alias: 'aside_activity_user_job_filter' } + + chill.aside_activity.export.user_scope_filter: + class: Chill\AsideActivityBundle\Export\Filter\ByUserScopeFilter + tags: + - { name: chill.export_filter, alias: 'aside_activity_user_scope_filter' } + + chill.aside_activity.export.user_filter: + class: Chill\AsideActivityBundle\Export\Filter\ByUserFilter + tags: + - { name: chill.export_filter, alias: 'aside_activity_user_filter' } + ## Aggregators chill.aside_activity.export.type_aggregator: class: Chill\AsideActivityBundle\Export\Aggregator\ByActivityTypeAggregator tags: - - { name: chill.export_aggregator, alias: activity_type_aggregator } \ No newline at end of file + - { name: chill.export_aggregator, alias: activity_type_aggregator } + + chill.aside_activity.export.user_job_aggregator: + class: Chill\AsideActivityBundle\Export\Aggregator\ByUserJobAggregator + tags: + - { name: chill.export_aggregator, alias: aside_activity_user_job_aggregator } + + chill.aside_activity.export.user_scope_aggregator: + class: Chill\AsideActivityBundle\Export\Aggregator\ByUserScopeAggregator + tags: + - { name: chill.export_aggregator, alias: aside_activity_user_scope_aggregator } diff --git a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml index 2b4b6788a..0c807479f 100644 --- a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml +++ b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml @@ -29,16 +29,16 @@ location: Lieu # Crud crud: - aside_activity: - title_view: Détail de l'activité annexe - title_new: Nouvelle activité annexe - title_edit: Édition d'une activité annexe - title_delete: Supprimer une activité annexe - button_delete: Supprimer - confirm_message_delete: Êtes-vous sûr de vouloir supprimer cette activité annexe? - aside_activity_category: - title_new: Nouvelle catégorie d'activité annexe - title_edit: Édition d'une catégorie de type d'activité + aside_activity: + title_view: Détail de l'activité annexe + title_new: Nouvelle activité annexe + title_edit: Édition d'une activité annexe + title_delete: Supprimer une activité annexe + button_delete: Supprimer + confirm_message_delete: Êtes-vous sûr de vouloir supprimer cette activité annexe? + aside_activity_category: + title_new: Nouvelle catégorie d'activité annexe + title_edit: Édition d'une catégorie de type d'activité #forms Create a new aside activity type: Nouvelle categorie d'activité annexe @@ -169,19 +169,43 @@ Aside activity configuration: Configuration des activités annexes # exports export: - Exports of aside activities: Exports des activités annexes - Count aside activities: Nombre d'activités annexes - Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères - filter: - Filter by aside activity date: Filtrer les activités annexes par date - Filter by aside activity type: Filtrer les activités annexes par type d'activité - 'Filtered by aside activity type: only %type%': "Filtré par type d'activité annexe: uniquement %type%" - This date should be after the date given in "Implied in an aside activity after this date" field: Cette date devrait être postérieure à la date donnée dans le champ "activités annexes après cette date" - Aside activities after this date: Actvitités annexes après cette date - Aside activities before this date: Actvitités annexes avant cette date - aggregator: - Group by aside activity type: Grouper les activités annexes par type d'activité - Aside activity type: Type d'activité annexe + aside_activity: + List of aside activities: Liste des activités annexes + createdAt: Création + updatedAt: Dernière mise à jour + agent_id: Utilisateur + creator_id: Créateur + main_scope: Service principal de l'utilisateur + main_center: Centre principal de l'utilisteur + aside_activity_type: Catégorie d'activité annexe + date: Date + duration: Durée + note: Note + + Exports of aside activities: Exports des activités annexes + Count aside activities: Nombre d'activités annexes + Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères + Average aside activities duration: Durée moyenne des activités annexes + Sum aside activities duration: Durée des activités annexes + filter: + Filter by aside activity date: Filtrer les activités annexes par date + Filter by aside activity type: Filtrer les activités annexes par type d'activité + 'Filtered by aside activity type: only %type%': "Filtré par type d'activité annexe: uniquement %type%" + Filtered by aside activities between %dateFrom% and %dateTo%: Filtré par date d'activité annexe, entre %dateFrom% et %dateTo% + This date should be after the date given in "Implied in an aside activity after this date" field: Cette date devrait être postérieure à la date donnée dans le champ "activités annexes après cette date" + Aside activities after this date: Actvitités annexes après cette date + Aside activities before this date: Actvitités annexes avant cette date + 'Filtered aside activity by user: only %users%': "Filtré par utilisateur: uniquement %users%" + Filter aside activity by user: Filtrer par utilisateur + 'Filtered aside activities by user jobs: only %jobs%': "Filtré par métier des utilisateurs: uniquement %jobs%" + Filter by user jobs: Filtrer les activités annexes par métier des utilisateurs + 'Filtered aside activities by user scope: only %scopes%': "Filtré par service des utilisateur: uniquement %scopes%" + Filter by user scope: Filtrer les activités annexes par service d'utilisateur + aggregator: + Group by aside activity type: Grouper les activités annexes par type d'activité + Aside activity type: Type d'activité annexe + Aggregate by user job: Grouper les activités annexes par métier des utilisateurs + Aggregate by user scope: Grouper les activités annexes par service des utilisateurs # ROLES CHILL_ASIDE_ACTIVITY_STATS: Statistiques pour les activités annexes diff --git a/src/Bundle/ChillMainBundle/Export/ExportInterface.php b/src/Bundle/ChillMainBundle/Export/ExportInterface.php index be43ad47a..79b59d241 100644 --- a/src/Bundle/ChillMainBundle/Export/ExportInterface.php +++ b/src/Bundle/ChillMainBundle/Export/ExportInterface.php @@ -83,9 +83,9 @@ interface ExportInterface extends ExportElementInterface * * @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 mixed $data The data from the export's form (as defined in `buildForm` + * @param mixed $data The data from the export's form (as defined in `buildForm`) * - * @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 pure-callable(null|string|int|float|'_header' $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(); }` */ public function getLabels($key, array $values, $data); diff --git a/src/Bundle/ChillMainBundle/Export/Helper/DateTimeHelper.php b/src/Bundle/ChillMainBundle/Export/Helper/DateTimeHelper.php index 86a2458b2..648632e15 100644 --- a/src/Bundle/ChillMainBundle/Export/Helper/DateTimeHelper.php +++ b/src/Bundle/ChillMainBundle/Export/Helper/DateTimeHelper.php @@ -35,6 +35,10 @@ class DateTimeHelper return ''; } + if ($value instanceof \DateTimeInterface) { + return $value; + } + // warning: won't work with DateTimeImmutable as we reset time a few lines later $date = DateTime::createFromFormat('Y-m-d', $value); $hasTime = false; From 68f7a832b4edde8d1e74face6cf6908133aecd2a Mon Sep 17 00:00:00 2001 From: Mathieu Jaumotte Date: Wed, 8 Feb 2023 14:42:16 +0000 Subject: [PATCH 17/45] Fixed: [activity] fetch all the available location, beyond the first page --- .../Controller/LocationController.php | 2 +- .../components/AdminLocation.vue | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Bundle/ChillMainBundle/Controller/LocationController.php b/src/Bundle/ChillMainBundle/Controller/LocationController.php index f3c4db082..232c9c6cf 100644 --- a/src/Bundle/ChillMainBundle/Controller/LocationController.php +++ b/src/Bundle/ChillMainBundle/Controller/LocationController.php @@ -28,7 +28,7 @@ class LocationController extends CRUDController protected function customizeQuery(string $action, Request $request, $query): void { - $query->where('e.availableForUsers = true'); //TODO not working + $query->where('e.availableForUsers = "TRUE"'); } protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) 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 9b3a6ade9..35b2f8847 100644 --- a/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/AdminLocation.vue +++ b/src/Bundle/ChillPersonBundle/Resources/public/vuejs/AccompanyingCourse/components/AdminLocation.vue @@ -34,7 +34,7 @@