Merge remote-tracking branch 'origin/master' into testing

This commit is contained in:
Julien Fastré 2023-06-18 22:02:14 +02:00
commit f7be53f790
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
114 changed files with 3389 additions and 549 deletions

6
.changes/header.tpl.md Normal file
View File

@ -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).

View File

677
.changes/v2.0.0.md Normal file
View File

@ -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
<!--
Coming soon...
DO NOT ADD unreleased items here. Add them under "Unreleased" title
### Test release yyyy-mm-dd
-->
## Stable releases
No stable releases for v2+

17
.changes/v2.1.0.md Normal file
View File

@ -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

12
.changes/v2.2.0.md Normal file
View File

@ -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

34
.changie.yaml Normal file
View File

@ -0,0 +1,34 @@
changesDir: .changes
unreleasedDir: unreleased
headerPath: header.tpl.md
changelogPath: CHANGELOG.md
versionExt: md
versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}'
kindFormat: '### {{.Kind}}'
changeFormat: >-
* {{ if not (eq .Custom.Issue "") }}([#{{ .Custom.Issue }}](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/{{ .Custom.Issue }})) {{- end }}{{.Body}}
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_

View File

@ -1,16 +1,50 @@
# 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.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
<!-- write down unreleased development here -->
* [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 +52,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.

View File

@ -650,8 +650,8 @@ final class ActivityController extends AbstractController
throw $this->createNotFoundException('Accompanying Period not found');
}
// TODO Add permission
// $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
// TODO Add permission
// $this->denyAccessUnlessGranted('CHILL_PERSON_SEE', $person);
} else {
throw $this->createNotFoundException('Person or Accompanying Period not found');
}

View File

@ -26,12 +26,12 @@ final class PersonMenuBuilder implements LocalMenuBuilderInterface
/**
* @var AuthorizationCheckerInterface
*/
protected $authorizationChecker;
private $authorizationChecker;
/**
* @var TranslatorInterface
*/
protected $translator;
private $translator;
public function __construct(
AuthorizationCheckerInterface $authorizationChecker,

View File

@ -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

View File

@ -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);
}
)
);
}

View File

@ -21,7 +21,7 @@ class CalculatorResult
public $label;
public $result;
public float $result;
public $type;
}

View File

@ -16,8 +16,14 @@
<td class="el-type">
{% if f.isResource %}
{{ f.resource.name|localize_translatable_string }}
{% if f.resource.getKind is same as 'other' %}
: {{ f.getComment }}
{% endif %}
{% else %}
{{ f.charge.name|localize_translatable_string }}
{% if f.charge.getKind is same as 'other' %}
: {{ f.getComment }}
{% endif %}
{% endif %}
</td>
<td>{{ f.amount|format_currency('EUR') }}</td>

View File

@ -13,6 +13,7 @@ namespace Chill\DocGeneratorBundle\Serializer\Normalizer;
use ArrayObject;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ReadableCollection;
use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
@ -51,7 +52,9 @@ class CollectionDocGenNormalizer implements ContextAwareNormalizerInterface, Nor
return false;
}
return $data instanceof Collection
|| (null === $data && Collection::class === ($context['docgen:expects'] ?? null));
return $data instanceof ReadableCollection
|| (null === $data && Collection::class === ($context['docgen:expects'] ?? null))
|| (null === $data && ReadableCollection::class === ($context['docgen:expects'] ?? null))
;
}
}

View File

