diff --git a/.changes/header.tpl.md b/.changes/header.tpl.md
new file mode 100644
index 000000000..df8faa7b2
--- /dev/null
+++ b/.changes/header.tpl.md
@@ -0,0 +1,6 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
+and is generated by [Changie](https://github.com/miniscruff/changie).
diff --git a/.changes/unreleased/.gitkeep b/.changes/unreleased/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/.changes/v2.0.0.md b/.changes/v2.0.0.md
new file mode 100644
index 000000000..8d70ecba6
--- /dev/null
+++ b/.changes/v2.0.0.md
@@ -0,0 +1,677 @@
+## 2.0.0
+
+* this is a release to relaunch our proceess of release with semantic versioning
+
+## Test releases
+
+### 2.0.0-beta3
+
+* [person][export] Fixed: rename the alias for `accompanying_period` to `acp` in filter associated with person
+* [activity][export] Feature: improve label for aliases in "Filter by activity type"
+* [activity][export] DX/Feature: use of an `ActivityTypeRepositoryInterface` instead of the old-style EntityRepository
+* [person][export] Fixed: some inconsistency with date filter on accompanying courses
+* [person][export] Fixed: use left join for related entities in accompanying course aggregators
+* [workflow] Feature: allow user to copy and send manually the access link for the workflow
+* [workflow] Feature: show the email addresses that received an access link for the workflow
+### 2.0.0-beta2
+
+* [workflow]: Fixed: the notification is sent when the user is added to the first step.
+* [budget] Feature: allow to desactivate some charges and resources, adding an `active` key in the configuration
+* [person] Feature: on Evaluation, allow to configure an URL from the admin
+
+### 2022-06
+
+* [workflow]: added pagination to workflow list page
+* [homepage_widget]: null error on tasks widget fixed
+* [person-thirdparty]: fix quick-add of names that consist of multiple parts (eg. De Vlieger) within onthefly modal person/thirdparty
+* [search]: Order of birthdate fields changed in advanced search to avoid confusion.
+* [workflow]: Constraint added to workflow (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/675)
+* [social_action]: only show active objectives (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/625)
+* [household]: Reposition and cut button for enfant hors menage have been deleted (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/620)
+* [admin]: Add crud for composition type in admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/611)
+* [social_action]: only show active objectives (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/625)
+
+## Test releases
+
+### 2022-05-30
+
+* fix creating a new AccompanyingPeriodWorkEvaluationDocument when replacing the document (the workflow was lost)
+
+### 2022-05-27
+
+* [storedobject] add title field on StoredObject entity + use it in activity documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/604)
+* [main] add a "read more..." on comment embeddable when overflown (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/604)
+* [person] add closing motive to closed acc course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/603)
+* [person] household filiation: fetch person info when unfolding person (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/586)
+* [admin] repair edit of social action in the admin (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/601)
+* [admin]: add select2 to Goal form type entity fields (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/702)
+* [main] allow hide permissions group list menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
+* [main] allow hide change user password menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
+* [main] filter user jobs by active jobs (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
+* [main] add civility to User (entity, migration and form type) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/577)
+* [admin] refactorisation of the admin section: reorganisation of the menu, translations, form types, new entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/592)
+* [admin] add admin section for languages and countries (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/596)
+* [activity] activity admin: translations + remove label field for comment on admin activity type (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/587)
+* [main] admin user_job: improvements (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/588)
+* [address] can add extra address info even if noAddress (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/576)
+
+
+### 2022-05-06
+
+* [person] add civility when creating a person (with the on-the-fly component or in the php form) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/557)
+* [person] add address when creating a person (with the on-the-fly component or in the php form) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/557)
+* [person] add household creation API point (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/557)
+
+### 2021-04-29
+
+* [person] prevent circular references in PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/527)
+* [person] add maritalStatusComment to PersonDocGenNormalizer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/582)
+* Load relationships without gender in french fixtures
+* Add command to remove old draft accompanying periods
+* [parcours]: If users assings him/herself as referrer and job is not null. Update parcours job (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/578)
+
+### 2021-04-28
+
+* [address] fix bug when editing address: update location and addressreferenceId + better update of the map in edition (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/593)
+* [main] avoid address reference search on undefined post code (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/561)
+* [person] prevent duplicate relationship in filiation/household graph (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/560)
+* [Documents] Validate storedObject and allow for null data (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/565)
+* [parcours]: Comments can be unpinned + edit/delete for all users that are allowed to edit parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/566)
+
+### 2021-04-26
+
+* [Datepickers] datepickers fixed when using keyboard to enter date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/545)
+* [social_action] Display 'agents traitants' in parcours resumé and social action list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/568)
+* [Person_search] Closed parcours shown within an accordeon that can be opened/closed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/574)
+
+### 2021-04-24
+
+* [notification email on course designation] allow raw string in email content generation
+* [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older
+* [Documents] Change wording 'créer' to 'enregistrer' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/634)
+* [Parcours]: The number of 'mes parcours' displayed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/572)
+* [Hompage_widget]: Renaming of tabs and removal of social actions tab (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/570)
+* [activity]: Ignore thirdparties when creating a social action via an activity (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/573)
+* [parcours]: change wording of warning message and button when user is not associated to a household yet (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/590#note_918370943)
+* [Accompanying period work evaluations] list documents associated to a work by creation date, and then by id, from the most recent to older
+* [Course comment] add validationConstraint NotNull and NotBlank on comment content, to avoid sql error
+* [Notifications] delay the sending of notificaiton to kernel.terminate
+* [Notifications / Period user change] fix the sending of notification when user changes
+* [Activity form] invert 'incoming' and 'receiving' in Activity form
+* [Activity form] keep the same order for 'attendee' field in new and edit form
+* [list with period] use "sameas" test operator to introduce requestor in list
+* [notification email on course designation] allow raw string in email content generation
+* [Accompanying period work] list evaluations associated to a work by startDate, and then by id, from the most recent to older
+* [evaluation_document] changing date to datetime in order to display the time at which document was created (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/569)
+
+
+### 2021-04-13
+
+* [person] household address: add a form for editing the validFrom date (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/541)
+* [person] householdmemberseditor: fix composition type bug in select form (vuejs) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/543)
+* [docgen] add more persons choices in docgen for course: amongst requestor (if person), resources of course (if person), and PersonResource (if person);
+* [docgen] add a new context with a list of activities in course
+* [docgen] add a comment in budget lines
+* [notifications] allow to send a notification to an email address. The address receive an access link
+* [adresses] add constraints in database to avoid errors later: postcode not null, and validfrom <= validto
+* [accompanying work editor] add a label on document title input
+
+### 2021-04-07
+
+* notification list: move action buttons outside of the toggle
+* fix detecting of non-read notification
+* filter users which are disabled in search user api
+* order query for location and add pagination in list
+* allow every person which has part for a workflow to see the workflow page
+* able to see the workflow if the evaluation document has been deleted
+* hardcode the list of supported mime types for edition with collabora
+* list of accompanying course: allow to see the pinned comment in list_item
+
+### 2021-04-06
+
+* [main] notification toggle read: correct js syntax for compilation in production (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/548)
+* [parcours] Display of interlocuteurs changed to flex-table in parcours edit page to prevent cut-off of information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/535)
+* [activity] espace entre les boutons pour supprimer les documents
+
+
+### continuous release in February and March
+
+* Creation of PickCivilityType, and implementation in PersonType and ThirdpartyType
+* [person] Accompanying course evaluation documents: disable the WOPI edit link if mimetype not supported and if no keyInfos
+(https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/585)
+* [activity] display error messages above the form in creating a new location (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/481)
+* [activity] show required field in activity edit/new by an asterix in the vuejs fields (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/494)
+* [ACL] fix allow to see the course, event if the scope'course does not contains the scope's user
+* [search] enforce limit of results for fetching rsults by search api https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/576
+* [activity] Fix delete button for document (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/554)
+* [activity] Add return path the document generation (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/553)
+* [person] add person ressource to person docgen normaliser (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/517)
+* [person] AccompanyingCourseWorkEdit: fix deleting evaluation documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/546)
+* [person] AccompanyingCourseWorkEdit: download existing documents (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/512)
+* [person] AccompanyingCourseWorkEdit: replace document by a new one (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/511)
+* [person] AccompanyingPeriodWork: add referrers to work, add doctrine event listener to add logged user to referrers collection and display a referrers list in work list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/502)
+* [person] AccompanyingPeriodWorkEvaluation: fix circular reference when serialising (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/495)
+* [person] order accompanying period by opening date in search persons, person and household period lists (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/493)
+* [parcours] autosave of the pinned comment for draft accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/477)
+* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472)
+* [main] filter user job in undispatch acc period to assign (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/472)
+* [person] Add url in accompanying period work evaluations entity and form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/476)
+* [person] Add document generation in admin and in person/{id}/document (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/464)
+* [activity] do not override location if already exist (when validating new activity) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/470)
+* [parcours] Toggle emergency/intensity only by referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/442)
+* [docstore] Add an API entrypoint for StoredObject (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
+* [person] Add the possibility of uploading existing documents to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
+* [person] Add title to AccPeriodWorkEvaluationDocument (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/466)
+* [person] Order social issues by the field "ordering" (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/388)
+* [Person/Household list] when listing other simultaneous members of an household, exclude the members on person, not on members (avoid to show two membersship with the same person)
+* [draft periods] add a delete button (if acl granted) on each draft period listed on draft period page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/463)
+* [Person] Display suffixText in RenderPerson, PersonText.vue, RenderPersonBox.vue (was made for displaying "enfant confie") (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/441)
+* [budget]: budget enabled for persons and households (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/469)
+* [person] residential address: show residential address or info in PersonRenderBox, refactor Residential Address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/439)
+* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345)
+* [documents] Improve flex-table item-col placement when long buttons and long metadata
+* [thirdparty] Fix display of multiple contact badges so they wrap onto next line (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/482)
+* [confidential] Fix position of toggle button so it does not cover text nor fall outside of box (no issue)
+* [parcours] Fix edit of both thirdparty and contact name (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/474)
+* [template] do not list inactive templates (for doc generator)
+* [household] bugfix if position of member is null, renderbox no longer throws an error (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/480)
+* [parcours] location cannot be removed if linked to a user (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/478)
+* [person] email added to twig personRenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/490)
+* [activity] Only youngest descendant is kept for social issues and actions (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/471)
+* [person] Add link to current household in person banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/484)
+* [address] person badge in address history changed to open OnTheFly with all person info (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/489)
+* [person] Change 'personne' with 'usager' and '&' with 'ET' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/499)
+* [thirdparty] Add parameter condition to display centers or not (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/500)
+* [phonenumber] Remove placeholder in phonenumber field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/496)
+* [person_resource] separate create page created to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/504)
+* [contact] add contact button color changed plus the pipe at the side removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/506)
+* [thirdparty] For contacts show current civility/profession in edit form + fix saving of edited information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/491)
+* [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505)
+* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486)
+* [thirdparty] add firstname field to thirdparty 'child' or 'contact' types (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/508)
+* [household] create-edit household composition placed in separate page to avoid confusion (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/505)
+* [blur] Improved positioning of toggle icon (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/486)
+* [parcours] List of parcours for a specific user so they can be reassigned in case of absence (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/509)
+* [thirdparty] Thirdparty view page, english text translated (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/534)
+* [social_action] Translation changed in evaluation section (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/512)
+* [filiation] Possible to add person (or create onthefly) to add to filiation graph + add relation (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/519)
+* [household] Within parcours listing page of household add create button (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/560)
+* [person_resource] bugfix when adding thirdparty or freetext resource + prevent personOwner themselves to be added. (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/526)
+* [aside_activity] style correction + sticky-form create button (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/529)
+* [budget] order within the menu adjusted (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/592)
+* [onthefly] fix create person. Bug was noticed in filiation (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/591)
+* [parcours] Create document buttons made sticky (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/532)
+* [person] Trailing guillemet removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/530)
+* [notification] Display of social action within workflow notification set to display block (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/537)
+* [onthefly] trim trailing whitespace in email of person and thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/542)
+
+* [action] Only youngest descendant is kept for social issues and actions (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/471)
+## Test releases
+
+### test release 2022-02-21
+
+* [notifications] Word 'un' changed to number '1' for notifications in user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/483)
+* [documents] 'gabarit' changed to 'modèle' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/405)
+* [person_resources] Menu name and order changed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/460)
+* workflow: fix sending notifications
+* [thirdparty] Extend the thirdparty search to thirdparty children (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/448)
+* [person]: AddPersons: allow creation of person or thirdparty only (no users) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422)
+* [person]: AddPersons: allow creation of person or thirdparty depending on allowed types (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422)
+* [person]: AddPersons: add suggestion of name when creating new person or thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/422)
+* [main] Address: fix small bug: when modifying an address without street (isNoAddress), also check errors if street is an empty string as back-end change null value to empty string for street (and streetNumber)
+* [main] Address: stronger client-side validation of addresses (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/449)
+* [person] accompanying course: filter suggested entities by open participations (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/415)
+[activity] can click through the cross icon for removing person in concerned group (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/476)
+[activity] correct associated persons by considering only open participations (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/476)
+* [person_resources]: Renderboxes used to display person/thirdparty info (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/465)
+* [Household]: Add end date in HouseholdMember form for 'enfant hors menage' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/434)
+* [homepage_widget]: If no sender then display as 'notification automatique' (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/435)
+* [parcours]: Order social activities and only display most recent three in parcours resumé (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/481)
+* [3party]: 3party: redirect to parent when contact (child) is opened in view page
+* [parcours / addresses]: launch an event when a person change address (either through changing household or because the household is associated to a new address). If the person is localising a course, the course location go back to a temporarily address.
+* [thirdparty]: address/phonenumber/email/fonction displayed in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/401)
+* [thirdparty_contact]: in search results the 'qualité' is displayed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/465)
+* [bug]: fix confidential toggle of address in thirdpartyrenderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/460)
+
+
+
+### test release 2022-02-14
+
+* AddPersons: remove ul-li html tags from AddPersons (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/419)
+* [doc-generator] do not set required fields for mainPerson, person1, person2 (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement#456)
+* [doc-generation] add age and obele in the mainPerson, person1 and person2 list + add obele in person renderString if addAge (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/370)
+* [person] accompanying course work: fix on-the-fly update of thirdParty
+* fix normalisation of accompanying course requestor api (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/378)
+* [person] add a returnPath when clicking on some Person or ThirdParty badge (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/427)
+* [person] accompanying course work: fix on-the-fly update of thirdParty
+* [on-the-fly] close modal only after validation
+* [person] correct thirdparty PATCH url + add email and altnames in AddPerson and serializer (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/433)
+* change order for accompanying course work list
+* [parcours]: Mes parcours brouillon added to user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/440)
+* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414)
+* [person]: style fix in parcours listing per person. (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/432)
+* [parcours]: Only the referrer can toggle the intensity of the parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/442)
+* [household]: display address of current household (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/415)
+* ajoute un ordre dans les localisation (api)
+* [pick entity]: fix translations in modal (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/419)
+* [homepage_widget]: fix translation on emergency badge (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/440)
+* [person]: create person and household added to button dropdown (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/454)
+* display full address in address.text in normalization. Adapt AddressRenderBox
+* [address]: Correction residential address 'depuis le' (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/459)
+* [Documents]: List view adapted to display more information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/414)
+* [Thirdparty_contact]: address blurred if confidential in view page (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/450)
+* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345)
+
+
+### test release 2021-02-01
+
+* renommer "dossier numéro" en "parcours numéro" dans les résultats de recherche
+* renomme date de début en date d'ouverture dans le formulaire parcours
+* [homepage widget] improve content tables, improve counter pluralization with style on number
+* [notification lists] add comments counter information
+* [workflows] fix popover header with previous transition
+* [parcours]: validation + message for closing parcours adjusted.
+* [household]: household composition double edit button replaced by a delete action (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/426)
+[fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413
+[homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.
+* [person]: Comment on marital status is possible even if marital status is not defined (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/421)
+* [parcours]: In the list of person results the requestor is not displayed if defined as anonymous (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/424)
+* [bugfix]: modal closes and newly created person/thirdparty is selected when multiple persons/thirdparties are created through the modal (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/429)
+* [person_resource]: Onthefly button added to view person/thirdparty and badge differentiation for a contact-thirdparty (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/428)
+* [workflow][notification] improve how notifications and workflows are 'attached' to entities: contextual list, counter, buttons and vue modal
+* [AddAddress] disable multiselect search, and rely only on most pertinent Cities and Street computed backend
+* [fast_actions] improve fast-actions buttons override mechanism, fix https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/413
+* [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.
+* [thirdparty] Add a contact to a thirdparty from within onTheFly (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/345)
+* [homepage widget] add vue homepage_widget with asynchone loading, give a global view resume of the user concerned actions, notifications, etc.
+
+
+### test release 2021-01-31
+
+* [person] accompanying course: optimisation: do not fetch some resources for the banner (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/409)
+* [person] accompanying course: close modal when edit participation (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420)
+* [person] accompanying course: treat validation error when editing on-the-fly entities (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/420)
+* [activity] show activity attendee (présence) in the activity list (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/412)
+* [activity] admin: change validation rule for social action visible field (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/413)
+* [parcours]: component added to change the opening date of a parcours (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/411)
+* [search]: listing of parcours display changed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/410)
+* [user]: page with accompanying periods to which is user is referent (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/408)
+* [person] age added to renderstring + renderbox/ vue component created to display person text (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/389)
+* [household member editor] allow to push to existing household
+
+
+### test release 2021-01-28
+
+* [person] improve filiations vis graph: disable physics, use chill colors for persons-households-course, increase label of relations, remove labels on household arrows and other improvements (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/286, https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/362)
+* [activity] Order activity by date and by id (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/364)
+* [main] increase length of 4 Address fields (change to TEXT, no size limits) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/277)
+* [main] Add confidential option for address, in edit and view (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/165)
+* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377)
+* [person] Add residential address entity, form and list for each person
+* [aside_activity]: dynamicUserPickerType used (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/399)
+* dispatching list
+
+
+### test release 2021-01-26
+
+* [parcours] comments truncated if too long + link added (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/406)
+* [person]: possibility to add person resources (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/382)
+* [person ressources]: module added
+
+
+### test release 2022-01-24
+
+* [person] name suggestions within create person form when person is created departing from a search input (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/377)
+* [notification: formulaire création] descend la box avec la description dans le bas du formulaire
+* [notification for activity]: fix link to activity
+* [notification] add "URGENT" before accompanying course with emergency = true
+* [notification] add a "read more" button on system notification
+* [notification] add `[Chill]` in the subject of each notification, automatically
+* [notification] add a counter for notification in activity list and accompanying period list, and search results
+* [parcours] bugfix if deathdate is not defined (eg. for a thirdparty) parcours is still displayed. Gave error before.
+* [workflow] add breadcrumb to show steps
+* [popover] add popover html popup mechanism (used by workflow breadcrumb)
+* [templates] improve updatedBy macro in item metadatas
+* [parcours]: bug fix when comment is pinned all other comments remain in the collection (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/385)
+* [workflow]
+ * add My workflow section with my opened subscriptions
+ * apply workflow on documents, accompanyingCourseWork and Evaluations
+* [wopi-link] a new vue component allow to open wopi link in a fullscreen chill-themed modal
+
+### test release 2022-01-19
+* vuejs: add dead information on all on-the-fly person render boxes, in vis graph and other templates (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/271)
+* [thirdparty] fix bug in 3rd party view: types was replaced by thirdPartyTypes
+* [main] location form type: fix unmapped address field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/246)
+* [activity] fix wrong import of js assets for adding and viewing documents in activity (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/83 & https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/176)
+* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380)
+* [forms] dynamic picker types for user/person/thirdparty types created (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/386)
+
+### test release 2022-01-17
+
+* [main] Add editableByUser field to locationType entity, adapt the admin template and add this condition in the location-type endpoint (see https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/297)
+* [main] Add mainLocation field to User entity and add it in user form type
+* rewrite page which allow to select activity
+* [main] Add mainLocation field to User entity and add it in user form type
+* [course list in person context] show full username/label for ref
+* [accompanying period work] remove the possibility to generate document from an accompanying period work
+* vuejs: add validation on required fields for AddPerson, Address and Location components
+* vuejs: treat 422 validation errors in locations and AddPerson components
+* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380)
+
+## Test releases
+* vuejs: add validation on required fields for AddPerson, Address and Location components
+* vuejs: treat 422 validation errors in locations and AddPerson components
+* [person]: space added between deathdate and age in twig renderbox (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/380)
+
+### test release 2022-01-12
+
+* fix thirdparty normalizer on telephone field: https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/322
+
+### test release 2022-01-11
+
+* vuejs: translate in French all multiselect widgets
+* [address] define address lines according postal standards for France and Belgium (default) and change AddressRender, chill_entity_render_box and AddressRenderBox.vue
+* [household] change translations (champs-libres/departement-de-la-vendee/accent-suivi-developpement#109)
+* [household] add address i18n in household component (champs-libres/departement-de-la-vendee/accent-suivi-developpement#158)
+* [household] add on the fly i18n in household component
+* [household] redirect to the household page when a household is created from a person (champs-libres/departement-de-la-vendee/accent-suivi-developpement#175)
+* [household] household member editor: display alert if some members have already an household (champs-libres/departement-de-la-vendee/accent-suivi-developpement#172)
+* [household] household member editor: do not add in new members if the member is included in the members of household (champs-libres/departement-de-la-vendee/accent-suivi-developpement#123)
+* [household] household member editor: remove markNoAddress button (champs-libres/departement-de-la-vendee/accent-suivi-developpement#109)
+* [person]: ordering fields in add person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/61)
+* [person]: Add email and alt names in add person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/61)
+* [accompanyingCourse] Add a delete action and delete buttons to delete a accompanying course when step = DRAFT (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/64)
+* [accompanyingCourse] Add a administrative location in the accompanying course, set the user current location as default, allow to select a location in a select field and do not allow to confirm the accompanying course if location is empty.
+* [accompanyingCourse] Add the administrative location in the available variables for document generation
+* AddAddress: optimize loading: wait for the user finish typing;
+* UserPicker: fix bug with deprecated role
+* docgen: add base context + tests
+* docgen: add age for person
+* [household menu] fix filiation order https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/265
+* [AddAddress]: optimize loading: wait for the user finish typing;
+* [UserPicker]: fix bug with deprecated role
+* [docgen]: add base context + tests
+* [docgen]: add age for person
+* [task]: fix dropdown menu style + fix bug in singleTaskController (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/338)
+* Household: fix bug when moving person on the same day (see https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/281)
+* Household: show date validFrom and validTo when moving
+* address reference: add index for refid
+* [accompanyingCourse_work] fix styles conflicts + fix bug with remove goal (remove goals one at a time)
+* [accompanyingCourse] improve masonry on resume page, add origin
+* [notification] new notification interface, can be associated to AccompanyingCourse/Period, Activities.
+ * List notifications, show, and comment in User section
+ * Notify button and contextual notification box on associated objects pages
+* [accompanyingCourse] add a comment for each resource associated. A modal allow to save comment. Comment is displayed in on-the-fly show modal of the accompanyingCourse context (edit page + resume page).
+
+### test release 2021-12-14
+
+* [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262)
+* [vendee/person] fix typo "situation professionelle" => "situation professionnelle"
+* [main] add availableForUsers condition from locationType in the location API endpoint (champs-libres/departement-de-la-vendee/accent-suivi-developpement#248)
+* [main] add the current location of the user as API point + add it in the activity location list (champs-libres/departement-de-la-vendee/accent-suivi-developpement#247)
+* [activity] improve show/new/edit templates, fix SEE and SEE_DETAILS acl
+* [badges] create specific badge for TMS, and make person/thirdparty badges clickable with on-the-fly modal in :
+ * concerned groups items (activity, calendar)
+ * accompanyingCourseWork lists
+ * accompanyingCourse lists
+* [acompanyingCourse] add initial comment on Resume page
+* [person] create button full width (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/330)
+
+### test release 2021-12-11
+
+* [main] add order field to civility
+* [main] change address format in case the country is France, in Address render box and address normalizer
+* [person] add validator for accompanying period with a test on social issues (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/76)
+* [activity] fix visibility for location
+* [origin] fix origin: use correctly the translatable strings
+ * /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');`
+* [person] redirect bug fixed.
+* [action] add an unrelated issue within action creation.
+* [origin] fix origin: use correctly the translatable strings
+ * /!\ everyone must update the origin table. As there is only one row, execute `update chill_person_accompanying_period_origin set label = jsonb_build_object('fr', 'appel téléphonique');`
+* [main] change order of civilities in civility fixtures (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
+* [person] set min attr in the minimum of children field (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
+* [person] add marital status date in person view (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
+* [person] show number of children + allow set number of children to null (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
+* [person] show acceptSMS option (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
+* [person] add death information in person render box in twig and vue render boxes (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/191)
+* [asideactivity] creation of aside activity category fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/262)
+* [vendee/person] fix typo "situation professionelle" => "situation professionnelle"
+* [accompanyingcourse_work] Changes in layout/behavior of edit form (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/321)
+* [badge-entity] design coherency between pills badge-person and 3 kinds of badge-thirdparty
+* [AddPersons] suggestions row are clickable, not only checkbox
+
+### test release 2021-12-06
+
+* [main] address: use search API end points for getting postal code and reference address (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
+* [main] address: in edit mode, select the encoded values in multiselect for address reference and city (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/316)
+* [person search] fix bug when using birthdate after and birthdate before
+* [person search] increase pertinence when lastname begins with search pattern
+* [activity/actions] Améliore la cohérence du design entre
+ * la page résumé d'un parcours (liste d'actions récentes et liste d'activités récentes)
+ * la page liste des actions
+ * la page liste des activités (contexte personne / contexte parcours)
+* [household] field to edit wheter person is titulaire of household or not removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/322)
+* [activity] create work if a work with same social action is not associated to the activity
+* [visgraph] improve and fix bugs on vis-network relationship graph
+* [bugfix] posting of birth- and deathdate through api fixed.
+* [suggestions] improve suggestions lists
+
+### Test release 2021-11-19 - bis
+
+* [household] do not allow to create two addresses on the same date
+* [activity] handle case when there is no social action associated to social issue
+* [activity] layout for issues / actions
+* [activity][bugfix] in edit mode, the form will now load the social action list
+
+
+### Test release 2021-11-29
+
+* [person] suggest entities (person | thirdparty) when creating/editing the accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/119)
+* [activity] add custom validation on the Activity class, based on what is required from the ActivityType (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/188)
+* [main] translate multiselect messages when selecting/creating address
+* [main] set the coordinates of the city when creating a new address OR choosing "pas d'adresse complète"
+* Use the user.label in accompanying course banner, instead of username;
+* fix: show validation message when closing accompanying course;
+* [thirdparty] link from modal to thirdparty detail page fixed (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/228)
+* [assets] new asset to style suggestions lists (with add/remove item link) (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/258)
+* [accompanyingCourseWorkEdit] improves hyphenation and line breaks for long badges
+* [acompanyingCourse] improve Resume page
+ * complete all needed informations,
+ * actions and activities are clickables,
+ * better placement with js masonry blocks on top of content area,
+ * https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/101
+ * https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/295
+* [activity/calendar] on show page, concerned groups of persons table adapt itself to isVisibles options
+* [activity] remove the "plus" button in activity list
+* [activity] check ACL on activity list in person context
+* [list for accompanying course in person] filter list using ACL
+* [validation] toasts are displayed for errors when modifying accompanying course (generalization required).
+* [period] only the user can enable confidentiality
+* add an endpoint for checking permissions. See https://gitlab.com/Chill-Projet/chill-bundles/-/merge_requests/232
+* [activity] for a new activity: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
+* [calendar] for a new rdv: suggest and create on-the-fly locations based on the accompanying course location + location of the suggested parties
+* [period] Validation added when period is confidential and confirmed -> user cannot be null.
+
+
+## Test releases
+
+### Test release 2021-11-22
+
+* [activity] delete admin_user_show in twig template because this route is not defined and should be defined
+* [activity] suggest requestor, user and ressources for adding persons|user|3rdparty
+* [calendar] suggest persons, professionals and invites for adding persons|3rdparty|user
+* [activity] take into account the restrictions on person|thirdparties|users visibilities defined in ActivityType
+* [main] Add currentLocation to the User entity + add a page for selecting this location + add in the user menu (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/133)
+* [activity] add user current location as default location for a new activity (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/133)
+* [task] Select2 field in task form to allow search for a user (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/167)
+* remove "search by phone configuration option": search by phone is now executed by default
+* remplacer le classement par ordre alphabétique par un classement par ordre de pertinence, qui tient compte:
+ * de la présence d'une string avec le nom de la ville;
+ * de la similarité;
+ * du fait que la recherche commence par une partie du mot recherché
+* ajouter la recherche par numéro de téléphone directement dans la barre de recherche et dans le formulaire recherche avancée;
+* ajouter la recherche par date de naissance directement dans la barre de recherche;
+* ajouter la recherche par ville dans la recherche avancée
+* ajouter un lien vers le ménage dans les résultats de recherche
+* ajouter l'id du parcours dans les résultats de recherche
+* ajouter le demandeur dans les résultats de recherche
+* ajout d'un bouton "recherche avancée" sur la page d'accueil
+* [person] create an accompanying course: add client-side validation if no origin (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/210)
+* [person] fix bounds for computing current person address: the new address appears immediatly
+* [docgen] create a normalizer and serializer for normalization on doc format
+* [person normalization] the key center is now "centers" and is an array. Empty array if no center
+* [accompanyingCourse] Ability to close accompanying course (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/296)
+* [task] Select2 field in task form to allow search for a user (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/167)
+* [list result] show all courses, except ones with period closed
+* [accompanyingCourse] improve banner with small carousel to display slide social-issues or slide associated persons (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/69)
+
+### Test release 2021-11-15
+
+* [main] fix adding multiple AddresseDeRelais (combine PickAddressType with ChillCollection)
+* [person]: do not suggest the current household of the person (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/51)
+* [person]: display other phone numbers in view + add message in case no others phone numbers (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/184)
+* unnecessary whitespace removed from person banner after person-id + double parentheses removed (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/290)
+* [person]: delete accompanying period work, including related objects (cascade) (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/36)
+* [address]: Display of incomplete address adjusted.
+* [household]: improve relationship graph
+ * add form to create/edit/delete relationship link,
+ * improve graph refresh mechanism
+ * add feature to export canvas as image (png)
+* [person suggest] In widget "add person", improve the pertinence of persons when one of the names starts with the pattern;
+* [person] do not ask for center any more on person creation
+* [3party] do not ask for center any more on 3party creation
+
+## Test releases
+
+### Test release 2021-11-08
+
+* [person]: Display the name of a user when searching after a User (TMS)
+* [person]: Add civility to the person
+* [person]: Various improvements on the edit person form
+* [person]: Set available_languages and available_countries as parameters for use in the edit person form
+* [activity] Bugfix: documents can now be added to an activity.
+* [tasks] improve tasks with filter order
+* [tasks] refactor singleControllerTasks: limit the number of conditions from the context
+* [validations] validation of accompanying period added: no duplicate participations or resources (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/60).
+* [renderbox] If gender of person is not defined, no icon is displayed instead of neuter-icon (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/129).
+* [confidential information] module added to blur confidential information (https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/248).
+* refactor `AuthorizationHelper` and `UserACLAwareRepository` to fix constructor, and separate logic for parent role helper into `ParentRoleHelper`
+* [main]: filter location and locationType in backend: exclude NULL names, only active and availableToUsers
+* [activity]: perform client-side validation & show/hide fields in the "new location" modal
+* [person]: normalize person with CenterResolverDispatcher and handle case where center is null or multiple in PersonRenderBox
+* [docstore] voter for PersonDocument and AccompanyingCourseDocument on the 2.0 way (using VoterHelperFactory)
+* [docstore] add authorization check inside controller and menu
+* [activity]: fix inheritance for role `ACTIVITY FULL` and add missing acl in menu
+* [person] show current address in search results
+* [person] show alt names in search results
+* [admin]: links to activity admin section added again.
+* [household]: endDate field deleted from household edit form.
+* [household]: View accompanying periods of current and old household members.
+* [tasks]: different layout for task list / my tasks, and fix link to tasks in alert or in warning
+* [admin]: links to activity admin section added again.
+* [household]: household addresses ordered by ValidFrom date and by id to show the last created address on top.
+* [socialWorkAction]: display of social issue and parent issues + banner context added.
+* [DBAL dependencies] Upgrade to DBAL 3.1
+
+### Test release 2021-10-27
+
+* [person]: delete double actions buttons on search person page
+* [person]: accompanying course work: remove creation date display the list of work + handle case when end date is null
+* [main]: Add new pages with a menu for managing location and location type in the admin
+* [main]: Add some fixtures for location type
+* [calendar]: Pass the location when transforming a calendar item (rdv) into an activity
+* [calendar]: Add a user menu for "my calendar"
+
+### Test release 2021-10-18
+
+* [3party]: french translation of contact and company
+* [3party]: show parent in list
+* [3party]: change color for badge "child"
+* [3party]: fix address creation
+* [household members editor] finalisation of editor
+* [AccompanyingCourse banner]: replace translation referrer (https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/70)
+* [Location]: add location system in activity and RV (calendar). User can choose in location list or create a new location.
+* [household]: add relationship page with dynamic data visualisation graph
+
+## Test releases
+
+### Test release 2021-10-11
+
+* Address: zoom on postal code geometry + fix origin of manually entered postal code
+
+* in the Address vue component, order the postal code and street address by alphabetic and numeric order
+
+* add 3 new fields to PostalCode and adapt postal code command and fixtures
+
+* [Aside activity] Fixes for aside activity
+
+ * categories with child
+ * fast creation buttons
+ * add ordering for types
+
+* [AccompanyingCourse Resume page] dashboard for AccompanyingCourseWork and for Activities;
+* Improve badges behaviour with small screens;
+
+* [ThirdParty]:
+
+ * third party list
+ * create a kind contact/institution when create a new thirdparty, and set contact embedded as kind=child;
+ * filter thirdparties in list
+
+* [FilterOrder]: add development kit for generating filter and ordering in list
+* [Capitalization of names] person names are capitalized on creation, on prePersist event
+* [On-The-Fly] modale works for showing, editing and creating person or thirdparty ;
+* [AccompanyingCourse Resume page] associated persons list, can see household when hover, and with show on-the-fly modale when clicking person ;
+
+### test release 2021-10-04
+
+* [Household editor][UI] Update how household suggestion and addresses are picked;
+
+ See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/80
+* [AddAddress] Handle address suggestion;
+* [CenterType][Create a person] when overriding the ACL rules, allow to show a PickCenterType
+ when no centers are reachable by the default ACL.
+* [Household] Show comment event if no address are associated with the household;
+* [Person results] Add requestor into search results:
+
+ * a badge "requestor" is shown into search results;
+ * periods where the person is only requestor (without participating) are also shown;
+
+ Issues:
+
+ * https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/13
+ * https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/199
+* [Person form] "accept sms" not required:
+
+ https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/37
+ https://gitlab.com/champs-libres/departement-de-la-vendee/chill/-/issues/221
+
+* [Household editor] suggest only temporarily addresses;
+ See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/82
+* On-The-Fly modale works for showing, editing and creating person and thirdparty ;
+* AccompanyingCourse Resume page: list associated persons by household, see household when hover, and show on-the-fly modale when clicking on person ;
+* [AddAddress] Handle address suggestion;
+* [AddAddress][Entity address]: add a link between address and address reference;
+* [Household editor] suggest household by comparing the temporary addresses from courses;
+
+ See https://gitlab.com/champs-libres/departement-de-la-vendee/accent-suivi-developpement/-/issues/81
+* On-The-Fly modale works for showing, editing and creating person and thirdparty
+
+
+## Test released
+
+
+
+## Stable releases
+
+No stable releases for v2+
+
diff --git a/.changes/v2.1.0.md b/.changes/v2.1.0.md
new file mode 100644
index 000000000..ade83aee0
--- /dev/null
+++ b/.changes/v2.1.0.md
@@ -0,0 +1,17 @@
+## v2.1.0 - 2023-06-12
+
+### Feature
+
+* [docgen] allow to pick a third party when generating a document in context Activity, AccompanyingPeriod
+
+### Fixed
+
+* ([#111](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/111)) List of "my accompanying periods": separate the active and closed periods in two different lists, and show the inactive_long and inactive_short periods
+
+### Security
+
+* ([#105](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/105)) Rights are checked for display of 'accompanying period' tab in household menu. Rights are also checked for creation of 'accompanying period' from within household context
+
+### DX
+
+* Add methods to RegroupmentRepository and fullfill Center / Regroupment Doctrine mapping
diff --git a/.changes/v2.2.0.md b/.changes/v2.2.0.md
new file mode 100644
index 000000000..5371cad35
--- /dev/null
+++ b/.changes/v2.2.0.md
@@ -0,0 +1,12 @@
+## v2.2.0 - 2023-06-18
+### Feature
+* When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document
+* Add notification to accompanying period work and work's evaluation's documents
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates
+### Fixed
+* use the correct annotation for the association between PersonCurrentCenter and Person
+* ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox
+* ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter
+### DX
+* DQL function OVERLAPSI: simplify expression in postgresql
diff --git a/.changes/v2.2.1.md b/.changes/v2.2.1.md
new file mode 100644
index 000000000..c96b18585
--- /dev/null
+++ b/.changes/v2.2.1.md
@@ -0,0 +1,3 @@
+## v2.2.1 - 2023-06-19
+### Fixed
+* ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation
diff --git a/.changes/v2.2.2.md b/.changes/v2.2.2.md
new file mode 100644
index 000000000..61d194b6d
--- /dev/null
+++ b/.changes/v2.2.2.md
@@ -0,0 +1,5 @@
+## v2.2.2 - 2023-06-26
+### Fixed
+* [Accompanying period comments]: order comments from the most recent to the oldest, in the list
+* Api: filter social action to keep only the currently activated
+* ([#82](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/82)) Fix deletion and re-creation of filiation relationship
diff --git a/.changes/v2.3.0.md b/.changes/v2.3.0.md
new file mode 100644
index 000000000..827a338de
--- /dev/null
+++ b/.changes/v2.3.0.md
@@ -0,0 +1,42 @@
+## v2.3.0 - 2023-06-27
+### Feature
+* ([#110](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/110)) Edit saved exports options: the saved exports options (forms, filters, aggregators) are now editable.
+* ([#103](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/103)) Get an unified list of document in person and accompanying period context
+* [export] Set the default date of calculation of the accompanying period's list as "today"
+* Force accompanying period user history to be unique for the same period and stardate/enddate [:warning: may encounter migration issue]
+
+ If some issue is encountered during migration, use this SQL to find the line which are in conflict, examine the problem and delete some of the concerning line
+*
+ ```sql
+ -- to see the line which are in conflict with another one
+ SELECT o.*
+ FROM chill_person_accompanying_period_user_history o
+ JOIN chill_person_accompanying_period_user_history c ON o.id < c.id AND o.accompanyingperiod_id = c.accompanyingperiod_id
+ WHERE tsrange(o.startdate, o.enddate, '[)') && tsrange(c.startdate, c.enddate, '[)')
+ ORDER BY accompanyingperiod_id;
+ -- to examine line in conflict for a given accompanyingperiod_id (given by the previous query)
+ SELECT * FROM chill_person_accompanying_period_user_history WHERE accompanyingperiod_id = IIIIDDDD order by startdate, enddate;
+ ```
+* Rename label of filter in French: "parcours actif" => "parcours ouvert", and "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture"
+
+### Traduction francophone des principaux changements
+
+* Les exports enregistrés sont éditables par l'utilisateur;
+* L'onglet "Document" dans les parcours et les dossiers d'usager affiche désormais les documents ajoutés à différents endroits.
+
+ Pour les parcours, il s'agit de:
+
+ - documents ajoutés directement dans le parcours;
+ - documents des échanges;
+ - documents des rendez-vous;
+ - documents des évaluations;
+ - documents directement ajoutés dans le dossier des usagers concernés par le parcours;
+
+ Pour les usagers, il s'agit de:
+
+ - documents des échanges;
+ - documents des parcours;
+ - documents des rendez-vous;
+ - documents des actions, des échanges, des rendez-vous, des évaluations ajoutés dans les parcours.
+* Dans la liste des parcours, la date de calcul des éléments associés est "aujourd'hui" par défaut.
+* Dans les exports, renommage des libellés des filtres: "parcours actif" => "parcours ouvert", et "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture"
diff --git a/.changes/v2.4.0.md b/.changes/v2.4.0.md
new file mode 100644
index 000000000..522957300
--- /dev/null
+++ b/.changes/v2.4.0.md
@@ -0,0 +1,36 @@
+## v2.4.0 - 2023-07-07
+
+### Feature
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] on "filter by user working" on accompanying period, add two dates to filters intervention within a period
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add an aggregator by user's job working on a course
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add an aggregator by user's scope working on a course
+* [export] on aggregator "user working on a course"
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a center aggregator for Person
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a filter on "job working on a course"
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add a filter on "scope working on a course"
+* ([#121](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/121)) Create a role "See Confidential Periods", separated from the "Reassign courses" role
+* ([#124](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/124)) Sync user absence / presence through microsoft outlook / graph api.
+
+### Fixed
+* ([#116](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/116)) On the accompanying course page, open the action on view mode if the user does not have right to update them (i.e. if the accompanying period is closed)
+* [export] Rename label for CurrentActionFilter (on accompanying period work) to make precision between "ouvert" and "sans date de fin"
+* Force the db to have either a person_location or a address_location, and avoid to have both also internally in the entity
+* [export] set rolling date on person age aggregator
+* [export] fix list when a person locating a course is without address
+* [export] remove unused condition on course about duration participation
+* Command to subscribe on MS Graph users calendars: improve the loop to be more efficient
+
+### DX
+* Rolling Date: can receive a null parameter
+
+### Traduction francophone des principaux changements
+
+- sur le "filtre par intervenant", ajoute deux dates pour limiter la période d'intervention;
+- ajout d'un regroupement par métier des intervenants sur un parcours;
+- ajout d'un regroupement par service des intervenants sur un parcours;
+- ajout d'un regroupement par utilisateur intervenant sur un parcours
+- ajout d'un regroupement "par centre de l'usager";
+- ajout d'un filtre "par métier intervenant sur un parcours";
+- ajout d'un filtre "par service intervenant sur un parcours";
+- création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot);
+- synchronisation de l'absence des utilisateurs par microsoft graph api
diff --git a/.changie.yaml b/.changie.yaml
new file mode 100644
index 000000000..8a25ed695
--- /dev/null
+++ b/.changie.yaml
@@ -0,0 +1,37 @@
+changesDir: .changes
+unreleasedDir: unreleased
+headerPath: header.tpl.md
+changelogPath: CHANGELOG.md
+versionExt: md
+versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
+kindFormat: '### {{.Kind}}'
+# Note: it is possible to add a `.custom.Long` text manually into the yaml file produced by `changie new`. This will add a long description.
+changeFormat: >-
+ * {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{ end }}{{.Body}} {{ if not (eq .Custom.Long "") }}
+
+ {{ .Custom.Long }}{{ end }}
+custom:
+ - key: Issue
+ label: Issue number (on chill-bundles repository) (optional)
+ optional: true
+ type: int
+ minInt: 1
+body:
+ # allow multiline messages
+ block: true
+kinds:
+ - label: Feature
+ auto: minor
+ - label: Deprecated
+ auto: minor
+ - label: Fixed
+ auto: patch
+ - label: Security
+ auto: patch
+ - label: DX
+ auto: patch
+newlines:
+ afterChangelogHeader: 1
+ beforeChangelogVersion: 1
+ endOfVersion: 1
+envPrefix: CHANGIE_
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3f1d75ed5..aa1eff8b1 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -34,6 +34,7 @@ variables:
stages:
- Composer install
- Tests
+ - Deploy
build:
stage: Composer install
@@ -121,3 +122,14 @@ unit_tests:
paths:
- bin
- tests/app/vendor/
+
+release:
+ stage: Deploy
+ image: registry.gitlab.com/gitlab-org/release-cli:latest
+ rules:
+ - if: $CI_COMMIT_TAG
+ script:
+ - echo "running release_job"
+ release:
+ tag_name: '$CI_COMMIT_TAG'
+ description: "./.changes/v$CI_COMMIT_TAG.md"
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
index 4b5bf98ee..31d64e600 100644
--- a/.php-cs-fixer.dist.php
+++ b/.php-cs-fixer.dist.php
@@ -13,6 +13,7 @@ $finder = PhpCsFixer\Finder::create();
$finder
->in(__DIR__.'/src')
+ ->in(__DIR__.'/utils')
->append([__FILE__])
->exclude(['docs/', 'tests/app'])
->notPath('tests/app')
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b2f951479..b74eea58d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,140 @@
# Changelog
-
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to
+adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
+and is generated by [Changie](https://github.com/miniscruff/changie).
-* [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for stable releases;
-* date versioning for test releases
-## Unreleased
+## v2.4.0 - 2023-07-07
+
+### Feature
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] on "filter by user working" on accompanying period, add two dates to filters intervention within a period
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add an aggregator by user's job working on a course
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add an aggregator by user's scope working on a course
+* [export] on aggregator "user working on a course"
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a center aggregator for Person
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] add a filter on "job working on a course"
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113)) [export] Add a filter on "scope working on a course"
+* ([#121](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/121)) Create a role "See Confidential Periods", separated from the "Reassign courses" role
+* ([#124](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/124)) Sync user absence / presence through microsoft outlook / graph api.
+
+### Fixed
+* ([#116](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/116)) On the accompanying course page, open the action on view mode if the user does not have right to update them (i.e. if the accompanying period is closed)
+* [export] Rename label for CurrentActionFilter (on accompanying period work) to make precision between "ouvert" and "sans date de fin"
+* Force the db to have either a person_location or a address_location, and avoid to have both also internally in the entity
+* [export] set rolling date on person age aggregator
+* [export] fix list when a person locating a course is without address
+* [export] remove unused condition on course about duration participation
+* Command to subscribe on MS Graph users calendars: improve the loop to be more efficient
+
+### DX
+* Rolling Date: can receive a null parameter
+
+### Traduction francophone des principaux changements
+
+- sur le "filtre par intervenant", ajoute deux dates pour limiter la période d'intervention;
+- ajout d'un regroupement par métier des intervenants sur un parcours;
+- ajout d'un regroupement par service des intervenants sur un parcours;
+- ajout d'un regroupement par utilisateur intervenant sur un parcours
+- ajout d'un regroupement "par centre de l'usager";
+- ajout d'un filtre "par métier intervenant sur un parcours";
+- ajout d'un filtre "par service intervenant sur un parcours";
+- création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot);
+- synchronisation de l'absence des utilisateurs par microsoft graph api
+
+## v2.3.0 - 2023-06-27
+### Feature
+* ([#110](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/110)) Edit saved exports options: the saved exports options (forms, filters, aggregators) are now editable.
+* ([#103](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/103)) Get an unified list of document in person and accompanying period context
+* [export] Set the default date of calculation of the accompanying period's list as "today"
+* Force accompanying period user history to be unique for the same period and stardate/enddate [:warning: may encounter migration issue]
+
+ If some issue is encountered during migration, use this SQL to find the line which are in conflict, examine the problem and delete some of the concerning line
+*
+ ```sql
+ -- to see the line which are in conflict with another one
+ SELECT o.*
+ FROM chill_person_accompanying_period_user_history o
+ JOIN chill_person_accompanying_period_user_history c ON o.id < c.id AND o.accompanyingperiod_id = c.accompanyingperiod_id
+ WHERE tsrange(o.startdate, o.enddate, '[)') && tsrange(c.startdate, c.enddate, '[)')
+ ORDER BY accompanyingperiod_id;
+ -- to examine line in conflict for a given accompanyingperiod_id (given by the previous query)
+ SELECT * FROM chill_person_accompanying_period_user_history WHERE accompanyingperiod_id = IIIIDDDD order by startdate, enddate;
+ ```
+* Rename label of filter in French: "parcours actif" => "parcours ouvert", and "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture"
+
+### Traduction francophone des principaux changements
+
+* Les exports enregistrés sont éditables par l'utilisateur;
+* L'onglet "Document" dans les parcours et les dossiers d'usager affiche désormais les documents ajoutés à différents endroits.
+
+ Pour les parcours, il s'agit de:
+
+ - documents ajoutés directement dans le parcours;
+ - documents des échanges;
+ - documents des rendez-vous;
+ - documents des évaluations;
+ - documents directement ajoutés dans le dossier des usagers concernés par le parcours;
+
+ Pour les usagers, il s'agit de:
+
+ - documents des échanges;
+ - documents des parcours;
+ - documents des rendez-vous;
+ - documents des actions, des échanges, des rendez-vous, des évaluations ajoutés dans les parcours.
+* Dans la liste des parcours, la date de calcul des éléments associés est "aujourd'hui" par défaut.
+* Dans les exports, renommage des libellés des filtres: "parcours actif" => "parcours ouvert", et "filtrer les parcours ouverts" => "Filtrer les parcours dont la date d'ouverture"
+
+## v2.2.2 - 2023-06-26
+### Fixed
+* [Accompanying period comments]: order comments from the most recent to the oldest, in the list
+* Api: filter social action to keep only the currently activated
+* ([#82](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/82)) Fix deletion and re-creation of filiation relationship
+
+## v2.2.1 - 2023-06-19
+### Fixed
+* ([#114](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/114)) [notification on document evaluation] fix entityId and return path when adding a notification on a document in an evaluation
+
+## v2.2.0 - 2023-06-18
+### Feature
+* When navigating from a workflow regarding to an evaluation's document to an accompanying course, scroll directly to the document, and blink to highlight this document
+* Add notification to accompanying period work and work's evaluation's documents
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[Export] Filter accompanying period by step at date: allow to pick multiple steps
+* ([#113](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/113))[export] add a filter on accompanying period: filter by step between two dates
+### Fixed
+* use the correct annotation for the association between PersonCurrentCenter and Person
+* ([#58](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/58))Fix birthdate timezone in PersonRenderBox
+* ([#55](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/55))Fix the notification counter
+### DX
+* DQL function OVERLAPSI: simplify expression in postgresql
+
+## v2.1.0 - 2023-06-12
+
+### Feature
+
+* [docgen] allow to pick a third party when generating a document in context Activity, AccompanyingPeriod
+
+### Fixed
+
+* ([#111](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/111)) List of "my accompanying periods": separate the active and closed periods in two different lists, and show the inactive_long and inactive_short periods
+
+### Security
+
+* ([#105](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/105)) Rights are checked for display of 'accompanying period' tab in household menu. Rights are also checked for creation of 'accompanying period' from within household context
+
+### DX
+
+* Add methods to RegroupmentRepository and fullfill Center / Regroupment Doctrine mapping
+
+## 2.0.0
+
+* this is a release to relaunch our proceess of release with semantic versioning
+
+## Test releases
+
+### 2.0.0-beta3
-
* [person][export] Fixed: rename the alias for `accompanying_period` to `acp` in filter associated with person
* [activity][export] Feature: improve label for aliases in "Filter by activity type"
* [activity][export] DX/Feature: use of an `ActivityTypeRepositoryInterface` instead of the old-style EntityRepository
@@ -18,9 +142,6 @@ and this project adheres to
* [person][export] Fixed: use left join for related entities in accompanying course aggregators
* [workflow] Feature: allow user to copy and send manually the access link for the workflow
* [workflow] Feature: show the email addresses that received an access link for the workflow
-
-## Test releases
-
### 2.0.0-beta2
* [workflow]: Fixed: the notification is sent when the user is added to the first step.
diff --git a/composer.json b/composer.json
index f6d3eb27a..a128b1b77 100644
--- a/composer.json
+++ b/composer.json
@@ -67,6 +67,7 @@
"fakerphp/faker": "^1.13",
"jangregor/phpstan-prophecy": "^1.0",
"nelmio/alice": "^3.8",
+ "nikic/php-parser": "^4.15",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
@@ -103,14 +104,16 @@
"Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle",
"Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
"Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle",
- "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src"
+ "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src",
+ "Chill\\Utils\\Rector\\": "utils/rector/src"
}
},
"autoload-dev": {
"psr-4": {
"App\\": "tests/app/src/",
"Chill\\DocGeneratorBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests",
- "Chill\\WopiBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests"
+ "Chill\\WopiBundle\\Tests\\": "src/Bundle/ChillDocGeneratorBundle/tests",
+ "Chill\\Utils\\Rector\\Tests\\": "utils/rector/tests"
}
},
"config": {
diff --git a/docs/source/_static/code/exports/BirthdateFilter.php b/docs/source/_static/code/exports/BirthdateFilter.php
index 64c1d53a9..e25d8b42f 100644
--- a/docs/source/_static/code/exports/BirthdateFilter.php
+++ b/docs/source/_static/code/exports/BirthdateFilter.php
@@ -62,7 +62,6 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac
{
$builder->add('date_from', DateType::class, [
'label' => 'Born after this date',
- 'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
@@ -70,12 +69,15 @@ class BirthdateFilter implements ExportElementValidatedInterface, FilterInterfac
$builder->add('date_to', DateType::class, [
'label' => 'Born before this date',
- 'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
]);
}
+ public function getFormDefaultData(): array
+ {
+ return ['date_from' => new DateTime(), 'date_to' => new DateTime()];
+ }
// here, we create a simple string which will describe the action of
// the filter in the Response
diff --git a/docs/source/_static/code/exports/CountPerson.php b/docs/source/_static/code/exports/CountPerson.php
index afe19c73b..be800e52c 100644
--- a/docs/source/_static/code/exports/CountPerson.php
+++ b/docs/source/_static/code/exports/CountPerson.php
@@ -36,6 +36,10 @@ class CountPerson implements ExportInterface
{
// this export does not add any form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes()
{
diff --git a/exports_alias_conventions.md b/exports_alias_conventions.md
index fd7844691..64df91030 100644
--- a/exports_alias_conventions.md
+++ b/exports_alias_conventions.md
@@ -18,6 +18,7 @@ These are alias conventions :
| | SocialIssue::class | acp.socialIssues | acpsocialissue |
| | User::class | acp.user | acpuser |
| | AccompanyingPeriopStepHistory::class | acp.stepHistories | acpstephistories |
+| | AccompanyingPeriodInfo::class | not existing (using custom WITH clause) | acpinfo |
| AccompanyingPeriodWork::class | | | acpw |
| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval |
| | User::class | acpw.referrers | acpwuser |
@@ -28,6 +29,8 @@ These are alias conventions :
| | Person::class | acppart.person | partperson |
| AccompanyingPeriodWorkEvaluation::class | | | workeval |
| | Evaluation::class | workeval.evaluation | eval |
+| AccompanyingPeriodInfo::class | | | acpinfo |
+| | User::class | acpinfo.user | acpinfo_user |
| Goal::class | | | goal |
| | Result::class | goal.results | goalresult |
| Person::class | | | person |
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 56b7c2228..62dbe0468 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -2,6 +2,7 @@ parameters:
level: 5
paths:
- src/
+ - utils/
tmpDir: .cache/
reportUnmatchedIgnoredErrors: false
excludePaths:
diff --git a/phpunit.rector.xml b/phpunit.rector.xml
new file mode 100644
index 000000000..f8d9d3a11
--- /dev/null
+++ b/phpunit.rector.xml
@@ -0,0 +1,29 @@
+
+
+
+
+ utils/rector/tests
+
+
+
+
+
+ utils/rector/src
+
+
+
diff --git a/rector.php b/rector.php
index ec9a0c684..c2e752c32 100644
--- a/rector.php
+++ b/rector.php
@@ -24,6 +24,9 @@ return static function (RectorConfig $rectorConfig): void {
LevelSetList::UP_TO_PHP_74
]);
+ // chill rules
+ $rectorConfig->rule(\Chill\Utils\Rector\Rector\ChillBundleAddFormDefaultDataOnExportFilterAggregatorRector::class);
+
// skip some path...
$rectorConfig->skip([
// make rector stuck for some files
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php
index 5c6656009..c23db738e 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByActivityNumberAggregator.php
@@ -40,6 +40,10 @@ class ByActivityNumberAggregator implements AggregatorInterface
{
// No form needed
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php
index 69149737b..917459de3 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByCreatorAggregator.php
@@ -52,6 +52,10 @@ class ByCreatorAggregator implements AggregatorInterface
{
// no form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php
index 89732412d..1a75357ae 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialActionAggregator.php
@@ -57,6 +57,10 @@ class BySocialActionAggregator implements AggregatorInterface
{
// no form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php
index 158e87664..9100a8c8f 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/BySocialIssueAggregator.php
@@ -57,6 +57,10 @@ class BySocialIssueAggregator implements AggregatorInterface
{
// no form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php
index c3ca6d59c..ac05b153c 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/ByThirdpartyAggregator.php
@@ -57,6 +57,10 @@ class ByThirdpartyAggregator implements AggregatorInterface
{
// no form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php
index 2c7ec1483..a4b5258e2 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/CreatorScopeAggregator.php
@@ -57,6 +57,10 @@ class CreatorScopeAggregator implements AggregatorInterface
{
// no form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php
index b4b23dc0b..4ede5b4c4 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/DateAggregator.php
@@ -29,14 +29,6 @@ class DateAggregator implements AggregatorInterface
private const DEFAULT_CHOICE = 'year';
- private TranslatorInterface $translator;
-
- public function __construct(
- TranslatorInterface $translator
- ) {
- $this->translator = $translator;
- }
-
public function addRole(): ?string
{
return null;
@@ -84,9 +76,12 @@ class DateAggregator implements AggregatorInterface
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
- 'data' => self::DEFAULT_CHOICE,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return ['frequency' => self::DEFAULT_CHOICE];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php
index ec4ce6315..c72609e2c 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ACPAggregators/LocationTypeAggregator.php
@@ -57,6 +57,10 @@ class LocationTypeAggregator implements AggregatorInterface
{
// no form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php
index 7cd16718e..a74428b4a 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityTypeAggregator.php
@@ -60,6 +60,10 @@ class ActivityTypeAggregator implements AggregatorInterface
{
// no form required for this aggregator
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data): Closure
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php
index 9bde692c6..2fab2af83 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUserAggregator.php
@@ -58,6 +58,10 @@ class ActivityUserAggregator implements AggregatorInterface
{
// nothing to add
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, $values, $data): Closure
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php
index 139f2743e..e1e9f161d 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersAggregator.php
@@ -56,6 +56,10 @@ class ActivityUsersAggregator implements AggregatorInterface
{
// nothing to add on the form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php
index 5741a0e58..721078989 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersJobAggregator.php
@@ -55,6 +55,10 @@ class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorI
{
// nothing to add in the form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php
index 15da300be..d7932e9e8 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/ActivityUsersScopeAggregator.php
@@ -55,6 +55,10 @@ class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\Aggregato
{
// nothing to add in the form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php
index eaccf95cb..2537f2cf6 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/PersonAggregators/ActivityReasonAggregator.php
@@ -110,6 +110,10 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
]
);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php
index 5f772e156..ae1dae375 100644
--- a/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php
+++ b/src/Bundle/ChillActivityBundle/Export/Aggregator/SentReceivedAggregator.php
@@ -47,6 +47,10 @@ class SentReceivedAggregator implements AggregatorInterface
{
// No form needed
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data): callable
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php
index 34771d077..6930784d3 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityDuration.php
@@ -39,6 +39,10 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface
public function buildForm(FormBuilderInterface $builder)
{
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes(): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php
index df21362cd..f1e926c64 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/AvgActivityVisitDuration.php
@@ -40,6 +40,10 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac
{
// TODO: Implement buildForm() method.
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes(): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php
index 6b7b1562d..d473a925a 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/CountActivity.php
@@ -39,6 +39,10 @@ class CountActivity implements ExportInterface, GroupedExportInterface
public function buildForm(FormBuilderInterface $builder)
{
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes(): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php
index 026e16f90..9ed6bcda2 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/ListActivity.php
@@ -44,6 +44,10 @@ class ListActivity implements ListInterface, GroupedExportInterface
{
$this->helper->buildForm($builder);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes()
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php
index e916cab54..8adb3b77f 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityDuration.php
@@ -40,6 +40,10 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface
{
// TODO: Implement buildForm() method.
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes(): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php
index 18a47289c..cc424e68b 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToACP/SumActivityVisitDuration.php
@@ -40,6 +40,10 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac
{
// TODO: Implement buildForm() method.
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes(): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php
index 4246df173..6360251b3 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/CountActivity.php
@@ -35,6 +35,10 @@ class CountActivity implements ExportInterface, GroupedExportInterface
public function buildForm(FormBuilderInterface $builder)
{
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes()
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php
index 60110c9a8..d14111ba7 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/ListActivity.php
@@ -88,6 +88,10 @@ class ListActivity implements ListInterface, GroupedExportInterface
])],
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes()
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php
index 050034954..e68d47cd3 100644
--- a/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php
+++ b/src/Bundle/ChillActivityBundle/Export/Export/LinkedToPerson/StatActivityDuration.php
@@ -53,6 +53,10 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
public function buildForm(FormBuilderInterface $builder)
{
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes()
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php
index c6616a4c6..4ffc3b38c 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ActivityTypeFilter.php
@@ -68,6 +68,10 @@ class ActivityTypeFilter implements FilterInterface
'expanded' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php
index 322393f32..add9c7c3d 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/ByCreatorFilter.php
@@ -52,6 +52,10 @@ class ByCreatorFilter implements FilterInterface
'multiple' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php
index d0c1b0fc7..08f6bbbc3 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialActionFilter.php
@@ -60,6 +60,10 @@ class BySocialActionFilter implements FilterInterface
'multiple' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php
index bbb882a65..bd6e2ef4a 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/BySocialIssueFilter.php
@@ -60,6 +60,10 @@ class BySocialIssueFilter implements FilterInterface
'multiple' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php
index b79c2ca10..807ba3903 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/EmergencyFilter.php
@@ -68,9 +68,12 @@ class EmergencyFilter implements FilterInterface
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
- 'data' => self::DEFAULT_CHOICE,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return ['accepted_emergency' => self::DEFAULT_CHOICE];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php
index 570f42ae0..b5dab4009 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/HasNoActivityFilter.php
@@ -44,6 +44,10 @@ class HasNoActivityFilter implements FilterInterface
{
//no form needed
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php
index 3d69d1633..312f3a6a0 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationFilter.php
@@ -46,6 +46,10 @@ class LocationFilter implements FilterInterface
'label' => 'pick location',
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php
index 5fe928b6c..1c3415460 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/LocationTypeFilter.php
@@ -65,6 +65,10 @@ class LocationTypeFilter implements FilterInterface
//'label' => false,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php
index 8daa7a781..0f2a1c7e4 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/SentReceivedFilter.php
@@ -69,9 +69,12 @@ class SentReceivedFilter implements FilterInterface
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
- 'data' => self::DEFAULT_CHOICE,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return ['accepted_sentreceived' => self::DEFAULT_CHOICE];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php
index 6350f3ace..9ae988579 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserFilter.php
@@ -61,6 +61,10 @@ class UserFilter implements FilterInterface
'label' => 'Creators',
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php
index 4319c100a..adb1e94f1 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ACPFilters/UserScopeFilter.php
@@ -71,6 +71,10 @@ class UserScopeFilter implements FilterInterface
'expanded' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php
index f2216c929..e582acc12 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityDateFilter.php
@@ -80,11 +80,9 @@ class ActivityDateFilter implements FilterInterface
$builder
->add('date_from', PickRollingDateType::class, [
'label' => 'Activities after this date',
- 'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
])
->add('date_to', PickRollingDateType::class, [
'label' => 'Activities before this date',
- 'data' => new RollingDate(RollingDate::T_TODAY),
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
@@ -127,6 +125,10 @@ class ActivityDateFilter implements FilterInterface
}
});
}
+ public function getFormDefaultData(): array
+ {
+ return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php
index b9d39c3ce..8dfcb543c 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityTypeFilter.php
@@ -78,6 +78,10 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
],
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php
index 2f6cd8462..a63ca2629 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/ActivityUsersFilter.php
@@ -56,6 +56,10 @@ class ActivityUsersFilter implements FilterInterface
'label' => 'Users',
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php
index c55d579e4..31cfde6b6 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/ActivityReasonFilter.php
@@ -82,6 +82,10 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
'expanded' => false,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php
index e3c85fe9c..490b5fd0c 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/PersonFilters/PersonHavingActivityBetweenDateFilter.php
@@ -112,7 +112,6 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
{
$builder->add('date_from', DateType::class, [
'label' => 'Implied in an activity after this date',
- 'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
@@ -120,7 +119,6 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
$builder->add('date_to', DateType::class, [
'label' => 'Implied in an activity before this date',
- 'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
@@ -130,7 +128,6 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
'class' => ActivityReason::class,
'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()),
'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
- 'data' => $this->activityReasonRepository->findAll(),
'multiple' => true,
'expanded' => false,
'label' => 'Activity reasons for those activities',
@@ -176,6 +173,10 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
}
});
}
+ public function getFormDefaultData(): array
+ {
+ return ['date_from' => new DateTime(), 'date_to' => new DateTime(), 'reasons' => $this->activityReasonRepository->findAll()];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php
index b52ef441c..e85b2d247 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersJobFilter.php
@@ -60,6 +60,10 @@ class UsersJobFilter implements FilterInterface
'expanded' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php
index 61b12264e..07ff509ce 100644
--- a/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php
+++ b/src/Bundle/ChillActivityBundle/Export/Filter/UsersScopeFilter.php
@@ -67,6 +67,10 @@ class UsersScopeFilter implements FilterInterface
'expanded' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php
new file mode 100644
index 000000000..ce70409ba
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepository.php
@@ -0,0 +1,198 @@
+buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content);
+
+ return $this->addFetchQueryByPersonACL($query, $person);
+ }
+
+ public function buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
+ {
+ $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
+ $activityMetadata = $this->em->getClassMetadata(Activity::class);
+
+ $query = new FetchQuery(
+ PersonActivityGenericDocProvider::KEY,
+ sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()),
+ sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')),
+ sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName())
+ );
+
+ $query->addJoinClause(
+ 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id'
+ );
+
+ $query->addJoinClause(
+ 'JOIN public.activity activity ON activity.id = activity_doc.activity_id'
+ );
+
+ $query->addWhereClause(
+ sprintf('activity.%s = ?', $activityMetadata->getSingleAssociationJoinColumnName('person')),
+ [$person->getId()],
+ [Types::INTEGER]
+ );
+
+ return $this->addWhereClauses($query, $startDate, $endDate, $content);
+ }
+
+ public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
+ {
+ $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
+ $activityMetadata = $this->em->getClassMetadata(Activity::class);
+
+ $query = new FetchQuery(
+ AccompanyingPeriodActivityGenericDocProvider::KEY,
+ sprintf('jsonb_build_object(\'id\', stored_obj.%s, \'activity_id\', activity.%s)', $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()),
+ sprintf('stored_obj.%s', $storedObjectMetadata->getColumnName('createdAt')),
+ sprintf('%s AS stored_obj', $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName())
+ );
+
+ $query->addJoinClause(
+ 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = stored_obj.id'
+ );
+
+ $query->addJoinClause(
+ 'JOIN public.activity activity ON activity.id = activity_doc.activity_id'
+ );
+
+ // add documents of activities from parcours context
+ $or = [];
+ $orParams = [];
+ $orTypes = [];
+ foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
+ if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) {
+ continue;
+ }
+
+ $or[] = sprintf(
+ '(activity.%s = ? AND stored_obj.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))',
+ $activityMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'),
+ $storedObjectMetadata->getColumnName('createdAt')
+ );
+ $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(),
+ DateTimeImmutable::createFromInterface($participation->getStartDate()),
+ null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())];
+ $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE];
+ }
+
+ if ([] === $or) {
+ $query->addWhereClause('TRUE = FALSE');
+
+ return $query;
+ }
+
+ $query->addWhereClause(sprintf('(%s)', implode(' OR ', $or)), $orParams, $orTypes);
+
+ return $this->addWhereClauses($query, $startDate, $endDate, $content);
+ }
+
+ private function addWhereClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
+ {
+ $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
+
+ if (null !== $startDate) {
+ $query->addWhereClause(
+ sprintf('stored_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
+ [$startDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $endDate) {
+ $query->addWhereClause(
+ sprintf('stored_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
+ [$endDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $content and '' !== $content) {
+ $query->addWhereClause(
+ 'stored_obj.title ilike ?',
+ ['%' . $content . '%'],
+ [Types::STRING]
+ );
+ }
+
+ return $query;
+ }
+
+ private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery
+ {
+ $activityMetadata = $this->em->getClassMetadata(Activity::class);
+
+ $reachableScopes = [];
+
+ foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
+ $reachableScopes = [
+ ...$reachableScopes,
+ ...$this->authorizationHelperForCurrentUser->getReachableScopes(ActivityVoter::SEE, $center)
+ ];
+ }
+
+ if ([] === $reachableScopes) {
+ $fetchQuery->addWhereClause('FALSE = TRUE');
+
+ return $fetchQuery;
+ }
+
+ $fetchQuery->addWhereClause(
+ sprintf(
+ 'activity.%s IN (%s)',
+ $activityMetadata->getSingleAssociationJoinColumnName('scope'),
+ implode(', ', array_fill(0, count($reachableScopes), '?'))
+ ),
+ array_map(static fn (Scope $s) => $s->getId(), $reachableScopes),
+ array_fill(0, count($reachableScopes), Types::INTEGER)
+ );
+
+ return $fetchQuery;
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php
new file mode 100644
index 000000000..9f4a9c0f8
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Repository/ActivityDocumentACLAwareRepositoryInterface.php
@@ -0,0 +1,37 @@
+
+
+
+ {% if document.isPending %}
+
{{ 'docgen.Doc generation is pending'|trans }}
+ {% elseif document.isFailure %}
+
{{ 'docgen.Doc generation failed'|trans }}
+ {% endif %}
+
+
+ {% if activity.accompanyingPeriod is not null and context == 'person' %}
+
+ {{ activity.accompanyingPeriod.id }}
+
+ {% endif %}
+
+
+
+ {{ activity.type.name | localize_translatable_string }}
+ {% if activity.emergency %}
+ {{ 'Emergency'|trans|upper }}
+ {% endif %}
+
+
+
+
+ {{ document.title|chill_print_or_message("No title") }}
+
+ {% if document.hasTemplate %}
+
+
{{ document.template.name|localize_translatable_string }}
+
+ {% endif %}
+
+
+
+
+
+ {{ document.createdAt|format_date('short') }}
+
+
+
+
+
+
+
+
+ {{ mmm.createdBy(document) }}
+
+
+ {% if is_granted('CHILL_ACTIVITY_SEE_DETAILS', activity) %}
+ -
+ {{ document|chill_document_button_group(document.title, is_granted('CHILL_ACTIVITY_UPDATE', activity), {small: false}) }}
+
+ {% endif %}
+ {% if is_granted('CHILL_ACTIVITY_SEE', activity)%}
+ -
+
+
+ {% endif %}
+ {% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
+ -
+
+
+ {% endif %}
+
+
+
+
diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php
index 624859eda..a20ca365b 100644
--- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php
+++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ActivityContext.php
@@ -24,6 +24,9 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
+use Chill\ThirdPartyBundle\Entity\ThirdParty;
+use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
+use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
@@ -55,6 +58,10 @@ class ActivityContext implements
private TranslatorInterface $translator;
+ private ThirdPartyRender $thirdPartyRender;
+
+ private ThirdPartyRepository $thirdPartyRepository;
+
public function __construct(
DocumentCategoryRepository $documentCategoryRepository,
NormalizerInterface $normalizer,
@@ -63,7 +70,9 @@ class ActivityContext implements
PersonRenderInterface $personRender,
PersonRepository $personRepository,
TranslatorInterface $translator,
- BaseContextData $baseContextData
+ BaseContextData $baseContextData,
+ ThirdPartyRender $thirdPartyRender,
+ ThirdPartyRepository $thirdPartyRepository
) {
$this->documentCategoryRepository = $documentCategoryRepository;
$this->normalizer = $normalizer;
@@ -73,6 +82,8 @@ class ActivityContext implements
$this->personRepository = $personRepository;
$this->translator = $translator;
$this->baseContextData = $baseContextData;
+ $this->thirdPartyRender = $thirdPartyRender;
+ $this->thirdPartyRepository = $thirdPartyRepository;
}
public function adminFormReverseTransform(array $data): array
@@ -89,6 +100,8 @@ class ActivityContext implements
'person1Label' => $data['person1Label'] ?? $this->translator->trans('docgen.person 1'),
'person2' => $data['person2'] ?? false,
'person2Label' => $data['person2Label'] ?? $this->translator->trans('docgen.person 2'),
+ 'thirdParty' => $data['thirdParty'] ?? false,
+ 'thirdPartyLabel' => $data['thirdPartyLabel'] ?? $this->translator->trans('thirdParty'),
];
}
@@ -118,6 +131,14 @@ class ActivityContext implements
->add('person2Label', TextType::class, [
'label' => 'person 2 label',
'required' => true,
+ ])
+ ->add('thirdParty', CheckboxType::class, [
+ 'required' => false,
+ 'label' => 'docgen.Ask for thirdParty',
+ ])
+ ->add('thirdPartyLabel', TextType::class, [
+ 'label' => 'docgen.thirdParty label',
+ 'required' => true,
]);
}
@@ -143,6 +164,20 @@ class ActivityContext implements
]);
}
}
+
+ $thirdParties = $entity->getThirdParties();
+ if ($options['thirdParty'] ?? false) {
+ $builder->add('thirdParty', EntityType::class, [
+ 'class' => ThirdParty::class,
+ 'choices' => $thirdParties,
+ 'choice_label' => fn (ThirdParty $p) => $this->thirdPartyRender->renderString($p, []),
+ 'multiple' => false,
+ 'required' => false,
+ 'expanded' => true,
+ 'label' => $options['thirdPartyLabel'],
+ 'placeholder' => $this->translator->trans('Any third party selected'),
+ ]);
+ }
}
public function contextGenerationDataDenormalize(DocGeneratorTemplate $template, $entity, array $data): array
@@ -157,6 +192,12 @@ class ActivityContext implements
}
}
+ if (null !== ($id = ($data['thirdParty'] ?? null))) {
+ $denormalized['thirdParty'] = $this->thirdPartyRepository->find($id);
+ } else {
+ $denormalized['thirdParty'] = null;
+ }
+
return $denormalized;
}
@@ -165,9 +206,11 @@ class ActivityContext implements
$normalized = [];
foreach (['mainPerson', 'person1', 'person2'] as $k) {
- $normalized[$k] = null === $data[$k] ? null : $data[$k]->getId();
+ $normalized[$k] = ($data[$k] ?? null)?->getId();
}
+ $normalized['thirdParty'] = ($data['thirdParty'] ?? null)?->getId();
+
return $normalized;
}
@@ -196,6 +239,13 @@ class ActivityContext implements
}
}
+ if ($options['thirdParty']) {
+ $data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'], 'docgen', [
+ 'docgen:expects' => ThirdParty::class,
+ 'groups' => 'docgen:read'
+ ]);
+ }
+
return $data;
}
@@ -235,7 +285,7 @@ class ActivityContext implements
{
$options = $template->getOptions();
- return $options['mainPerson'] || $options['person1'] || $options['person2'];
+ return $options['mainPerson'] || $options['person1'] || $options['person2'] || $options ['thirdParty'];
}
public function storeGenerated(DocGeneratorTemplate $template, StoredObject $storedObject, object $entity, array $contextGenerationData): void
diff --git a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php
index 3da451f2e..045d09beb 100644
--- a/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php
+++ b/src/Bundle/ChillActivityBundle/Service/DocGenerator/ListActivitiesByAccompanyingPeriodContext.php
@@ -140,26 +140,36 @@ class ListActivitiesByAccompanyingPeriodContext implements
return $normalized;
}
+ /**
+ * @return list
+ */
private function filterActivitiesByUser(array $activities, User $user): array
{
- return array_filter(
- $activities,
- function ($activity) use ($user) {
- $activityUsernames = array_map(static fn ($user) => $user['username'], $activity['users'] ?? []);
- return in_array($user->getUsername(), $activityUsernames, true);
- }
+ return array_values(
+ array_filter(
+ $activities,
+ function ($activity) use ($user) {
+ $activityUsernames = array_map(static fn ($user) => $user['username'], $activity['users'] ?? []);
+ return in_array($user->getUsername(), $activityUsernames, true);
+ }
+ )
);
}
+ /**
+ * @return list
+ */
private function filterWorksByUser(array $works, User $user): array
{
- return array_filter(
- $works,
- function ($work) use ($user) {
- $workUsernames = array_map(static fn ($user) => $user['username'], $work['referrers'] ?? []);
+ return array_values(
+ array_filter(
+ $works,
+ function ($work) use ($user) {
+ $workUsernames = array_map(static fn ($user) => $user['username'], $work['referrers'] ?? []);
- return in_array($user->getUsername(), $workUsernames, true);
- }
+ return in_array($user->getUsername(), $workUsernames, true);
+ }
+ )
);
}
diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php
new file mode 100644
index 000000000..334b5d2df
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/AccompanyingPeriodActivityGenericDocProvider.php
@@ -0,0 +1,114 @@
+em->getClassMetadata(StoredObject::class);
+ $activityMetadata = $this->em->getClassMetadata(Activity::class);
+
+ $query = new FetchQuery(
+ self::KEY,
+ sprintf("jsonb_build_object('id', doc_obj.%s, 'activity_id', activity.%s)", $storedObjectMetadata->getSingleIdentifierColumnName(), $activityMetadata->getSingleIdentifierColumnName()),
+ 'doc_obj.'.$storedObjectMetadata->getColumnName('createdAt'),
+ $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName().' AS doc_obj'
+ );
+
+ $query->addJoinClause(
+ 'JOIN public.activity_storedobject activity_doc ON activity_doc.storedobject_id = doc_obj.id'
+ );
+
+ $query->addJoinClause(
+ 'JOIN public.activity activity ON activity.id = activity_doc.activity_id'
+ );
+
+ $query->addWhereClause(
+ 'activity.accompanyingperiod_id = ?',
+ [$accompanyingPeriod->getId()],
+ [Types::INTEGER]
+ );
+
+ if (null !== $startDate) {
+ $query->addWhereClause(
+ sprintf('doc_obj.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
+ [$startDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $endDate) {
+ $query->addWhereClause(
+ sprintf('doc_obj.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
+ [$endDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $content) {
+ $query->addWhereClause(
+ 'doc_obj.title ilike ?',
+ ['%' . $content . '%'],
+ [Types::STRING]
+ );
+ }
+
+ return $query;
+ }
+
+ /**
+ * @param AccompanyingPeriod $accompanyingPeriod
+ * @return bool
+ */
+ public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
+ {
+ return $this->security->isGranted(ActivityVoter::SEE, $accompanyingPeriod);
+ }
+
+ public function isAllowedForPerson(Person $person): bool
+ {
+ return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person);
+ }
+
+ public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
+ {
+ return $this->activityDocumentACLAwareRepository
+ ->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content);
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php
new file mode 100644
index 000000000..cf96449ab
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Providers/PersonActivityGenericDocProvider.php
@@ -0,0 +1,53 @@
+personActivityDocumentACLAwareRepository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(
+ $person,
+ $startDate,
+ $endDate,
+ $content
+ );
+ }
+
+ /**
+ * @param Person $person
+ * @return bool
+ */
+ public function isAllowedForPerson(Person $person): bool
+ {
+ return $this->security->isGranted(ActivityVoter::SEE, $person);
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php
new file mode 100644
index 000000000..c465dbca1
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Service/GenericDoc/Renderers/AccompanyingPeriodActivityGenericDocRenderer.php
@@ -0,0 +1,52 @@
+objectRepository = $storedObjectRepository;
+ $this->activityRepository = $activityRepository;
+ }
+
+ public function supports(GenericDocDTO $genericDocDTO, $options = []): bool
+ {
+ return $genericDocDTO->key === AccompanyingPeriodActivityGenericDocProvider::KEY || $genericDocDTO->key === PersonActivityGenericDocProvider::KEY;
+ }
+
+ public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
+ {
+ return '@ChillActivity/GenericDoc/activity_document.html.twig';
+ }
+
+ public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
+ {
+ return [
+ 'activity' => $this->activityRepository->find($genericDocDTO->identifiers['activity_id']),
+ 'document' => $this->objectRepository->find($genericDocDTO->identifiers['id']),
+ 'context' => $genericDocDTO->getContext(),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php
new file mode 100644
index 000000000..ce4f318e3
--- /dev/null
+++ b/src/Bundle/ChillActivityBundle/Tests/Repository/ActivityDocumentACLAwareRepositoryTest.php
@@ -0,0 +1,126 @@
+entityManager = self::$container->get(EntityManagerInterface::class);
+ $this->centerResolverManager = self::$container->get(CenterResolverManagerInterface::class);
+ $this->authorizationHelperForCurrentUser = self::$container->get(AuthorizationHelperForCurrentUserInterface::class);
+ $this->security = self::$container->get(Security::class);
+ }
+
+ /**
+ * @dataProvider provideDataForPerson
+ */
+ public function testBuildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, array $reachableScopes, bool $_unused, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void
+ {
+ $authorizationHelper = $this->prophesize(AuthorizationHelperForCurrentUserInterface::class);
+ $authorizationHelper->getReachableScopes(ActivityVoter::SEE, Argument::any())
+ ->willReturn($reachableScopes);
+
+ $repository = new ActivityDocumentACLAwareRepository(
+ $this->entityManager,
+ $this->centerResolverManager,
+ $authorizationHelper->reveal(),
+ $this->security
+ );
+
+ $query = $repository->buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content);
+ ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
+
+ $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types);
+
+ self::assertIsInt($nb);
+ }
+
+ /**
+ * @dataProvider provideDataForPerson
+ */
+ public function testBuildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, array $_unused, bool $canSeePeriod, ?\DateTimeImmutable $startDate, ?\DateTimeImmutable $endDate, ?string $content): void
+ {
+ $security = $this->prophesize(Security::class);
+ $security->isGranted(ActivityVoter::SEE, Argument::type(AccompanyingPeriod::class))
+ ->willReturn($canSeePeriod);
+
+ $repository = new ActivityDocumentACLAwareRepository(
+ $this->entityManager,
+ $this->centerResolverManager,
+ $this->authorizationHelperForCurrentUser,
+ $security->reveal()
+ );
+
+ $query = $repository->buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext($person, $startDate, $endDate, $content);
+
+ ['sql' => $sql, 'params' => $params, 'types' => $types] = (new FetchQueryToSqlBuilder())->toSql($query);
+
+ $nb = $this->entityManager->getConnection()->fetchOne("SELECT COUNT(*) FROM ({$sql}) sq", $params, $types);
+
+ self::assertIsInt($nb);
+ }
+
+ public function provideDataForPerson(): iterable
+ {
+ $this->setUp();
+
+ if (null === $person = $this->entityManager->createQuery("SELECT p FROM " . Person::class . " p WHERE SIZE(p.accompanyingPeriodParticipations) > 0 ")
+ ->setMaxResults(1)
+ ->getSingleResult()) {
+ throw new \RuntimeException("no person in dtabase");
+ }
+
+ if ([] === $scopes = $this->entityManager->createQuery("SELECT s FROM " . Scope::class . " s ")->setMaxResults(5)->getResult()) {
+ throw new \RuntimeException("no scopes in database");
+ }
+
+ yield [$person, [], true, null, null, null];
+ yield [$person, $scopes, true, null, null, null];
+ yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), null, null];
+ yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), null];
+ yield [$person, $scopes, true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"];
+ yield [$person, $scopes, true, null, new \DateTimeImmutable("1 week ago"), "content"];
+ yield [$person, [], true, new \DateTimeImmutable("1 month ago"), new \DateTimeImmutable("1 week ago"), "content"];
+ }
+
+}
diff --git a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php
index dad597676..2c9272b08 100644
--- a/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php
+++ b/src/Bundle/ChillActivityBundle/Timeline/TimelineActivityProvider.php
@@ -161,6 +161,7 @@ class TimelineActivityProvider implements TimelineProviderInterface
// loop on reachable scopes
foreach ($reachableScopes as $scope) {
+ /** @phpstan-ignore-next-line */
if (in_array($scope->getId(), $scopes_ids, true)) {
continue;
}
diff --git a/src/Bundle/ChillActivityBundle/config/services.yaml b/src/Bundle/ChillActivityBundle/config/services.yaml
index d55f86d4f..18be76ec9 100644
--- a/src/Bundle/ChillActivityBundle/config/services.yaml
+++ b/src/Bundle/ChillActivityBundle/config/services.yaml
@@ -38,3 +38,6 @@ services:
Chill\ActivityBundle\Service\EntityInfo\:
resource: '../Service/EntityInfo/'
+
+ Chill\ActivityBundle\Service\GenericDoc\:
+ resource: '../Service/GenericDoc/'
diff --git a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
index e08e1fb62..55d755fd6 100644
--- a/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillActivityBundle/translations/messages.fr.yml
@@ -373,3 +373,8 @@ export:
is sent: envoyé
is received: reçu
Group activity by sentreceived: Grouper les échanges par envoyé / reçu
+
+generic_doc:
+ filter:
+ keys:
+ accompanying_period_activity_document: Document des échanges des parcours
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php
index 32418e3c3..8ae43534d 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByActivityTypeAggregator.php
@@ -50,6 +50,10 @@ class ByActivityTypeAggregator implements AggregatorInterface
{
// No form needed
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php
index d2fe7c2f2..f09a704e2 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserJobAggregator.php
@@ -57,6 +57,10 @@ class ByUserJobAggregator implements AggregatorInterface
{
// nothing to add in the form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php
index 6c06ee756..3dd465db2 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Aggregator/ByUserScopeAggregator.php
@@ -57,6 +57,10 @@ class ByUserScopeAggregator implements AggregatorInterface
{
// nothing to add in the form
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getLabels($key, array $values, $data)
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php
index f3db629cb..2b28062f6 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/AvgAsideActivityDuration.php
@@ -34,6 +34,10 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac
public function buildForm(FormBuilderInterface $builder)
{
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes(): array
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php
index 9204fad4b..91210f764 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/CountAsideActivity.php
@@ -34,6 +34,10 @@ class CountAsideActivity implements ExportInterface, GroupedExportInterface
public function buildForm(FormBuilderInterface $builder)
{
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes(): array
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php
index aee168174..b46e120b5 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/ListAsideActivity.php
@@ -73,6 +73,10 @@ final class ListAsideActivity implements ListInterface, GroupedExportInterface
public function buildForm(FormBuilderInterface $builder)
{
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes()
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php
index af17a2591..741f129f1 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Export/SumAsideActivityDuration.php
@@ -34,6 +34,10 @@ class SumAsideActivityDuration implements ExportInterface, GroupedExportInterfac
public function buildForm(FormBuilderInterface $builder)
{
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function getAllowedFormattersTypes(): array
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php
index 3ad8e0e93..3d389f5b3 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByActivityTypeFilter.php
@@ -76,6 +76,10 @@ class ByActivityTypeFilter implements FilterInterface
},
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php
index 7a1b6f4dc..98a254bc4 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByDateFilter.php
@@ -72,11 +72,9 @@ class ByDateFilter implements FilterInterface
$builder
->add('date_from', PickRollingDateType::class, [
'label' => 'export.filter.Aside activities after this date',
- 'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
])
->add('date_to', PickRollingDateType::class, [
'label' => 'export.filter.Aside activities before this date',
- 'data' => new RollingDate(RollingDate::T_TODAY),
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
@@ -119,6 +117,10 @@ class ByDateFilter implements FilterInterface
}
});
}
+ public function getFormDefaultData(): array
+ {
+ return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php
index 795c813cd..2858d3417 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserFilter.php
@@ -53,6 +53,10 @@ class ByUserFilter implements FilterInterface
'label' => 'Creators',
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php
index 86194b123..55f477768 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserJobFilter.php
@@ -60,6 +60,10 @@ class ByUserJobFilter implements FilterInterface
'expanded' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php
index 4342e11eb..8fa51866d 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php
+++ b/src/Bundle/ChillAsideActivityBundle/src/Export/Filter/ByUserScopeFilter.php
@@ -67,6 +67,10 @@ class ByUserScopeFilter implements FilterInterface
'expanded' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml
index cc428390c..25d07bd22 100644
--- a/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml
+++ b/src/Bundle/ChillAsideActivityBundle/src/translations/messages.fr.yml
@@ -176,11 +176,12 @@ export:
agent_id: Utilisateur
creator_id: Créateur
main_scope: Service principal de l'utilisateur
- main_center: Centre principal de l'utilisteur
+ main_center: Centre principal de l'utilisateur
aside_activity_type: Catégorie d'activité annexe
date: Date
duration: Durée
note: Note
+ id: Identifiant
Exports of aside activities: Exports des activités annexes
Count aside activities: Nombre d'activités annexes
diff --git a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php
index 380e641a6..2a67486f0 100644
--- a/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php
+++ b/src/Bundle/ChillBudgetBundle/Calculator/CalculatorResult.php
@@ -21,7 +21,7 @@ class CalculatorResult
public $label;
- public $result;
+ public float $result;
public $type;
}
diff --git a/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php b/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php
index b90fb83d4..193b04934 100644
--- a/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php
+++ b/src/Bundle/ChillCalendarBundle/Command/MapAndSubscribeUserCalendarCommand.php
@@ -18,9 +18,12 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Command;
+use Chill\CalendarBundle\Exception\UserAbsenceSyncException;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\EventsOnUserSubscriptionCreator;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MapCalendarToUser;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSGraphUserRepository;
+use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync;
+use Chill\MainBundle\Repository\UserRepositoryInterface;
use DateInterval;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
@@ -30,32 +33,17 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class MapAndSubscribeUserCalendarCommand extends Command
+final class MapAndSubscribeUserCalendarCommand extends Command
{
- private EntityManagerInterface $em;
-
- private EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator;
-
- private LoggerInterface $logger;
-
- private MapCalendarToUser $mapCalendarToUser;
-
- private MSGraphUserRepository $userRepository;
-
public function __construct(
- EntityManagerInterface $em,
- EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator,
- LoggerInterface $logger,
- MapCalendarToUser $mapCalendarToUser,
- MSGraphUserRepository $userRepository
+ private readonly EntityManagerInterface $em,
+ private readonly EventsOnUserSubscriptionCreator $eventsOnUserSubscriptionCreator,
+ private readonly LoggerInterface $logger,
+ private readonly MapCalendarToUser $mapCalendarToUser,
+ private readonly UserRepositoryInterface $userRepository,
+ private readonly MSUserAbsenceSync $userAbsenceSync,
) {
parent::__construct('chill:calendar:msgraph-user-map-subscribe');
-
- $this->em = $em;
- $this->eventsOnUserSubscriptionCreator = $eventsOnUserSubscriptionCreator;
- $this->logger = $logger;
- $this->mapCalendarToUser = $mapCalendarToUser;
- $this->userRepository = $userRepository;
}
public function execute(InputInterface $input, OutputInterface $output): int
@@ -67,83 +55,109 @@ class MapAndSubscribeUserCalendarCommand extends Command
/** @var DateInterval $interval the interval before the end of the expiration */
$interval = new DateInterval('P1D');
$expiration = (new DateTimeImmutable('now'))->add(new DateInterval($input->getOption('subscription-duration')));
- $total = $this->userRepository->countByMostOldSubscriptionOrWithoutSubscriptionOrData($interval);
+ $users = $this->userRepository->findAllAsArray('fr');
$created = 0;
$renewed = 0;
- $this->logger->info(self::class . ' the number of user to get - renew', [
- 'total' => $total,
+ $this->logger->info(self::class . ' start user to get - renew', [
'expiration' => $expiration->format(DateTimeImmutable::ATOM),
]);
- while ($offset < $total) {
- $users = $this->userRepository->findByMostOldSubscriptionOrWithoutSubscriptionOrData(
- $interval,
- $limit,
- $offset
- );
+ foreach ($users as $u) {
+ ++$offset;
- foreach ($users as $user) {
- if (!$this->mapCalendarToUser->hasUserId($user)) {
- $this->mapCalendarToUser->writeMetadata($user);
- }
-
- if ($this->mapCalendarToUser->hasUserId($user)) {
- // we first try to renew an existing subscription, if any.
- // if not, or if it fails, we try to create a new one
- if ($this->mapCalendarToUser->hasActiveSubscription($user)) {
- $this->logger->debug(self::class . ' renew a subscription for', [
- 'userId' => $user->getId(),
- 'username' => $user->getUsernameCanonical(),
- ]);
-
- ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
- = $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration);
- $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
-
- if (0 !== $expirationTs) {
- ++$renewed;
- } else {
- $this->logger->warning(self::class . ' could not renew subscription for a user', [
- 'userId' => $user->getId(),
- 'username' => $user->getUsernameCanonical(),
- ]);
- }
- }
-
- if (!$this->mapCalendarToUser->hasActiveSubscription($user)) {
- $this->logger->debug(self::class . ' create a subscription for', [
- 'userId' => $user->getId(),
- 'username' => $user->getUsernameCanonical(),
- ]);
-
- ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
- = $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration);
- $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
-
- if (0 !== $expirationTs) {
- ++$created;
- } else {
- $this->logger->warning(self::class . ' could not create subscription for a user', [
- 'userId' => $user->getId(),
- 'username' => $user->getUsernameCanonical(),
- ]);
- }
- }
- }
-
- ++$offset;
+ if (false === $u['enabled']) {
+ continue;
}
- $this->em->flush();
- $this->em->clear();
+ $user = $this->userRepository->find($u['id']);
+
+ if (null === $user) {
+ $this->logger->error("could not find user by id", ['uid' => $u['id']]);
+ $output->writeln("could not find user by id : " . $u['id']);
+ continue;
+ }
+
+ if (!$this->mapCalendarToUser->hasUserId($user)) {
+ $user = $this->mapCalendarToUser->writeMetadata($user);
+
+ // if user still does not have userid, continue
+ if (!$this->mapCalendarToUser->hasUserId($user)) {
+ $this->logger->warning("user does not have a counterpart on ms api", ['userId' => $user->getId(), 'email' => $user->getEmail()]);
+ $output->writeln(sprintf("giving up for user with email %s and id %s", $user->getEmail(), $user->getId()));
+
+ continue;
+ }
+ }
+
+ // sync user absence
+ try {
+ $this->userAbsenceSync->syncUserAbsence($user);
+ } catch (UserAbsenceSyncException $e) {
+ $this->logger->error("could not sync user absence", ['userId' => $user->getId(), 'email' => $user->getEmail(), 'exception' => $e->getTraceAsString(), "message" => $e->getMessage()]);
+ $output->writeln(sprintf("Could not sync user absence: id: %s and email: %s", $user->getId(), $user->getEmail()));
+ throw $e;
+ }
+
+ // we first try to renew an existing subscription, if any.
+ // if not, or if it fails, we try to create a new one
+ if ($this->mapCalendarToUser->hasActiveSubscription($user)) {
+ $this->logger->debug(self::class . ' renew a subscription for', [
+ 'userId' => $user->getId(),
+ 'username' => $user->getUsernameCanonical(),
+ ]);
+
+ ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
+ = $this->eventsOnUserSubscriptionCreator->renewSubscriptionForUser($user, $expiration);
+ $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
+
+ if (0 !== $expirationTs) {
+ ++$renewed;
+ } else {
+ $this->logger->warning(self::class . ' could not renew subscription for a user', [
+ 'userId' => $user->getId(),
+ 'username' => $user->getUsernameCanonical(),
+ ]);
+ }
+ }
+
+ if (!$this->mapCalendarToUser->hasActiveSubscription($user)) {
+ $this->logger->debug(self::class . ' create a subscription for', [
+ 'userId' => $user->getId(),
+ 'username' => $user->getUsernameCanonical(),
+ ]);
+
+ ['secret' => $secret, 'id' => $id, 'expiration' => $expirationTs]
+ = $this->eventsOnUserSubscriptionCreator->createSubscriptionForUser($user, $expiration);
+ $this->mapCalendarToUser->writeSubscriptionMetadata($user, $expirationTs, $id, $secret);
+
+ if (0 !== $expirationTs) {
+ ++$created;
+ } else {
+ $this->logger->warning(self::class . ' could not create subscription for a user', [
+ 'userId' => $user->getId(),
+ 'username' => $user->getUsernameCanonical(),
+ ]);
+ }
+ }
+
+
+ if (0 === $offset % $limit) {
+ $this->em->flush();
+ $this->em->clear();
+ }
}
+ $this->em->flush();
+ $this->em->clear();
+
$this->logger->warning(self::class . ' process executed', [
'created' => $created,
'renewed' => $renewed,
]);
+ $output->writeln("users synchronized");
+
return 0;
}
@@ -152,7 +166,7 @@ class MapAndSubscribeUserCalendarCommand extends Command
parent::configure();
$this
- ->setDescription('MSGraph: collect user metadata and create subscription on events for users')
+ ->setDescription('MSGraph: collect user metadata and create subscription on events for users, and sync the user absence-presence')
->addOption(
'renew-before-end-interval',
'r',
diff --git a/src/Bundle/ChillCalendarBundle/Exception/UserAbsenceSyncException.php b/src/Bundle/ChillCalendarBundle/Exception/UserAbsenceSyncException.php
new file mode 100644
index 000000000..a5e5a679a
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Exception/UserAbsenceSyncException.php
@@ -0,0 +1,20 @@
+ true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php
index 59019ac03..4260ecd99 100644
--- a/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php
+++ b/src/Bundle/ChillCalendarBundle/Export/Filter/BetweenDatesFilter.php
@@ -60,12 +60,12 @@ class BetweenDatesFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder
- ->add('date_from', PickRollingDateType::class, [
- 'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
- ])
- ->add('date_to', PickRollingDateType::class, [
- 'data' => new RollingDate(RollingDate::T_TODAY),
- ]);
+ ->add('date_from', PickRollingDateType::class, [])
+ ->add('date_to', PickRollingDateType::class, []);
+ }
+ public function getFormDefaultData(): array
+ {
+ return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
}
public function describeAction($data, $format = 'string'): array
diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php
index d6c38e163..5ffb7c01f 100644
--- a/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php
+++ b/src/Bundle/ChillCalendarBundle/Export/Filter/CalendarRangeFilter.php
@@ -69,9 +69,12 @@ class CalendarRangeFilter implements FilterInterface
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
- 'data' => self::DEFAULT_CHOICE,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return ['hasCalendarRange' => self::DEFAULT_CHOICE];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php
index 69fb24447..d02bb8e9d 100644
--- a/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php
+++ b/src/Bundle/ChillCalendarBundle/Export/Filter/JobFilter.php
@@ -76,6 +76,10 @@ class JobFilter implements FilterInterface
'expanded' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string'): array
{
diff --git a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php
index 08d9ae023..d8a40da72 100644
--- a/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php
+++ b/src/Bundle/ChillCalendarBundle/Export/Filter/ScopeFilter.php
@@ -76,6 +76,10 @@ class ScopeFilter implements FilterInterface
'expanded' => true,
]);
}
+ public function getFormDefaultData(): array
+ {
+ return [];
+ }
public function describeAction($data, $format = 'string')
{
diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php
deleted file mode 100644
index c523a1e92..000000000
--- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSGraphUserRepository.php
+++ /dev/null
@@ -1,84 +0,0 @@
-'msgraph' ?? 'subscription_events_expiration'
- OR (attributes->'msgraph' ?? 'subscription_events_expiration' AND (attributes->'msgraph'->>'subscription_events_expiration')::int < EXTRACT(EPOCH FROM (NOW() + :interval::interval)))
- LIMIT :limit OFFSET :offset
- ;
- SQL;
-
- private EntityManagerInterface $entityManager;
-
- public function __construct(EntityManagerInterface $entityManager)
- {
- $this->entityManager = $entityManager;
- }
-
- public function countByMostOldSubscriptionOrWithoutSubscriptionOrData(DateInterval $interval): int
- {
- $rsm = new ResultSetMapping();
- $rsm->addScalarResult('c', 'c');
-
- $sql = strtr(self::MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH, [
- '{select}' => 'COUNT(u) AS c',
- 'LIMIT :limit OFFSET :offset' => '',
- ]);
-
- return $this->entityManager->createNativeQuery($sql, $rsm)->setParameters([
- 'interval' => $interval,
- ])->getSingleScalarResult();
- }
-
- /**
- * @return array|User[]
- */
- public function findByMostOldSubscriptionOrWithoutSubscriptionOrData(DateInterval $interval, int $limit = 50, int $offset = 0): array
- {
- $rsm = new ResultSetMappingBuilder($this->entityManager);
- $rsm->addRootEntityFromClassMetadata(User::class, 'u');
-
- return $this->entityManager->createNativeQuery(
- strtr(self::MOST_OLD_SUBSCRIPTION_OR_ANY_MS_GRAPH, ['{select}' => $rsm->generateSelectClause()]),
- $rsm
- )->setParameters([
- 'interval' => $interval,
- 'limit' => $limit,
- 'offset' => $offset,
- ])->getResult();
- }
-}
diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php
new file mode 100644
index 000000000..c70072a47
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReader.php
@@ -0,0 +1,69 @@
+mapCalendarToUser->getUserId($user);
+
+ if (null === $id) {
+ return null;
+ }
+
+ try {
+ $automaticRepliesSettings = $this->machineHttpClient
+ ->request('GET', 'users/' . $id . '/mailboxSettings/automaticRepliesSetting')
+ ->toArray(true);
+ } catch (ClientExceptionInterface|DecodingExceptionInterface|RedirectionExceptionInterface|TransportExceptionInterface $e) {
+ throw new UserAbsenceSyncException("Error receiving response for mailboxSettings", 0, $e);
+ } catch (ServerExceptionInterface $e) {
+ throw new UserAbsenceSyncException("Server error receiving response for mailboxSettings", 0, $e);
+ }
+
+ if (!array_key_exists("status", $automaticRepliesSettings)) {
+ throw new \LogicException("no key \"status\" on automatic replies settings: " . json_encode($automaticRepliesSettings, JSON_THROW_ON_ERROR));
+ }
+
+ return match ($automaticRepliesSettings['status']) {
+ 'disabled' => false,
+ 'alwaysEnabled' => true,
+ 'scheduled' =>
+ RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledStartDateTime']['dateTime']) < $this->clock->now()
+ && RemoteEventConverter::convertStringDateWithoutTimezone($automaticRepliesSettings['scheduledEndDateTime']['dateTime']) > $this->clock->now(),
+ default => throw new UserAbsenceSyncException("this status is not documented by Microsoft")
+ };
+ }
+
+}
diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderInterface.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderInterface.php
new file mode 100644
index 000000000..a918bb7ea
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderInterface.php
@@ -0,0 +1,22 @@
+absenceReader->isUserAbsent($user);
+
+ if (null === $absence) {
+ return;
+ }
+
+ if ($absence === $user->isAbsent()) {
+ // nothing to do
+ return;
+ }
+
+ $this->logger->info("will change user absence", ['userId' => $user->getId()]);
+
+ if ($absence) {
+ $this->logger->debug("make user absent", ['userId' => $user->getId()]);
+ $user->setAbsenceStart($this->clock->now());
+ } else {
+ $this->logger->debug("make user present", ['userId' => $user->getId()]);
+ $user->setAbsenceStart(null);
+ }
+ }
+}
diff --git a/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php b/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php
index f56735de7..6a986c9db 100644
--- a/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php
+++ b/src/Bundle/ChillCalendarBundle/RemoteCalendar/DependencyInjection/RemoteCalendarCompilerPass.php
@@ -23,6 +23,8 @@ use Chill\CalendarBundle\Command\MapAndSubscribeUserCalendarCommand;
use Chill\CalendarBundle\Controller\RemoteCalendarConnectAzureController;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineHttpClient;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MachineTokenStorage;
+use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceReaderInterface;
+use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraph\MSUserAbsenceSync;
use Chill\CalendarBundle\RemoteCalendar\Connector\MSGraphRemoteCalendarConnector;
use Chill\CalendarBundle\RemoteCalendar\Connector\NullRemoteCalendarConnector;
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
@@ -37,17 +39,13 @@ class RemoteCalendarCompilerPass implements CompilerPassInterface
public function process(ContainerBuilder $container)
{
$config = $container->getParameter('chill_calendar');
- $connector = null;
- if (!$config['remote_calendars_sync']['enabled']) {
- $connector = NullRemoteCalendarConnector::class;
- }
-
- if ($config['remote_calendars_sync']['microsoft_graph']['enabled']) {
+ if (true === $config['remote_calendars_sync']['microsoft_graph']['enabled']) {
$connector = MSGraphRemoteCalendarConnector::class;
$container->setAlias(HttpClientInterface::class . ' $machineHttpClient', MachineHttpClient::class);
} else {
+ $connector = NullRemoteCalendarConnector::class;
// remove services which cannot be loaded
$container->removeDefinition(MapAndSubscribeUserCalendarCommand::class);
$container->removeDefinition(AzureGrantAdminConsentAndAcquireToken::class);
@@ -55,16 +53,14 @@ class RemoteCalendarCompilerPass implements CompilerPassInterface
$container->removeDefinition(MachineTokenStorage::class);
$container->removeDefinition(MachineHttpClient::class);
$container->removeDefinition(MSGraphRemoteCalendarConnector::class);
+ $container->removeDefinition(MSUserAbsenceReaderInterface::class);
+ $container->removeDefinition(MSUserAbsenceSync::class);
}
if (!$container->hasAlias(Azure::class) && $container->hasDefinition('knpu.oauth2.client.azure')) {
$container->setAlias(Azure::class, 'knpu.oauth2.provider.azure');
}
- if (null === $connector) {
- throw new RuntimeException('Could not configure remote calendar');
- }
-
foreach ([
NullRemoteCalendarConnector::class,
MSGraphRemoteCalendarConnector::class, ] as $serviceId) {
diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js b/src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js
new file mode 100644
index 000000000..56a8ce563
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/chill.js
@@ -0,0 +1 @@
+import './scss/badge.scss';
diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss
new file mode 100644
index 000000000..ffcda8f0f
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/badge.scss
@@ -0,0 +1,25 @@
+@import '~ChillPersonAssets/chill/scss/mixins.scss';
+@import '~ChillMainAssets/module/bootstrap/shared';
+
+.badge-calendar {
+ display: inline-block;
+ background-color: #f3f3f3;
+
+ .title_label {
+ @include chill_badge($chill-l-gray);
+ }
+
+ .title_action {
+ padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x);
+ margin-right: 1rem;
+
+ font-size: var(--bs-badge-font-size);
+ font-weight: var(--bs-badge-font-weight);
+ line-height: 1;
+ color: var(--bs-badge-color);
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ }
+}
+
diff --git a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss
index a2c0c4b89..ce54b0fa8 100644
--- a/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss
+++ b/src/Bundle/ChillCalendarBundle/Resources/public/chill/scss/calendar.scss
@@ -17,4 +17,4 @@ span.calendarRangeItems {
text-decoration: none;
padding: 3px;
}
-}
\ No newline at end of file
+}
diff --git a/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig
new file mode 100644
index 000000000..facf5be50
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Resources/views/GenericDoc/calendar_document.html.twig
@@ -0,0 +1,75 @@
+{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
+{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
+{% import '@ChillPerson/Macro/updatedBy.html.twig' as mmm %}
+
+{% set c = document.calendar %}
+
+
+
+
+ {% if document.storedObject.isPending %}
+
{{ 'docgen.Doc generation is pending'|trans }}
+ {% elseif document.storedObject.isFailure %}
+
{{ 'docgen.Doc generation failed'|trans }}
+ {% endif %}
+
+
+ {% if c.accompanyingPeriod is not null and context == 'person' %}
+
+ {{ c.accompanyingPeriod.id }}
+
+ {% endif %}
+
+
+
+
+ {{ 'Calendar'|trans }}
+ {% if c.endDate.diff(c.startDate).days >= 1 %}
+ {{ c.startDate|format_datetime('short', 'short') }}
+ - {{ c.endDate|format_datetime('short', 'short') }}
+ {% else %}
+ {{ c.startDate|format_datetime('short', 'short') }}
+ - {{ c.endDate|format_datetime('none', 'short') }}
+ {% endif %}
+
+
+
+
+
+ {{ document.storedObject.title|chill_print_or_message("No title") }}
+
+ {% if document.storedObject.hasTemplate %}
+
+
{{ document.storedObject.template.name|localize_translatable_string }}
+
+ {% endif %}
+
+
+
+
+
+ {{ document.storedObject.createdAt|format_date('short') }}
+
+
+
+
+
+
+
+ {{ mmm.createdBy(document) }}
+
+
+ {% if is_granted('CHILL_CALENDAR_DOC_SEE', document) %}
+ -
+ {{ document.storedObject|chill_document_button_group(document.storedObject.title, is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c)) }}
+
+ {% endif %}
+ {% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', c) %}
+ -
+
+
+ {% endif %}
+
+
+
+
diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php
new file mode 100644
index 000000000..c33ccd853
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/AccompanyingPeriodCalendarGenericDocProvider.php
@@ -0,0 +1,192 @@
+em->getClassMetadata(CalendarDoc::class);
+ $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
+ $calendarMetadata = $this->em->getClassMetadata(Calendar::class);
+
+ $query = new FetchQuery(
+ self::KEY,
+ sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')),
+ 'cd.'.$storedObjectMetadata->getColumnName('createdAt'),
+ $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd'
+ );
+ $query->addJoinClause(
+ sprintf(
+ 'JOIN %s doc_store ON doc_store.%s = cd.%s',
+ $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
+ $storedObjectMetadata->getColumnName('id'),
+ $classMetadata->getSingleAssociationJoinColumnName('storedObject')
+ )
+ );
+
+ $query->addJoinClause(
+ sprintf(
+ 'JOIN %s calendar ON calendar.%s = cd.%s',
+ $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(),
+ $calendarMetadata->getColumnName('id'),
+ $classMetadata->getSingleAssociationJoinColumnName('calendar')
+ )
+ );
+
+ $query->addWhereClause(
+ sprintf(
+ 'calendar.%s = ?',
+ $calendarMetadata->getAssociationMapping('accompanyingPeriod')['joinColumns'][0]['name']
+ ),
+ [$accompanyingPeriod->getId()],
+ [Types::INTEGER]
+ );
+
+ return $query;
+ }
+
+ public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
+ {
+ return $this->security->isGranted(CalendarVoter::SEE, $accompanyingPeriod);
+ }
+
+ public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
+ {
+ $classMetadata = $this->em->getClassMetadata(CalendarDoc::class);
+ $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
+ $calendarMetadata = $this->em->getClassMetadata(Calendar::class);
+
+ $query = new FetchQuery(
+ self::KEY,
+ sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')),
+ 'cd.'.$storedObjectMetadata->getColumnName('createdAt'),
+ $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd'
+ );
+ $query->addJoinClause(
+ sprintf(
+ 'JOIN %s doc_store ON doc_store.%s = cd.%s',
+ $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
+ $storedObjectMetadata->getColumnName('id'),
+ $classMetadata->getSingleAssociationJoinColumnName('storedObject')
+ )
+ );
+
+ $query->addJoinClause(
+ sprintf(
+ 'JOIN %s calendar ON calendar.%s = cd.%s',
+ $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(),
+ $calendarMetadata->getColumnName('id'),
+ $classMetadata->getSingleAssociationJoinColumnName('calendar')
+ )
+ );
+
+ // get the documents associated with accompanying periods in which person participates
+ $or = [];
+ $orParams = [];
+ $orTypes = [];
+ foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
+ if (!$this->security->isGranted(CalendarVoter::SEE, $participation->getAccompanyingPeriod())) {
+ continue;
+ }
+
+ $or[] = sprintf(
+ '(calendar.%s = ? AND cd.%s BETWEEN ?::date AND COALESCE(?::date, \'infinity\'::date))',
+ $calendarMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod'),
+ $storedObjectMetadata->getColumnName('createdAt')
+ );
+ $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(),
+ DateTimeImmutable::createFromInterface($participation->getStartDate()),
+ null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())];
+ $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE];
+ }
+
+ if ([] === $or) {
+ $query->addWhereClause('TRUE = FALSE');
+
+ return $query;
+ }
+ return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
+ }
+
+ public function isAllowedForPerson(Person $person): bool
+ {
+ // check that the person is allowed to see an accompanying period. If yes, the
+ // ACL on each accompanying period will be checked when the query is build
+ return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person);
+ }
+
+ private function addWhereClausesToQuery(FetchQuery $query, ?DateTimeImmutable $startDate, ?DateTimeImmutable $endDate, ?string $content): FetchQuery
+ {
+ $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
+
+ if (null !== $startDate) {
+ $query->addWhereClause(
+ sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
+ [$startDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $endDate) {
+ $query->addWhereClause(
+ sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
+ [$endDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $content) {
+ $query->addWhereClause(
+ sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')),
+ ['%' . $content . '%'],
+ [Types::STRING]
+ );
+ }
+
+ return $query;
+ }
+
+
+}
diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php
new file mode 100644
index 000000000..f5d4b3cbb
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Providers/PersonCalendarGenericDocProvider.php
@@ -0,0 +1,126 @@
+em->getClassMetadata(StoredObject::class);
+
+ if (null !== $startDate) {
+ $query->addWhereClause(
+ sprintf('doc_store.%s >= ?', $storedObjectMetadata->getColumnName('createdAt')),
+ [$startDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $endDate) {
+ $query->addWhereClause(
+ sprintf('doc_store.%s < ?', $storedObjectMetadata->getColumnName('createdAt')),
+ [$endDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $content) {
+ $query->addWhereClause(
+ sprintf('doc_store.%s ilike ?', $storedObjectMetadata->getColumnName('title')),
+ ['%' . $content . '%'],
+ [Types::STRING]
+ );
+ }
+
+ return $query;
+ }
+
+ /**
+ * @throws MappingException
+ */
+ public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
+ {
+ $classMetadata = $this->em->getClassMetadata(CalendarDoc::class);
+ $storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
+ $calendarMetadata = $this->em->getClassMetadata(Calendar::class);
+
+ $query = new FetchQuery(
+ self::KEY,
+ sprintf("jsonb_build_object('id', cd.%s)", $classMetadata->getColumnName('id')),
+ 'cd.'.$storedObjectMetadata->getColumnName('createdAt'),
+ $classMetadata->getSchemaName().'.'.$classMetadata->getTableName().' AS cd'
+ );
+ $query->addJoinClause(
+ sprintf(
+ 'JOIN %s doc_store ON doc_store.%s = cd.%s',
+ $storedObjectMetadata->getSchemaName().'.'.$storedObjectMetadata->getTableName(),
+ $storedObjectMetadata->getColumnName('id'),
+ $classMetadata->getSingleAssociationJoinColumnName('storedObject')
+ )
+ );
+
+ $query->addJoinClause(
+ sprintf(
+ 'JOIN %s calendar ON calendar.%s = cd.%s',
+ $calendarMetadata->getSchemaName().'.'.$calendarMetadata->getTableName(),
+ $calendarMetadata->getColumnName('id'),
+ $classMetadata->getSingleAssociationJoinColumnName('calendar')
+ )
+ );
+
+ $query->addWhereClause(
+ sprintf('calendar.%s = ?', $calendarMetadata->getSingleAssociationJoinColumnName('person')),
+ [$person->getId()],
+ [Types::INTEGER]
+ );
+
+ return $this->addWhereClausesToQuery($query, $startDate, $endDate, $content);
+ }
+
+ /**
+ * @param Person $person
+ * @return bool
+ */
+ public function isAllowedForPerson(Person $person): bool
+ {
+ return $this->security->isGranted(CalendarVoter::SEE, $person);
+ }
+}
diff --git a/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php
new file mode 100644
index 000000000..b15c091c6
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Service/GenericDoc/Renderers/AccompanyingPeriodCalendarGenericDocRenderer.php
@@ -0,0 +1,46 @@
+repository = $calendarDocRepository;
+ }
+
+ public function supports(GenericDocDTO $genericDocDTO, $options = []): bool
+ {
+ return $genericDocDTO->key === AccompanyingPeriodCalendarGenericDocProvider::KEY || $genericDocDTO->key === PersonCalendarGenericDocProvider::KEY;
+ }
+
+ public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
+ {
+ return '@ChillCalendar/GenericDoc/calendar_document.html.twig';
+ }
+
+ public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
+ {
+ return [
+ 'document' => $this->repository->find($genericDocDTO->identifiers['id']),
+ 'context' => $genericDocDTO->getContext(),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php
new file mode 100644
index 000000000..089477fda
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceReaderTest.php
@@ -0,0 +1,176 @@
+prophesize(MapCalendarToUser::class);
+ $mapUser->getUserId($user)->willReturn('1234');
+ $clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00'));
+
+ $absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock);
+
+ self::assertEquals($expected, $absenceReader->isUserAbsent($user), $message);
+ }
+
+ public function testIsUserAbsentWithoutRemoteId(): void
+ {
+ $user = new User();
+ $client = new MockHttpClient();
+
+ $mapUser = $this->prophesize(MapCalendarToUser::class);
+ $mapUser->getUserId($user)->willReturn(null);
+ $clock = new MockClock(new \DateTimeImmutable('2023-07-07T12:00:00'));
+
+ $absenceReader = new MSUserAbsenceReader($client, $mapUser->reveal(), $clock);
+
+ self::assertNull($absenceReader->isUserAbsent($user), "when no user found, absence should be null");
+ }
+
+ public function provideDataTestUserAbsence(): iterable
+ {
+ // contains data that was retrieved from microsoft graph api on 2023-07-06
+
+ yield [
+ <<<'JSON'
+ {
+ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
+ "status": "disabled",
+ "externalAudience": "none",
+ "internalReplyMessage": "Je suis en congé.",
+ "externalReplyMessage": "",
+ "scheduledStartDateTime": {
+ "dateTime": "2023-07-06T12:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "scheduledEndDateTime": {
+ "dateTime": "2023-07-07T12:00:00.0000000",
+ "timeZone": "UTC"
+ }
+ }
+ JSON,
+ false,
+ "User is present"
+ ];
+
+ yield [
+ <<<'JSON'
+ {
+ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
+ "status": "scheduled",
+ "externalAudience": "none",
+ "internalReplyMessage": "Je suis en congé.",
+ "externalReplyMessage": "",
+ "scheduledStartDateTime": {
+ "dateTime": "2023-07-06T11:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "scheduledEndDateTime": {
+ "dateTime": "2023-07-21T11:00:00.0000000",
+ "timeZone": "UTC"
+ }
+ }
+ JSON,
+ true,
+ 'User is absent with absence scheduled, we are within this period'
+ ];
+
+ yield [
+ <<<'JSON'
+ {
+ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
+ "status": "scheduled",
+ "externalAudience": "none",
+ "internalReplyMessage": "Je suis en congé.",
+ "externalReplyMessage": "",
+ "scheduledStartDateTime": {
+ "dateTime": "2023-07-08T11:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "scheduledEndDateTime": {
+ "dateTime": "2023-07-21T11:00:00.0000000",
+ "timeZone": "UTC"
+ }
+ }
+ JSON,
+ false,
+ 'User is present: absence is scheduled for later'
+ ];
+
+ yield [
+ <<<'JSON'
+ {
+ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
+ "status": "scheduled",
+ "externalAudience": "none",
+ "internalReplyMessage": "Je suis en congé.",
+ "externalReplyMessage": "",
+ "scheduledStartDateTime": {
+ "dateTime": "2023-07-05T11:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "scheduledEndDateTime": {
+ "dateTime": "2023-07-06T11:00:00.0000000",
+ "timeZone": "UTC"
+ }
+ }
+ JSON,
+ false,
+ 'User is present: absence is past'
+ ];
+
+ yield [
+ <<<'JSON'
+ {
+ "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('4feb0ae3-7ffb-48dd-891e-c86b2cdeefd4')/mailboxSettings/automaticRepliesSetting",
+ "status": "alwaysEnabled",
+ "externalAudience": "none",
+ "internalReplyMessage": "Je suis en congé.",
+ "externalReplyMessage": "",
+ "scheduledStartDateTime": {
+ "dateTime": "2023-07-06T12:00:00.0000000",
+ "timeZone": "UTC"
+ },
+ "scheduledEndDateTime": {
+ "dateTime": "2023-07-07T12:00:00.0000000",
+ "timeZone": "UTC"
+ }
+ }
+ JSON,
+ true,
+ "User is absent: absence is always enabled"
+ ];
+ }
+
+}
diff --git a/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSyncTest.php b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSyncTest.php
new file mode 100644
index 000000000..1b0f1e416
--- /dev/null
+++ b/src/Bundle/ChillCalendarBundle/Tests/RemoteCalendar/Connector/MSGraph/MSUserAbsenceSyncTest.php
@@ -0,0 +1,68 @@
+prophesize(MSUserAbsenceReaderInterface::class);
+ $userAbsenceReader->isUserAbsent($user)->willReturn($absenceFromMicrosoft);
+
+ $clock = new MockClock(new \DateTimeImmutable('2023-07-01T12:00:00'));
+
+ $syncer = new MSUserAbsenceSync($userAbsenceReader->reveal(), $clock, new NullLogger());
+
+ $syncer->syncUserAbsence($user);
+
+ self::assertEquals($expectedAbsence, $user->isAbsent(), $message);
+ self::assertEquals($expectedAbsenceStart, $user->getAbsenceStart(), $message);
+ }
+
+ public function provideDataTestSyncUserAbsence(): iterable
+ {
+ yield [new User(), false, false, null, "user present remains present"];
+ yield [new User(), true, true, new \DateTimeImmutable('2023-07-01T12:00:00'), "user present becomes absent"];
+
+ $user = new User();
+ $user->setAbsenceStart($abs = new \DateTimeImmutable("2023-07-01T12:00:00"));
+ yield [$user, true, true, $abs, "user absent remains absent"];
+
+ $user = new User();
+ $user->setAbsenceStart($abs = new \DateTimeImmutable("2023-07-01T12:00:00"));
+ yield [$user, false, false, null, "user absent becomes present"];
+
+ yield [new User(), null, false, null, "user not syncable: presence do not change"];
+
+ $user = new User();
+ $user->setAbsenceStart($abs = new \DateTimeImmutable("2023-07-01T12:00:00"));
+ yield [$user, null, true, $abs, "user not syncable: absence do not change"];
+ }
+}
diff --git a/src/Bundle/ChillCalendarBundle/chill.webpack.config.js b/src/Bundle/ChillCalendarBundle/chill.webpack.config.js
index e82210087..9d45a3142 100644
--- a/src/Bundle/ChillCalendarBundle/chill.webpack.config.js
+++ b/src/Bundle/ChillCalendarBundle/chill.webpack.config.js
@@ -1,6 +1,8 @@
// this file loads all assets from the Chill calendar bundle
module.exports = function(encore, entries) {
+ entries.push(__dirname + '/Resources/public/chill/chill.js');
+
encore.addAliases({
ChillCalendarAssets: __dirname + '/Resources/public'
});
diff --git a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml
index a1228c381..95faec742 100644
--- a/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml
+++ b/src/Bundle/ChillCalendarBundle/translations/messages.fr.yml
@@ -44,6 +44,7 @@ crud:
title_edit: Modifier le motif d'annulation
chill_calendar:
+ Document: Document d'un rendez-vous
form:
The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement.
Create for referrer: Créer pour le référent
@@ -66,6 +67,7 @@ chill_calendar:
Document outdated: La date et l'heure du rendez-vous ont été modifiés après la création du document
+
remote_ms_graph:
freebusy_statuses:
busy: Occupé
@@ -146,3 +148,9 @@ CHILL_CALENDAR_CALENDAR_EDIT: Modifier les rendez-vous
CHILL_CALENDAR_CALENDAR_DELETE: Supprimer les rendez-vous
CHILL_CALENDAR_CALENDAR_SEE: Voir les rendez-vous
+
+generic_doc:
+ filter:
+ keys:
+ accompanying_period_calendar_document: Document des rendez-vous des parcours
+ person_calendar_document: Document des rendez-vous de l'usager
diff --git a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php
index 81c71f45f..8dcbe72c3 100644
--- a/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php
+++ b/src/Bundle/ChillDocStoreBundle/ChillDocStoreBundle.php
@@ -11,8 +11,21 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle;
+use Chill\DocStoreBundle\GenericDoc\GenericDocForAccompanyingPeriodProviderInterface;
+use Chill\DocStoreBundle\GenericDoc\GenericDocForPersonProviderInterface;
+use Chill\DocStoreBundle\GenericDoc\Twig\GenericDocRendererInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ChillDocStoreBundle extends Bundle
{
+ public function build(ContainerBuilder $container)
+ {
+ $container->registerForAutoconfiguration(GenericDocForAccompanyingPeriodProviderInterface::class)
+ ->addTag('chill_doc_store.generic_doc_accompanying_period_provider');
+ $container->registerForAutoconfiguration(GenericDocForPersonProviderInterface::class)
+ ->addTag('chill_doc_store.generic_doc_person_provider');
+ $container->registerForAutoconfiguration(GenericDocRendererInterface::class)
+ ->addTag('chill_doc_store.generic_doc_renderer');
+ }
}
diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php
index 1bc3db221..384eeb510 100644
--- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php
+++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentAccompanyingCourseController.php
@@ -39,10 +39,6 @@ class DocumentAccompanyingCourseController extends AbstractController
protected TranslatorInterface $translator;
- private AccompanyingCourseDocumentRepository $courseRepository;
-
- private PaginatorFactory $paginatorFactory;
-
/**
* DocumentAccompanyingCourseController constructor.
*/
@@ -50,14 +46,10 @@ class DocumentAccompanyingCourseController extends AbstractController
TranslatorInterface $translator,
EventDispatcherInterface $eventDispatcher,
AuthorizationHelper $authorizationHelper,
- PaginatorFactory $paginatorFactory,
- AccompanyingCourseDocumentRepository $courseRepository
) {
$this->translator = $translator;
$this->eventDispatcher = $eventDispatcher;
$this->authorizationHelper = $authorizationHelper;
- $this->paginatorFactory = $paginatorFactory;
- $this->courseRepository = $courseRepository;
}
/**
@@ -82,7 +74,7 @@ class DocumentAccompanyingCourseController extends AbstractController
return $this->redirect($request->query->get('returnPath'));
}
- return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]);
+ return $this->redirectToRoute('chill_docstore_generic-doc_by-period_index', ['id' => $course->getId()]);
}
return $this->render(
@@ -136,40 +128,6 @@ class DocumentAccompanyingCourseController extends AbstractController
);
}
- /**
- * @Route("/", name="accompanying_course_document_index", methods="GET")
- */
- public function index(AccompanyingPeriod $course): Response
- {
- $em = $this->getDoctrine()->getManager();
-
- if (null === $course) {
- throw $this->createNotFoundException('Accompanying period not found');
- }
-
- $this->denyAccessUnlessGranted(AccompanyingCourseDocumentVoter::SEE, $course);
-
- $total = $this->courseRepository->countByCourse($course);
- $pagination = $this->paginatorFactory->create($total);
-
- $documents = $this->courseRepository
- ->findBy(
- ['course' => $course],
- ['date' => 'DESC', 'id' => 'DESC'],
- $pagination->getItemsPerPage(),
- $pagination->getCurrentPageFirstItemNumber()
- );
-
- return $this->render(
- 'ChillDocStoreBundle:AccompanyingCourseDocument:index.html.twig',
- [
- 'documents' => $documents,
- 'accompanyingCourse' => $course,
- 'pagination' => $pagination,
- ]
- );
- }
-
/**
* @Route("/new", name="accompanying_course_document_new", methods="GET|POST")
*/
@@ -202,7 +160,7 @@ class DocumentAccompanyingCourseController extends AbstractController
$this->addFlash('success', $this->translator->trans('The document is successfully registered'));
- return $this->redirectToRoute('accompanying_course_document_index', ['course' => $course->getId()]);
+ return $this->redirectToRoute('chill_docstore_generic-doc_by-period_index', ['id' => $course->getId()]);
}
if ($form->isSubmitted() && !$form->isValid()) {
diff --git a/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php b/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php
index 6fcb6a8e5..20e8e9b03 100644
--- a/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php
+++ b/src/Bundle/ChillDocStoreBundle/Controller/DocumentPersonController.php
@@ -45,10 +45,6 @@ class DocumentPersonController extends AbstractController
protected TranslatorInterface $translator;
- private PaginatorFactory $paginatorFactory;
-
- private PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository;
-
/**
* DocumentPersonController constructor.
*/
@@ -56,14 +52,10 @@ class DocumentPersonController extends AbstractController
TranslatorInterface $translator,
EventDispatcherInterface $eventDispatcher,
AuthorizationHelper $authorizationHelper,
- PaginatorFactory $paginatorFactory,
- PersonDocumentACLAwareRepositoryInterface $personDocumentACLAwareRepository
) {
$this->translator = $translator;
$this->eventDispatcher = $eventDispatcher;
$this->authorizationHelper = $authorizationHelper;
- $this->paginatorFactory = $paginatorFactory;
- $this->personDocumentACLAwareRepository = $personDocumentACLAwareRepository;
}
/**
@@ -88,7 +80,7 @@ class DocumentPersonController extends AbstractController
return $this->redirect($request->query->get('returnPath'));
}
- return $this->redirectToRoute('person_document_index', ['person' => $person->getId()]);
+ return $this->redirectToRoute('chill_docstore_generic-doc_by-person_index', ['id' => $person->getId()]);
}
return $this->render(
@@ -160,45 +152,6 @@ class DocumentPersonController extends AbstractController
);
}
- /**
- * @Route("/", name="person_document_index", methods="GET")
- */
- public function index(Person $person): Response
- {
- $em = $this->getDoctrine()->getManager();
-
- if (null === $person) {
- throw $this->createNotFoundException('Person not found');
- }
-
- $this->denyAccessUnlessGranted(PersonVoter::SEE, $person);
-
- $total = $this->personDocumentACLAwareRepository->countByPerson($person);
- $pagination = $this->paginatorFactory->create($total);
-
- $documents = $this->personDocumentACLAwareRepository->findByPerson(
- $person,
- ['date' => 'DESC', 'id' => 'DESC'],
- $pagination->getItemsPerPage(),
- $pagination->getCurrentPageFirstItemNumber()
- );
-
- $event = new PrivacyEvent($person, [
- 'element_class' => PersonDocument::class,
- 'action' => 'index',
- ]);
- $this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
-
- return $this->render(
- 'ChillDocStoreBundle:PersonDocument:index.html.twig',
- [
- 'documents' => $documents,
- 'person' => $person,
- 'pagination' => $pagination,
- ]
- );
- }
-
/**
* @Route("/new", name="person_document_new", methods="GET|POST")
*/
@@ -233,7 +186,7 @@ class DocumentPersonController extends AbstractController
$this->addFlash('success', $this->translator->trans('The document is successfully registered'));
- return $this->redirectToRoute('person_document_index', ['person' => $person->getId()]);
+ return $this->redirectToRoute('chill_docstore_generic-doc_by-person_index', ['id' => $person->getId()]);
}
if ($form->isSubmitted() && !$form->isValid()) {
diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php
new file mode 100644
index 000000000..70c41db50
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForAccompanyingPeriodController.php
@@ -0,0 +1,98 @@
+security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod)) {
+ throw new AccessDeniedHttpException("not allowed to see the documents for accompanying period");
+ }
+
+ $filterBuilder = $this->filterOrderHelperFactory
+ ->create(self::class)
+ ->addSearchBox()
+ ->addDateRange('dateRange', 'generic_doc.filter.date-range');
+
+ if ([] !== $places = $this->manager->placesForAccompanyingPeriod($accompanyingPeriod)) {
+ $filterBuilder->addCheckbox('places', $places, [], array_map(
+ static fn (string $k) => 'generic_doc.filter.keys.' . $k,
+ $places
+ ));
+ }
+
+ $filter = $filterBuilder
+ ->build();
+
+ ['to' => $endDate, 'from' => $startDate ] = $filter->getDateRangeData('dateRange');
+ $content = $filter->getQueryString();
+
+ $nb = $this->manager->countDocForAccompanyingPeriod(
+ $accompanyingPeriod,
+ $startDate,
+ $endDate,
+ $content,
+ $filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : []
+ );
+ $paginator = $this->paginator->create($nb);
+
+ $documents = $this->manager->findDocForAccompanyingPeriod(
+ $accompanyingPeriod,
+ $paginator->getCurrentPageFirstItemNumber(),
+ $paginator->getItemsPerPage(),
+ $startDate,
+ $endDate,
+ $content,
+ $filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : []
+ );
+
+ return new Response($this->twig->render(
+ '@ChillDocStore/GenericDoc/accompanying_period_list.html.twig',
+ [
+ 'accompanyingCourse' => $accompanyingPeriod,
+ 'pagination' => $paginator,
+ 'documents' => iterator_to_array($documents),
+ 'filter' => $filter,
+ ]
+ ));
+ }
+
+}
diff --git a/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php
new file mode 100644
index 000000000..3484e0904
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/Controller/GenericDocForPerson.php
@@ -0,0 +1,95 @@
+security->isGranted(PersonDocumentVoter::SEE, $person)) {
+ throw new AccessDeniedHttpException("not allowed to see the documents for person");
+ }
+
+ $filterBuilder = $this->filterOrderHelperFactory
+ ->create(self::class)
+ ->addSearchBox()
+ ->addDateRange('dateRange', 'generic_doc.filter.date-range');
+
+ if ([] !== $places = $this->manager->placesForPerson($person)) {
+ $filterBuilder->addCheckbox('places', $places, [], array_map(
+ static fn (string $k) => 'generic_doc.filter.keys.' . $k,
+ $places
+ ));
+ }
+
+ $filter = $filterBuilder
+ ->build();
+
+ ['to' => $endDate, 'from' => $startDate ] = $filter->getDateRangeData('dateRange');
+ $content = $filter->getQueryString();
+
+ $nb = $this->manager->countDocForPerson(
+ $person,
+ $startDate,
+ $endDate,
+ $content,
+ $filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : []
+ );
+ $paginator = $this->paginator->create($nb);
+
+ $documents = $this->manager->findDocForPerson(
+ $person,
+ $paginator->getCurrentPageFirstItemNumber(),
+ $paginator->getItemsPerPage(),
+ $startDate,
+ $endDate,
+ $content,
+ $filter->hasCheckBox('places') ? array_values($filter->getCheckboxData('places')) : []
+ );
+
+ return new Response($this->twig->render(
+ '@ChillDocStore/GenericDoc/person_list.html.twig',
+ [
+ 'person' => $person,
+ 'pagination' => $paginator,
+ 'documents' => iterator_to_array($documents),
+ 'filter' => $filter,
+ ]
+ ));
+ }
+
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php
new file mode 100644
index 000000000..30e07a841
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQuery.php
@@ -0,0 +1,233 @@
+
+ */
+ private array $joins = [];
+
+ /**
+ * @var list>
+ */
+ private array $joinParams = [];
+
+ /**
+ * @var array>
+ */
+ private array $joinTypes = [];
+
+ /**
+ * @var array
+ */
+ private array $wheres = [];
+
+ /**
+ * @var array>
+ */
+ private array $whereParams = [];
+
+ /**
+ * @var array>
+ */
+ private array $whereTypes = [];
+
+ public function __construct(
+ private readonly string $selectKeyString,
+ private readonly string $selectIdentifierJsonB,
+ private readonly string $selectDate,
+ private string $from = '',
+ private array $selectIdentifierParams = [],
+ private array $selectIdentifierTypes = [],
+ private array $selectDateParams = [],
+ private array $selectDateTypes = [],
+ ) {
+ }
+
+ public function addJoinClause(string $sql, array $params = [], array $types = []): int
+ {
+ $this->joins[] = $sql;
+ $this->joinParams[] = $params;
+ $this->joinTypes[] = $types;
+
+ return count($this->joins) - 1;
+ }
+
+ public function addWhereClause(string $sql, array $params = [], array $types = []): int
+ {
+ $this->wheres[] = $sql;
+ $this->whereParams[] = $params;
+ $this->whereTypes[] = $types;
+
+ return count($this->wheres) - 1;
+ }
+
+ public function removeWhereClause(int $index): void
+ {
+ if (!array_key_exists($index, $this->wheres)) {
+ throw new \UnexpectedValueException("this index does not exists");
+ }
+
+ unset($this->wheres[$index], $this->whereParams[$index], $this->whereTypes[$index]);
+
+ }
+
+ public function removeJoinClause(int $index): void
+ {
+ if (!array_key_exists($index, $this->joins)) {
+ throw new \UnexpectedValueException("this index does not exists");
+ }
+
+ unset($this->joins[$index], $this->joinParams[$index], $this->joinTypes[$index]);
+
+ }
+
+ public function getSelectKeyString(): string
+ {
+ return $this->selectKeyString;
+ }
+
+ public function getSelectIdentifierJsonB(): string
+ {
+ return $this->selectIdentifierJsonB;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSelectIdentifierParams(): array
+ {
+ return $this->selectIdentifierParams;
+ }
+
+ public function getSelectIdentifiersTypes(): array
+ {
+ return $this->selectIdentifierTypes;
+ }
+
+ public function getSelectDate(): string
+ {
+ return $this->selectDate;
+ }
+
+ public function getSelectDateTypes(): array
+ {
+ return $this->selectDateTypes;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getSelectDateParams(): array
+ {
+ return $this->selectDateParams;
+ }
+
+ public function getFromQuery(): string
+ {
+ return $this->from . " " . implode(' ', $this->joins);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getFromQueryParams(): array
+ {
+ $result = [];
+
+ foreach ($this->joinParams as $params) {
+ $result = [...$result, ...$params];
+ }
+
+ return $result;
+ }
+
+ public function getFromQueryTypes(): array
+ {
+ $result = [];
+
+ foreach ($this->joinTypes as $types) {
+ $result = [...$result, ...$types];
+ }
+
+ return $result;
+ }
+
+ public function getWhereQuery(): string
+ {
+ return implode(' AND ', $this->wheres);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getWhereQueryParams(): array
+ {
+ $result = [];
+
+ foreach ($this->whereParams as $params) {
+ $result = [...$result, ...$params];
+ }
+
+ return $result;
+ }
+
+ public function getWhereQueryTypes(): array
+ {
+ $result = [];
+
+ foreach ($this->whereTypes as $types) {
+ $result = [...$result, ...$types];
+ }
+
+ return $result;
+ }
+
+ public function setSelectIdentifierParams(array $selectIdentifierParams): self
+ {
+ $this->selectIdentifierParams = $selectIdentifierParams;
+
+ return $this;
+ }
+
+ public function setSelectDateParams(array $selectDateParams): self
+ {
+ $this->selectDateParams = $selectDateParams;
+
+ return $this;
+ }
+
+ public function setFrom(string $from): self
+ {
+ $this->from = $from;
+
+ return $this;
+ }
+
+ public function setSelectIdentifierTypes(array $selectIdentifierTypes): self
+ {
+ $this->selectIdentifierTypes = $selectIdentifierTypes;
+
+ return $this;
+ }
+
+ public function setSelectDateTypes(array $selectDateTypes): self
+ {
+ $this->selectDateTypes = $selectDateTypes;
+
+ return $this;
+ }
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php
new file mode 100644
index 000000000..e46795457
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryInterface.php
@@ -0,0 +1,67 @@
+
+ */
+ public function getSelectIdentifierParams(): array;
+
+ /**
+ * @return list
+ */
+ public function getSelectIdentifiersTypes(): array;
+
+ public function getSelectDate(): string;
+
+ /**
+ * @return list
+ */
+ public function getSelectDateParams(): array;
+
+ /**
+ * @return list
+ */
+ public function getSelectDateTypes(): array;
+
+ public function getFromQuery(): string;
+
+ /**
+ * @return list
+ */
+ public function getFromQueryParams(): array;
+
+ /**
+ * @return list
+ */
+ public function getFromQueryTypes(): array;
+
+ public function getWhereQuery(): string;
+
+ /**
+ * @return list
+ */
+ public function getWhereQueryParams(): array;
+
+ /**
+ * @return list
+ */
+ public function getWhereQueryTypes(): array;
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php
new file mode 100644
index 000000000..2c0c59cff
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/FetchQueryToSqlBuilder.php
@@ -0,0 +1,57 @@
+, types: list}
+ */
+ public function toSql(FetchQueryInterface $query): array
+ {
+ $sql = strtr(self::SQL, [
+ '{{ key }}' => $query->getSelectKeyString(),
+ '{{ identifiers }}' => $query->getSelectIdentifierJsonB(),
+ '{{ date }}' => $query->getSelectDate(),
+ '{{ from }}' => $query->getFromQuery(),
+ '{{ where }}' => '' === ($w = $query->getWhereQuery()) ? '' : 'WHERE ' . $w,
+ ]);
+
+ $params = [
+ ...$query->getSelectIdentifierParams(),
+ ...$query->getSelectDateParams(),
+ ...$query->getFromQueryParams(),
+ ...$query->getWhereQueryParams()
+ ];
+
+ $types = [
+ ...$query->getSelectIdentifiersTypes(),
+ ...$query->getSelectDateTypes(),
+ ...$query->getFromQueryTypes(),
+ ...$query->getWhereQueryTypes(),
+ ];
+
+ return ['sql' => $sql, 'params' => $params, 'types' => $types];
+ }
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php
new file mode 100644
index 000000000..fe9bf7e4f
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocDTO.php
@@ -0,0 +1,31 @@
+linked instanceof AccompanyingPeriod ? 'accompanying-period' : 'person';
+ }
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php
new file mode 100644
index 000000000..0d3cb1c32
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/GenericDocForAccompanyingPeriodProviderInterface.php
@@ -0,0 +1,31 @@
+
+ */
+ private iterable $providersForAccompanyingPeriod,
+
+ /**
+ * @var iterable
+ */
+ private iterable $providersForPerson,
+ private Connection $connection,
+ ) {
+ $this->builder = new FetchQueryToSqlBuilder();
+ }
+
+ /**
+ * @param list $places
+ * @throws Exception
+ */
+ public function countDocForAccompanyingPeriod(
+ AccompanyingPeriod $accompanyingPeriod,
+ ?\DateTimeImmutable $startDate = null,
+ ?\DateTimeImmutable $endDate = null,
+ ?string $content = null,
+ array $places = []
+ ): int {
+ ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places);
+
+ return $this->countDoc($sql, $params, $types);
+ }
+
+ private function countDoc(string $sql, array $params, array $types): int
+ {
+ if ($sql === '') {
+ return 0;
+ }
+
+ $countSql = "SELECT count(*) AS c FROM ({$sql}) AS sq";
+ $result = $this->connection->executeQuery($countSql, $params, $types);
+
+ $number = $result->fetchOne();
+
+ if (false === $number) {
+ throw new \UnexpectedValueException("number of documents failed to load");
+ }
+
+ return $number;
+ }
+
+ public function countDocForPerson(
+ Person $person,
+ ?\DateTimeImmutable $startDate = null,
+ ?\DateTimeImmutable $endDate = null,
+ ?string $content = null,
+ array $places = []
+ ): int {
+ ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person, $startDate, $endDate, $content, $places);
+
+ return $this->countDoc($sql, $params, $types);
+ }
+
+ /**
+ * @param list $places places to search. When empty, search in all places
+ * @return iterable
+ * @throws Exception
+ */
+ public function findDocForAccompanyingPeriod(
+ AccompanyingPeriod $accompanyingPeriod,
+ int $offset = 0,
+ int $limit = 20,
+ ?\DateTimeImmutable $startDate = null,
+ ?\DateTimeImmutable $endDate = null,
+ ?string $content = null,
+ array $places = []
+ ): iterable {
+ ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod, $startDate, $endDate, $content, $places);
+
+ return $this->findDocs($accompanyingPeriod, $sql, $params, $types, $offset, $limit);
+ }
+
+ /**
+ * @throws \JsonException
+ * @throws Exception
+ */
+ private function findDocs(AccompanyingPeriod|Person $linked, string $sql, array $params, array $types, int $offset, int $limit): iterable
+ {
+ if ($sql === '') {
+ return [];
+ }
+
+ $runSql = "{$sql} ORDER BY doc_date DESC LIMIT ? OFFSET ?";
+ $runParams = [...$params, ...[$limit, $offset]];
+ $runTypes = [...$types, ...[Types::INTEGER, Types::INTEGER]];
+
+ foreach ($this->connection->iterateAssociative($runSql, $runParams, $runTypes) as $row) {
+ yield new GenericDocDTO(
+ $row['key'],
+ json_decode($row['identifiers'], true, 512, JSON_THROW_ON_ERROR),
+ new \DateTimeImmutable($row['doc_date']),
+ $linked,
+ );
+ }
+ }
+
+ /**
+ * @param list $places places to search. When empty, search in all places
+ * @return iterable
+ */
+ public function findDocForPerson(
+ Person $person,
+ int $offset = 0,
+ int $limit = 20,
+ ?\DateTimeImmutable $startDate = null,
+ ?\DateTimeImmutable $endDate = null,
+ ?string $content = null,
+ array $places = []
+ ): iterable {
+ ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person, $startDate, $endDate, $content, $places);
+
+ return $this->findDocs($person, $sql, $params, $types, $offset, $limit);
+ }
+
+ public function placesForPerson(Person $person): array
+ {
+ ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($person);
+
+ return $this->places($sql, $params, $types);
+ }
+
+ public function placesForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): array
+ {
+ ['sql' => $sql, 'params' => $params, 'types' => $types] = $this->buildUnionQuery($accompanyingPeriod);
+
+ return $this->places($sql, $params, $types);
+ }
+
+ private function places(string $sql, array $params, array $types): array
+ {
+ if ($sql === '') {
+ return [];
+ }
+
+ $runSql = "SELECT DISTINCT key FROM ({$sql}) AS sq ORDER BY key";
+
+ $keys = [];
+
+ foreach ($this->connection->iterateAssociative($runSql, $params, $types) as $k) {
+ $keys[] = $k['key'];
+ }
+
+ return $keys;
+ }
+
+ /**
+ * @param list $places places to search. When empty, search in all places
+ */
+ private function buildUnionQuery(
+ AccompanyingPeriod|Person $linked,
+ ?\DateTimeImmutable $startDate = null,
+ ?\DateTimeImmutable $endDate = null,
+ ?string $content = null,
+ array $places = [],
+ ): array {
+ $queries = [];
+
+ if ($linked instanceof AccompanyingPeriod) {
+ foreach ($this->providersForAccompanyingPeriod as $provider) {
+ if (!$provider->isAllowedForAccompanyingPeriod($linked)) {
+ continue;
+ }
+ $queries[] = $provider->buildFetchQueryForAccompanyingPeriod($linked, $startDate, $endDate, $content);
+ }
+ } else {
+ foreach ($this->providersForPerson as $provider) {
+ if (!$provider->isAllowedForPerson($linked)) {
+ continue;
+ }
+ $queries[] = $provider->buildFetchQueryForPerson($linked, $startDate, $endDate, $content);
+ }
+ }
+ $sql = [];
+ $params = [];
+ $types = [];
+
+ foreach ($queries as $query) {
+ if ([] !== $places and !in_array($query->getSelectKeyString(), $places, true)) {
+ continue;
+ }
+
+ ['sql' => $q, 'params' => $p, 'types' => $t ] = $this->builder->toSql($query);
+
+ $sql[] = $q;
+ $params = [...$params, ...$p];
+ $types = [...$types, ...$t];
+ }
+
+ return ['sql' => implode(' UNION ', $sql), 'params' => $params, 'types' => $types];
+ }
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php
new file mode 100644
index 000000000..fd36f7976
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/AccompanyingCourseDocumentGenericDocProvider.php
@@ -0,0 +1,147 @@
+entityManager->getClassMetadata(AccompanyingCourseDocument::class);
+
+ $query = new FetchQuery(
+ self::KEY,
+ sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
+ $classMetadata->getColumnName('date'),
+ $classMetadata->getSchemaName() . '.' . $classMetadata->getTableName()
+ );
+
+ $query->addWhereClause(
+ sprintf('%s = ?', $classMetadata->getSingleAssociationJoinColumnName('course')),
+ [$accompanyingPeriod->getId()],
+ [Types::INTEGER]
+ );
+
+ return $this->addWhereClause($query, $startDate, $endDate, $content);
+ }
+
+ public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
+ {
+ return $this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $accompanyingPeriod);
+ }
+
+ public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
+ {
+ $classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
+
+ $query = new FetchQuery(
+ self::KEY,
+ sprintf('jsonb_build_object(\'id\', %s)', $classMetadata->getIdentifierColumnNames()[0]),
+ $classMetadata->getColumnName('date'),
+ $classMetadata->getSchemaName() . '.' . $classMetadata->getTableName() . ' AS acc_course_document'
+ );
+
+ $atLeastOne = false;
+ $or = [];
+ $orParams = [];
+ $orTypes = [];
+
+ foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
+ if (!$this->security->isGranted(AccompanyingCourseDocumentVoter::SEE, $participation->getAccompanyingPeriod())) {
+ continue;
+ }
+
+ $atLeastOne = true;
+
+ $or[] = sprintf(
+ "(acc_course_document.%s = ? AND acc_course_document.%s BETWEEN ? AND COALESCE(?, 'infinity'::date))",
+ $classMetadata->getSingleAssociationJoinColumnName('course'),
+ $classMetadata->getColumnName('date')
+ );
+ $orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(), $participation->getStartDate(), $participation->getEndDate()];
+ $orTypes = [...$orTypes, Types::INTEGER, Types::DATE_MUTABLE, Types::DATE_MUTABLE];
+ }
+
+ if (!$atLeastOne) {
+ // there aren't any period allowed to be seen. Add an unreachable condition
+ $query->addWhereClause('TRUE = FALSE');
+
+ return $query;
+ }
+
+ $query->addWhereClause('(' . implode(' OR ', $or) . ')', $orParams, $orTypes);
+
+ return $this->addWhereClause($query, $startDate, $endDate, $content);
+ }
+
+ public function isAllowedForPerson(Person $person): bool
+ {
+ return $this->security->isGranted(AccompanyingPeriodVoter::SEE, $person);
+ }
+
+ private function addWhereClause(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
+ {
+ $classMetadata = $this->entityManager->getClassMetadata(AccompanyingCourseDocument::class);
+
+ if (null !== $startDate) {
+ $query->addWhereClause(
+ sprintf('? <= %s', $classMetadata->getColumnName('date')),
+ [$startDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $endDate) {
+ $query->addWhereClause(
+ sprintf('? >= %s', $classMetadata->getColumnName('date')),
+ [$endDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $content and '' !== $content) {
+ $query->addWhereClause(
+ sprintf(
+ '(%s ilike ? OR %s ilike ?)',
+ $classMetadata->getColumnName('title'),
+ $classMetadata->getColumnName('description')
+ ),
+ ['%' . $content . '%', '%' . $content . '%'],
+ [Types::STRING, Types::STRING]
+ );
+ }
+
+ return $query;
+ }
+
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php
new file mode 100644
index 000000000..613f8d758
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Providers/PersonDocumentGenericDocProvider.php
@@ -0,0 +1,65 @@
+personDocumentACLAwareRepository->buildFetchQueryForPerson(
+ $person,
+ $startDate,
+ $endDate,
+ $content
+ );
+ }
+
+ public function isAllowedForPerson(Person $person): bool
+ {
+ return $this->security->isGranted(PersonDocumentVoter::SEE, $person);
+ }
+
+ public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null, ?string $origin = null): FetchQueryInterface
+ {
+ return $this->personDocumentACLAwareRepository->buildFetchQueryForAccompanyingPeriod($accompanyingPeriod, $startDate, $endDate, $content);
+ }
+
+ public function isAllowedForAccompanyingPeriod(AccompanyingPeriod $accompanyingPeriod): bool
+ {
+ // we assume that the user is allowed to see at least one person of the course
+ // this will be double checked when running the query
+ return true;
+ }
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php
new file mode 100644
index 000000000..c32620030
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Renderer/AccompanyingCourseDocumentGenericDocRenderer.php
@@ -0,0 +1,59 @@
+key === AccompanyingCourseDocumentGenericDocProvider::KEY
+ || $genericDocDTO->key === PersonDocumentGenericDocProvider::KEY;
+ }
+
+ public function getTemplate(GenericDocDTO $genericDocDTO, $options = []): string
+ {
+ return '@ChillDocStore/List/list_item.html.twig';
+ }
+
+ public function getTemplateData(GenericDocDTO $genericDocDTO, $options = []): array
+ {
+ if (AccompanyingCourseDocumentGenericDocProvider::KEY === $genericDocDTO->key) {
+ return [
+ 'document' => $doc = $this->accompanyingCourseDocumentRepository->find($genericDocDTO->identifiers['id']),
+ 'accompanyingCourse' => $doc->getCourse(),
+ 'options' => $options,
+ 'context' => $genericDocDTO->getContext(),
+ ];
+ }
+ // this is a person
+ return [
+ 'document' => $doc = $this->personDocumentRepository->find($genericDocDTO->identifiers['id']),
+ 'person' => $doc->getPerson(),
+ 'options' => $options,
+ 'context' => $genericDocDTO->getContext(),
+ ];
+ }
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtension.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtension.php
new file mode 100644
index 000000000..308d85cd7
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtension.php
@@ -0,0 +1,28 @@
+ true,
+ 'is_safe' => ['html'],
+ ])
+ ];
+ }
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php
new file mode 100644
index 000000000..2dee0ed0b
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocExtensionRuntime.php
@@ -0,0 +1,50 @@
+
+ */
+ private iterable $renderers,
+ ) {
+ }
+
+ /**
+ * @throws RuntimeError
+ * @throws SyntaxError
+ * @throws LoaderError
+ */
+ public function renderGenericDoc(Environment $twig, GenericDocDTO $genericDocDTO, array $options = []): string
+ {
+ foreach ($this->renderers as $renderer) {
+ if ($renderer->supports($genericDocDTO)) {
+ return $twig->render(
+ $renderer->getTemplate($genericDocDTO, $options),
+ $renderer->getTemplateData($genericDocDTO, $options),
+ );
+ }
+ }
+
+ throw new \LogicException("no renderer found");
+ }
+
+}
diff --git a/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php
new file mode 100644
index 000000000..940001f4a
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/GenericDoc/Twig/GenericDocRendererInterface.php
@@ -0,0 +1,24 @@
+security->isGranted(AccompanyingCourseDocumentVoter::SEE, $course)) {
$menu->addChild($this->translator->trans('Documents'), [
- 'route' => 'accompanying_course_document_index',
+ 'route' => 'chill_docstore_generic-doc_by-period_index',
'routeParameters' => [
- 'course' => $course->getId(),
+ 'id' => $course->getId(),
],
])
->setExtras([
@@ -80,9 +80,9 @@ final class MenuBuilder implements LocalMenuBuilderInterface
if ($this->security->isGranted(PersonDocumentVoter::SEE, $person)) {
$menu->addChild($this->translator->trans('Documents'), [
- 'route' => 'person_document_index',
+ 'route' => 'chill_docstore_generic-doc_by-person_index',
'routeParameters' => [
- 'person' => $person->getId(),
+ 'id' => $person->getId(),
],
])
->setExtras([
diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php
index 23dcc4e0b..26a42b894 100644
--- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php
+++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepository.php
@@ -12,30 +12,33 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Repository;
use Chill\DocStoreBundle\Entity\PersonDocument;
+use Chill\DocStoreBundle\GenericDoc\FetchQuery;
+use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
+use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
+use Chill\MainBundle\Entity\Scope;
+use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
+use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
+use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
+use Chill\PersonBundle\Entity\AccompanyingPeriod;
+use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person;
+use DateTimeImmutable;
+use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Security;
-class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface
+final readonly class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareRepositoryInterface
{
- private AuthorizationHelperInterface $authorizationHelper;
-
- private CenterResolverDispatcher $centerResolverDispatcher;
-
- private EntityManagerInterface $em;
-
- private Security $security;
-
- public function __construct(EntityManagerInterface $em, AuthorizationHelperInterface $authorizationHelper, CenterResolverDispatcher $centerResolverDispatcher, Security $security)
- {
- $this->em = $em;
- $this->authorizationHelper = $authorizationHelper;
- $this->centerResolverDispatcher = $centerResolverDispatcher;
- $this->security = $security;
+ public function __construct(
+ private EntityManagerInterface $em,
+ private CenterResolverManagerInterface $centerResolverManager,
+ private AuthorizationHelperForCurrentUserInterface $authorizationHelperForCurrentUser,
+ private Security $security,
+ ) {
}
public function buildQueryByPerson(Person $person): QueryBuilder
@@ -49,6 +52,128 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito
return $qb;
}
+
+ public function buildFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface
+ {
+ $query = $this->buildBaseFetchQueryForPerson($person, $startDate, $endDate, $content);
+
+ return $this->addFetchQueryByPersonACL($query, $person);
+ }
+
+ public function buildFetchQueryForAccompanyingPeriod(AccompanyingPeriod $period, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface
+ {
+ $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
+ $participationMetadata = $this->em->getClassMetadata(AccompanyingPeriodParticipation::class);
+
+ $query = new FetchQuery(
+ PersonDocumentGenericDocProvider::KEY,
+ sprintf('jsonb_build_object(\'id\', person_document.%s)', $personDocMetadata->getSingleIdentifierColumnName()),
+ sprintf('person_document.%s', $personDocMetadata->getColumnName('date')),
+ sprintf('%s AS person_document', $personDocMetadata->getSchemaName().'.'.$personDocMetadata->getTableName())
+ );
+
+ $query->addJoinClause(
+ sprintf(
+ 'JOIN %s AS participation ON participation.%s = person_document.%s '.
+ 'AND person_document.%s BETWEEN participation.%s AND COALESCE(participation.%s, \'infinity\'::date)',
+ $participationMetadata->getTableName(),
+ $participationMetadata->getSingleAssociationJoinColumnName('person'),
+ $personDocMetadata->getSingleAssociationJoinColumnName('person'),
+ $personDocMetadata->getColumnName('date'),
+ $participationMetadata->getColumnName('startDate'),
+ $participationMetadata->getColumnName('endDate')
+ )
+ );
+
+ $query->addWhereClause(
+ sprintf('participation.%s = ?', $participationMetadata->getSingleAssociationJoinColumnName('accompanyingPeriod')),
+ [$period->getId()],
+ [Types::INTEGER]
+ );
+
+ // can we see the document for this person ?
+ $orPersonId = [];
+ foreach ($period->getParticipations() as $participation) {
+ if (!$this->security->isGranted(PersonDocumentVoter::SEE, $participation->getPerson())) {
+ continue;
+ }
+ $orPersonId[] = $participation->getPerson()->getId();
+
+ }
+
+ if ([] === $orPersonId) {
+ $query->addWhereClause('FALSE = TRUE');
+
+ return $query;
+ }
+
+ $query->addWhereClause(
+ sprintf(
+ 'participation.%s IN (%s)',
+ $participationMetadata->getSingleAssociationJoinColumnName('person'),
+ implode(', ', array_fill(0, count($orPersonId), '?'))
+ ),
+ $orPersonId,
+ array_fill(0, count($orPersonId), Types::INTEGER)
+ );
+
+ return $this->addFilterClauses($query, $startDate, $endDate, $content);
+ }
+
+ public function buildBaseFetchQueryForPerson(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
+ {
+ $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
+
+ $query = new FetchQuery(
+ PersonDocumentGenericDocProvider::KEY,
+ sprintf('jsonb_build_object(\'id\', person_document.%s)', $personDocMetadata->getSingleIdentifierColumnName()),
+ sprintf('person_document.%s', $personDocMetadata->getColumnName('date')),
+ sprintf('%s AS person_document', $personDocMetadata->getSchemaName().'.'.$personDocMetadata->getTableName())
+ );
+
+ $query->addWhereClause(
+ sprintf('person_document.%s = ?', $personDocMetadata->getSingleAssociationJoinColumnName('person')),
+ [$person->getId()],
+ [Types::INTEGER]
+ );
+
+ return $this->addFilterClauses($query, $startDate, $endDate, $content);
+ }
+
+ private function addFilterClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
+ {
+ $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
+
+ if (null !== $startDate) {
+ $query->addWhereClause(
+ sprintf('? <= %s', $personDocMetadata->getColumnName('date')),
+ [$startDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $endDate) {
+ $query->addWhereClause(
+ sprintf('? >= %s', $personDocMetadata->getColumnName('date')),
+ [$endDate],
+ [Types::DATE_IMMUTABLE]
+ );
+ }
+
+ if (null !== $content and '' !== $content) {
+ $query->addWhereClause(
+ sprintf(
+ '(%s ilike ? OR %s ilike ?)',
+ $personDocMetadata->getColumnName('title'),
+ $personDocMetadata->getColumnName('description')
+ ),
+ ['%' . $content . '%', '%' . $content . '%'],
+ [Types::STRING, Types::STRING]
+ );
+ }
+ return $query;
+ }
+
public function countByPerson(Person $person): int
{
$qb = $this->buildQueryByPerson($person)->select('COUNT(d)');
@@ -75,16 +200,58 @@ class PersonDocumentACLAwareRepository implements PersonDocumentACLAwareReposito
private function addACL(QueryBuilder $qb, Person $person): void
{
- $center = $this->centerResolverDispatcher->resolveCenter($person);
+ $reachableScopes = [];
- $reachableScopes = $this->authorizationHelper
- ->getReachableScopes(
- $this->security->getUser(),
- PersonDocumentVoter::SEE,
- $center
- );
+ foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
+ $reachableScopes = [
+ ...$reachableScopes,
+ ...$this->authorizationHelperForCurrentUser
+ ->getReachableScopes(
+ PersonDocumentVoter::SEE,
+ $center
+ )
+ ];
+ }
+
+ if ([] === $reachableScopes) {
+ $qb->andWhere("'FALSE' = 'TRUE'");
+
+ return;
+ }
$qb->andWhere($qb->expr()->in('d.scope', ':scopes'))
->setParameter('scopes', $reachableScopes);
}
+
+ private function addFetchQueryByPersonACL(FetchQuery $fetchQuery, Person $person): FetchQuery
+ {
+ $personDocMetadata = $this->em->getClassMetadata(PersonDocument::class);
+
+ $reachableScopes = [];
+
+ foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
+ $reachableScopes = [
+ ...$reachableScopes,
+ ...$this->authorizationHelperForCurrentUser->getReachableScopes(PersonDocumentVoter::SEE, $center)
+ ];
+ }
+
+ if ([] === $reachableScopes) {
+ $fetchQuery->addWhereClause('FALSE = TRUE');
+
+ return $fetchQuery;
+ }
+
+ $fetchQuery->addWhereClause(
+ sprintf(
+ 'person_document.%s IN (%s)',
+ $personDocMetadata->getSingleAssociationJoinColumnName('scope'),
+ implode(', ', array_fill(0, count($reachableScopes), '?'))
+ ),
+ array_map(static fn (Scope $s) => $s->getId(), $reachableScopes),
+ array_fill(0, count($reachableScopes), Types::INTEGER)
+ );
+
+ return $fetchQuery;
+ }
}
diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php
index 6c4bd2e9a..f1bc70812 100644
--- a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php
+++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentACLAwareRepositoryInterface.php
@@ -11,11 +11,33 @@ declare(strict_types=1);
namespace Chill\DocStoreBundle\Repository;
+use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
+use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
interface PersonDocumentACLAwareRepositoryInterface
{
+ /**
+ * @deprecated use fetch query for listing and counting person documents
+ */
public function countByPerson(Person $person): int;
+ /**
+ * @deprecated use fetch query for listing and counting person documents
+ */
public function findByPerson(Person $person, array $orderBy = [], int $limit = 20, int $offset = 0): array;
+
+ public function buildFetchQueryForPerson(
+ Person $person,
+ ?\DateTimeImmutable $startDate = null,
+ ?\DateTimeImmutable $endDate = null,
+ ?string $content = null
+ ): FetchQueryInterface;
+
+ public function buildFetchQueryForAccompanyingPeriod(
+ AccompanyingPeriod $period,
+ ?\DateTimeImmutable $startDate = null,
+ ?\DateTimeImmutable $endDate = null,
+ ?string $content = null
+ ): FetchQueryInterface;
}
diff --git a/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php
new file mode 100644
index 000000000..40afdc220
--- /dev/null
+++ b/src/Bundle/ChillDocStoreBundle/Repository/PersonDocumentRepository.php
@@ -0,0 +1,57 @@
+
+ */
+readonly class PersonDocumentRepository implements ObjectRepository
+{
+ private EntityRepository $repository;
+
+ public function __construct(
+ private EntityManagerInterface $entityManager
+ ) {
+ $this->repository = $this->entityManager->getRepository($this->getClassName());
+ }
+
+ public function find($id): ?PersonDocument
+ {
+ return $this->repository->find($id);
+ }
+
+ public function findAll()
+ {
+ return $this->repository->findAll();
+ }
+
+ public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null)
+ {
+ return $this->repository->findBy($criteria, $orderBy, $limit, $offset);
+ }
+
+ public function findOneBy(array $criteria): ?PersonDocument
+ {
+ return $this->repository->findOneBy($criteria);
+ }
+
+ public function getClassName(): string
+ {
+ return PersonDocument::class;
+ }
+}
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue
index aa99a223f..be482cf7e 100644
--- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue
+++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/ConvertButton.vue
@@ -44,3 +44,9 @@ async function download_and_open(event: Event): Promise {
}
+
+
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue
index b22035bee..0248ef30b 100644
--- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue
+++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/DownloadButton.vue
@@ -1,52 +1,96 @@
-
-
- Télécharger
-
+
+
+ Télécharger
+
+
+
+ Ouvrir
+
+
+
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue
index d68f60f86..ea244192e 100644
--- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue
+++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/WopiEditButton.vue
@@ -40,5 +40,8 @@ async function beforeLeave(event: Event): Promise {
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts
index 5b61a108d..c0b0fa940 100644
--- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts
+++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/StoredObjectButton/helpers.ts
@@ -149,16 +149,21 @@ async function download_and_decrypt_doc(urlGenerator: string, keyData: JsonWebKe
}
if (iv.length === 0) {
+ console.log('returning document immediatly');
return rawResponse.blob();
}
+ console.log('start decrypting doc');
+
const rawBuffer = await rawResponse.arrayBuffer();
try {
const key = await window.crypto.subtle
.importKey('jwk', keyData, { name: algo }, false, ['decrypt']);
+ console.log('key created');
const decrypted = await window.crypto.subtle
.decrypt({ name: algo, iv: iv }, key, rawBuffer);
+ console.log('doc decrypted');
return Promise.resolve(new Blob([decrypted]));
} catch (e) {
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig
index c9bb608cf..d6f23d09d 100644
--- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig
+++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/delete.html.twig
@@ -31,8 +31,8 @@
'title' : 'Delete document ?'|trans,
'display_content' : block('docdescription'),
'confirm_question' : 'Are you sure you want to remove this document ?'|trans,
- 'cancel_route' : 'accompanying_course_document_index',
- 'cancel_parameters' : {'course' : accompanyingCourse.id, 'id': document.id},
+ 'cancel_route' : 'chill_docstore_generic-doc_by-period_index',
+ 'cancel_parameters' : {'id' : accompanyingCourse.id},
'form' : delete_form
} ) }}
{% endblock %}
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/edit.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/edit.html.twig
index 0ca5661fc..326814502 100644
--- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/edit.html.twig
+++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/edit.html.twig
@@ -21,7 +21,7 @@
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig
deleted file mode 100644
index 7a013260c..000000000
--- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/index.html.twig
+++ /dev/null
@@ -1,52 +0,0 @@
-{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
-
-{% set activeRouteKey = '' %}
-
-{% block title %}
- {{ 'Documents' }}
-{% endblock %}
-
-{% block js %}
- {{ parent() }}
- {{ 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_docgen_picktemplate') }}
- {{ encore_entry_link_tags('mod_entity_workflow_pick') }}
- {{ encore_entry_link_tags('mod_document_action_buttons_group') }}
-{% endblock %}
-
-{% block content %}
-
-
{{ 'Documents' }}
-
- {% if documents|length == 0 %}
-
{{ 'No documents'|trans }}
- {% else %}
-
- {% for document in documents %}
- {% include '@ChillDocStore/List/list_item.html.twig' %}
- {% endfor %}
-
- {% endif %}
-
- {{ chill_pagination(pagination) }}
-
-
-
- {% if is_granted('CHILL_ACCOMPANYING_COURSE_DOCUMENT_CREATE', accompanyingCourse) %}
-
- {% endif %}
-
-
-{% endblock %}
diff --git a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig
index 01be1a5d7..3fb692c78 100644
--- a/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig
+++ b/src/Bundle/ChillDocStoreBundle/Resources/views/AccompanyingCourseDocument/new.html.twig
@@ -25,7 +25,7 @@