@ -13,6 +13,7 @@ namespace Chill\DocGeneratorBundle\Serializer\Normalizer;
use Chill\DocGeneratorBundle\Serializer\Helper\NormalizeNullValueHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\ReadableCollection;
use ReflectionClass;
use RuntimeException;
use Symfony\Component\PropertyAccess\PropertyAccess;
@ -271,6 +272,14 @@ class DocGenObjectNormalizer implements NormalizerAwareInterface, NormalizerInte
if ($isTranslatable) {
$data[$key] = $this->translatableStringHelper
->localize($value);
} elseif ($value instanceof ReadableCollection) {
// when normalizing collection, we should not preserve keys (to ensure that the result is a list)
// this is why we make call to the normalizer again to use the CollectionDocGenNormalizer
$data[$key] =
$this->normalizer->normalize($value, $format, array_merge(
$objectContext,
$attribute->getNormalizationContextForGroups($expectedGroups)
));
} elseif (is_iterable($value)) {
$arr = [];

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\DocGeneratorBundle\tests\Serializer\Normalizer;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Criteria;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
/**
* @internal
* @coversNothing
*/
class CollectionDocGenNormalizerTest extends KernelTestCase
{
private NormalizerInterface $normalizer;
protected function setUp(): void
{
self::bootKernel();
$this->normalizer = self::$container->get(NormalizerInterface::class);
}
public function testNormalizeFilteredArray(): void
{
$coll = new ArrayCollection([
(object) ['v' => 'foo'],
(object) ['v' => 'bar'],
(object) ['v' => 'baz'],
]);
//filter to get non continuous indexes
$criteria = new Criteria();
$criteria->where(Criteria::expr()->neq('v', 'bar'));
$filtered = $coll->matching($criteria);
$normalized = $this->normalizer->normalize($filtered, 'docgen', []);
self::assertIsArray($normalized);
self::assertArrayHasKey(0, $normalized);
self::assertArrayHasKey(1, $normalized);
}
}

View File

@ -44,3 +44,9 @@ async function download_and_open(event: Event): Promise<void> {
}
</script>
<style scoped lang="sass">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style>

View File

@ -1,51 +1,96 @@
<template>
<a :class="props.classes" @click="download_and_open($event)">
<i class="fa fa-download"></i>
Télécharger
</a>
<a v-if="!state.is_ready" :class="props.classes" @click="download_and_open($event)">
<i class="fa fa-download"></i>
Télécharger
</a>
<a v-else :class="props.classes" target="_blank" :type="props.storedObject.type" :download="buildDocumentName()" :href="state.href_url" ref="open_button">
<i class="fa fa-external-link"></i>
Ouvrir
</a>
</template>
<script lang="ts" setup>
import {reactive} from "vue";
import {reactive, ref, nextTick, onMounted} from "vue";
import {build_download_info_link, download_and_decrypt_doc} from "./helpers";
import mime from "mime";
import {StoredObject} from "../../types";
interface DownloadButtonConfig {
storedObject: StoredObject,
classes: {[k: string]: boolean},
filename?: string,
storedObject: StoredObject,
classes: { [k: string]: boolean },
filename?: string,
}
interface DownloadButtonState {
content: null|string
is_ready: boolean,
is_running: boolean,
href_url: string,
}
const props = defineProps<DownloadButtonConfig>();
const state: DownloadButtonState = reactive({content: null});
const state: DownloadButtonState = reactive({is_ready: false, is_running: false, href_url: "#"});
const open_button = ref<HTMLAnchorElement | null>(null);
function buildDocumentName(): string {
const document_name = props.filename || 'document';
const ext = mime.getExtension(props.storedObject.type);
if (null !== ext) {
return document_name + '.' + ext;
}
return document_name;
}
async function download_and_open(event: Event): Promise<void> {
const button = event.target as HTMLAnchorElement;
const button = event.target as HTMLAnchorElement;
if (null === state.content) {
event.preventDefault();
if (state.is_running) {
console.log('state is running, aborting');
return;
}
state.is_running = true;
if (state.is_ready) {
console.log('state is ready. This should not happens');
return;
}
const urlInfo = build_download_info_link(props.storedObject.filename);
let raw;
const raw = await download_and_decrypt_doc(urlInfo, props.storedObject.keyInfos, new Uint8Array(props.storedObject.iv));
state.content = window.URL.createObjectURL(raw);
button.href = window.URL.createObjectURL(raw);
button.type = props.storedObject.type;
button.download = props.filename || 'document';
const ext = mime.getExtension(props.storedObject.type);
if (null !== ext) {
button.download = button.download + '.' + ext;
try {
raw = await download_and_decrypt_doc(urlInfo, props.storedObject.keyInfos, new Uint8Array(props.storedObject.iv));
} catch (e) {
console.error("error while downloading and decrypting document");
console.error(e);
throw e;
}
}
button.click();
console.log('document downloading (and decrypting) successfully');
console.log('creating the url')
state.href_url = window.URL.createObjectURL(raw);
console.log('url created', state.href_url);
state.is_running = false;
state.is_ready = true;
console.log('new button marked as ready');
console.log('will click on button');
console.log('openbutton is now', open_button.value);
await nextTick();
console.log('next tick actions');
console.log('openbutton after next tick', open_button.value);
open_button.value?.click();
console.log('open button should have been clicked');
}
</script>
<style scoped lang="sass">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style>

View File

@ -40,5 +40,8 @@ async function beforeLeave(event: Event): Promise<true> {
</script>
<style scoped lang="sass">
i.fa::before {
color: var(--bs-dropdown-link-hover-color);
}
</style>

View File

@ -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) {

View File

@ -1,5 +1,5 @@
{%- import "@ChillDocStore/Macro/macro.html.twig" as m -%}
<div
<div class="d-inline-flex"
data-download-buttons
data-stored-object="{{ document_json|json_encode|escape('html_attr') }}"
data-can-edit="{{ can_edit ? '1' : '0' }}"

View File

@ -18,6 +18,7 @@ No document found: Aucun document trouvé
The document is successfully registered: Le document est enregistré
The document is successfully updated: Le document est mis à jour
Any description: Aucune description
See the document: Voir le document
document:
Any title: Aucun titre

View File

@ -2,11 +2,15 @@
<p>{% transchoice total with { '%pattern%' : pattern } %}%total% events match the search %pattern%{% endtranschoice %}</p>
<style>
table.events td:last-child {
width: 15em;
}
</style>
{% if events|length > 0 %}
<p>{{ 'Results %start%-%end% of %total%'|trans({ '%start%' : start, '%end%': start + events|length, '%total%' : total } ) }}</p>
<table class="table events">
<table class="table table-bordered border-dark align-middle events">
<thead>
<tr>
<th class="chill-red">{{ 'Name'|trans }}</th>
@ -25,7 +29,7 @@
<ul class="record_actions">
<li>
{# {% if is_granted('CHILL_EVENT_SEE_DETAILS', event) %} #}
<a href="{{ path('chill_event__event_show', { 'event_id' : event.id } ) }}" class="btn btn-dark">
<a href="{{ path('chill_event__event_show', { 'event_id' : event.id } ) }}" class="btn btn-view">
{{ 'See'|trans }}
</a>
{# {% endif %} #}

View File

@ -24,7 +24,7 @@
{% block content %}
<h2>{{ 'Events participation' |trans }}</h2>
<table class="table table-striped table-bordered mt-3 events">
<table class="table table-striped table-bordered border-dark align-middle mt-3 events">
<thead>
<tr>
<th class="chill-green">{{ 'Date'|trans }}</th>

View File

@ -18,10 +18,10 @@
</a>
</li>
<li>
{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-chill-green' } }) }}
{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-submit' } }) }}
</li>
</ul>
{{ form_end(form) }}
</div>
{% endblock %}

View File

@ -1,14 +1,14 @@
{% extends '@ChillEvent/layout.html.twig' %}
{% block title 'Event : %label%'|trans({ '%label%' : event.name } ) %}
{% import 'ChillPersonBundle:Person:macro.html.twig' as person_macro %}
{% import 'ChillPersonBundle:Person:macro.html.twig' as person_macro %}
{% block event_content -%}
<div class="col-10">
<h1>{{ 'Details of an event'|trans }}</h1>
<table class="table record_properties">
<table class="table table-bordered border-dark align-middle">
<tbody>
<tr>
<th>{{ 'Name'|trans }}</th>
@ -33,7 +33,7 @@
</tbody>
</table>
<ul class="record_actions">
{% set returnPath = app.request.get('return_path') %}
@ -64,13 +64,13 @@
</li>
</ul>
<h2>{{ 'Participations'|trans }}</h2>
{% set count = event.participations|length %}
<p>{% transchoice count %}%count% participations to this event{% endtranschoice %}</p>
{% if count > 0 %}
<table class="table">
<table class="table table-bordered border-dark align-middle">
<thead>
<tr>
<th>{{ 'Person'|trans }}</th>
@ -108,10 +108,10 @@
{% endfor %}
</tbody>
</table>
{% endif %}
<ul class="record_actions">
{% if count > 0 %}
<li><a href="{{ path('chill_event_participation_edit_multiple', { 'event_id' : event.id } ) }}" class="btn btn-edit">{{ 'Edit all the participations'|trans }}</a></li>

View File

@ -12,13 +12,14 @@ declare(strict_types=1);
namespace Chill\EventBundle\Search;
use Chill\EventBundle\Entity\Event;
use Chill\EventBundle\Repository\EventRepository;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Search\AbstractSearch;
use Chill\MainBundle\Search\SearchInterface;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Templating\EngineInterface as TemplatingEngine;
use function count;
@ -40,15 +41,17 @@ class EventSearch extends AbstractSearch
{
public const NAME = 'event_regular';
private $security;
/**
* @var EntityRepository
* @var EventRepository
*/
private $er;
/**
* @var AuthorizationHelper
*/
private $helper;
private $authorizationHelper;
/**
* @var PaginatorFactory
@ -60,21 +63,16 @@ class EventSearch extends AbstractSearch
*/
private $templating;
/**
* @var \Chill\MainBundle\Entity\User
*/
private $user;
public function __construct(
TokenStorageInterface $tokenStorage,
EntityRepository $eventRepository,
Security $security,
EventRepository $eventRepository,
AuthorizationHelper $authorizationHelper,
TemplatingEngine $templating,
PaginatorFactory $paginatorFactory
) {
$this->user = $tokenStorage->getToken()->getUser();
$this->security = $security;
$this->er = $eventRepository;
$this->helper = $authorizationHelper;
$this->authorizationHelper = $authorizationHelper;
$this->templating = $templating;
$this->paginationFactory = $paginatorFactory;
}
@ -101,7 +99,7 @@ class EventSearch extends AbstractSearch
if ('html' === $format) {
return $this->templating->render(
'ChillEventBundle:Event:list.html.twig',
'@ChillEvent/Event/list.html.twig',
[
'events' => $this->search($terms, $start, $limit, $options),
'pattern' => $this->recomposePattern($terms, $this->getAvailableTerms(), $terms['_domain']),
@ -140,8 +138,10 @@ class EventSearch extends AbstractSearch
protected function composeQuery(QueryBuilder &$qb, $terms)
{
// add security clauses
$reachableCenters = $this->helper
->getReachableCenters($this->user, 'CHILL_EVENT_SEE');
$reachableCenters = $this->authorizationHelper->getReachableCenters(
$this->security->getUser(),
'CHILL_EVENT_SEE'
);
if (count($reachableCenters) === 0) {
// add a clause to block all events
@ -152,8 +152,9 @@ class EventSearch extends AbstractSearch
$orWhere = $qb->expr()->orX();
foreach ($reachableCenters as $center) {
$circles = $this->helper->getReachableScopes(
$this->user,
$n = $n+1;
$circles = $this->authorizationHelper->getReachableScopes(
$this->security->getUser(),
'CHILL_EVENT_SEE',
$center
);

View File

@ -17,7 +17,7 @@ services:
factory: ['@doctrine.orm.entity_manager', getRepository]
arguments:
- 'Chill\EventBundle\Entity\Role'
chill_event.repository.status:
class: Doctrine\ORM\EntityRepository
factory: ['@doctrine.orm.entity_manager', getRepository]

View File

@ -1,12 +1,11 @@
services:
chill_event.search_events:
class: Chill\EventBundle\Search\EventSearch
Chill\EventBundle\Search\EventSearch:
arguments:
- "@security.token_storage"
- "@chill_event.repository.event"
- "@chill.main.security.authorization.helper"
- "@templating"
- "@chill_main.paginator_factory"
$security: '@Symfony\Component\Security\Core\Security'
$eventRepository: "@chill_event.repository.event"
$authorizationHelper: "@chill.main.security.authorization.helper"
$templating: "@templating"
$paginatorFactory: "@chill_main.paginator_factory"
tags:
- { name: chill.search, alias: 'event_regular' }

View File

@ -30,7 +30,7 @@ The event was created: L'événement a été créé
#crud participation
Edit all the participations: Modifier toutes les participations
Edit the participation: Modifier la participation
Edit the participation: Modifier la participation à l'événement
Participation Edit: Modifier une participation
Add a participation: Ajouter un participant
Participation creation: Ajouter une participation

View File

@ -36,5 +36,4 @@ class SynchronizeEntityInfoViewsCommand extends Command
return 0;
}
}

View File

@ -16,14 +16,18 @@ use Chill\MainBundle\Entity\RoleScope;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Form\PermissionsGroupType;
use Chill\MainBundle\Form\Type\ComposedRoleScopeType;
use Chill\MainBundle\Repository\PermissionsGroupRepository;
use Chill\MainBundle\Repository\RoleScopeRepository;
use Chill\MainBundle\Security\RoleProvider;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\EntityManagerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@ -32,62 +36,28 @@ use function array_key_exists;
/**
* Class PermissionsGroupController.
*/
class PermissionsGroupController extends AbstractController
final class PermissionsGroupController extends AbstractController
{
/**
* @var RoleHierarchy
*/
private $roleHierarchy;
/**
* @var RoleProvider
*/
private $roleProvider;
/**
* @var TranslatableStringHelper
*/
private $translatableStringHelper;
/**
* @var TranslatorInterface
*/
private $translator;
/**
* @var ValidatorInterface
*/
private $validator;
/**
* PermissionsGroupController constructor.
*/
public function __construct(
TranslatableStringHelper $translatableStringHelper,
RoleProvider $roleProvider,
RoleHierarchy $roleHierarchy,
TranslatorInterface $translator,
ValidatorInterface $validator
private readonly TranslatableStringHelper $translatableStringHelper,
private readonly RoleProvider $roleProvider,
private readonly RoleHierarchyInterface $roleHierarchy,
private readonly TranslatorInterface $translator,
private readonly ValidatorInterface $validator,
private readonly EntityManagerInterface $em,
private readonly PermissionsGroupRepository $permissionsGroupRepository,
private readonly RoleScopeRepository $roleScopeRepository,
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->roleProvider = $roleProvider;
$this->roleHierarchy = $roleHierarchy;
$this->translator = $translator;
$this->validator = $validator;
}
/**
* @param int $id
*
* @throws type
*
* @return Respon
*/
public function addLinkRoleScopeAction(Request $request, $id)
public function addLinkRoleScopeAction(Request $request, int $id): Response
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($id);
$permissionsGroup = $this->permissionsGroupRepository->find($id);
if (!$permissionsGroup) {
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
@ -106,7 +76,7 @@ class PermissionsGroupController extends AbstractController
$violations = $this->validator->validate($permissionsGroup);
if ($violations->count() === 0) {
$em->flush();
$this->em->flush();
$this->addFlash(
'notice',
@ -166,16 +136,15 @@ class PermissionsGroupController extends AbstractController
/**
* Creates a new PermissionsGroup entity.
*/
public function createAction(Request $request)
public function createAction(Request $request): Response
{
$permissionsGroup = new PermissionsGroup();
$form = $this->createCreateForm($permissionsGroup);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($permissionsGroup);
$em->flush();
$this->em->persist($permissionsGroup);
$this->em->flush();
return $this->redirect($this->generateUrl(
'admin_permissionsgroup_edit',
@ -191,18 +160,11 @@ class PermissionsGroupController extends AbstractController
/**
* remove an association between permissionsGroup and roleScope.
*
* @param int $pgid permissionsGroup id
* @param int $rsid roleScope id
*
* @return redirection to edit form
*/
public function deleteLinkRoleScopeAction($pgid, $rsid)
public function deleteLinkRoleScopeAction(int $pgid, int $rsid): Response
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($pgid);
$roleScope = $em->getRepository(\Chill\MainBundle\Entity\RoleScope::class)->find($rsid);
$permissionsGroup = $this->permissionsGroupRepository->find($pgid);
$roleScope = $this->roleScopeRepository->find($rsid);
if (!$permissionsGroup) {
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
@ -214,7 +176,7 @@ class PermissionsGroupController extends AbstractController
try {
$permissionsGroup->removeRoleScope($roleScope);
} catch (RuntimeException $ex) {
} catch (RuntimeException) {
$this->addFlash(
'notice',
$this->translator->trans("The role '%role%' and circle "
@ -231,7 +193,7 @@ class PermissionsGroupController extends AbstractController
));
}
$em->flush();
$this->em->flush();
if ($roleScope->getScope() !== null) {
$this->addFlash(
@ -260,14 +222,10 @@ class PermissionsGroupController extends AbstractController
/**
* Displays a form to edit an existing PermissionsGroup entity.
*
* @param mixed $id
*/
public function editAction($id)
public function editAction(int $id): Response
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($id);
$permissionsGroup = $this->permissionsGroupRepository->find($id);
if (!$permissionsGroup) {
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
@ -311,11 +269,9 @@ class PermissionsGroupController extends AbstractController
/**
* Lists all PermissionsGroup entities.
*/
public function indexAction()
public function indexAction(): Response
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->findAll();
$entities = $this->permissionsGroupRepository->findAllOrderedAlphabetically();
return $this->render('@ChillMain/PermissionsGroup/index.html.twig', [
'entities' => $entities,
@ -325,7 +281,7 @@ class PermissionsGroupController extends AbstractController
/**
* Displays a form to create a new PermissionsGroup entity.
*/
public function newAction()
public function newAction(): Response
{
$permissionsGroup = new PermissionsGroup();
$form = $this->createCreateForm($permissionsGroup);
@ -338,14 +294,10 @@ class PermissionsGroupController extends AbstractController
/**
* Finds and displays a PermissionsGroup entity.
*
* @param mixed $id
*/
public function showAction($id)
public function showAction(int $id): Response
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)->find($id);
$permissionsGroup = $this->permissionsGroupRepository->find($id);
if (!$permissionsGroup) {
throw $this->createNotFoundException('Unable to find PermissionsGroup entity.');
@ -393,15 +345,10 @@ class PermissionsGroupController extends AbstractController
/**
* Edits an existing PermissionsGroup entity.
*
* @param mixed $id
*/
public function updateAction(Request $request, $id)
public function updateAction(Request $request, int $id): Response
{
$em = $this->getDoctrine()->getManager();
$permissionsGroup = $em
->getRepository(\Chill\MainBundle\Entity\PermissionsGroup::class)
$permissionsGroup = $this->permissionsGroupRepository
->find($id);
if (!$permissionsGroup) {
@ -413,7 +360,7 @@ class PermissionsGroupController extends AbstractController
$editForm->handleRequest($request);
if ($editForm->isValid()) {
$em->flush();
$this->em->flush();
return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', ['id' => $id]));
}
@ -452,18 +399,11 @@ class PermissionsGroupController extends AbstractController
/**
* get a role scope by his parameters. The role scope is persisted if it
* doesn't exists in database.
*
* @param Scope $scope
* @param string $role
*
* @return RoleScope
* doesn't exist in database.
*/
protected function getPersistentRoleScopeBy($role, ?Scope $scope = null)
protected function getPersistentRoleScopeBy(string $role, ?Scope $scope = null): RoleScope
{
$em = $this->getDoctrine()->getManager();
$roleScope = $em->getRepository(\Chill\MainBundle\Entity\RoleScope::class)
$roleScope = $this->roleScopeRepository
->findOneBy(['role' => $role, 'scope' => $scope]);
if (null === $roleScope) {
@ -471,7 +411,7 @@ class PermissionsGroupController extends AbstractController
->setRole($role)
->setScope($scope);
$em->persist($roleScope);
$this->em->persist($roleScope);
}
return $roleScope;
@ -479,10 +419,8 @@ class PermissionsGroupController extends AbstractController
/**
* creates a form to add a role scope to permissionsgroup.
*
* @return \Symfony\Component\Form\Form The form
*/
private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup)
private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup): FormInterface
{
return $this->createFormBuilder()
->setAction($this->generateUrl(
@ -499,10 +437,8 @@ class PermissionsGroupController extends AbstractController
* Creates a form to create a PermissionsGroup entity.
*
* @param PermissionsGroup $permissionsGroup The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(PermissionsGroup $permissionsGroup)
private function createCreateForm(PermissionsGroup $permissionsGroup): FormInterface
{
$form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [
'action' => $this->generateUrl('admin_permissionsgroup_create'),
@ -518,13 +454,11 @@ class PermissionsGroupController extends AbstractController
* Creates a form to delete a link to roleScope.
*
* @param mixed $permissionsGroup The entity id
*
* @return \Symfony\Component\Form\Form The form
*/
private function createDeleteRoleScopeForm(
PermissionsGroup $permissionsGroup,
RoleScope $roleScope
) {
): FormInterface {
return $this->createFormBuilder()
->setAction($this->generateUrl(
'admin_permissionsgroup_delete_role_scope',
@ -537,12 +471,8 @@ class PermissionsGroupController extends AbstractController
/**
* Creates a form to edit a PermissionsGroup entity.
*
* @param PermissionsGroup $permissionsGroup The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(PermissionsGroup $permissionsGroup)
private function createEditForm(PermissionsGroup $permissionsGroup): FormInterface
{
$form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, [
'action' => $this->generateUrl('admin_permissionsgroup_update', ['id' => $permissionsGroup->getId()]),
@ -556,10 +486,8 @@ class PermissionsGroupController extends AbstractController
/**
* expand roleScopes to be easily shown in template.
*
* @return array
*/
private function getExpandedRoles(array $roleScopes)
private function getExpandedRoles(array $roleScopes): array
{
$expandedRoles = [];
@ -567,10 +495,10 @@ class PermissionsGroupController extends AbstractController
if (!array_key_exists($roleScope->getRole(), $expandedRoles)) {
$expandedRoles[$roleScope->getRole()] =
array_map(
static fn (Role $role) => $role->getRole(),
static fn ($role) => $role,
$this->roleHierarchy
->getReachableRoles(
[new Role($roleScope->getRole())]
->getReachableRoleNames(
[$roleScope->getRole()]
)
);
}

View File

@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use League\Csv\Writer;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class UserExportController
{
public function __construct(
private UserRepositoryInterface $userRepository,
private Security $security,
private TranslatorInterface $translator,
) {
}
/**
* @throws \League\Csv\CannotInsertRecord
* @throws \League\Csv\Exception
* @throws \League\Csv\UnavailableStream
*
* @Route("/{_locale}/admin/main/users/export/list.{_format}", requirements={"_format": "csv"}, name="chill_main_users_export_list")
*/
public function userList(Request $request, string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$users = $this->userRepository->findAllAsArray($request->getLocale());
$csv = Writer::createFromPath('php://temp', 'r+');
$csv->insertOne(
array_map(
fn (string $e) => $this->translator->trans('admin.users.export.' . $e),
[
'id',
'username',
'email',
'enabled',
'civility_id',
'civility_abbreviation',
'civility_name',
'label',
'mainCenter_id' ,
'mainCenter_name',
'mainScope_id',
'mainScope_name',
'userJob_id',
'userJob_name',
'currentLocation_id',
'currentLocation_name',
'mainLocation_id',
'mainLocation_name',
'absenceStart'
]
)
);
$csv->addFormatter(fn (array $row) => null !== ($row['absenceStart'] ?? null) ? array_merge($row, ['absenceStart' => $row['absenceStart']->format('Y-m-d')]) : $row);
$csv->insertAll($users);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; users.csv',
]
);
}
/**
* @return StreamedResponse
* @throws \League\Csv\CannotInsertRecord
* @throws \League\Csv\Exception
* @throws \League\Csv\UnavailableStream
*
* @Route("/{_locale}/admin/main/users/export/permissions.{_format}", requirements={"_format": "csv"}, name="chill_main_users_export_permissions")
*/
public function userPermissionsList(string $_format = 'csv'): StreamedResponse
{
if (!$this->security->isGranted('ROLE_ADMIN')) {
throw new AccessDeniedHttpException('Only ROLE_ADMIN can export this list');
}
$userPermissions = $this->userRepository->findAllUserACLAsArray();
$csv = Writer::createFromPath('php://temp', 'r+');
$csv->insertOne(
array_map(
fn (string $e) => $this->translator->trans('admin.users.export.' . $e),
[
'id',
'username',
'email',
'label',
'enabled',
'center_id',
'center_name',
'permissionsGroup_id',
'permissionsGroup_name',
]
)
);
$csv->insertAll($userPermissions);
return new StreamedResponse(
function () use ($csv) {
foreach ($csv->chunk(1024) as $chunk) {
echo $chunk;
flush();
}
},
Response::HTTP_OK,
[
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; users.csv',
]
);
}
}

View File

@ -359,9 +359,9 @@ class WorkflowController extends AbstractController
}
// TODO symfony 5: add those "future" on context ($workflow->apply($entityWorkflow, $transition, $context)
$entityWorkflow->futureCcUsers = $transitionForm['future_cc_users']->getData();
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData();
$entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData();
$entityWorkflow->futureCcUsers = $transitionForm['future_cc_users']->getData() ?? [];
$entityWorkflow->futureDestUsers = $transitionForm['future_dest_users']->getData() ?? [];
$entityWorkflow->futureDestEmails = $transitionForm['future_dest_emails']->getData() ?? [];
$workflow->apply($entityWorkflow, $transition);

View File

@ -90,16 +90,14 @@ class OverlapsI extends FunctionNode
if ($part instanceof PathExpression) {
return sprintf(
"CASE WHEN %s IS NOT NULL THEN %s ELSE '%s'::date END",
$part->dispatch($sqlWalker),
"COALESCE(%s, '%s'::date)",
$part->dispatch($sqlWalker),
$p
);
}
return sprintf(
"CASE WHEN %s::date IS NOT NULL THEN %s::date ELSE '%s'::date END",
$part->dispatch($sqlWalker),
"COALESCE(%s::date, '%s'::date)",
$part->dispatch($sqlWalker),
$p
);

View File

@ -48,12 +48,19 @@ class Center implements HasCenterInterface
*/
private string $name = '';
/**
* @var Collection<Regroupment>
* @ORM\ManyToMany(targetEntity=Regroupment::class, mappedBy="centers")
*/
private Collection $regroupments;
/**
* Center constructor.
*/
public function __construct()
{
$this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection();
$this->groupCenters = new ArrayCollection();
$this->regroupments = new ArrayCollection();
}
/**
@ -106,6 +113,14 @@ class Center implements HasCenterInterface
return $this->name;
}
/**
* @return Collection<Regroupment>
*/
public function getRegroupments(): Collection
{
return $this->regroupments;
}
/**
* @param $name
*

View File

@ -22,11 +22,12 @@ use Doctrine\ORM\Mapping as ORM;
class Regroupment
{
/**
* @var Center
* @ORM\ManyToMany(
* targetEntity=Center::class
* targetEntity=Center::class,
* inversedBy="regroupments"
* )
* @ORM\Id
* @var Collection<Center>
*/
private Collection $centers;
@ -52,6 +53,26 @@ class Regroupment
$this->centers = new ArrayCollection();
}
public function addCenter(Center $center): self
{
if (!$this->centers->contains($center)) {
$this->centers->add($center);
$center->getRegroupments()->add($this);
}
return $this;
}
public function removeCenter(Center $center): self
{
if ($this->centers->contains($center)) {
$this->centers->removeElement($center);
$center->getRegroupments()->removeElement($this);
}
return $this;
}
public function getCenters(): Collection
{
return $this->centers;

View File

@ -506,11 +506,11 @@ class User implements UserInterface
*
* @return User
*/
public function setUsername($name)
public function setUsername(?string $name)
{
$this->username = $name;
$this->username = (string) $name;
if (empty($this->getLabel())) {
if ("" === trim($this->getLabel())) {
$this->setLabel($name);
}

View File

@ -31,7 +31,7 @@ class RegroupmentType extends AbstractType
->add('centers', EntityType::class, [
'class' => Center::class,
'multiple' => true,
'attr' => ['class' => 'select2'],
'expanded' => true,
])
->add('isActive', CheckboxType::class, [
'label' => 'Actif ?',

View File

@ -43,6 +43,10 @@ class EntityToJsonTransformer implements DataTransformerInterface
public function reverseTransform($value)
{
if ("" === $value) {
return null;
}
$denormalized = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
if ($this->multiple) {
@ -56,10 +60,6 @@ class EntityToJsonTransformer implements DataTransformerInterface
);
}
if ('' === $value) {
return null;
}
return $this->denormalizeOne($denormalized);
}

View File

@ -34,9 +34,13 @@ class NotificationPresence
$this->notificationRepository = $notificationRepository;
}
public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array
/**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array{unread: int, sent: int, total: int}
*/
public function countNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): array
{
if (array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) {
if ([] === $more && array_key_exists($relatedEntityClass, $this->cache) && array_key_exists($relatedEntityId, $this->cache[$relatedEntityClass])) {
return $this->cache[$relatedEntityClass][$relatedEntityId];
}
@ -46,21 +50,25 @@ class NotificationPresence
$counter = $this->notificationRepository->countNotificationByRelatedEntityAndUserAssociated(
$relatedEntityClass,
$relatedEntityId,
$user
$user,
$more
);
$this->cache[$relatedEntityClass][$relatedEntityId] = $counter;
if ([] === $more) {
$this->cache[$relatedEntityClass][$relatedEntityId] = $counter;
}
return $counter;
}
return ['unread' => 0, 'read' => 0];
return ['unread' => 0, 'sent' => 0, 'total' => 0];
}
/**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array|Notification[]
*/
public function getNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId): array
public function getNotificationsForClassAndEntity(string $relatedEntityClass, int $relatedEntityId, array $more = []): array
{
$user = $this->security->getUser();
@ -68,7 +76,8 @@ class NotificationPresence
return $this->notificationRepository->findNotificationByRelatedEntityAndUserAssociated(
$relatedEntityClass,
$relatedEntityId,
$user
$user,
$more
);
}

View File

@ -34,24 +34,30 @@ class NotificationTwigExtensionRuntime implements RuntimeExtensionInterface
$this->urlGenerator = $urlGenerator;
}
public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
public function counterNotificationFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): string
{
return $environment->render(
'@ChillMain/Notification/extension_counter_notifications_for.html.twig',
[
'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId),
'counter' => $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId, $more),
]
);
}
public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $options = []): array
/**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
*/
public function countNotificationsFor(string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): array
{
return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId);
return $this->notificationPresence->countNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId, $more);
}
public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $options = []): string
/**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
*/
public function listNotificationsFor(Environment $environment, string $relatedEntityClass, int $relatedEntityId, array $more = [], array $options = []): string
{
$notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId);
$notifications = $this->notificationPresence->getNotificationsForClassAndEntity($relatedEntityClass, $relatedEntityId, $more);
if ([] === $notifications) {
return '';

View File

@ -29,6 +29,15 @@ final class NotificationRepository implements ObjectRepository
private EntityRepository $repository;
private const BASE_COUNTER_SQL = <<<'SQL'
SELECT
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = :userid and cmnau.notification_id = cmn.id))::int) AS unread,
SUM((cmn.sender_id = :userid)::int) AS sent,
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_user cmnau_all WHERE user_id = :userid and cmnau_all.notification_id = cmn.id))::int) + SUM((cmn.sender_id = :userid)::int) AS total
FROM chill_main_notification cmn
SQL;
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
@ -51,29 +60,45 @@ final class NotificationRepository implements ObjectRepository
->getSingleScalarResult();
}
public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
/**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array{unread: int, sent: int, total: int}
*/
public function countNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more = []): array
{
if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) {
$sql =
'SELECT
SUM((EXISTS (SELECT 1 AS c FROM chill_main_notification_addresses_unread cmnau WHERE user_id = :userid and cmnau.notification_id = cmn.id))::int) AS unread,
SUM((cmn.sender_id = :userid)::int) AS sent,
COUNT(cmn.*) AS total
FROM chill_main_notification cmn
WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL';
$sqlParams = ['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()];
$this->notificationByRelatedEntityAndUserAssociatedStatement =
$this->em->getConnection()->prepare($sql);
if ([] === $more) {
if (null === $this->notificationByRelatedEntityAndUserAssociatedStatement) {
$sql = self::BASE_COUNTER_SQL . ' WHERE relatedentityclass = :relatedEntityClass AND relatedentityid = :relatedEntityId AND sender_id IS NOT NULL';
$this->notificationByRelatedEntityAndUserAssociatedStatement =
$this->em->getConnection()->prepare($sql);
}
$results = $this->notificationByRelatedEntityAndUserAssociatedStatement
->executeQuery($sqlParams);
$result = $results->fetchAssociative();
$results->free();
} else {
$wheres = [];
foreach ([
['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId],
...$more
] as $k => ['relatedEntityClass' => $relClass, 'relatedEntityId' => $relId]) {
$wheres[] = "(relatedEntityClass = :relatedEntityClass_{$k} AND relatedEntityId = :relatedEntityId_{$k})";
$sqlParams["relatedEntityClass_{$k}"] = $relClass;
$sqlParams["relatedEntityId_{$k}"] = $relId;
}
$sql = self::BASE_COUNTER_SQL . ' WHERE sender_id IS NOT NULL AND (' . implode(' OR ', $wheres) . ')';
$result = $this->em->getConnection()->fetchAssociative($sql, $sqlParams);
}
$results = $this->notificationByRelatedEntityAndUserAssociatedStatement
->executeQuery(['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId, 'userid' => $user->getId()]);
$result = $results->fetchAssociative();
$results->free();
return $result;
return array_map(fn (?int $number) => $number ?? 0, $result);
}
public function countUnreadByUser(User $user): int
@ -167,8 +192,8 @@ final class NotificationRepository implements ObjectRepository
}
/**
* @param mixed|null $limit
* @param mixed|null $offset
* @param int|null $limit
* @param int|null $offset
*
* @return Notification[]
*/
@ -178,13 +203,15 @@ final class NotificationRepository implements ObjectRepository
}
/**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
* @return array|Notification[]
*/
public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): array
public function findNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more): array
{
return
$this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user)
$this->buildQueryNotificationByRelatedEntityAndUserAssociated($relatedEntityClass, $relatedEntityId, $user, $more)
->select('n')
->addOrderBy('n.date', 'DESC')
->getQuery()
->getResult();
}
@ -222,13 +249,36 @@ final class NotificationRepository implements ObjectRepository
return Notification::class;
}
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user): QueryBuilder
/**
* @param list<array{relatedEntityClass: class-string, relatedEntityId: int}> $more
*/
private function buildQueryNotificationByRelatedEntityAndUserAssociated(string $relatedEntityClass, int $relatedEntityId, User $user, array $more = []): QueryBuilder
{
$qb = $this->repository->createQueryBuilder('n');
// add condition for related entity (in main arguments, and in more)
$or = $qb->expr()->orX($qb->expr()->andX(
$qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'),
$qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')
));
$qb
->where($qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'))
->andWhere($qb->expr()->eq('n.relatedEntityId', ':relatedEntityId'))
->setParameter('relatedEntityClass', $relatedEntityClass)
->setParameter('relatedEntityId', $relatedEntityId);
foreach ($more as $k => ['relatedEntityClass' => $relatedClass, 'relatedEntityId' => $relatedId]) {
$or->add(
$qb->expr()->andX(
$qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass_'.$k),
$qb->expr()->eq('n.relatedEntityId', ':relatedEntityId_'.$k)
)
);
$qb
->setParameter('relatedEntityClass_'.$k, $relatedClass)
->setParameter('relatedEntityId_'.$k, $relatedId);
}
$qb
->andWhere($or)
->andWhere($qb->expr()->isNotNull('n.sender'))
->andWhere(
$qb->expr()->orX(
@ -236,8 +286,6 @@ final class NotificationRepository implements ObjectRepository
$qb->expr()->eq('n.sender', ':user')
)
)
->setParameter('relatedEntityClass', $relatedEntityClass)
->setParameter('relatedEntityId', $relatedEntityId)
->setParameter('user', $user);
return $qb;

View File

@ -38,6 +38,19 @@ final class PermissionsGroupRepository implements ObjectRepository
return $this->repository->findAll();
}
/**
* @return list<PermissionsGroup>
*/
public function findAllOrderedAlphabetically(): array
{
$qb = $this->repository->createQueryBuilder('pg');
return $qb->select(['pg', 'pg.name AS HIDDEN sort_name'])
->orderBy('sort_name')
->getQuery()
->getResult();
}
/**
* @param mixed|null $limit
* @param mixed|null $offset

View File

@ -14,6 +14,8 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Regroupment;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\Persistence\ObjectRepository;
final class RegroupmentRepository implements ObjectRepository
@ -59,6 +61,30 @@ final class RegroupmentRepository implements ObjectRepository
return $this->repository->findOneBy($criteria, $orderBy);
}
/**
* @throws NonUniqueResultException
* @throws NoResultException
*/
public function findOneByName(string $name): ?Regroupment
{
return $this->repository->createQueryBuilder('r')
->where('LOWER(r.name) = LOWER(:searched)')
->setParameter('searched', $name)
->getQuery()
->getSingleResult();
}
/**
* @return array<Regroupment>
*/
public function findRegroupmentAssociatedToNoCenter(): array
{
return $this->repository->createQueryBuilder('r')
->where('SIZE(r.centers) = 0')
->getQuery()
->getResult();
}
public function getClassName()
{
return Regroupment::class;

View File

@ -13,6 +13,7 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\GroupCenter;
use Chill\MainBundle\Entity\User;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
@ -76,6 +77,81 @@ final class UserRepository implements UserRepositoryInterface
return $this->repository->findAll();
}
/**
* @param string $lang
*/
public function findAllAsArray(string $lang): iterable
{
$dql = sprintf(<<<'DQL'
SELECT
u.id AS id,
u.username AS username,
u.email,
u.enabled,
IDENTITY(u.civility) AS civility_id,
JSON_EXTRACT(civility.abbreviation, :lang) AS civility_abbreviation,
JSON_EXTRACT(civility.name, :lang) AS civility_name,
u.label,
mainCenter.id AS mainCenter_id,
mainCenter.name AS mainCenter_name,
IDENTITY(u.mainScope) AS mainScope_id,
JSON_EXTRACT(mainScope.name, :lang) AS mainScope_name,
IDENTITY(u.userJob) AS userJob_id,
JSON_EXTRACT(userJob.label, :lang) AS userJob_name,
currentLocation.id AS currentLocation_id,
currentLocation.name AS currentLocation_name,
mainLocation.id AS mainLocation_id,
mainLocation.name AS mainLocation_name,
u.absenceStart
FROM Chill\MainBundle\Entity\User u
LEFT JOIN u.civility civility
LEFT JOIN u.currentLocation currentLocation
LEFT JOIN u.mainLocation mainLocation
LEFT JOIN u.mainCenter mainCenter
LEFT JOIN u.mainScope mainScope
LEFT JOIN u.userJob userJob
ORDER BY u.label
DQL);
$query = $this->entityManager->createQuery($dql)
->setHydrationMode(AbstractQuery::HYDRATE_ARRAY)
->setParameter('lang', $lang)
;
foreach ($query->toIterable() as $u) {
yield $u;
}
}
public function findAllUserACLAsArray(): iterable
{
$sql = <<<'SQL'
SELECT
u.id,
u.username,
u.email,
u.label,
u.enabled,
c.id AS center_id,
c.name AS center_name,
pg.id AS permissionsGroup_id,
pg.name AS permissionsGroup_name
FROM users u
LEFT JOIN user_groupcenter ON u.id = user_groupcenter.user_id
LEFT JOIN group_centers ON user_groupcenter.groupcenter_id = group_centers.id
LEFT JOIN centers c on group_centers.center_id = c.id
LEFT JOIN permission_groups pg on group_centers.permissionsgroup_id = pg.id
ORDER BY u.username, c.name, pg.name
SQL;
$query = $this->entityManager->getConnection()->executeQuery($sql);
foreach ($query->iterateAssociative() as $u) {
yield $u;
}
}
/**
* @param mixed|null $limit
* @param mixed|null $offset

View File

@ -14,6 +14,9 @@ namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\User;
use Doctrine\Persistence\ObjectRepository;
/**
* @template ObjectRepository<User>
*/
interface UserRepositoryInterface extends ObjectRepository
{
public function countBy(array $criteria): int;
@ -24,20 +27,25 @@ interface UserRepositoryInterface extends ObjectRepository
public function countByUsernameOrEmail(string $pattern): int;
public function find($id, $lockMode = null, $lockVersion = null): ?User;
/**
* @return User[]
*/
public function findAll(): array;
/**
* @param mixed|null $limit
* @param mixed|null $offset
* Find a list of all users.
*
* @return User[]
* The main purpose for this method is to provide a lightweight list of all users in the database.
*
* @param string $lang The lang to display all the translatable string (no fallback if not present)
* @return iterable<array{id: int, username: string, email: string, enabled: bool, civility_id: int, civility_abbreviation: string, civility_name: string, label: string, mainCenter_id: int, mainCenter_name: string, mainScope_id: int, mainScope_name: string, userJob_id: int, userJob_name: string, currentLocation_id: int, currentLocation_name: string, mainLocation_id: int, mainLocation_name: string, absenceStart: \DateTimeImmutable}>
*/
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array;
public function findAllAsArray(string $lang): iterable;
/**
* Find a list of permissions associated to each users.
*
* The main purpose for this method is to provide a lightweight list of all permissions group and center
* associated to each user.
*
* @return iterable<array{id: int, username: string, email: string, enabled: bool, center_id: int, center_name: string, permissionsGroup_id: int, permissionsGroup_name: string}>
*/
public function findAllUserACLAsArray(): iterable;
/**
* @return array|User[]
@ -53,8 +61,6 @@ interface UserRepositoryInterface extends ObjectRepository
public function findByUsernameOrEmail(string $pattern, ?array $orderBy = [], ?int $limit = null, ?int $offset = null): array;
public function findOneBy(array $criteria, ?array $orderBy = null): ?User;
public function findOneByUsernameOrEmail(string $pattern): ?User;
/**
@ -68,6 +74,4 @@ interface UserRepositoryInterface extends ObjectRepository
* @param mixed $flag
*/
public function findUsersHavingFlags($flag, array $amongstUsers = []): array;
public function getClassName(): string;
}

View File

@ -39,8 +39,13 @@
{{ 'This will eventually restrict your possibilities in filtering the data.'|trans }}</p>
<h3 class="m-3">{{ 'Center'|trans }}</h3>
{{ form_widget(form.centers.center) }}
<div class="mb-3 mt-3">
<input id="toggle-check-all" class="btn btn-misc" type= "button" onclick='uncheckAll(this)' value="{{ 'uncheck all centers'|trans|e('html_attr') }}"/>
</div>
{% if form.centers.regroupment is defined %}
<h3 class="m-3">{{ 'Pick aggregated centers'|trans }}</h3>
{{ form_widget(form.centers.regroupment) }}
@ -53,3 +58,15 @@
</div>
{% endblock content %}
{% block js %}
<script>
const uncheckAll = () => {
const allCenters = document.getElementsByName('centers[center][]');
allCenters.forEach(checkbox => checkbox.checked = false)
}
</script>
{% endblock js %}

View File

@ -9,4 +9,4 @@
{{ 'notification.counter unread notifications'|trans({'unread': counter.unread }) }}
</span>
{% endif %}
</div>
</div>

View File

@ -98,6 +98,18 @@
<li class='cancel'>
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
</li>
<li>
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-download"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{{ path('chill_main_users_export_list') }}">{{ 'admin.users.export_list_csv'|trans }}</a></li>
<li><a class="dropdown-item" href="{{ path('chill_main_users_export_permissions') }}">{{ 'admin.users.export_permissions_csv'|trans }}</a></li>
</ul>
</div>
</li>
<li>
<a href="{{ path('chill_crud_admin_user_new') }}" class="btn btn-create">{{ 'Create'|trans }}</a>
</li>

View File

@ -37,3 +37,6 @@ services:
Chill\MainBundle\Controller\RegroupmentController:
autowire: true
autoconfigure: true
Chill\MainBundle\Controller\UserExportController:
tags: ['controller.service_arguments']

View File

@ -285,6 +285,8 @@ The export will contains only data from the picked centers.: L'export ne contien
This will eventually restrict your possibilities in filtering the data.: Les possibilités de filtrages seront adaptées aux droits de consultation pour les centres choisis.
Go to export options: Vers la préparation de l'export
Pick aggregated centers: Regroupement de centres
uncheck all centers: Désélectionner tous les centres
check all centers: Sélectionner tous les centres
# export creation step 'export' : choose aggregators, filtering and formatter
Formatter: Mise en forme
Choose the formatter: Choisissez le format d'export voulu.
@ -610,3 +612,32 @@ absence:
You are listed as absent, as of: Votre absence est indiquée à partir du
No absence listed: Aucune absence indiquée.
Is absent: Absent?
admin:
users:
export_list_csv: Liste des utilisateurs (format CSV)
export_permissions_csv: Association utilisateurs - groupes de permissions - centre (format CSV)
export:
id: Identifiant
username: Nom d'utilisateur
email: Courriel
enabled: Activé
civility_id: Identifiant civilité
civility_abbreviation: Abbréviation civilité
civility_name: Civilité
label: Label
mainCenter_id: Identifiant centre principal
mainCenter_name: Centre principal
mainScope_id: Identifiant service principal
mainScope_name: Service principal
userJob_id: Identifiant métier
userJob_name: Métier
currentLocation_id: Identifiant localisation actuelle
currentLocation_name: Localisation actuelle
mainLocation_id: Identifiant localisation principale
mainLocation_name: Localisation principale
absenceStart: Absent à partir du
center_id: Identifiant du centre
center_name: Centre
permissionsGroup_id: Identifiant du groupe de permissions
permissionsGroup_name: Groupe de permissions

View File

@ -51,7 +51,10 @@ class UserRefEventSubscriber implements EventSubscriberInterface
public function onStateEntered(EnteredEvent $enteredEvent): void
{
if ($enteredEvent->getMarking()->has(AccompanyingPeriod::STEP_CONFIRMED)) {
if (
$enteredEvent->getMarking()->has(AccompanyingPeriod::STEP_CONFIRMED)
and $enteredEvent->getTransition()->getName() === 'confirm'
) {
$this->onPeriodConfirmed($enteredEvent->getSubject());
}
}

View File

@ -27,7 +27,7 @@ readonly class AccompanyingPeriodStepChangeCronjob implements CronJobInterface
{
$now = $this->clock->now();
if ($now->sub(new \DateInterval('P1D')) < $cronJobExecution->getLastStart()) {
if (null !== $cronJobExecution && $now->sub(new \DateInterval('P1D')) < $cronJobExecution->getLastStart()) {
return false;
}

View File

@ -34,5 +34,4 @@ class AccompanyingPeriodStepChangeMessageHandler implements MessageHandlerInterf
($this->changer)($period, $message->getTransition());
}
}

View File

@ -84,5 +84,4 @@ class AccompanyingPeriodStepChangeRequestor
$this->messageBus->dispatch(new AccompanyingPeriodStepChangeRequestMessage($accompanyingPeriodId, 'mark_active'));
}
}
}

View File

@ -13,7 +13,11 @@ namespace Chill\PersonBundle\Actions\Remove;
use Chill\PersonBundle\Actions\ActionEvent;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Relationships\Relationship;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@ -42,7 +46,7 @@ class PersonMove
protected $eventDispatcher;
public function __construct(
EntityManagerInterface $em,
EntityManagerInterface $em,
EventDispatcherInterface $eventDispatcher
) {
$this->em = $em;
@ -84,8 +88,11 @@ class PersonMove
}
foreach ($metadata->getAssociationMappings() as $field => $mapping) {
if (Person::class === $mapping['targetEntity']) {
if (in_array($metadata->getName(), $toDelete, true)) {
if (in_array($mapping['sourceEntity'], $this->getIgnoredEntities(), true)) {
continue;
}
if (Person::class === $mapping['targetEntity'] and true === $mapping['isOwningSide']) {
if (in_array($mapping['sourceEntity'], $toDelete, true)) {
$sql = $this->createDeleteSQL($metadata, $from, $field);
$event = new ActionEvent(
$from->getId(),
@ -120,7 +127,7 @@ class PersonMove
return $sqls;
}
protected function createDeleteSQL(ClassMetadata $metadata, Person $from, $field): string
private function createDeleteSQL(ClassMetadata $metadata, Person $from, $field): string
{
$mapping = $metadata->getAssociationMapping($field);
@ -137,26 +144,41 @@ class PersonMove
);
}
protected function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string
private function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string
{
$mapping = $metadata->getAssociationMapping($field);
// Set part of the query, aka <here> in "UPDATE table SET <here> "
$sets = [];
foreach ($mapping['joinColumns'] as $columns) {
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
}
$conditions = [];
$tableName = '';
foreach ($mapping['joinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
if (array_key_exists('joinTable', $mapping)) {
$tableName = (null !== ($mapping['joinTable']['schema'] ?? null) ? $mapping['joinTable']['schema'] . '.' : '')
. $mapping['joinTable']['name'];
foreach ($mapping['joinTable']['inverseJoinColumns'] as $columns) {
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
}
foreach ($mapping['joinTable']['inverseJoinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
}
} elseif (array_key_exists('joinColumns', $mapping)) {
$tableName = $this->getTableName($metadata);
foreach ($mapping['joinColumns'] as $columns) {
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
}
foreach ($mapping['joinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
}
}
return sprintf(
'UPDATE %s SET %s WHERE %s',
$this->getTableName($metadata),
$tableName,
implode(' ', $sets),
implode(' AND ', $conditions)
);
@ -166,10 +188,23 @@ class PersonMove
* return an array of classes where entities should be deleted
* instead of moved.
*/
protected function getDeleteEntities(): array
private function getDeleteEntities(): array
{
return [
AccompanyingPeriod::class,
Person\PersonCenterHistory::class,
HouseholdMember::class,
AccompanyingPeriodParticipation::class,
AccompanyingPeriod\AccompanyingPeriodWork::class,
Relationship::class
];
}
private function getIgnoredEntities(): array
{
return [
Person\PersonCurrentAddress::class,
PersonHouseholdAddress::class,
Person\PersonCenterCurrent::class,
];
}

View File

@ -42,10 +42,18 @@ final class ImportSocialWorkMetadata extends Command
protected function configure()
{
$description = 'Imports a structured table containing social issues, social actions, objectives, results and evaluations.';
$help = 'File to csv format, no headers, semi-colon as delimiter, datas sorted by alphabetical order, column after column.'. PHP_EOL
. 'Columns are: social issues parent, social issues child, social actions parent, social actions child, goals, results, evaluations.'. PHP_EOL
. PHP_EOL
. 'See social_work_metadata.csv as example.'. PHP_EOL;
$this
->setName('chill:person:import-socialwork')
->addOption('filepath', 'f', InputOption::VALUE_REQUIRED, 'The file to import.')
->addOption('language', 'l', InputOption::VALUE_OPTIONAL, 'The default language');
->addOption('language', 'l', InputOption::VALUE_OPTIONAL, 'The default language')
->setDescription($description)
->setHelp($help);
}
protected function execute(InputInterface $input, OutputInterface $output)

View File

@ -247,7 +247,7 @@ class PersonDuplicateController extends Controller
);
$duplicatePersons = $this->similarPersonMatcher->
matchPerson($person, $personNotDuplicateRepository, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL);
matchPerson($person, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL, false);
$notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person);
@ -264,14 +264,14 @@ class PersonDuplicateController extends Controller
$nb_activity = $em->getRepository(Activity::class)->findBy(['person' => $id]);
$nb_document = $em->getRepository(PersonDocument::class)->findBy(['person' => $id]);
$nb_event = $em->getRepository(Participation::class)->findBy(['person' => $id]);
// $nb_event = $em->getRepository(Participation::class)->findBy(['person' => $id]);
$nb_task = $em->getRepository(SingleTask::class)->countByParameters(['person' => $id]);
$person = $em->getRepository(Person::class)->findOneBy(['id' => $id]);
return [
'nb_activity' => count($nb_activity),
'nb_document' => count($nb_document),
'nb_event' => count($nb_event),
// 'nb_event' => count($nb_event),
'nb_task' => $nb_task,
'nb_addresses' => count($person->getAddresses()),
];

View File

@ -12,9 +12,11 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class UserAccompanyingPeriodController extends AbstractController
@ -32,12 +34,24 @@ class UserAccompanyingPeriodController extends AbstractController
/**
* @Route("/{_locale}/person/accompanying-periods/my", name="chill_person_accompanying_period_user")
*/
public function listAction(Request $request)
public function listAction(Request $request): Response
{
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']]);
$active = $request->query->getBoolean('active', true);
$steps = match ($active) {
true => [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
],
false => [
AccompanyingPeriod::STEP_CLOSED,
]
};
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => $steps]);
$pagination = $this->paginatorFactory->create($total);
$accompanyingPeriods = $this->accompanyingPeriodRepository->findBy(
['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']],
['user' => $this->getUser(), 'step' => $steps],
['openingDate' => 'DESC'],
$pagination->getItemsPerPage(),
$pagination->getCurrentPageFirstItemNumber()
@ -46,13 +60,14 @@ class UserAccompanyingPeriodController extends AbstractController
return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [
'accompanyingPeriods' => $accompanyingPeriods,
'pagination' => $pagination,
'active' => $active,
]);
}
/**
* @Route("/{_locale}/person/accompanying-periods/my/drafts", name="chill_person_accompanying_period_draft_user")
*/
public function listDraftsAction(Request $request)
public function listDraftsAction(): Response
{
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => 'DRAFT']);
$pagination = $this->paginatorFactory->create($total);

View File

@ -59,6 +59,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
* )
* @Serializer\Groups({"read", "docgen:read"})
* @ORM\OrderBy({"startDate": "DESC", "id": "DESC"})
* @var Collection<AccompanyingPeriodWorkEvaluation>
*
* @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer`
*/
@ -278,6 +279,9 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this->accompanyingPeriod;
}
/**
* @return Collection<AccompanyingPeriodWorkEvaluation>
*/
public function getAccompanyingPeriodWorkEvaluations(): Collection
{
return $this->accompanyingPeriodWorkEvaluations;

View File

@ -79,6 +79,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* )
* @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"})
* @Serializer\Groups({"read"})
* @var Collection<AccompanyingPeriodWorkEvaluationDocument>
*/
private Collection $documents;
@ -204,7 +205,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
}
/**
* @return Collection
* @return Collection<AccompanyingPeriodWorkEvaluationDocument>
*/
public function getDocuments()
{

View File

@ -213,6 +213,10 @@ class Household
return null;
}
/**
* @Serializer\Groups({"docgen:read"})
* @Serializer\SerializedName("current_composition")
*/
public function getCurrentComposition(?DateTimeImmutable $at = null): ?HouseholdComposition
{
$at ??= new DateTimeImmutable('today');

View File

@ -44,6 +44,7 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Assert\GreaterThanOrEqual(propertyPath="startDate", groups={"Default", "household_composition"})
* @Serializer\Groups({"docgen:read"})
*/
private ?DateTimeImmutable $endDate = null;
@ -56,6 +57,7 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
/**
* @ORM\ManyToOne(targetEntity=HouseholdCompositionType::class)
* @ORM\JoinColumn(nullable=false)
* @Serializer\Groups({"docgen:read"})
*/
private ?HouseholdCompositionType $householdCompositionType = null;
@ -71,12 +73,14 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
* @ORM\Column(type="integer", nullable=true, options={"default": null})
* @Assert\NotNull
* @Assert\GreaterThanOrEqual(0, groups={"Default", "household_composition"})
* @Serializer\Groups({"docgen:read"})
*/
private ?int $numberOfChildren = null;
/**
* @ORM\Column(type="date_immutable", nullable=false)
* @Assert\NotNull(groups={"Default", "household_composition"})
* @Serializer\Groups({"docgen:read"})
*/
private ?DateTimeImmutable $startDate = null;

View File

@ -45,7 +45,7 @@ class PersonCenterCurrent
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity=Person::class, inversedBy="centerCurrent")
* @ORM\OneToOne(targetEntity=Person::class, inversedBy="centerCurrent")
*/
private Person $person;

View File

@ -98,7 +98,7 @@ final class GeographicalUnitStatAggregator implements AggregatorInterface
'acp_geog_units'
);
$qb->andWhere($qb->expr()->eq('acp_geog_units.layer', ':acp_geog_unit_layer'));
$qb->andWhere($qb->expr()->in('acp_geog_units.layer', ':acp_geog_unit_layer'));
$qb->setParameter('acp_geog_unit_layer', $data['level']);
@ -129,6 +129,8 @@ final class GeographicalUnitStatAggregator implements AggregatorInterface
'class' => GeographicalUnitLayer::class,
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
'multiple' => true,
'expanded' => true,
]);
}

View File

@ -101,6 +101,8 @@ class GeographicalUnitAggregator implements AggregatorInterface
'class' => GeographicalUnitLayer::class,
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
'multiple' => true,
'expanded' => true,
]);
}

View File

@ -107,7 +107,6 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface
return $this->aggregateStringHelper->getLabelMulti($key, $values, 'export.list.household.' . $key);
case 'compositionType':
//dump($values);
return $this->translatableStringHelper->getLabel($key, $values, 'export.list.household.' . $key);
default:

View File

@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
class StepFilterBetweenDates implements FilterInterface
{
private const DEFAULT_CHOICE = [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
];
private const STEPS = [
'course.draft' => AccompanyingPeriod::STEP_DRAFT,
'course.confirmed' => AccompanyingPeriod::STEP_CONFIRMED,
'course.closed' => AccompanyingPeriod::STEP_CLOSED,
'course.inactive_short' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
'course.inactive_long' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
];
private RollingDateConverterInterface $rollingDateConverter;
private TranslatorInterface $translator;
public function __construct(
RollingDateConverterInterface $rollingDateConverter,
TranslatorInterface $translator
) {
$this->rollingDateConverter = $rollingDateConverter;
$this->translator = $translator;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$alias = 'acp_filter_by_step_between_dat_alias';
$steps = 'acp_filter_by_step_between_dat_steps';
$from = 'acp_filter_by_step_between_dat_from';
$to = 'acp_filter_by_step_between_dat_to';
$qb
->andWhere(
$qb->expr()->exists(
"SELECT 1 FROM " . AccompanyingPeriod\AccompanyingPeriodStepHistory::class . " {$alias} " .
"WHERE {$alias}.step IN (:{$steps}) AND OVERLAPSI ({$alias}.startDate, {$alias}.endDate),(:{$from}, :{$to}) = TRUE " .
"AND {$alias}.period = acp"
)
)
->setParameter($from, $this->rollingDateConverter->convert($data['date_from']))
->setParameter($to, $this->rollingDateConverter->convert($data['date_to']))
->setParameter($steps, $data['accepted_steps_multi']);
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('accepted_steps_multi', ChoiceType::class, [
'label' => 'export.filter.course.by_step.steps',
'choices' => self::STEPS,
'multiple' => true,
'expanded' => true,
])
->add('date_from', PickRollingDateType::class, [
'label' => 'export.filter.course.by_step.date_from',
])
->add('date_to', PickRollingDateType::class, [
'label' => 'export.filter.course.by_step.date_to',
]);
}
public function getFormDefaultData(): array
{
return [
'accepted_steps_multi' => self::DEFAULT_CHOICE,
'date_from' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
'date_to' => new RollingDate(RollingDate::T_TODAY),
];
}
public function describeAction($data, $format = 'string')
{
$steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']);
return ['export.filter.course.by_step.Filtered by steps: only %step% and between %date_from% and %date_to%', [
'%step%' => implode(', ', $steps),
'%date_from%' => $this->rollingDateConverter->convert($data['date_from'])->format('d-m-Y'),
'%date_to%' => $this->rollingDateConverter->convert($data['date_to'])->format('d-m-Y'),
]];
}
public function getTitle()
{
return 'export.filter.course.by_step.Filter by step between dates';
}
}

View File

@ -23,11 +23,15 @@ use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function in_array;
class StepFilter implements FilterInterface
class StepFilterOnDate implements FilterInterface
{
private const A = 'acp_filter_bystep_stephistories';
private const DEFAULT_CHOICE = AccompanyingPeriod::STEP_CONFIRMED;
private const DEFAULT_CHOICE = [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
];
private const P = 'acp_step_filter_date';
@ -79,7 +83,7 @@ class StepFilter implements FilterInterface
$qb->expr()->in(self::A . '.step', ':acp_filter_by_step_steps')
)
->setParameter(self::P, $this->rollingDateConverter->convert($data['calc_date']))
->setParameter('acp_filter_by_step_steps', $data['accepted_steps']);
->setParameter('acp_filter_by_step_steps', $data['accepted_steps_multi']);
}
public function applyOn()
@ -90,30 +94,36 @@ class StepFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('accepted_steps', ChoiceType::class, [
->add('accepted_steps_multi', ChoiceType::class, [
'label' => 'export.filter.course.by_step.steps',
'choices' => self::STEPS,
'multiple' => false,
'multiple' => true,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
])
->add('calc_date', PickRollingDateType::class, [
'label' => 'export.filter.course.by_step.date_calc',
'data' => new RollingDate(RollingDate::T_TODAY),
]);
}
public function getFormDefaultData(): array
{
return [
'accepted_steps_multi' => self::DEFAULT_CHOICE,
'calc_date' => new RollingDate(RollingDate::T_TODAY),
];
}
public function describeAction($data, $format = 'string')
{
$step = array_flip(self::STEPS)[$data['accepted_steps']];
$steps = array_map(fn (string $step) => $this->translator->trans(array_flip(self::STEPS)[$step]), $data['accepted_steps_multi']);
return ['Filtered by steps: only %step%', [
'%step%' => $this->translator->trans($step),
'%step%' => implode(', ', $steps),
]];
}
public function getTitle()
{
return 'Filter by step';
return 'export.filter.course.by_step.Filter by step';
}
}

View File

@ -12,7 +12,9 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Menu;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Security;
use Symfony\Contracts\Translation\TranslatorInterface;
class HouseholdMenuBuilder implements LocalMenuBuilderInterface
@ -22,9 +24,12 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
*/
protected $translator;
public function __construct(TranslatorInterface $translator)
private Security $security;
public function __construct(TranslatorInterface $translator, Security $security)
{
$this->translator = $translator;
$this->security = $security;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters): void
@ -53,12 +58,14 @@ class HouseholdMenuBuilder implements LocalMenuBuilderInterface
], ])
->setExtras(['order' => 17]);
$menu->addChild($this->translator->trans('household.Accompanying period'), [
'route' => 'chill_person_household_accompanying_period',
'routeParameters' => [
'household_id' => $household->getId(),
], ])
->setExtras(['order' => 20]);
if ($this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['household'])) {
$menu->addChild($this->translator->trans('household.Accompanying period'), [
'route' => 'chill_person_household_accompanying_period',
'routeParameters' => [
'household_id' => $household->getId(),
],])
->setExtras(['order' => 20]);
}
$menu->addChild($this->translator->trans('household.Addresses'), [
'route' => 'chill_person_household_addresses',

View File

@ -15,6 +15,7 @@ use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Knp\Menu\MenuItem;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Security\Core\Security;
@ -106,17 +107,19 @@ class PersonMenuBuilder implements LocalMenuBuilderInterface
->setExtras([
'order' => 99999,
]);
/*
$menu->addChild($this->translator->trans('Person duplicate'), [
'route' => 'chill_person_duplicate_view',
'routeParameters' => [
'person_id' => $parameters['person']->getId(),
],
])
->setExtras([
'order' => 99999,
]);
*/
if ($this->security->isGranted(PersonVoter::DUPLICATE, $parameters['person'])) {
$menu->addChild($this->translator->trans('Person duplicate'), [
'route' => 'chill_person_duplicate_view',
'routeParameters' => [
'person_id' => $parameters['person']->getId(),
],
])
->setExtras([
'order' => 99999,
]);
}
if (
'visible' === $this->showAccompanyingPeriod
&& $this->security->isGranted(AccompanyingPeriodVoter::SEE, $parameters['person'])

View File

@ -27,7 +27,7 @@ final class AccompanyingPeriodNotificationHandler implements NotificationHandler
public function getTemplate(Notification $notification, array $options = []): string
{
return 'ChillPersonBundle:AccompanyingPeriod:showInNotification.html.twig';
return '@ChillPerson/AccompanyingPeriod/showInNotification.html.twig';
}
public function getTemplateData(Notification $notification, array $options = []): array

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Notification;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocumentRepository;
final class AccompanyingPeriodWorkEvaluationDocumentNotificationHandler implements NotificationHandlerInterface
{
private AccompanyingPeriodWorkEvaluationDocumentRepository $accompanyingPeriodWorkEvaluationDocumentRepository;
public function __construct(AccompanyingPeriodWorkEvaluationDocumentRepository $accompanyingPeriodWorkEvaluationDocumentRepository)
{
$this->accompanyingPeriodWorkEvaluationDocumentRepository = $accompanyingPeriodWorkEvaluationDocumentRepository;
}
public function getTemplate(Notification $notification, array $options = []): string
{
return '@ChillPerson/AccompanyingCourseWork/showEvaluationDocumentInNotification.html.twig';
}
public function getTemplateData(Notification $notification, array $options = []): array
{
return [
'notification' => $notification,
'document' => $doc = $this->accompanyingPeriodWorkEvaluationDocumentRepository->find($notification->getRelatedEntityId()),
'evaluation' => $doc?->getAccompanyingPeriodWorkEvaluation(),
];
}
public function supports(Notification $notification, array $options = []): bool
{
return $notification->getRelatedEntityClass() === AccompanyingPeriodWorkEvaluationDocument::class;
}
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Notification;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
final class AccompanyingPeriodWorkNotificationHandler implements NotificationHandlerInterface
{
private AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository;
public function __construct(AccompanyingPeriodWorkRepository $accompanyingPeriodWorkRepository)
{
$this->accompanyingPeriodWorkRepository = $accompanyingPeriodWorkRepository;
}
public function getTemplate(Notification $notification, array $options = []): string
{
return '@ChillPerson/AccompanyingCourseWork/showInNotification.html.twig';
}
public function getTemplateData(Notification $notification, array $options = []): array
{
return [
'notification' => $notification,
'work' => $this->accompanyingPeriodWorkRepository->find($notification->getRelatedEntityId()),
];
}
public function supports(Notification $notification, array $options = []): bool
{
return $notification->getRelatedEntityClass() === AccompanyingPeriodWork::class;
}
}

View File

@ -89,5 +89,4 @@ readonly class AccompanyingPeriodInfoRepository implements AccompanyingPeriodInf
{
return AccompanyingPeriodInfo::class;
}
}

View File

@ -122,7 +122,8 @@
<add-evaluation
v-for="e in pickedEvaluations"
v-bind:key="e.key"
v-bind:evaluation="e">
v-bind:evaluation="e"
v-bind:docAnchorId="this.docAnchorId">
</add-evaluation>
<!-- box to add new evaluation -->
@ -296,7 +297,21 @@
@go-to-generate-workflow="goToGenerateWorkflow"
></list-workflow-modal>
</li>
<li>
<button v-if="AmIRefferer"
class="btn btn-notify"
@click="goToGenerateNotification(false)"
></button>
<template v-else>
<button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" :title="$t('notification_send')" data-bs-toggle="dropdown" aria-expanded="false">&nbsp;</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li><a class="dropdown-item" @click="goToGenerateNotification(true)">{{ $t('notification_notify_referrer') }}</a></li>
<li><a class="dropdown-item" @click="goToGenerateNotification(false)">{{ $t('notification_notify_any') }}</a></li>
</ul>
</template>
</li>
<li v-if="!isPosting">
<button class="btn btn-save" @click="submit">
{{ $t('action.save') }}
@ -366,7 +381,10 @@ const i18n = {
no_referrers: "Aucun agent traitant",
choose_referrers: "Choisir des agents traitants",
remove_referrer: "Enlever l'agent",
private_comment: "Commentaire privé"
private_comment: "Commentaire privé",
notification_notify_referrer: "Notifier le référent",
notification_notify_any: "Notifier d'autres utilisateurs",
notification_send: "Envoyer une notification",
}
}
};
@ -389,6 +407,7 @@ export default {
i18n,
data() {
return {
docAnchorId: null,
isExpanded: false,
editor: ClassicEditor,
showAddObjective: false,
@ -428,7 +447,14 @@ export default {
},
};
},
computed: {
beforeMount() {
const urlParams = new URLSearchParams(window.location.search);
this.docAnchorId = urlParams.get('doc_id');
},
mounted() {
this.scrollToElement(this.docAnchorId);
},
computed: {
...mapState([
'work',
'resultsForAction',
@ -441,6 +467,7 @@ export default {
'isPosting',
'errors',
'templatesAvailablesForAction',
'me',
]),
...mapGetters([
'hasResultsForAction',
@ -498,6 +525,10 @@ export default {
this.$store.commit('setPersonsPickedIds', v);
}
},
AmIRefferer() {
return (!(this.work.accompanyingPeriod.user && this.me
&& (this.work.accompanyingPeriod.user.id !== this.me.id)));
}
},
methods: {
toggleSelect() {
@ -548,6 +579,19 @@ export default {
return this.$store.dispatch('submit', callback)
.catch(e => { console.log(e); throw e; });
},
goToGenerateNotification(tos) {
console.log('save before leave to notification');
const callback = (data) => {
if (tos === true) {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork&entityId=${this.work.id}&tos[0]=${this.work.accompanyingPeriod.user.id}&returnPath=/fr/person/accompanying-period/${this.work.accompanyingPeriod.id}/work`);
} else {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork&entityId=${this.work.id}&returnPath=/fr/person/accompanying-period/${this.work.accompanyingPeriod.id}/work`);
}
}
return this.$store.dispatch('submit', callback)
.catch(e => {console.log(e); throw e});
},
submit() {
this.$store.dispatch('submit').catch((error) => {
if (error.name === 'ValidationException' || error.name === 'AccessException') {
@ -559,7 +603,7 @@ export default {
});
},
saveFormOnTheFly(payload) {
console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
// console.log('saveFormOnTheFly: type', payload.type, ', data', payload.data);
let body = { type: payload.type };
body.name = payload.data.text;
@ -581,6 +625,12 @@ export default {
this.$toast.open({message: 'An error occurred'});
}
})
},
scrollToElement(docAnchorId) {
const documentEl = document.getElementById(`document_${docAnchorId}`);
if (documentEl) {
documentEl.scrollIntoView({behavior: 'smooth'});
}
}
}
};

View File

@ -11,7 +11,7 @@
</div>
<div>
<form-evaluation ref="FormEvaluation" :key="evaluation.key" :evaluation="evaluation"></form-evaluation>
<form-evaluation ref="FormEvaluation" :key="evaluation.key" :evaluation="evaluation" :docAnchorId="docAnchorId"></form-evaluation>
<ul class="record_actions">
<li v-if="evaluation.workflows_availables.length > 0">
@ -85,7 +85,7 @@ export default {
Modal,
ListWorkflowModal,
},
props: ['evaluation'],
props: ['evaluation', 'docAnchorId'],
i18n,
data() {
return {

View File

@ -79,8 +79,8 @@
<h5>{{ $t('Documents') }} :</h5>
<div class="flex-table">
<div class="item-bloc" v-for="(d, i) in evaluation.documents" :key="d.id">
<div class="item-row">
<div class="item-bloc" v-for="(d, i) in evaluation.documents" :key="d.id" :class="[parseInt(this.docAnchorId) === d.id ? 'bg-blink' : 'nothing']">
<div :id="`document_${d.id}`" class="item-row">
<div class="input-group input-group-lg mb-3 row">
<label class="col-sm-3 col-form-label">Titre du document:</label>
<div class="col-sm-9">
@ -92,7 +92,7 @@
:data-key="i"
@input="onInputDocumentTitle"/>
</div>
</div>
</div>
</div>
<div class="item-row">
<div class="item-col item-meta">
@ -102,27 +102,32 @@
</div>
<div class="item-row">
<div class="item-col">
<ul class="record_actions" >
<li v-if="d.workflows_availables.length > 0">
<list-workflow-modal
:workflows="d.workflows"
:allowCreate="true"
relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument"
:relatedEntityId="d.id"
:workflowsAvailables="d.workflows_availables"
:preventDefaultMoveToGenerate="true"
:goToGenerateWorkflowPayload="{doc: d}"
@go-to-generate-workflow="goToGenerateWorkflowEvaluationDocument"
></list-workflow-modal>
</li>
<ul class="record_actions">
<li v-if="d.workflows_availables.length > 0">
<list-workflow-modal
:workflows="d.workflows"
:allowCreate="true"
relatedEntityClass="Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument"
:relatedEntityId="d.id"
:workflowsAvailables="d.workflows_availables"
:preventDefaultMoveToGenerate="true"
:goToGenerateWorkflowPayload="{doc: d}"
@go-to-generate-workflow="goToGenerateWorkflowEvaluationDocument"
></list-workflow-modal>
</li>
<li>
<add-async-upload
:buttonTitle="$t('replace')"
:options="asyncUploadOptions"
:btnClasses="{'btn': true, 'btn-edit': true}"
@addDocument="(arg) => replaceDocument(d, arg)"
>
</add-async-upload>
<button
v-if="AmIRefferer"
class="btn btn-notify"
@click="goToGenerateDocumentNotification(d, false)">
</button>
<template v-else>
<button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" :title="$t('notification_send')" data-bs-toggle="dropdown" aria-expanded="false">&nbsp;</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li><a class="dropdown-item" @click="goToGenerateDocumentNotification(d, true)">{{ $t('notification_notify_referrer') }}</a></li>
<li><a class="dropdown-item" @click="goToGenerateDocumentNotification(d, false)">{{ $t('notification_notify_any') }}</a></li>
</ul>
</template>
</li>
<li>
<document-action-buttons-group
@ -133,6 +138,15 @@
@on-stored-object-status-change="onStatusDocumentChanged"
></document-action-buttons-group>
</li>
<li>
<add-async-upload
:buttonTitle="$t('replace')"
:options="asyncUploadOptions"
:btnClasses="{'btn': true, 'btn-edit': true}"
@addDocument="(arg) => replaceDocument(d, arg)"
>
</add-async-upload>
</li>
<li v-if="d.workflows.length === 0">
<a class="btn btn-delete" @click="removeDocument(d)">
</a>
@ -214,14 +228,17 @@ const i18n = {
template_title: "Nom du template",
browse: "Ajouter un document",
replace: "Remplacer",
download: "Télécharger le fichier existant"
download: "Télécharger le fichier existant",
notification_notify_referrer: "Notifier le référent",
notification_notify_any: "Notifier d'autres utilisateurs",
notification_send: "Envoyer une notification",
}
}
};
export default {
name: "FormEvaluation",
props: ['evaluation'],
props: ['evaluation', 'docAnchorId'],
components: {
ckeditor: CKEditor.component,
PickTemplate,
@ -260,8 +277,14 @@ export default {
},
computed: {
...mapState([
'isPosting'
'isPosting',
'work',
'me',
]),
AmIRefferer() {
return (!(this.$store.state.work.accompanyingPeriod.user && this.$store.state.me
&& (this.$store.state.work.accompanyingPeriod.user.id !== this.$store.state.me.id)));
},
getTemplatesAvailables() {
return this.$store.getters.getTemplatesAvailablesForEvaluation(this.evaluation.evaluation);
},
@ -390,6 +413,18 @@ export default {
return this.$store.dispatch('submit', callback)
.catch(e => { console.log(e); throw e; });
},
goToGenerateDocumentNotification(document, tos){
const callback = (data) => {
let evaluation = data.accompanyingPeriodWorkEvaluations.find(e => e.key === this.evaluation.key);
if (tos === true) {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument&entityId=${document.id}&tos[0]=${this.$store.state.work.accompanyingPeriod.user.id}&returnPath=/fr/person/accompanying-period/work/${evaluation.id}/edit`)
} else {
window.location.assign(`/fr/notification/create?entityClass=Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument&entityId=${document.id}&returnPath=/fr/person/accompanying-period/work/${evaluation.id}/edit`)
}
};
return this.$store.dispatch('submit', callback)
.catch(e => {console.log(e); throw e});
}
},
}
</script>
@ -402,4 +437,19 @@ export default {
ul.document-upload {
justify-content: flex-start;
}
.bg-blink{
color: #050000;
padding: 10px;
display: inline-block;
border-radius: 5px;
animation: blinkingBackground 2.2s infinite;
animation-iteration-count: 2;
}
@keyframes blinkingBackground{
0% { background-color: #ed776d;}
50% { background-color: #ffffff;}
100% { background-color: #ed776d;}
}
</style>

View File

@ -35,6 +35,7 @@ const store = createStore({
referrers: window.accompanyingCourseWork.referrers,
isPosting: false,
errors: [],
me: null
},
getters: {
socialAction(state) {
@ -130,6 +131,9 @@ const store = createStore({
}
},
mutations: {
setWhoAmiI(state, me) {
state.me = me;
},
setEvaluationsPicked(state, evaluations) {
state.evaluationsPicked = evaluations.map((e, index) => {
var k = Object.assign(e, {
@ -385,6 +389,19 @@ const store = createStore({
},
},
actions: {
getWhoAmI({ commit }) {
let url = `/api/1.0/main/whoami.json`;
window.fetch(url)
.then(response => {
if (response.ok) {
return response.json();
}
throw { m: 'Error while retriving results for goal', s: response.status, b: response.body };
})
.then(data => {
commit('setWhoAmiI', data);
});
},
updateThirdParty({ commit }, payload) {
commit('updateThirdParty', payload);
},
@ -514,6 +531,7 @@ store.commit('setEvaluationsPicked', window.accompanyingCourseWork.accompanyingP
store.dispatch('getReachablesResultsForAction');
store.dispatch('getReachablesGoalsForAction');
store.dispatch('getReachablesEvaluationsForAction');
store.dispatch('getWhoAmI');
store.state.evaluationsPicked.forEach(evaluation => {
store.dispatch('fetchTemplatesAvailablesForEvaluation', evaluation.evaluation)

View File

@ -207,7 +207,7 @@
</template>
<script>
import {dateToISO} from 'ChillMainAssets/chill/js/date';
import {dateToISO, ISOToDate} from 'ChillMainAssets/chill/js/date';
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
import Confidential from 'ChillMainAssets/vuejs/_components/Confidential.vue';
import BadgeEntity from 'ChillMainAssets/vuejs/_components/BadgeEntity.vue';
@ -262,7 +262,7 @@ export default {
},
birthdate: function () {
if (this.person.birthdate !== null || this.person.birthdate === "undefined") {
return new Date(this.person.birthdate.datetime);
return ISOToDate(this.person.birthdate.datetime);
} else {
return "";
}

View File

@ -5,7 +5,8 @@
# - displayAction: [true|false] default: false
# - displayFontSmall: [true|false] default: false
#}
<div class="item-bloc{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<div
class="item-bloc{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<div class="item-row">
<h2 class="badge-title">
@ -111,9 +112,11 @@
</div>
{% if displayContent is not defined or displayContent == 'short' %}
<div class="item-row column{% if displayFontSmall is defined and displayFontSmall == true %} smallfont{% endif %}">
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {
'displayContent': displayContent
<div
class="item-row column{% if displayFontSmall is defined and displayFontSmall == true %} smallfont{% endif %}">
{% include '@ChillPerson/AccompanyingCourseWork/_objectifs_results_evaluations.html.twig' with {
'displayContent': displayContent,
'onlyone': false
} %}
</div>
{% endif %}
@ -121,35 +124,56 @@
{% if displayContent is not defined or displayContent == 'short' %}
<div class="item-row separator">
{% import '@ChillPerson/AccompanyingCourseWork/_macros.html.twig' as macro %}
{% import '@ChillPerson/AccompanyingCourseWork/_macros.html.twig' as macro %}
<div class="item-col item-meta">
{{ macro.metadata(w) }}
</div>
{% if displayAction is defined and displayAction == true %}
<ul class="item-col record_actions">
<li>{{ macro.workflowButton(w) }}</li>
<li>
<a class="btn btn-show" title="{{ 'Show'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': w.id }) }}"
></a>
</li>
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %}
<ul class="item-col record_actions">
<li>{{ macro.workflowButton(w) }}</li>
{% if displayNotification is defined and displayNotification == true %}
<li>
<div class="d-grid gap-2 {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}btn-group{% endif %}" {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}role="group"{% endif %}>
{% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}
<button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" title="{{ 'notification.Notify'|trans }}" data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': w.id, 'tos': [accompanyingCourse.user.id]}) }}">{{ 'notification.Notify referrer'|trans }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': w.id}) }}">{{ 'notification.Notify any'|trans }}</a>
</li>
</ul>
{% else %}
<a class="btn btn-notify" title="{{ 'notification.Notify'|trans }}" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': w.id}) }}">
</a>
{% endif %}
</div>
</li>
{% endif %}
<li>
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
<a class="btn btn-show" title="{{ 'Show'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': w.id }) }}"
></a>
</li>
{% endif %}
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE', w) %}
<li>
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
></a>
</li>
{% endif %}
</ul>
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w) %}
<li>
<a class="btn btn-edit" title="{{ 'Edit'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_edit', { 'id': w.id }) }}"
></a>
</li>
{% endif %}
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_DELETE', w) %}
<li>
<a class="btn btn-delete" title="{{ 'Delete'|trans }}"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': w.id } ) }}"
></a>
</li>
{% endif %}
</ul>
{% endif %}
</div>
{% endif %}
@ -157,47 +181,51 @@
</div>
{#
# This is for 'long' version of content
# Note: this include is wrapped in a flex-table container.
# We start by closing the flex-table so we can add more.
# At the end we leave the last flex-table open, as it will be closed in the container.
# This is for 'long' version of content
# Note: this include is wrapped in a flex-table container.
# We start by closing the flex-table so we can add more.
# At the end we leave the last flex-table open, as it will be closed in the container.
#}
{% if displayContent is defined and displayContent == 'long' %}
</div>
{% if w.results|length > 0 or w.goals|length > 0 or w.accompanyingPeriodWorkEvaluations|length > 0 %}
<h2 class="chill-blue">{{ 'Dispositifs' }}</h2>
<div class="flex-table">{# new flex-table wrapper #}
<div
class="item-bloc colored{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {
'displayContent': displayContent,
'onlyone' : false
} %}
</div>
</div>
{% endif %}
{% if w.results|length > 0 or w.goals|length > 0 or w.accompanyingPeriodWorkEvaluations|length > 0 %}
<h2 class="chill-blue">{{ 'Dispositifs' }}</h2>
<h2 class="chill-blue">{{ 'Comments'|trans }}</h2>
<div class="flex-table">{# new flex-table wrapper #}
<div class="item-bloc colored{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
{% include 'ChillPersonBundle:AccompanyingCourseWork:_objectifs_results_evaluations.html.twig' with {
'displayContent': displayContent
} %}
</div>
<div class="flex-table">
<div
class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<h3 class="chill-beige">Public</h3>
{% if w.note is not empty %}
<blockquote class="chill-user-quote">
{{ w.note|chill_entity_render_box({'metadata': true }) }}
</blockquote>
{% else %}
<span class="chill-no-data-statement">{{ 'No comment associated'|trans }}</span>
{% endif %}
</div>
{% if w.privateComment.hasCommentForUser(app.user) %}
<div
class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<h3 class="chill-beige">Privé</h3>
<blockquote class="chill-user-quote private-quote">
{{ w.privateComment.commentForUser(app.user)|chill_markdown_to_html }}
</blockquote>
</div>
{% endif %}
<h2 class="chill-blue">{{ 'Comments'|trans }}</h2>
<div class="flex-table">
<div class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<h3 class="chill-beige">Public</h3>
{% if w.note is not empty %}
<blockquote class="chill-user-quote">
{{ w.note|chill_entity_render_box({'metadata': true }) }}
</blockquote>
{% else %}
<span class="chill-no-data-statement">{{ 'No comment associated'|trans }}</span>
{% endif %}
</div>
{% if w.privateComment.hasCommentForUser(app.user) %}
<div class="item-bloc no-altern{% if displayContent is defined %} {{ displayContent }}{% endif %}{% if itemBlocClass is defined %} {{ itemBlocClass }}{% endif %}">
<h3 class="chill-beige">Privé</h3>
<blockquote class="chill-user-quote private-quote">
{{ w.privateComment.commentForUser(app.user)|chill_markdown_to_html }}
</blockquote>
</div>
{% endif %}
{# Here flex-table stay open ! read above #}
{% endif %}
{% endif %}

View File

@ -1,7 +1,17 @@
{% macro metadata(w) %}
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) %}
{% if notif_counter.total > 0 %}
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id) }}
{% macro metadata(w, include_notif_counter = true) %}
{% if include_notif_counter == true %}
{% set more = [] %}
{% for e in w.accompanyingPeriodWorkEvaluations %}
{% for d in e.documents %}
{% set more = more|merge([{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id}]) %}
{% endfor %}
{% endfor %}
{% set notif_counter = chill_count_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, more) %}
{% if notif_counter.total > 0 %}
{{ chill_counter_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, more) }}
{% endif %}
{% endif %}
{% import '@ChillPerson/Macro/updatedBy.html.twig' as macro %}
{{ macro.updatedBy(w) }}
@ -20,4 +30,4 @@
{% endfor %}
{% endfor %}
{{ chill_entity_workflow_list('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', w.id, [], suppEvaluations) }}
{% endmacro %}
{% endmacro %}

View File

@ -3,6 +3,7 @@
# - displayContent: [short|long] default: short
#}
{% if w.results|length > 0 %}
<table class="obj-res-eval">
<thead>
<th class="obj"><h4 class="title_label">{{ 'accompanying_course_work.goal'|trans }}</h4></th>
@ -64,9 +65,11 @@
</th>
</thead>
<tbody>
{% for e in w.accompanyingPeriodWorkEvaluations %}
<tr>
<td class="eval">
{% if onlyone|default(false) %}
{% for e in w.accompanyingPeriodWorkEvaluations %}
{% if evalId is defined and evalId == e.id %}
<tr>
<td class="eval">
<ul class="eval_title">
<li>
{{ e.evaluation.title|localize_translatable_string }}
@ -78,13 +81,15 @@
{% if e.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ e.endDate|format_date('short') }}</b>
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
@ -92,13 +97,15 @@
{% if e.maxDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ e.maxDate|format_date('short') }}</b>
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
@ -107,13 +114,15 @@
{% if e.warningInterval and e.warningInterval.d > 0 %}
<li>
{% set days = (e.warningInterval.d + e.warningInterval.m * 30) %}
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
<span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
<span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
@ -122,45 +131,166 @@
{% if e.timeSpent is not null and e.timeSpent > 0 %}
<li>
{% set minutes = (e.timeSpent / 60) %}
<span class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> {{ 'duration.minute'|trans({ '{m}' : minutes }) }}
<span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> {{ 'duration.minute'|trans({ '{m}' : minutes }) }}
</li>
{% elseif displayContent is defined and displayContent == 'long' %}
<li>
<span class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span>
<span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
</ul>
</li>
</ul>
{% if displayContent is defined and displayContent == 'long' %}
{% endif %}
{% if e.comment is not empty %}
<blockquote class="chill-user-quote">{{ e.comment|chill_entity_render_box }}</blockquote>
{% endfor %}
{% if recordAction is defined %}
{{ recordAction }}
{% endif %}
{% else %}
{% for e in w.accompanyingPeriodWorkEvaluations %}
<tr>
<td class="eval">
<ul class="eval_title">
<li>
{{ e.evaluation.title|localize_translatable_string }}
<ul class="columns">
<li>
<span
class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ e.startDate|format_date('short') }}</b>
</li>
{% if e.endDate %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ e.endDate|format_date('short') }}</b>
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
{% endif %}
{% if e.maxDate %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ e.maxDate|format_date('short') }}</b>
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
{% endif %}
{% if e.warningInterval and e.warningInterval.d > 0 %}
<li>
{% set days = (e.warningInterval.d + e.warningInterval.m * 30) %}
<span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% else %}
{% if displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
{% endif %}
{% if e.timeSpent is not null and e.timeSpent > 0 %}
<li>
{% set minutes = (e.timeSpent / 60) %}
<span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span> {{ 'duration.minute'|trans({ '{m}' : minutes }) }}
</li>
{% elseif displayContent is defined and displayContent == 'long' %}
<li>
<span
class="item-key">{{ 'accompanying_course_work.timeSpent'|trans ~ ' : ' }}</span>
<span class="chill-no-data-statement">{{ 'Not given'|trans }}</span>
</li>
{% endif %}
</ul>
</li>
</ul>
{% if recordAction is defined %}
{{ recordAction }}
{% endif %}
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% if displayContent is defined and displayContent == 'long' %}
{% if e.comment is not empty %}
<blockquote
class="chill-user-quote">{{ e.comment|chill_entity_render_box }}</blockquote>
{% endif %}
{% import "@ChillDocStore/Macro/macro.html.twig" as m %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
{% if e.documents|length > 0 %}
<table class="table table-hover align-middle mt-4 mx-auto">
{% for d in e.documents %}
<tr>
<td class="border-0">{{ d.title }}</td>
<td class="border-0">{{ mm.mimeIcon(d.storedObject.type) }}</td>
<td class="border-0 text-end">
{% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}
<button id="btnGroupNotifyButtons" type="button" class="btn btn-sm btn-notify dropdown-toggle" title="{{ 'notification.Notify'|trans }}"
data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
'entityId': d.id, 'tos': [accompanyingCourse.user.id]}) }}">{{ 'notification.Notify referrer'|trans }}</a>
</li>
<li>
<a class="dropdown-item"
href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
'entityId': d.id}) }}">{{ 'notification.Notify any'|trans }}</a>
</li>
</ul>
{% else %}
<a class="btn btn-notify btn-sm"
title="{{ 'notification.Notify'|trans }}"
href="{{ chill_path_add_return_path('chill_main_notification_create', {
'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument',
'entityId': d.id }) }}"></a>
{% endif %}
{{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}
</td>
</tr>
{% endfor %}
</table>
{% else %}
<span class="chill-no-data-statement">{{ 'No document found'|trans }}</span>
{% endif %}
{% if e.documents|length > 0 %}
<table class="table mt-4 mx-auto">
{% for d in e.documents %}
<tr class="border-0">
<td class="border-0">{{ d.title }}</td>
<td class="border-0">{{ mm.mimeIcon(d.storedObject.type) }}</td>
<td class="border-0 text-end">{{ d.storedObject|chill_document_button_group(d.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', w), {'small': true}) }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<span class="chill-no-data-statement">{{ 'No document found'|trans }}</span>
{% endif %}
{% endif %}
</td>
</tr>
{% endfor %}
</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
{% endif %}

View File

@ -26,7 +26,8 @@
'displayAction': true,
'displayContent': 'short',
'displayFontSmall': true,
'itemBlocClass': ''
'itemBlocClass': '',
'displayNotification': true
} %}
{% endfor %}
</div>

View File

@ -27,7 +27,20 @@
'displayContent': 'long',
'itemBlocClass': 'uniq extended',
} %}
<div class="p-3 mt-3">{{ macro.metadata(work) }}</div>
<div class="p-3 mt-3">{{ macro.metadata(work, false) }}</div>
</div>
<div class="notification notification-list">
{% set more = [] %}
{% for e in work.accompanyingPeriodWorkEvaluations %}
{% for d in e.documents %}
{% set more = more|merge([{'relatedEntityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument', 'relatedEntityId': d.id}]) %}
{% endfor %}
{% endfor %}
{% set notifications = chill_list_notifications('Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', work.id, more) %}
{% if notifications is not empty %}
{{ notifications|raw }}
{% endif %}
</div>
<ul class="record_actions sticky-form-buttons">
@ -36,6 +49,25 @@
class="btn btn-cancel">{{ 'Back to the list'|trans }}</a>
</li>
<li>{{ macro.workflowButton(work) }}</li>
<li>
<div class="d-grid gap-2 {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}btn-group{% endif %}" {% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}role="group"{% endif %}>
{% if accompanyingCourse.hasUser and accompanyingCourse.user is not same as(app.user) %}
<button id="btnGroupNotifyButtons" type="button" class="btn btn-notify dropdown-toggle" title="{{ 'notification.Notify'|trans }}" data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupNotifyButtons">
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': work.id, 'tos': [accompanyingCourse.user.id]}) }}">{{ 'notification.Notify referrer'|trans }}</a>
</li>
<li>
<a class="dropdown-item" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': work.id}) }}">{{ 'notification.Notify any'|trans }}</a>
</li>
</ul>
{% else %}
<a class="btn btn-notify" title="{{ 'notification.Notify'|trans }}" href="{{ chill_path_add_return_path('chill_main_notification_create', {'entityClass': 'Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWork', 'entityId': work.id}) }}">
</a>
{% endif %}
</div>
</li>
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', work) %}
<li>
<a class="btn btn-edit"
@ -47,7 +79,8 @@
<li>
<a class="btn btn-delete"
href="{{ path('chill_person_accompanying_period_work_delete', { 'id': work.id } ) }}"
>{{ 'Delete'|trans }}</a>
title = "{{ 'Delete'|trans }}"
></a>
</li>
{% endif %}
</ul>

View File

@ -0,0 +1,134 @@
{% if document is not null %}
{% import "@ChillDocStore/Macro/macro_mimeicon.html.twig" as mm %}
<div class="flex-table accompanying-course-work">
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_EVALUATION_DOCUMENT_SHOW', document) %}
{% set doc = document %}
<div class="item-bloc evaluation-item bg-chill-llight-gray">
<div class="item-row mb-2">
<h1>{{ "Document"|trans }}: {{ doc.title }}</h1>
</div>
<div class="item-row mb-2">
<h2 class="badge-title">
<span class="title_label"></span>
<span class="title_action">
{{ evaluation.accompanyingPeriodWork.socialAction|chill_entity_render_string }}
<ul class="small_in_title columns mt-1">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.startDate|format_date('short') }}</b>
</li>
{% if evaluation.accompanyingPeriodWork.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.accompanyingPeriodWork.endDate|format_date('short') }}</b>
</li>
{% endif %}
</ul>
</span>
</h2>
</div>
<div class="item-row mb-2">
<div class="item-col" style="width: 17%;">
<h4 class="title_label">
{{ 'Participants'|trans }}
</h4>
</div>
<div class="item-col list">
{% for p in evaluation.accompanyingPeriodWork.persons %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {
targetEntity: { name: 'person', id: p.id },
action: 'show',
displayBadge: true,
buttonText: p|chill_entity_render_string,
isDead: p.deathdate is not null
} %}
{% endfor %}
</div>
</div>
<div class="item-row column">
<table class="obj-res-eval my-3">
<thead>
<tr>
<th class="eval">
<h4 class="title_label">
{{ 'Évaluation'|trans }}
</h4>
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="eval">
<ul class="eval_title">
<li class="my-2">
{{ evaluation.evaluation.title|localize_translatable_string }}
<ul class="columns pt-2">
<li>
<span class="item-key">{{ 'accompanying_course_work.start_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.startDate|format_date('short') }}</b>
</li>
{% if evaluation.endDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.end_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.endDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.maxDate %}
<li>
<span class="item-key">{{ 'accompanying_course_work.max_date'|trans ~ ' : ' }}</span>
<b>{{ evaluation.maxDate|format_date('short') }}</b>
</li>
{% endif %}
{% if evaluation.warningInterval and evaluation.warningInterval.d > 0 %}
<li>
{% set days = (evaluation.warningInterval.d + evaluation.warningInterval.m * 30) %}
<span class="item-key">{{ 'accompanying_course_work.warning_interval'|trans ~ ' : ' }}</span>
{{ 'accompanying_course_work.%days% days before max_date'|trans({'%days%': days }) }}
</li>
{% endif %}
<li>
{% if evaluation.createdBy is not null %}
<span class="item-key">créé par</span>
<b>{{ evaluation.createdBy.username }}</b>
{% endif %}
{% if evaluation.createdAt is not null %}
<span class="item-key">{{ 'le'|trans }}</span>
<b>{{ evaluation.createdAt|format_date('short') }}</b>
{% endif %}
</li>
</ul>
{% if evaluation.comment %}
<blockquote class="chill-user-quote" style="margin-left: 0;">
{{ evaluation.comment }}
</blockquote>
{% endif %}
</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
<div class="item-row">
<ul class="record_actions">
<li>
<a class="btn btn-show"
href="{{ path('chill_person_accompanying_period_work_show', { 'id': document.accompanyingPeriodWorkEvaluation.accompanyingPeriodWork.id }) }}"
title="{{ 'See the document'|trans }}"></a>
</li>
</ul>
</div>
</div>
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'This is the minimal period details'|trans ~ ': ' ~ document.id }}<br>
{{ 'You are getting a notification for a period you are not allowed to see'|trans }}
</div>
{% endif %}
</div>
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'You are getting a notification for a period which does not exists any more'|trans }}
</div>
{% endif %}

View File

@ -0,0 +1,30 @@
{% macro recordAction(work) %}
<li>
<a class="btn btn-show" title="{{ 'Show'|trans }}"
href="{{ chill_path_add_return_path('chill_person_accompanying_period_work_show', {'id': work.id }) }}"
></a>
</li>
{% endmacro %}
{% if work is not null %}
<div class="flex-table accompanying-course-work">
{% if is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_SEE', work) %}
{% include "@ChillPerson/AccompanyingCourseWork/_item.html.twig" with {
'itemBlocClass': 'bg-chill-llight-gray',
'displayAction': true,
'displayContent': 'short',
'displayFontSmall': true,
'displayNotification:':true,
'w': work
} %}
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'This is the minimal period details'|trans ~ ': ' ~ work.id }}<br>
{{ 'You are getting a notification for a period you are not allowed to see'|trans }}
</div>
{% endif %}
</div>
{% else %}
<div class="alert alert-warning border-warning border-1">
{{ 'You are getting a notification for a period which does not exists any more'|trans }}
</div>
{% endif %}

View File

@ -16,11 +16,13 @@
</div>
<div class="wh-col">
{% if period.step == 'DRAFT' %}
<span class="badge bg-secondary">{{- 'Draft'|trans|upper -}}</span>
{% elseif period.step == 'CONFIRMED' %}
<span class="badge bg-primary">{{- 'Confirmed'|trans|upper -}}</span>
{% else %}
<span class="badge bg-danger">{{- 'Closed'|trans|upper -}}</span>
<span class="badge bg-secondary" style="font-size: 85%;" title="{{ 'course.draft'|trans|e('html_attr') }}">{{ 'course.draft'|trans }}</span>
{% elseif period.step == 'CLOSED' %}
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.closed'|trans|e('html_attr') }}">{{ 'course.closed'|trans }}</span>
{% elseif period.step == 'CONFIRMED_INACTIVE_SHORT' %}
<span class="badge bg-chill-yellow text-primary" style="font-size: 85%;" title="{{ 'course.inactive_short'|trans|e('html_attr') }}">{{ 'course.inactive_short'|trans }}</span>
{% elseif period.step == 'CONFIRMED_INACTIVE_LONG' %}
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.inactive_long'|trans|e('html_attr') }}">{{ 'course.inactive_long'|trans }}</span>
{% endif %}
</div>
</div>

View File

@ -8,7 +8,7 @@
{% if period is not null %}
<div class="flex-table">
{% if is_granted('CHILL_PERSON_ACCOMPANYING_PERIOD_SEE', period) %}
{% include 'ChillPersonBundle:AccompanyingPeriod:_list_item.html.twig' with {
{% include "@ChillPerson/AccompanyingPeriod/_list_item.html.twig" with {
'recordAction': _self.recordAction(notification.relatedEntityId),
'itemBlocClass': 'bg-chill-llight-gray'
} %}

View File

@ -17,6 +17,15 @@
<div class="col-md-10">
<h1>{{ 'My accompanying periods'|trans }}</h1>
<ul class="nav nav-pills justify-content-center">
<li class="nav-item">
<a class="nav-link {% if active == true %}active{% endif %}" aria-current="page" href="{{ chill_path_forward_return_path('chill_person_accompanying_period_user', {'active': true}) }}">{{ ['Confirmed'|trans, 'course.inactive_short'|trans, 'course.inactive_long'|trans]|join(', ') }}</a>
</li>
<li class="nav-item ">
<a class="nav-link {% if active == false %}active{% endif %}" href="{{ chill_path_forward_return_path('chill_person_accompanying_period_user', {'active': false}) }}">{{ 'course.closed'|trans }}</a>
</li>
</ul>
<p>{{ 'Number of periods'|trans }}: <span class="badge rounded-pill bg-primary">{{ pagination.totalItems }}</span></p>
<div class="flex-table accompanyingcourse-list">

View File

@ -40,13 +40,14 @@
{{ 'Household summary'|trans }}
</a>
</li>
{# TODO: add ACL to check if user is allowed to edit household? #}
<li>
<a class="btn btn-create"
href="{{ path ('chill_household_accompanying_course_new', {'household_id' : household.id } ) }}" role="button">
{{ 'Create an accompanying period'|trans }}
</a>
</li>
{% if is_granted('CHILL_PERSON_HOUSEHOLD_EDIT', household) %}
<li>
<a class="btn btn-create"
href="{{ path ('chill_household_accompanying_course_new', {'household_id' : household.id } ) }}" role="button">
{{ 'Create an accompanying period'|trans }}
</a>
</li>
{% endif %}
</ul>
</div>

View File

@ -39,7 +39,7 @@
<li><b>{{ person.counters.nb_activity }}</b> {{ (person.counters.nb_activity > 1)? 'échanges' : 'échange' }}</li>
<li><b>{{ person.counters.nb_task }}</b> {{ (person.counters.nb_task > 1)? 'tâches' : 'tâche' }}</li>
<li><b>{{ person.counters.nb_document }}</b> {{ (person.counters.nb_document > 1)? 'documents' : 'document' }}</li>
<li><b>{{ person.counters.nb_event }}</b> {{ (person.counters.nb_event > 1)? 'événements' : 'événement' }}</li>
{# <li><b>{{ person.counters.nb_event }}</b> {{ (person.counters.nb_event > 1)? 'événements' : 'événement' }}</li>#}
<li><b>{{ person.counters.nb_addresses }}</b> {{ (person.counters.nb_addresses > 1)? 'adresses' : 'adresse' }}</li>
</ul>

View File

@ -25,7 +25,7 @@
<h1>{{ 'Merge duplicate persons folders'|trans }}</h1>
<div class="col-md-6">
<div class="col-md-11">
<p><b>{{ 'Old person'|trans }}</b>:
{{ 'Old person explain'|trans }}
</p>
@ -43,7 +43,7 @@
</div>
</div>
<div class="col-md-6">
<div class="col-md-11">
<p><b>{{ 'New person'|trans }}</b>:
{{ 'New person explain'|trans }}
</p>
@ -63,10 +63,10 @@
{{ form_start(form) }}
<div class="col-md-4 centered">
<div class="col-md-12 centered">
<div class="container-fluid" style="padding-top: 1em;">
<div class="col-1 clear" style="padding-top: 10px;">
<div class="clear" style="padding-top: 10px;">
{{ form_widget(form.confirm) }}
</div>
<div class="col-11">

View File

@ -3,6 +3,14 @@
{{ 'workflow.SocialAction deleted'|trans }}
</div>
{% else %}
<div class="flex-table accompanying-course-work">
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with {
'w': work,
'displayAction': false,
'displayContent': 'short',
'itemBlocClass': 'bg-chill-light-gray'
} %}
</div>
{% if display_action is defined and display_action == true %}
<ul class="record_actions">
<li>

View File

@ -123,7 +123,7 @@
<ul class="record_actions">
<li>{{ doc.storedObject|chill_document_button_group(doc.title, is_granted('CHILL_MAIN_ACCOMPANYING_PERIOD_WORK_UPDATE', evaluation.accompanyingPeriodWork)) }}</li>
<li>
<a class="btn btn-show" href="{{ path('chill_person_accompanying_period_work_edit', {'id': evaluation.accompanyingPeriodWork.id}) }}">
<a class="btn btn-show" href="{{ path('chill_person_accompanying_period_work_edit', {'id': evaluation.accompanyingPeriodWork.id, 'doc_id': doc.id}) }}">
{{ 'Show'|trans }}
</a>
</li>

View File

@ -18,6 +18,7 @@ use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;
@ -119,6 +120,7 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleH
->generate(self::class)
->addCheckFor(null, [self::CREATE, self::REASSIGN_BULK])
->addCheckFor(AccompanyingPeriod::class, [self::TOGGLE_CONFIDENTIAL, ...self::ALL])
->addCheckFor(Household::class, [self::SEE])
->addCheckFor(Person::class, [self::SEE, self::CREATE])
->addCheckFor(Center::class, [self::STATS])
->build();

View File

@ -22,10 +22,14 @@ use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\DocumentCategoryRepository;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
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 DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
@ -40,6 +44,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists;
/**
* @see AccompanyingPeriodContextTest
* @template-implements DocGeneratorContextWithPublicFormInterface<AccompanyingPeriod>
*/
class AccompanyingPeriodContext implements
@ -62,6 +67,10 @@ class AccompanyingPeriodContext implements
private TranslatorInterface $translator;
private ThirdPartyRender $thirdPartyRender;
private ThirdPartyRepository $thirdPartyRepository;
public function __construct(
DocumentCategoryRepository $documentCategoryRepository,
NormalizerInterface $normalizer,
@ -70,7 +79,9 @@ class AccompanyingPeriodContext implements
PersonRenderInterface $personRender,
PersonRepository $personRepository,
TranslatorInterface $translator,
BaseContextData $baseContextData
BaseContextData $baseContextData,
ThirdPartyRender $thirdPartyRender,
ThirdPartyRepository $thirdPartyRepository
) {
$this->documentCategoryRepository = $documentCategoryRepository;
$this->normalizer = $normalizer;
@ -80,6 +91,8 @@ class AccompanyingPeriodContext implements
$this->personRepository = $personRepository;
$this->translator = $translator;
$this->baseContextData = $baseContextData;
$this->thirdPartyRender = $thirdPartyRender;
$this->thirdPartyRepository = $thirdPartyRepository;
}
public function adminFormReverseTransform(array $data): array
@ -103,11 +116,12 @@ class AccompanyingPeriodContext 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('Third party'),
];
if (array_key_exists('category', $data)) {
$r['category'] = array_key_exists('category', $data) ?
$this->documentCategoryRepository->find($data['category']) : null;
$r['category'] = $this->documentCategoryRepository->find($data['category']);
}
return $r;
@ -140,6 +154,14 @@ class AccompanyingPeriodContext implements
'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,
])
->add('category', EntityType::class, [
'placeholder' => 'Choose a document category',
'class' => DocumentCategory::class,
@ -190,6 +212,28 @@ class AccompanyingPeriodContext implements
]);
}
}
$thirdParties = [...array_values(array_filter([$entity->getRequestorThirdParty()])), ...array_values(array_filter(
array_map(
fn (Resource $r): ?ThirdParty => $r->getThirdParty(),
$entity->getResources()->filter(
static fn (Resource $r): bool => null !== $r->getThirdParty()
)->toArray()
)
))];
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 getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
@ -215,6 +259,13 @@ class AccompanyingPeriodContext implements
}
}
if ($options['thirdParty']) {
$data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'], 'docgen', [
'docgen:expects' => ThirdParty::class,
'groups' => 'docgen:read'
]);
}
return $data;
}
@ -254,7 +305,7 @@ class AccompanyingPeriodContext implements
{
$options = $template->getOptions();
return $options['mainPerson'] || $options['person1'] || $options['person2'];
return $options['mainPerson'] || $options['person1'] || $options['person2'] || $options ['thirdParty'];
}
public function contextGenerationDataNormalize(DocGeneratorTemplate $template, $entity, array $data): array
@ -264,6 +315,8 @@ class AccompanyingPeriodContext implements
$normalized[$k] = null !== ($data[$k] ?? null) ? $data[$k]->getId() : null;
}
$normalized['thirdParty'] = ($data['thirdParty'] ?? null)?->getId();
return $normalized;
}
@ -279,6 +332,12 @@ class AccompanyingPeriodContext implements
}
}
if (null !== ($id = ($data['thirdParty'] ?? null))) {
$denormalized['thirdParty'] = $this->thirdPartyRepository->find($id);
} else {
$denormalized['thirdParty'] = null;
}
return $denormalized;
}

View File

@ -18,13 +18,18 @@ use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
use Chill\PersonBundle\Entity\SocialWork\Evaluation;
use Chill\PersonBundle\Repository\SocialWork\EvaluationRepository;
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\FormBuilderInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @implements DocGeneratorContextWithPublicFormInterface<AccompanyingPeriodWorkEvaluation>
@ -43,18 +48,26 @@ class AccompanyingPeriodWorkEvaluationContext implements
private TranslatableStringHelperInterface $translatableStringHelper;
private ThirdPartyRender $thirdPartyRender;
private TranslatorInterface $translator;
public function __construct(
AccompanyingPeriodWorkContext $accompanyingPeriodWorkContext,
EntityManagerInterface $em,
EvaluationRepository $evaluationRepository,
NormalizerInterface $normalizer,
TranslatableStringHelperInterface $translatableStringHelper
TranslatableStringHelperInterface $translatableStringHelper,
ThirdPartyRender $thirdPartyRender,
TranslatorInterface $translator
) {
$this->accompanyingPeriodWorkContext = $accompanyingPeriodWorkContext;
$this->em = $em;
$this->evaluationRepository = $evaluationRepository;
$this->normalizer = $normalizer;
$this->translatableStringHelper = $translatableStringHelper;
$this->thirdPartyRender = $thirdPartyRender;
$this->translator = $translator;
}
public function adminFormReverseTransform(array $data): array
@ -102,6 +115,31 @@ class AccompanyingPeriodWorkEvaluationContext implements
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
{
$this->accompanyingPeriodWorkContext->buildPublicForm($builder, $template, $entity->getAccompanyingPeriodWork());
$thirdParties = [...array_values(array_filter($entity->getAccompanyingPeriodWork()->getThirdParties()->toArray())), ...array_values(array_filter([$entity->getAccompanyingPeriodWork()->getHandlingThierParty()])), ...array_values(
array_filter(
array_map(
fn (Resource $r): ?ThirdParty => $r->getThirdParty(),
$entity->getAccompanyingPeriodWork()->getAccompanyingPeriod()->getResources()->filter(
static fn (Resource $r): bool => null !== $r->getThirdParty()
)->toArray()
)
)
)];
$options = $template->getOptions();
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 getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
@ -116,7 +154,6 @@ class AccompanyingPeriodWorkEvaluationContext implements
AbstractNormalizer::GROUPS => ['docgen:read'],
]
);
return $data;
}

View File

@ -26,14 +26,22 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Person\PersonResource;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\ResidentialAddress;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Repository\ResidentialAddressRepository;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use LogicException;
use Service\DocGenerator\PersonContextTest;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Core\Security;
@ -43,6 +51,9 @@ use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists;
use function count;
/**
* @see PersonContextTest
*/
final class PersonContext implements PersonContextInterface
{
private AuthorizationHelperInterface $authorizationHelper;
@ -67,6 +78,12 @@ final class PersonContext implements PersonContextInterface
private TranslatorInterface $translator;
private ThirdPartyRender $thirdPartyRender;
private ThirdPartyRepository $thirdPartyRepository;
private ResidentialAddressRepository $residentialAddressRepository;
public function __construct(
AuthorizationHelperInterface $authorizationHelper,
BaseContextData $baseContextData,
@ -78,7 +95,10 @@ final class PersonContext implements PersonContextInterface
ScopeRepositoryInterface $scopeRepository,
Security $security,
TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper
TranslatableStringHelperInterface $translatableStringHelper,
ThirdPartyRender $thirdPartyRender,
ThirdPartyRepository $thirdPartyRepository,
ResidentialAddressRepository $residentialAddressRepository
) {
$this->authorizationHelper = $authorizationHelper;
$this->centerResolverManager = $centerResolverManager;
@ -91,6 +111,9 @@ final class PersonContext implements PersonContextInterface
$this->showScopes = $parameterBag->get('chill_main')['acl']['form_show_scopes'];
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->thirdPartyRender = $thirdPartyRender;
$this->thirdPartyRepository = $thirdPartyRepository;
$this->residentialAddressRepository = $residentialAddressRepository;
}
public function adminFormReverseTransform(array $data): array
@ -110,11 +133,12 @@ final class PersonContext implements PersonContextInterface
$r = [
'mainPerson' => $data['mainPerson'] ?? false,
'mainPersonLabel' => $data['mainPersonLabel'] ?? $this->translator->trans('docgen.Main person'),
'thirdParty' => $data['thirdParty'] ?? false,
'thirdPartyLabel' => $data['thirdPartyLabel'] ?? $this->translator->trans('Third party'),
];
if (array_key_exists('category', $data)) {
$r['category'] = array_key_exists('category', $data) ?
$this->documentCategoryRepository->find($data['category']) : null;
$r['category'] = $this->documentCategoryRepository->find($data['category']);
}
return $r;
@ -131,6 +155,14 @@ final class PersonContext implements PersonContextInterface
->setParameter('docClass', PersonDocument::class),
'choice_label' => fn ($entity = null) => $entity ? $this->translatableStringHelper->localize($entity->getName()) : '',
'required' => true,
])
->add('thirdParty', CheckboxType::class, [
'required' => false,
'label' => 'docgen.Ask for thirdParty',
])
->add('thirdPartyLabel', TextType::class, [
'label' => 'docgen.thirdParty label',
'required' => true,
]);
}
@ -139,12 +171,47 @@ final class PersonContext implements PersonContextInterface
*/
public function buildPublicForm(FormBuilderInterface $builder, DocGeneratorTemplate $template, $entity): void
{
$options = $template->getOptions();
$builder->add('title', TextType::class, [
'required' => true,
'label' => 'docgen.Document title',
'data' => $this->translatableStringHelper->localize($template->getName()),
]);
$thirdParties = [...array_values(
array_filter(
array_map(
fn (ResidentialAddress $r): ?ThirdParty => $r->getHostThirdParty(),
$this
->residentialAddressRepository
->findCurrentResidentialAddressByPerson($entity)
)
)
), ...array_values(
array_filter(
array_map(
fn (PersonResource $r): ?ThirdParty => $r->getThirdParty(),
$entity->getResources()->filter(
static fn (PersonResource $r): bool => null !== $r->getThirdParty()
)->toArray()
)
)
)];
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'),
]);
}
if ($this->isScopeNecessary($entity)) {
$builder->add('scope', ScopePickerType::class, [
'center' => $this->centerResolverManager->resolveCenters($entity),
@ -156,10 +223,6 @@ final class PersonContext implements PersonContextInterface
public function getData(DocGeneratorTemplate $template, $entity, array $contextGenerationData = []): array
{
if (!$entity instanceof Person) {
throw new UnexpectedTypeException($entity, Person::class);
}
$data = [];
$data = array_merge($data, $this->baseContextData->getData($contextGenerationData['creator'] ?? null));
$data['person'] = $this->normalizer->normalize($entity, 'docgen', [
@ -170,6 +233,13 @@ final class PersonContext implements PersonContextInterface
'docgen:person:with-budget' => true,
]);
if ($template->getOptions()['thirdParty']) {
$data['thirdParty'] = $this->normalizer->normalize($contextGenerationData['thirdParty'], 'docgen', [
'docgen:expects' => ThirdParty::class,
'groups' => 'docgen:read'
]);
}
return $data;
}
@ -223,6 +293,7 @@ final class PersonContext implements PersonContextInterface
return [
'title' => $data['title'] ?? '',
'scope_id' => $scope instanceof Scope ? $scope->getId() : null,
'thirdParty' => ($data['thirdParty'] ?? null)?->getId(),
];
}
@ -242,6 +313,7 @@ final class PersonContext implements PersonContextInterface
return [
'title' => $data['title'] ?? '',
'scope' => $scope,
'thirdParty' => null !== ($id = ($data['thirdParty'] ?? null)) ? $this->thirdPartyRepository->find($id) : null,
];
}

Some files were not shown because too many files have changed in this diff Show More