Compare commits

..

47 Commits

Author SHA1 Message Date
111f72d996 Change free text field to choice field within cancel reason form 2025-09-29 19:27:15 +02:00
67c29728f3 Change if-logic in CalendarEntityListener.php
CalendarRemovedMessage should be triggered upon 'editing' the Calendar entity setting it's status to canceled. Otherwise trigger CalendarMessage instead
2025-09-29 19:26:40 +02:00
0f62ddc3d6 Change naming of CancelReason constant 2025-09-29 19:25:08 +02:00
db3833190e Fix translation of admin cancel reason column 2025-09-29 19:24:03 +02:00
fbb4e8c784 Add cancel reason within the cancel label 2025-09-29 19:22:59 +02:00
a41414a460 Fix dependencyInjection error for cancel reason controller implementing CRUDController 2025-09-29 19:19:12 +02:00
a3c1aa4679 Set isActive on cancelReason to true by default 2025-09-29 19:18:43 +02:00
99b801fb8a Remove double controller method after rebase on master 2025-09-29 17:04:22 +02:00
4087911262 Pipeline fixes 2025-09-29 17:01:49 +02:00
d6a50888e0 Use translation with capital letter 2025-09-29 17:01:00 +02:00
705f3b6f76 Only change sms status upon cancellation if sendSMS is true 2025-09-29 16:59:21 +02:00
6f8b25f457 Re-establish canceled tag and strikethrough 2025-09-29 16:59:20 +02:00
ffef8b70a3 work on syncing with remote calendar 2025-09-29 16:58:57 +02:00
43a3d5c902 Set the sms status to 'canceled' 2025-09-29 16:58:57 +02:00
197b17fa76 Use a different button type 2025-09-29 16:58:55 +02:00
229d62e676 Add translation, make reason required, and change template for form 2025-09-29 16:57:54 +02:00
b51a7cfc57 Fix form to submit cancel reason 2025-09-29 16:57:35 +02:00
5d40948a63 Add cancel reason form 2025-09-29 16:57:33 +02:00
9c1df94796 Initial setup of controller and templates 2025-09-29 16:57:07 +02:00
24eb220e90 Pipeline corrections 2025-09-29 16:55:39 +02:00
9a262a3f43 Adjust display of canceled calendar items in the list 2025-09-29 16:54:48 +02:00
b421e0b0af Add a logger notice when calendar is canceled 2025-09-29 16:54:21 +02:00
20b82ddbfc Set the sms status to 'canceled' 2025-09-29 16:54:21 +02:00
a568d3c5a2 Use a different button type 2025-09-29 16:54:20 +02:00
260a42c35a Only show cancel button with EDIT permission 2025-09-29 16:53:54 +02:00
5b4f47bfaa Add translation, make reason required, and change template for form 2025-09-29 16:53:19 +02:00
60706ae75b Fix form to submit cancel reason 2025-09-29 16:53:19 +02:00
b561b12213 Add cancel reason form 2025-09-29 16:53:17 +02:00
f4369f0e3c Initial setup of controller and templates 2025-09-29 16:52:27 +02:00
afa755f5a5 Add condition for preparing cancellation message 2025-09-29 16:51:29 +02:00
cb2308f808 template adjustments + add missing translations 2025-09-29 16:51:29 +02:00
432650e081 work on syncing with remote calendar 2025-09-29 16:51:29 +02:00
4215a90b0e Set the sms status to 'canceled' 2025-09-29 16:51:29 +02:00
24daae6ee5 Add translation, make reason required, and change template for form 2025-09-29 16:51:29 +02:00
5ae26a46eb Fix form to submit cancel reason 2025-09-29 16:51:29 +02:00
aec32b9bf8 Add cancel reason form 2025-09-29 16:51:29 +02:00
5b59bef990 Initial setup of controller and templates 2025-09-29 16:51:29 +02:00
e70de780a7 Pipeline corrections 2025-09-29 16:51:29 +02:00
a546ec1e6e Adjust display of canceled calendar items in the list 2025-09-29 16:51:29 +02:00
a5417855f2 Add a logger notice when calendar is canceled 2025-09-29 16:51:29 +02:00
2234093971 Set the sms status to 'canceled' 2025-09-29 16:51:29 +02:00
be499bee07 Use a different button type 2025-09-29 16:51:29 +02:00
55faeaec39 Only show cancel button with EDIT permission 2025-09-29 16:51:28 +02:00
ac24df3f3c Add translation, make reason required, and change template for form 2025-09-29 16:51:28 +02:00
64dc458844 Fix form to submit cancel reason 2025-09-29 16:51:28 +02:00
5f57b5eae3 Add cancel reason form 2025-09-29 16:51:28 +02:00
95de2c863d Initial setup of controller and templates 2025-09-29 16:51:28 +02:00
83 changed files with 644 additions and 1733 deletions

View File

@@ -0,0 +1,6 @@
kind: Fixed
body: Increased the number of required characters when setting a new password in Chill from 9 to 14 - GDPR compliance
time: 2025-09-18T11:40:44.858533536+02:00
custom:
Issue: "426"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: Fix the rendering of list of StoredObjectVersions, where there are kept version (before converting to pdf) and intermediate versions deleted
time: 2025-10-03T22:40:44.685474863+02:00
custom:
Issue: ""
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: Fixed
body: 'Notification: fix editing of sent notification by removing form.addressesEmails, a field that no longer exists'
time: 2025-10-06T12:13:15.45905994+02:00
custom:
Issue: "434"
SchemaChange: No schema change

View File

@@ -1,6 +0,0 @@
kind: UX
body: Change the terms 'cercle' and 'centre' to 'service', and 'territoire' respectively.
time: 2025-10-06T12:39:32.514056818+02:00
custom:
Issue: "425"
SchemaChange: No schema change

View File

@@ -16,5 +16,5 @@
- ajout d'un filtre et regroupement par usager participant sur les échanges - ajout d'un filtre et regroupement par usager participant sur les échanges
- ajout d'un regroupement: par type d'activité associé au parcours; - ajout d'un regroupement: par type d'activité associé au parcours;
- trie les filtre et regroupements par ordre alphabétique dans els exports - trie les filtre et regroupements par ordre alphabétique dans els exports
- ajout d'un paramètre qui permet de désactiver le filtre par territoire dans les exports - ajout d'un paramètre qui permet de désactiver le filtre par centre dans les exports
- correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date" - correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date"

View File

@@ -29,7 +29,7 @@
- ajout d'un regroupement par métier des intervenants sur un parcours; - ajout d'un regroupement par métier des intervenants sur un parcours;
- ajout d'un regroupement par service des intervenants sur un parcours; - ajout d'un regroupement par service des intervenants sur un parcours;
- ajout d'un regroupement par utilisateur intervenant sur un parcours - ajout d'un regroupement par utilisateur intervenant sur un parcours
- ajout d'un regroupement "par territoire de l'usager"; - ajout d'un regroupement "par centre de l'usager";
- ajout d'un filtre "par métier intervenant sur un parcours"; - ajout d'un filtre "par métier intervenant sur un parcours";
- ajout d'un filtre "par service intervenant sur un parcours"; - ajout d'un filtre "par service intervenant sur un parcours";
- création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot); - création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot);

View File

@@ -1,13 +0,0 @@
## v4.5.0 - 2025-10-03
### Feature
* Only allow delete of attachment on workflows that are not final
* Move up signature buttons on index workflow page for easier access
* Filter out document from attachment list if it is the same as the workflow document
* Block edition on attached document on workflow, if the workflow is finalized or sent external
* Convert workflow's attached document to pdf while sending them external
* After a signature is canceled or rejected, going to a waiting page until the post-process routines apply a workflow transition
### Fixed
* ([#426](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/426)) Increased the number of required characters when setting a new password in Chill from 9 to 14 - GDPR compliance
* Fix permissions on storedObject which are subject by a workflow
### DX
* Introduce a WaitingScreen component to display a waiting screen

View File

@@ -1,4 +0,0 @@
## v4.5.1 - 2025-10-03
### Fixed
* Add missing javascript dependency
* Add exception handling for conversion of attachment on sending external, when documens are already in pdf

View File

@@ -6,59 +6,40 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
and is generated by [Changie](https://github.com/miniscruff/changie). and is generated by [Changie](https://github.com/miniscruff/changie).
## v4.5.1 - 2025-10-03
### Fixed
* Add missing javascript dependency
* Add exception handling for conversion of attachment on sending external, when documens are already in pdf
## v4.5.0 - 2025-10-03
### Feature
* Only allow delete of attachment on workflows that are not final
* Move up signature buttons on index workflow page for easier access
* Filter out document from attachment list if it is the same as the workflow document
* Block edition on attached document on workflow, if the workflow is finalized or sent external
* Convert workflow's attached document to pdf while sending them external
* After a signature is canceled or rejected, going to a waiting page until the post-process routines apply a workflow transition
### Fixed
* ([#426](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/426)) Increased the number of required characters when setting a new password in Chill from 9 to 14 - GDPR compliance
* Fix permissions on storedObject which are subject by a workflow
### DX
* Introduce a WaitingScreen component to display a waiting screen
## v4.4.2 - 2025-09-12 ## v4.4.2 - 2025-09-12
### Fixed ### Fixed
* Fix document generation and workflow generation do not work on accompanying period work documents * Fix document generation and workflow generation do not work on accompanying period work documents
## v4.4.1 - 2025-09-11 ## v4.4.1 - 2025-09-11
### Fixed ### Fixed
* fix translations in duplicate evaluation document modal and realign close modal button * fix translations in duplicate evaluation document modal and realign close modal button
## v4.4.0 - 2025-09-11 ## v4.4.0 - 2025-09-11
### Feature ### Feature
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works * ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Allow the merge of two accompanying period works
* ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation * ([#369](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/369)) Duplication of a document to another accompanying period work evaluation
* ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works * ([#359](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/359)) Fusion of two accompanying period works
### Fixed ### Fixed
* Fix display of 'duplicate' and 'merge' buttons in CRUD templates * Fix display of 'duplicate' and 'merge' buttons in CRUD templates
* Fix saving notification preferences in user's profile * Fix saving notification preferences in user's profile
## v4.3.0 - 2025-09-08 ## v4.3.0 - 2025-09-08
### Feature ### Feature
* ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges * ([#409](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/409)) Add 45 and 60 min calendar ranges
* Add a command to generate a list of permissions * Add a command to generate a list of permissions
* ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date * ([#412](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/412)) Add an absence end date
**Schema Change**: Add columns or tables **Schema Change**: Add columns or tables
### Fixed ### Fixed
* fix date formatting in calendar range display * fix date formatting in calendar range display
* Change route URL to avoid clash with person duplicate controller method * Change route URL to avoid clash with person duplicate controller method
## v4.2.1 - 2025-09-03 ## v4.2.1 - 2025-09-03
### Fixed ### Fixed
* Fix exports to work with DirectExportInterface * Fix exports to work with DirectExportInterface
### DX ### DX
* Improve error message when a stored object cannot be written on local disk * Improve error message when a stored object cannot be written on local disk
## v4.2.0 - 2025-09-02 ## v4.2.0 - 2025-09-02
### Feature ### Feature
@@ -73,26 +54,26 @@ and is generated by [Changie](https://github.com/miniscruff/changie).
## v4.1.0 - 2025-08-26 ## v4.1.0 - 2025-08-26
### Feature ### Feature
* ([#400](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/400)) Add filter to social actions list to filter out actions where current user intervenes * ([#400](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/400)) Add filter to social actions list to filter out actions where current user intervenes
* ([#399](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/399)) Show filters on list pages unfolded by default * ([#399](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/399)) Show filters on list pages unfolded by default
* Expansion of event module with new fields in the creation form: thematic, internal/external animator, responsable, and budget elements. Filtering options in the event list + adapted exports * Expansion of event module with new fields in the creation form: thematic, internal/external animator, responsable, and budget elements. Filtering options in the event list + adapted exports
**Schema Change**: Add columns or tables **Schema Change**: Add columns or tables
### Fixed ### Fixed
* ([#382](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/382)) adjust display logic for accompanying period dates, include closing date if period is closed. * ([#382](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/382)) adjust display logic for accompanying period dates, include closing date if period is closed.
* ([#384](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/384)) add min and step attributes to integer field in DateIntervalType * ([#384](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/384)) add min and step attributes to integer field in DateIntervalType
### UX ### UX
* Limit display of participations in event list * Limit display of participations in event list
## v4.0.2 - 2025-07-09 ## v4.0.2 - 2025-07-09
### Fixed ### Fixed
* Fix add missing translation * Fix add missing translation
* Fix the transfer of evaluations and documents during of accompanyingperiodwork * Fix the transfer of evaluations and documents during of accompanyingperiodwork
## v4.0.1 - 2025-07-08 ## v4.0.1 - 2025-07-08
### Fixed ### Fixed
* Fix package.json for compilation * Fix package.json for compilation
## v4.0.0 - 2025-07-08 ## v4.0.0 - 2025-07-08
### Feature ### Feature
@@ -171,30 +152,30 @@ framework:
## v3.12.1 - 2025-06-30 ## v3.12.1 - 2025-06-30
### Fixed ### Fixed
* Fix loading of the list of documents * Fix loading of the list of documents
## v3.12.0 - 2025-06-30 ## v3.12.0 - 2025-06-30
### Feature ### Feature
* ([#377](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/377)) Add the document file name to the document title when a user upload a document, unless there is already a document title. * ([#377](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/377)) Add the document file name to the document title when a user upload a document, unless there is already a document title.
* Add desactivation date for social action and issue csv export * Add desactivation date for social action and issue csv export
* Add Emoji and Fullscreen feature to ckeditor configuration * Add Emoji and Fullscreen feature to ckeditor configuration
* ([#321](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/321)) Create editor which allow us to toggle between rich and simple text editor * ([#321](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/321)) Create editor which allow us to toggle between rich and simple text editor
* Do not remove workflow which are automatically canceled after staling for more than 30 days * Do not remove workflow which are automatically canceled after staling for more than 30 days
### Fixed ### Fixed
* ([#376](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/376)) trying to prevent bug of typeerror in doc-history + improved display of document history * ([#376](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/376)) trying to prevent bug of typeerror in doc-history + improved display of document history
* ([#381](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/381)) Display previous participation in acc course work even if the person has left the acc course * ([#381](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/381)) Display previous participation in acc course work even if the person has left the acc course
* ([#372](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/372)) Fix display of text in calendar events * ([#372](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/372)) Fix display of text in calendar events
* Add missing translation for user_group.no_user_groups * Add missing translation for user_group.no_user_groups
* Fix admin entity edit actions for event admin entities and activity reason (category) entities * Fix admin entity edit actions for event admin entities and activity reason (category) entities
* ([#392](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/392)) Allow null and cast as string to setContent method for NewsItem * ([#392](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/392)) Allow null and cast as string to setContent method for NewsItem
* ([#393](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/393)) Doc Generation: the "dump only" method send the document as an email attachment. * ([#393](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/393)) Doc Generation: the "dump only" method send the document as an email attachment.
### DX ### DX
* ([#352](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/352)) Remove dead code for wopi-link module * ([#352](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/352)) Remove dead code for wopi-link module
* Replace library node-sass by sass, and upgrade bootstrap to version 5.3 (yarn upgrade / install is required) * Replace library node-sass by sass, and upgrade bootstrap to version 5.3 (yarn upgrade / install is required)
### UX ### UX
* ([#374](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/374)) Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page * ([#374](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/374)) Remove default filter in_progress for the page 'my tasks'; Allows for new tasks to be displayed upon opening of the page
* Improve labeling of fields in person resource creation form * Improve labeling of fields in person resource creation form
## v3.11.0 - 2025-04-17 ## v3.11.0 - 2025-04-17
### Feature ### Feature
@@ -218,11 +199,11 @@ framework:
## v3.10.3 - 2025-03-18 ## v3.10.3 - 2025-03-18
### DX ### DX
* Eslint fixes * Eslint fixes
## v3.10.2 - 2025-03-17 ## v3.10.2 - 2025-03-17
### Fixed ### Fixed
* Replace a ts-expect-error with a ts-ignore * Replace a ts-expect-error with a ts-ignore
## v3.10.1 - 2025-03-17 ## v3.10.1 - 2025-03-17
### DX ### DX
@@ -230,37 +211,37 @@ framework:
## v3.10.0 - 2025-03-17 ## v3.10.0 - 2025-03-17
### Feature ### Feature
* ([#363](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/363)) Display social actions grouped per social issue within activity form * ([#363](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/363)) Display social actions grouped per social issue within activity form
### Fixed ### Fixed
* ([#362](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/362)) Fix Dependency Injection, which prevented to save the CalendarRange * ([#362](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/362)) Fix Dependency Injection, which prevented to save the CalendarRange
* ([#368](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/368)) fix search query for user groups * ([#368](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/368)) fix search query for user groups
## v3.9.2 - 2025-02-27 ## v3.9.2 - 2025-02-27
### Fixed ### Fixed
* Use fetchResults method to fetch all social issues instead of only the first page * Use fetchResults method to fetch all social issues instead of only the first page
## v3.9.1 - 2025-02-27 ## v3.9.1 - 2025-02-27
### Fixed ### Fixed
* Fix post/patch request with missing 'type' property for gender * Fix post/patch request with missing 'type' property for gender
## v3.9.0 - 2025-02-27 ## v3.9.0 - 2025-02-27
### Feature ### Feature
* ([#349](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/349)) Suggest all referrers within actions of the accompanying period when creating an activity * ([#349](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/349)) Suggest all referrers within actions of the accompanying period when creating an activity
* ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add possibility to export a csv with all social issues and social actions * ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add possibility to export a csv with all social issues and social actions
* ([#360](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/360)) Restore document to previous kept version when a workflow is canceled * ([#360](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/360)) Restore document to previous kept version when a workflow is canceled
* ([#341](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/341)) Add a list of third parties from within the admin (csv download) * ([#341](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/341)) Add a list of third parties from within the admin (csv download)
### Fixed ### Fixed
* fix generation of document with accompanying period context, and list of activities and works * fix generation of document with accompanying period context, and list of activities and works
### DX ### DX
* ([#333](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/333)) Create an unique source of trust for translations * ([#333](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/333)) Create an unique source of trust for translations
## v3.8.2 - 2025-02-10 ## v3.8.2 - 2025-02-10
### Fixed ### Fixed
* ([#358](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/358)) Remove "filter" button on list of documents in the workflow's "add attachement" modal * ([#358](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/358)) Remove "filter" button on list of documents in the workflow's "add attachement" modal
## v3.8.1 - 2025-02-05 ## v3.8.1 - 2025-02-05
### Fixed ### Fixed
* Fix household link in the parcours banner * Fix household link in the parcours banner
## v3.8.0 - 2025-02-03 ## v3.8.0 - 2025-02-03
### Feature ### Feature
@@ -276,7 +257,7 @@ framework:
## v3.7.1 - 2025-01-21 ## v3.7.1 - 2025-01-21
### Fixed ### Fixed
* Fix legacy configuration processor for notifier component * Fix legacy configuration processor for notifier component
## v3.7.0 - 2025-01-21 ## v3.7.0 - 2025-01-21
### Feature ### Feature
@@ -343,33 +324,33 @@ chill_main:
## v3.6.0 - 2025-01-16 ## v3.6.0 - 2025-01-16
### Feature ### Feature
* Importer for addresses does not fails when the postal code is not found with some addresses, and compute a recap list of all addresses that could not be imported. This recap list can be send by email. * Importer for addresses does not fails when the postal code is not found with some addresses, and compute a recap list of all addresses that could not be imported. This recap list can be send by email.
* ([#346](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/346)) Create a driver for storing documents on disk (instead of openstack object store) * ([#346](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/346)) Create a driver for storing documents on disk (instead of openstack object store)
* Add address importer from french Base d'Adresse Nationale (BAN) * Add address importer from french Base d'Adresse Nationale (BAN)
* ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add csv export for social issues and social actions * ([#343](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/343)) Add csv export for social issues and social actions
### Fixed ### Fixed
* Export: fix missing alias in activity between certain dates filter. Condition added for alias. * Export: fix missing alias in activity between certain dates filter. Condition added for alias.
## v3.5.3 - 2025-01-07 ## v3.5.3 - 2025-01-07
### Fixed ### Fixed
* Fix the EntityToJsonTransformer to return an empty array if the value is "" * Fix the EntityToJsonTransformer to return an empty array if the value is ""
## v3.5.2 - 2024-12-19 ## v3.5.2 - 2024-12-19
### Fixed ### Fixed
* ([#345](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/345)) Export: activity filtering of users that were associated to an activity between certain dates. Results contained activities that were not within the specified date range" * ([#345](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/345)) Export: activity filtering of users that were associated to an activity between certain dates. Results contained activities that were not within the specified date range"
## v3.5.1 - 2024-12-16 ## v3.5.1 - 2024-12-16
### Fixed ### Fixed
* Filiation: fix the display of the gender label in the graph * Filiation: fix the display of the gender label in the graph
* Wrap handling of PdfSignedMessage into transactions * Wrap handling of PdfSignedMessage into transactions
## v3.5.0 - 2024-12-09 ## v3.5.0 - 2024-12-09
### Feature ### Feature
* ([#318](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/318)) Show all the pages of the documents in the signature app * ([#318](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/318)) Show all the pages of the documents in the signature app
### Fixed ### Fixed
* Wrap the signature's change state into a transaction, to avoid race conditions * Wrap the signature's change state into a transaction, to avoid race conditions
* Fix display of gender label * Fix display of gender label
## v3.4.3 - 2024-12-05 ## v3.4.3 - 2024-12-05
### Fixed ### Fixed
@@ -378,76 +359,76 @@ chill_main:
## v3.4.2 - 2024-12-05 ## v3.4.2 - 2024-12-05
### Fixed ### Fixed
* ([#329](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/329)) Fix the serialization of gender for the generation of documents * ([#329](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/329)) Fix the serialization of gender for the generation of documents
* ([#337](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/337)) Enforce unique contraint on activity storedobject * ([#337](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/337)) Enforce unique contraint on activity storedobject
### DX ### DX
* ([#310](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/310)) Clean migrations, to reduce the number of bloated migration when running diff on schema * ([#310](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/310)) Clean migrations, to reduce the number of bloated migration when running diff on schema
## v3.4.1 - 2024-11-22 ## v3.4.1 - 2024-11-22
### Fixed ### Fixed
* Set the workflow's title to notification content and subject * Set the workflow's title to notification content and subject
## v3.4.0 - 2024-11-20 ## v3.4.0 - 2024-11-20
### Feature ### Feature
* ([#314](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/314)) Admin: improve document type admin form with a select field for related class. * ([#314](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/314)) Admin: improve document type admin form with a select field for related class.
Admin: Allow administrator to assign multiple group centers in one go to a user. Admin: Allow administrator to assign multiple group centers in one go to a user.
## v3.3.0 - 2024-11-20 ## v3.3.0 - 2024-11-20
### Feature ### Feature
* Electronic signature * Electronic signature
Implementation of the electronic signature for documents within chill. Implementation of the electronic signature for documents within chill.
* ([#286](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/286)) The behavoir of the voters for stored objects is adjusted so as to limit edit and delete possibilities to users related to the activity, social action or workflow entity. * ([#286](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/286)) The behavoir of the voters for stored objects is adjusted so as to limit edit and delete possibilities to users related to the activity, social action or workflow entity.
* ([#288](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/288)) Metadata form added for person signatures * ([#288](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/288)) Metadata form added for person signatures
* Add a signature step in workflow, which allow to apply an electronic signature on documents * Add a signature step in workflow, which allow to apply an electronic signature on documents
* Keep an history of each version of a stored object. * Keep an history of each version of a stored object.
* Add a "send external" step in workflow, which allow to send stored objects and other elements to remote people, by sending them a public url * Add a "send external" step in workflow, which allow to send stored objects and other elements to remote people, by sending them a public url
### Fixed ### Fixed
* Adjust household list export to include households even if their address is NULL * Adjust household list export to include households even if their address is NULL
* Remove validation of date string on deathDate * Remove validation of date string on deathDate
## v3.2.4 - 2024-11-06 ## v3.2.4 - 2024-11-06
### Fixed ### Fixed
* Fix compilation of chill assets * Fix compilation of chill assets
## v3.2.3 - 2024-11-05 ## v3.2.3 - 2024-11-05
### Fixed ### Fixed
* ([#315](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/315)) Fix display of accompanying period work referrers. Only current referrers should be displayed. * ([#315](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/315)) Fix display of accompanying period work referrers. Only current referrers should be displayed.
Fix color of Chill footer Fix color of Chill footer
## v3.2.2 - 2024-10-31 ## v3.2.2 - 2024-10-31
### Fixed ### Fixed
* Fix gender translation for unknown * Fix gender translation for unknown
## v3.2.1 - 2024-10-31 ## v3.2.1 - 2024-10-31
### Fixed ### Fixed
* Add the possibility of unknown to the gender entity * Add the possibility of unknown to the gender entity
* Fix the fusion of person doubles by excluding accompanyingPeriod work entities to be deleted. They are moved instead. * Fix the fusion of person doubles by excluding accompanyingPeriod work entities to be deleted. They are moved instead.
## v3.2.0 - 2024-10-30 ## v3.2.0 - 2024-10-30
### Feature ### Feature
* Introduce a gender entity * Introduce a gender entity
## v3.1.1 - 2024-10-01 ## v3.1.1 - 2024-10-01
### Fixed ### Fixed
* ([#308](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/308)) Show only the current referrer in the page "show" for an accompanying period workf * ([#308](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/308)) Show only the current referrer in the page "show" for an accompanying period workf
* ([#309](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/309)) Correctly compute the grouping by referrer aggregator * ([#309](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/309)) Correctly compute the grouping by referrer aggregator
* Fixed typing of custom field long choice and custom field group * Fixed typing of custom field long choice and custom field group
## v3.1.0 - 2024-08-30 ## v3.1.0 - 2024-08-30
### Feature ### Feature
* Add export aggregator to aggregate activities by household + filter persons that are not part of an accompanyingperiod during a certain timeframe. * Add export aggregator to aggregate activities by household + filter persons that are not part of an accompanyingperiod during a certain timeframe.
## v3.0.0 - 2024-08-26 ## v3.0.0 - 2024-08-26
### Fixed ### Fixed
* Fix delete action for accompanying periods in draft state * Fix delete action for accompanying periods in draft state
* Fix connection to azure when making an calendar event in chill * Fix connection to azure when making an calendar event in chill
* CollectionType js fixes for remove button and adding multiple entries * CollectionType js fixes for remove button and adding multiple entries
## v2.24.0 - 2024-09-11 ## v2.24.0 - 2024-09-11
### Feature ### Feature
* ([#306](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/306)) When a document is converted or downloaded in the browser, this document is removed from the browser memory after 45s. Future click on the button re-download the document. * ([#306](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/306)) When a document is converted or downloaded in the browser, this document is removed from the browser memory after 45s. Future click on the button re-download the document.
## v2.23.0 - 2024-07-23 & 2024-07-19 ## v2.23.0 - 2024-07-23 & 2024-07-19
### Feature ### Feature
@@ -482,13 +463,13 @@ Fix color of Chill footer
## v2.22.2 - 2024-07-03 ## v2.22.2 - 2024-07-03
### Fixed ### Fixed
* Remove scope required for event participation stats * Remove scope required for event participation stats
## v2.22.1 - 2024-07-01 ## v2.22.1 - 2024-07-01
### Fixed ### Fixed
* Remove debug word * Remove debug word
### DX ### DX
* Add a command for reading official address DB from Luxembourg and update chill addresses * Add a command for reading official address DB from Luxembourg and update chill addresses
## v2.22.0 - 2024-06-25 ## v2.22.0 - 2024-06-25
### Feature ### Feature
@@ -531,7 +512,7 @@ Fix color of Chill footer
## v2.20.1 - 2024-06-05 ## v2.20.1 - 2024-06-05
### Fixed ### Fixed
* Do not allow StoredObjectCreated for edit and convert buttons * Do not allow StoredObjectCreated for edit and convert buttons
## v2.20.0 - 2024-06-05 ## v2.20.0 - 2024-06-05
### Fixed ### Fixed
@@ -578,96 +559,96 @@ Fix color of Chill footer
## v2.18.2 - 2024-04-12 ## v2.18.2 - 2024-04-12
### Fixed ### Fixed
* ([#250](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/250)) Postal codes import : fix the source URL and the keys to handle each record * ([#250](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/250)) Postal codes import : fix the source URL and the keys to handle each record
## v2.18.1 - 2024-03-26 ## v2.18.1 - 2024-03-26
### Fixed ### Fixed
* Fix layout issue in document generation for admin (minor) * Fix layout issue in document generation for admin (minor)
## v2.18.0 - 2024-03-26 ## v2.18.0 - 2024-03-26
### Feature ### Feature
* ([#268](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/268)) Improve admin UX to configure document templates for document generation * ([#268](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/268)) Improve admin UX to configure document templates for document generation
### Fixed ### Fixed
* ([#267](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/267)) Fix the join between job and user in the user list (admin): show only the current user job * ([#267](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/267)) Fix the join between job and user in the user list (admin): show only the current user job
## v2.17.0 - 2024-03-19 ## v2.17.0 - 2024-03-19
### Feature ### Feature
* ([#237](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/237)) New export filter for social actions with an evaluation created between two dates * ([#237](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/237)) New export filter for social actions with an evaluation created between two dates
* ([#258](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/258)) In the list of accompangying period, add the list of person's centers and the duration of the course * ([#258](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/258)) In the list of accompangying period, add the list of person's centers and the duration of the course
* ([#238](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/238)) Allow to customize list person with new fields * ([#238](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/238)) Allow to customize list person with new fields
* ([#159](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/159)) Admin can publish news on the homepage * ([#159](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/159)) Admin can publish news on the homepage
### Fixed ### Fixed
* ([#264](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/264)) Fix languages: load the languages in all availables languages configured for Chill * ([#264](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/264)) Fix languages: load the languages in all availables languages configured for Chill
* ([#259](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/259)) Keep a consistent behaviour between the filtering of activities within the document generation (model "accompanying period with activities"), and the same filter in the list of activities for an accompanying period * ([#259](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/259)) Keep a consistent behaviour between the filtering of activities within the document generation (model "accompanying period with activities"), and the same filter in the list of activities for an accompanying period
## v2.16.3 - 2024-02-26 ## v2.16.3 - 2024-02-26
### Fixed ### Fixed
* ([#236](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/236)) Fix translation of user job -> 'service' must be 'métier' * ([#236](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/236)) Fix translation of user job -> 'service' must be 'métier'
### UX ### UX
* ([#232](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/232)) Order user jobs and services alphabetically in export filters * ([#232](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/232)) Order user jobs and services alphabetically in export filters
## v2.16.2 - 2024-02-21 ## v2.16.2 - 2024-02-21
### Fixed ### Fixed
* Check for null values in closing motive of parcours d'accompagnement for correct rendering of template * Check for null values in closing motive of parcours d'accompagnement for correct rendering of template
## v2.16.1 - 2024-02-09 ## v2.16.1 - 2024-02-09
### Fixed ### Fixed
* Force bootstrap version to avoid error in builds with newer version * Force bootstrap version to avoid error in builds with newer version
## v2.16.0 - 2024-02-08 ## v2.16.0 - 2024-02-08
### Feature ### Feature
* ([#231](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/231)) Create new filter for persons having a participation in an accompanying period during a certain time span * ([#231](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/231)) Create new filter for persons having a participation in an accompanying period during a certain time span
* ([#241](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/241)) [Export][List of accompanyign period] Add two columns: the list of persons participating to the period, and their ids * ([#241](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/241)) [Export][List of accompanyign period] Add two columns: the list of persons participating to the period, and their ids
* ([#244](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/244)) Add capability to generate export about change of steps of accompanying period, and generate exports for this * ([#244](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/244)) Add capability to generate export about change of steps of accompanying period, and generate exports for this
* ([#253](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/253)) Export: group accompanying period by person participating * ([#253](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/253)) Export: group accompanying period by person participating
* ([#243](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/243)) Export: add filter for courses not linked to a reference address * ([#243](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/243)) Export: add filter for courses not linked to a reference address
* ([#229](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/229)) Allow to group activities linked with accompanying period by reason * ([#229](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/229)) Allow to group activities linked with accompanying period by reason
* ([#115](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/115)) Prevent social work to be saved when another user edited conccurently the social work * ([#115](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/115)) Prevent social work to be saved when another user edited conccurently the social work
* Modernize the event bundle, with some new fields and multiple improvements * Modernize the event bundle, with some new fields and multiple improvements
### Fixed ### Fixed
* ([#220](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/220)) Fix error in logs about wrong typing of eventArgs in onEditNotificationComment method * ([#220](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/220)) Fix error in logs about wrong typing of eventArgs in onEditNotificationComment method
* ([#256](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/256)) Fix the conditions upon which social actions should be optional or required in relation to social issues within the activity creation form * ([#256](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/256)) Fix the conditions upon which social actions should be optional or required in relation to social issues within the activity creation form
### UX ### UX
* ([#260](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/260)) Order list of centers alphabetically in dropdown 'user' section admin. * ([#260](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/260)) Order list of centers alphabetically in dropdown 'user' section admin.
## v2.15.2 - 2024-01-11 ## v2.15.2 - 2024-01-11
### Fixed ### Fixed
* Fix the id_seq used when creating a new accompanying period participation during fusion of two person files * Fix the id_seq used when creating a new accompanying period participation during fusion of two person files
### DX ### DX
* Set placeholder to False for expanded EntityType form fields where required is set to False. * Set placeholder to False for expanded EntityType form fields where required is set to False.
## v2.15.1 - 2023-12-20 ## v2.15.1 - 2023-12-20
### Fixed ### Fixed
* Fix the household export query to exclude accompanying periods that are in draft state. * Fix the household export query to exclude accompanying periods that are in draft state.
### DX ### DX
* ([#167](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/167)) Fixed readthedocs compilation by updating readthedocs config file and requirements for Sphinx * ([#167](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/167)) Fixed readthedocs compilation by updating readthedocs config file and requirements for Sphinx
## v2.15.0 - 2023-12-11 ## v2.15.0 - 2023-12-11
### Feature ### Feature
* ([#191](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/191)) Add export "number of household associate with an exchange" * ([#191](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/191)) Add export "number of household associate with an exchange"
* ([#235](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/235)) Export: add dates on the filter "filter course by activity type" * ([#235](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/235)) Export: add dates on the filter "filter course by activity type"
### Fixed ### Fixed
* ([#214](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/214)) Fix error when posting an empty comment on an accompanying period. * ([#214](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/214)) Fix error when posting an empty comment on an accompanying period.
* ([#233](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/233)) Fix "filter evaluation by evaluation type" (and add select2 to the list of evaluation types to pick) * ([#233](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/233)) Fix "filter evaluation by evaluation type" (and add select2 to the list of evaluation types to pick)
* ([#234](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/234)) Fix "filter aside activity by date" * ([#234](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/234)) Fix "filter aside activity by date"
* ([#228](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/228)) Fix export of activity for people created before the introduction of the createdAt column on person (during v1) * ([#228](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/228)) Fix export of activity for people created before the introduction of the createdAt column on person (during v1)
* ([#246](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/246)) Do not show activities, evaluations and social work when associated to a confidential accompanying period, except for the users which are allowed to see them * ([#246](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/246)) Do not show activities, evaluations and social work when associated to a confidential accompanying period, except for the users which are allowed to see them
## v2.14.1 - 2023-11-29 ## v2.14.1 - 2023-11-29
### Fixed ### Fixed
* Export: fix list person with custom fields * Export: fix list person with custom fields
* ([#100](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/100)) Add a paginator to budget elements (resource and charge types) in the admin * ([#100](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/100)) Add a paginator to budget elements (resource and charge types) in the admin
* Fix error in ListEvaluation when "handling agents" are alone * Fix error in ListEvaluation when "handling agents" are alone
## v2.14.0 - 2023-11-24 ## v2.14.0 - 2023-11-24
### Feature ### Feature
* ([#161](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/161)) Export: in filter "Filter accompanying period work (social action) by type, goal and result", order the items alphabetically or with the defined order * ([#161](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/161)) Export: in filter "Filter accompanying period work (social action) by type, goal and result", order the items alphabetically or with the defined order
### Fixed ### Fixed
* ([#141](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/141)) Export: on filter "action by type goals, and results", restore the fields when editing a saved export * ([#141](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/141)) Export: on filter "action by type goals, and results", restore the fields when editing a saved export
* ([#219](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/219)) Export: fix the list of accompanying period work, when the "calc date" is null * ([#219](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/219)) Export: fix the list of accompanying period work, when the "calc date" is null
* ([#222](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/222)) Fix rendering of custom fields * ([#222](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/222)) Fix rendering of custom fields
* Fix various errors in custom fields administration * Fix various errors in custom fields administration
## v2.13.0 - 2023-11-21 ## v2.13.0 - 2023-11-21
### Feature ### Feature
@@ -681,7 +662,7 @@ Fix color of Chill footer
## v2.12.1 - 2023-11-16 ## v2.12.1 - 2023-11-16
### Fixed ### Fixed
* ([#208](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/208)) Export: fix loading of form for "filter action by type, goal and result" * ([#208](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/208)) Export: fix loading of form for "filter action by type, goal and result"
## v2.12.0 - 2023-11-15 ## v2.12.0 - 2023-11-15
### Feature ### Feature
@@ -712,36 +693,36 @@ Fix color of Chill footer
## v2.11.0 - 2023-11-07 ## v2.11.0 - 2023-11-07
### Feature ### Feature
* ([#194](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/194)) Export: add a filter "filter activity by creator job" * ([#194](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/194)) Export: add a filter "filter activity by creator job"
### Fixed ### Fixed
* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix "group accompanying period by geographical unit": take into account the accompanying periods when the period is not located within an unit * ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix "group accompanying period by geographical unit": take into account the accompanying periods when the period is not located within an unit
* Fix "group activity by creator job" aggregator * Fix "group activity by creator job" aggregator
## v2.10.6 - 2023-11-07 ## v2.10.6 - 2023-11-07
### Fixed ### Fixed
* ([#182](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/182)) Fix merging of double person files. Adjustement relationship sql statement * ([#182](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/182)) Fix merging of double person files. Adjustement relationship sql statement
* ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix aggregator by geographical unit on person: avoid inconsistencies * ([#185](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/185)) Export: fix aggregator by geographical unit on person: avoid inconsistencies
## v2.10.5 - 2023-11-05 ## v2.10.5 - 2023-11-05
### Fixed ### Fixed
* ([#183](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/183)) Fix "problem during download" on some filters, which used a wrong data type * ([#183](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/183)) Fix "problem during download" on some filters, which used a wrong data type
* ([#184](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/184)) Fix filter "activity by date" * ([#184](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/184)) Fix filter "activity by date"
## v2.10.4 - 2023-10-26 ## v2.10.4 - 2023-10-26
### Fixed ### Fixed
* Fix null value constraint errors when merging relationships in doubles * Fix null value constraint errors when merging relationships in doubles
## v2.10.3 - 2023-10-26 ## v2.10.3 - 2023-10-26
### Fixed ### Fixed
* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Replace old method of getting translator with injection of translatorInterface * ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Replace old method of getting translator with injection of translatorInterface
## v2.10.2 - 2023-10-26 ## v2.10.2 - 2023-10-26
### Fixed ### Fixed
* ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Use injection of translator instead of ->get(). * ([#175](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/175)) Use injection of translator instead of ->get().
## v2.10.1 - 2023-10-24 ## v2.10.1 - 2023-10-24
### Fixed ### Fixed
* Fix export controller when generating an export without any data in session * Fix export controller when generating an export without any data in session
## v2.10.0 - 2023-10-24 ## v2.10.0 - 2023-10-24
### Feature ### Feature
@@ -761,16 +742,16 @@ Fix color of Chill footer
- ajout d'un filtre et regroupement par usager participant sur les échanges - ajout d'un filtre et regroupement par usager participant sur les échanges
- ajout d'un regroupement: par type d'activité associé au parcours; - ajout d'un regroupement: par type d'activité associé au parcours;
- trie les filtre et regroupements par ordre alphabétique dans els exports - trie les filtre et regroupements par ordre alphabétique dans els exports
- ajout d'un paramètre qui permet de désactiver le filtre par territoire dans les exports - ajout d'un paramètre qui permet de désactiver le filtre par centre dans les exports
- correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date" - correction de l'interface de date dans les filtres et regroupements "par statut du parcours à la date"
## v2.9.2 - 2023-10-17 ## v2.9.2 - 2023-10-17
### Fixed ### Fixed
* Fix possible null values in string's entities * Fix possible null values in string's entities
## v2.9.1 - 2023-10-17 ## v2.9.1 - 2023-10-17
### Fixed ### Fixed
* Fix the handling of activity form when editing or creating an activity in an accompanying period with multiple centers * Fix the handling of activity form when editing or creating an activity in an accompanying period with multiple centers
## v2.9.0 - 2023-10-17 ## v2.9.0 - 2023-10-17
### Feature ### Feature
@@ -818,57 +799,57 @@ But if you do not need this any more, you must ensure that the configuration key
## v2.7.0 - 2023-09-27 ## v2.7.0 - 2023-09-27
### Feature ### Feature
* ([#155](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/155)) The regulation list load accompanying periods by exact postal code (address associated with postal code), and not by the content of the postal code (postal code with same code's string) * ([#155](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/155)) The regulation list load accompanying periods by exact postal code (address associated with postal code), and not by the content of the postal code (postal code with same code's string)
### Fixed ### Fixed
* ([#142](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/142)) Fix the label of filter ActivityTypeFilter to a more obvious one * ([#142](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/142)) Fix the label of filter ActivityTypeFilter to a more obvious one
* ([#140](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/140)) [export] Fix association of filter "filter location by type" which did not appears on "list of activities" * ([#140](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/140)) [export] Fix association of filter "filter location by type" which did not appears on "list of activities"
## v2.6.3 - 2023-09-19 ## v2.6.3 - 2023-09-19
### Fixed ### Fixed
* Remove id property from document * Remove id property from document
mappedsuperclass mappedsuperclass
## v2.6.2 - 2023-09-18 ## v2.6.2 - 2023-09-18
### Fixed ### Fixed
* Fix doctrine mapping of AbstractTaskPlaceEvent and SingleTaskPlaceEvent: id property moved. * Fix doctrine mapping of AbstractTaskPlaceEvent and SingleTaskPlaceEvent: id property moved.
## v2.6.1 - 2023-09-14 ## v2.6.1 - 2023-09-14
### Fixed ### Fixed
* Filter out active centers in exports, which uses a different PickCenterType. * Filter out active centers in exports, which uses a different PickCenterType.
## v2.6.0 - 2023-09-14 ## v2.6.0 - 2023-09-14
### Feature ### Feature
* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Add locations in Aside Activity. By default, suggest user location, otherwise a select with all locations. * ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Add locations in Aside Activity. By default, suggest user location, otherwise a select with all locations.
* ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Adapt Aside Activity exports: display location, filter by location, group by location * ([#133](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/133)) Adapt Aside Activity exports: display location, filter by location, group by location
* Use the CRUD controller for center entity + add the isActive property to be able to mask instances of Center that are no longer in use. * Use the CRUD controller for center entity + add the isActive property to be able to mask instances of Center that are no longer in use.
### Fixed ### Fixed
* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) reinstate the fusion of duplicate persons * ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) reinstate the fusion of duplicate persons
* Missing translation in Work Actions exports * Missing translation in Work Actions exports
* Reimplement the mission type filter on tasks, only for instances that have a config parameter indicating true for this. * Reimplement the mission type filter on tasks, only for instances that have a config parameter indicating true for this.
* ([#135](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/135)) Corrects a typing error in 2 filters, which caused an * ([#135](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/135)) Corrects a typing error in 2 filters, which caused an
error when trying to reedit a saved export error when trying to reedit a saved export
* ([#136](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/136)) [household] when moving a person to a sharing position to a not-sharing position on the same household on the same date, remove the previous household membership on the same household. This fix duplicate member. * ([#136](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/136)) [household] when moving a person to a sharing position to a not-sharing position on the same household on the same date, remove the previous household membership on the same household. This fix duplicate member.
* Add missing translation for comment field placeholder in repositionning household editor. * Add missing translation for comment field placeholder in repositionning household editor.
* Do not send an email to creator twice when adding a comment to a notification * Do not send an email to creator twice when adding a comment to a notification
* ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) Fix gestion doublon functionality to work with chill bundles v2 * ([#107](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/107)) Fix gestion doublon functionality to work with chill bundles v2
### UX ### UX
* Uniformize badge-person in household banner (background, size) * Uniformize badge-person in household banner (background, size)
## v2.5.3 - 2023-07-20 ## v2.5.3 - 2023-07-20
### Fixed ### Fixed
* ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous. * ([#132](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/132)) Rendez-vous documents created would appear in all documents lists of all persons with an accompanying period. Or statements are now added to the where clause to filter out documents that come from unrelated accompanying period/ or person rendez-vous.
## v2.5.2 - 2023-07-15 ## v2.5.2 - 2023-07-15
### Fixed ### Fixed
* [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead) * [Collate Address] when updating address point, do not use the point's address reference if the similarity is below the requirement for associating the address reference and the address (it uses the postcode's center instead)
## v2.5.1 - 2023-07-14 ## v2.5.1 - 2023-07-14
### Fixed ### Fixed
* [collate addresses] block collating addresses to another address reference where the address reference is already the best match * [collate addresses] block collating addresses to another address reference where the address reference is already the best match
## v2.5.0 - 2023-07-14 ## v2.5.0 - 2023-07-14
### Feature ### Feature
@@ -941,7 +922,7 @@ error when trying to reedit a saved export
- ajout d'un regroupement par métier des intervenants sur un parcours; - ajout d'un regroupement par métier des intervenants sur un parcours;
- ajout d'un regroupement par service des intervenants sur un parcours; - ajout d'un regroupement par service des intervenants sur un parcours;
- ajout d'un regroupement par utilisateur intervenant sur un parcours - ajout d'un regroupement par utilisateur intervenant sur un parcours
- ajout d'un regroupement "par territoire de l'usager"; - ajout d'un regroupement "par centre de l'usager";
- ajout d'un filtre "par métier intervenant sur un parcours"; - ajout d'un filtre "par métier intervenant sur un parcours";
- ajout d'un filtre "par service intervenant sur un parcours"; - ajout d'un filtre "par service intervenant sur un parcours";
- création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot); - création d'un rôle spécifique pour voir les parcours confidentiels (et séparer de celui de la liste qui permet de ré-assigner les parcours en lot);

View File

@@ -23,8 +23,8 @@ class "Document" {
- text description - text description
- ArrayCollection_DocumentCategory categories - ArrayCollection_DocumentCategory categories
- varchar_150 content #link to openstack - varchar_150 content #link to openstack
- Territoire territoire - Center center
- Service service - Cercle cercle
- User user - User user
- DateTime date # Creation date - DateTime date # Creation date
} }

View File

@@ -38,7 +38,7 @@ Certaines données sont historisées:
- les référents d'un parcours; - les référents d'un parcours;
- les statuts d'un parcours; - les statuts d'un parcours;
- la liaison entre les territoires et les usagers; - la liaison entre les centres et les usagers;
- etc. - etc.
Dans ces cas-là, Chill crée généralement deux colonnes, qui sont habituellement nommées :code:`startDate` et :code:`endDate`. Lorsque la colonne :code:`endDate` est à :code:`NULL`, cela signifie que la période n'est pas "fermée". La colonne :code:`startDate` n'est pas nullable. Dans ces cas-là, Chill crée généralement deux colonnes, qui sont habituellement nommées :code:`startDate` et :code:`endDate`. Lorsque la colonne :code:`endDate` est à :code:`NULL`, cela signifie que la période n'est pas "fermée". La colonne :code:`startDate` n'est pas nullable.

View File

@@ -1,6 +1,6 @@
order,table_schema,table_name,commentaire order,table_schema,table_name,commentaire
1,chill_3party,party_category,Catégorie de tiers 1,chill_3party,party_category,Catégorie de tiers
2,chill_3party,party_center,Association entre les tiers et les territoires (déprécié) 2,chill_3party,party_center,Association entre les tiers et les centres (déprécié)
3,chill_3party,party_profession,Profession du tiers (déprécié) 3,chill_3party,party_profession,Profession du tiers (déprécié)
4,chill_3party,third_party,Tiers 4,chill_3party,third_party,Tiers
5,chill_3party,thirdparty_category,association tiers - catégories 5,chill_3party,thirdparty_category,association tiers - catégories
@@ -54,7 +54,7 @@ order,table_schema,table_name,commentaire
53,public,activitytpresence,Présence aux échanges 53,public,activitytpresence,Présence aux échanges
54,public,activitytype,Types d'échanges 54,public,activitytype,Types d'échanges
55,public,activitytypecategory,Catégories de types d'échanges 55,public,activitytypecategory,Catégories de types d'échanges
56,public,centers,"Territoires (territoires, agences, etc.)" 56,public,centers,"Centres (territoires, agences, etc.)"
57,public,chill_activity_activity_chill_person_socialaction, 57,public,chill_activity_activity_chill_person_socialaction,
58,public,chill_activity_activity_chill_person_socialissue 58,public,chill_activity_activity_chill_person_socialissue
59,public,chill_docgen_template,Gabarits de documents 59,public,chill_docgen_template,Gabarits de documents
@@ -111,7 +111,7 @@ order,table_schema,table_name,commentaire
110,public,chill_person_marital_status,Etats civils 110,public,chill_person_marital_status,Etats civils
111,public,chill_person_not_duplicate, 111,public,chill_person_not_duplicate,
112,public,chill_person_person,Usagers 112,public,chill_person_person,Usagers
113,public,chill_person_person_center_history,Historique des territoires d'un usagers 113,public,chill_person_person_center_history,Historique des centres d'un usagers
114,public,chill_person_persons_to_addresses,Déprécié 114,public,chill_person_persons_to_addresses,Déprécié
115,public,chill_person_phone,Numéros d etéléphone supplémentaires d'un usager 115,public,chill_person_phone,Numéros d etéléphone supplémentaires d'un usager
116,public,chill_person_relations,Types de relations de filiation 116,public,chill_person_relations,Types de relations de filiation
@@ -142,7 +142,7 @@ order,table_schema,table_name,commentaire
141,public,permission_groups 141,public,permission_groups
142,public,permissionsgroup_rolescope 142,public,permissionsgroup_rolescope
143,public,persons_spoken_languages 143,public,persons_spoken_languages
144,public,regroupment,Regroupement de territoires 144,public,regroupment,Regroupement de centres
145,public,regroupment_center, 145,public,regroupment_center,
146,public,role_scopes, 146,public,role_scopes,
147,public,scopes,Services 147,public,scopes,Services
Can't render this file because it has a wrong number of fields in line 28.

View File

@@ -55,7 +55,6 @@
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@types/leaflet": "^1.9.3", "@types/leaflet": "^1.9.3",
"@vueuse/core": "^13.9.0",
"bootstrap-icons": "^1.11.3", "bootstrap-icons": "^1.11.3",
"dropzone": "^5.7.6", "dropzone": "^5.7.6",
"es6-promise": "^4.2.8", "es6-promise": "^4.2.8",

View File

@@ -10,7 +10,7 @@ Attendee: Présence de l'usager
attendee: présence de l'usager attendee: présence de l'usager
list_reasons: liste des sujets list_reasons: liste des sujets
user_username: nom de l'utilisateur user_username: nom de l'utilisateur
circle_name: nom du service circle_name: nom du cercle
Remark: Commentaire Remark: Commentaire
No comments: Aucun commentaire No comments: Aucun commentaire
Add a new activity: Ajouter une nouvel échange Add a new activity: Ajouter une nouvel échange
@@ -20,7 +20,7 @@ not present: absent
Delete: Supprimer Delete: Supprimer
Update: Mettre à jour Update: Mettre à jour
Update activity: Modifier l'échange Update activity: Modifier l'échange
Scope: Service Scope: Cercle
Activity data: Données de l'échange Activity data: Données de l'échange
Activity location: Localisation de l'échange Activity location: Localisation de l'échange
No reason associated: Aucun sujet No reason associated: Aucun sujet
@@ -398,7 +398,7 @@ export:
sent received: Envoyé ou reçu sent received: Envoyé ou reçu
emergency: Urgence emergency: Urgence
accompanying course id: Identifiant du parcours accompanying course id: Identifiant du parcours
course circles: Services du parcours course circles: Cercles du parcours
travelTime: Durée de déplacement travelTime: Durée de déplacement
durationTime: Durée durationTime: Durée
id: Identifiant id: Identifiant

View File

@@ -177,7 +177,7 @@ export:
agent_id: Utilisateur agent_id: Utilisateur
creator_id: Créateur creator_id: Créateur
main_scope: Service principal de l'utilisateur main_scope: Service principal de l'utilisateur
main_center: Territoire principal de l'utilisateur main_center: Centre principal de l'utilisateur
aside_activity_type: Catégorie d'activité annexe aside_activity_type: Catégorie d'activité annexe
date: Date date: Date
duration: Durée duration: Durée

View File

@@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\Controller;
use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Form\CalendarType; use Chill\CalendarBundle\Form\CalendarType;
use Chill\CalendarBundle\Form\CancelType;
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface; use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface; use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
use Chill\CalendarBundle\Security\Voter\CalendarVoter; use Chill\CalendarBundle\Security\Voter\CalendarVoter;
@@ -30,6 +31,7 @@ use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Chill\PersonBundle\Security\Authorization\PersonVoter; use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\ThirdPartyBundle\Entity\ThirdParty; use Chill\ThirdPartyBundle\Entity\ThirdParty;
use Doctrine\ORM\EntityManagerInterface;
use http\Exception\UnexpectedValueException; use http\Exception\UnexpectedValueException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -60,6 +62,7 @@ class CalendarController extends AbstractController
private readonly UserRepositoryInterface $userRepository, private readonly UserRepositoryInterface $userRepository,
private readonly TranslatorInterface $translator, private readonly TranslatorInterface $translator,
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry, private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
private readonly EntityManagerInterface $em,
) {} ) {}
/** /**
@@ -111,6 +114,55 @@ class CalendarController extends AbstractController
]); ]);
} }
#[Route(path: '/{_locale}/calendar/calendar/{id}/cancel', name: 'chill_calendar_calendar_cancel')]
public function cancelAction(Calendar $calendar, Request $request): Response
{
// Deal with sms being sent or not
// Communicate cancellation with the remote calendar.
$this->denyAccessUnlessGranted(CalendarVoter::EDIT, $calendar);
[$person, $accompanyingPeriod] = [$calendar->getPerson(), $calendar->getAccompanyingPeriod()];
$form = $this->createForm(CancelType::class, $calendar);
$form->add('submit', SubmitType::class);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = '@ChillCalendar/Calendar/cancelCalendarByAccompanyingCourse.html.twig';
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_period', ['id' => $accompanyingPeriod->getId()]);
} elseif ($person instanceof Person) {
$view = '@ChillCalendar/Calendar/cancelCalendarByPerson.html.twig';
$redirectRoute = $this->generateUrl('chill_calendar_calendar_list_by_person', ['id' => $person->getId()]);
} else {
throw new \RuntimeException('nor person or accompanying period');
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->logger->notice('A calendar event has been cancelled', [
'by_user' => $this->getUser()->getUsername(),
'calendar_id' => $calendar->getId(),
]);
$calendar->setStatus($calendar::STATUS_CANCELED);
$calendar->setSmsStatus($calendar::SMS_CANCEL_PENDING);
$this->em->flush();
$this->addFlash('success', $this->translator->trans('chill_calendar.calendar_canceled'));
return new RedirectResponse($redirectRoute);
}
return $this->render($view, [
'calendar' => $calendar,
'form' => $form->createView(),
'accompanyingCourse' => $accompanyingPeriod,
'person' => $person,
]);
}
/** /**
* Edit a calendar item. * Edit a calendar item.
*/ */

View File

@@ -35,7 +35,7 @@ class LoadCancelReason extends Fixture implements FixtureGroupInterface
$arr = [ $arr = [
['name' => CancelReason::CANCELEDBY_USER], ['name' => CancelReason::CANCELEDBY_USER],
['name' => CancelReason::CANCELEDBY_PERSON], ['name' => CancelReason::CANCELEDBY_PERSON],
['name' => CancelReason::CANCELEDBY_DONOTCOUNT], ['name' => CancelReason::CANCELEDBY_OTHER],
]; ];
foreach ($arr as $a) { foreach ($arr as $a) {

View File

@@ -18,14 +18,14 @@ use Doctrine\ORM\Mapping as ORM;
#[ORM\Table(name: 'chill_calendar.cancel_reason')] #[ORM\Table(name: 'chill_calendar.cancel_reason')]
class CancelReason class CancelReason
{ {
final public const CANCELEDBY_DONOTCOUNT = 'CANCELEDBY_DONOTCOUNT'; final public const CANCELEDBY_OTHER = 'CANCELEDBY_OTHER';
final public const CANCELEDBY_PERSON = 'CANCELEDBY_PERSON'; final public const CANCELEDBY_PERSON = 'CANCELEDBY_PERSON';
final public const CANCELEDBY_USER = 'CANCELEDBY_USER'; final public const CANCELEDBY_USER = 'CANCELEDBY_USER';
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => true])]
private ?bool $active = null; private bool $active = true;
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)] #[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
private ?string $canceledBy = null; private ?string $canceledBy = null;

View File

@@ -15,7 +15,7 @@ use Chill\CalendarBundle\Entity\CancelReason;
use Chill\MainBundle\Form\Type\TranslatableStringFormType; use Chill\MainBundle\Form\Type\TranslatableStringFormType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -28,7 +28,14 @@ class CancelReasonType extends AbstractType
->add('active', CheckboxType::class, [ ->add('active', CheckboxType::class, [
'required' => false, 'required' => false,
]) ])
->add('canceledBy', TextType::class); ->add('canceledBy', ChoiceType::class, [
'choices' => [
'chill_calendar.canceled_by.user' => CancelReason::CANCELEDBY_USER,
'chill_calendar.canceled_by.person' => CancelReason::CANCELEDBY_PERSON,
'chill_calendar.canceled_by.other' => CancelReason::CANCELEDBY_OTHER,
],
'required' => true,
]);
} }
public function configureOptions(OptionsResolver $resolver) public function configureOptions(OptionsResolver $resolver)

View File

@@ -0,0 +1,42 @@
<?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\CalendarBundle\Form;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CancelReason;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CancelType extends AbstractType
{
public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper) {}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cancelReason', EntityType::class, [
'class' => CancelReason::class,
'required' => true,
'choice_label' => fn (CancelReason $cancelReason) => $this->translatableStringHelper->localize($cancelReason->getName()),
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Calendar::class,
]);
}
}

View File

@@ -21,6 +21,7 @@ namespace Chill\CalendarBundle\Messenger\Doctrine;
use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Messenger\Message\CalendarMessage; use Chill\CalendarBundle\Messenger\Message\CalendarMessage;
use Chill\CalendarBundle\Messenger\Message\CalendarRemovedMessage; use Chill\CalendarBundle\Messenger\Message\CalendarRemovedMessage;
use Chill\MainBundle\Entity\User;
use Doctrine\ORM\Event\PostPersistEventArgs; use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Event\PostRemoveEventArgs; use Doctrine\ORM\Event\PostRemoveEventArgs;
use Doctrine\ORM\Event\PostUpdateEventArgs; use Doctrine\ORM\Event\PostUpdateEventArgs;
@@ -31,6 +32,17 @@ class CalendarEntityListener
{ {
public function __construct(private readonly MessageBusInterface $messageBus, private readonly Security $security) {} public function __construct(private readonly MessageBusInterface $messageBus, private readonly Security $security) {}
private function getAuthenticatedUser(): User
{
$user = $this->security->getUser();
if (!$user instanceof User) {
throw new \LogicException('Expected an instance of User.');
}
return $user;
}
public function postPersist(Calendar $calendar, PostPersistEventArgs $args): void public function postPersist(Calendar $calendar, PostPersistEventArgs $args): void
{ {
if (!$calendar->preventEnqueueChanges) { if (!$calendar->preventEnqueueChanges) {
@@ -38,7 +50,7 @@ class CalendarEntityListener
new CalendarMessage( new CalendarMessage(
$calendar, $calendar,
CalendarMessage::CALENDAR_PERSIST, CalendarMessage::CALENDAR_PERSIST,
$this->security->getUser() $this->getAuthenticatedUser()
) )
); );
} }
@@ -50,7 +62,7 @@ class CalendarEntityListener
$this->messageBus->dispatch( $this->messageBus->dispatch(
new CalendarRemovedMessage( new CalendarRemovedMessage(
$calendar, $calendar,
$this->security->getUser() $this->getAuthenticatedUser()
) )
); );
} }
@@ -58,12 +70,19 @@ class CalendarEntityListener
public function postUpdate(Calendar $calendar, PostUpdateEventArgs $args): void public function postUpdate(Calendar $calendar, PostUpdateEventArgs $args): void
{ {
if (!$calendar->preventEnqueueChanges) { if ($calendar->getStatus() === $calendar::STATUS_CANCELED) {
$this->messageBus->dispatch(
new CalendarRemovedMessage(
$calendar,
$this->getAuthenticatedUser()
)
);
} elseif (!$calendar->preventEnqueueChanges) {
$this->messageBus->dispatch( $this->messageBus->dispatch(
new CalendarMessage( new CalendarMessage(
$calendar, $calendar,
CalendarMessage::CALENDAR_UPDATE, CalendarMessage::CALENDAR_UPDATE,
$this->security->getUser() $this->getAuthenticatedUser()
) )
); );
} }

View File

@@ -70,6 +70,8 @@ class CalendarRemovedMessage
public function getRemoteId(): string public function getRemoteId(): string
{ {
dump($this->remoteId);
return $this->remoteId; return $this->remoteId;
} }
} }

View File

@@ -1,5 +1,6 @@
services: services:
Chill\CalendarBundle\Controller\: Chill\CalendarBundle\Controller\:
autowire: true autowire: true
autoconfigure: true
resource: '../../../Controller' resource: '../../../Controller'
tags: ['controller.service_arguments'] tags: ['controller.service_arguments']

View File

@@ -9,9 +9,20 @@
<div class="item-row main"> <div class="item-row main">
<div class="item-col"> <div class="item-col">
<div class="wrap-header"> <div class="wrap-header">
<div class="wl-row">
{% if calendar.status == 'canceled' %}
<div class="badge rounded-pill bg-danger">
<span>{{ 'chill_calendar.canceled'|trans }}: </span>
<span>{{ calendar.cancelReason.name|localize_translatable_string }}</span>
</div>
{% endif %}
</div>
<div class="wl-row"> <div class="wl-row">
<div class="wl-col title"> <div class="wl-col title">
<p class="date-label"> <p class="date-label">
{% if calendar.status == 'canceled' %}
<del>
{% endif %}
{% if context == 'person' and calendar.context == 'accompanying_period' %} {% if context == 'person' and calendar.context == 'accompanying_period' %}
<a href="{{ chill_path_add_return_path('chill_person_accompanying_course_index', {'accompanying_period_id': calendar.accompanyingPeriod.id}) }}" style="text-decoration: none;"> <a href="{{ chill_path_add_return_path('chill_person_accompanying_course_index', {'accompanying_period_id': calendar.accompanyingPeriod.id}) }}" style="text-decoration: none;">
<span class="badge bg-primary"> <span class="badge bg-primary">
@@ -19,6 +30,9 @@
</span> </span>
</a> </a>
{% endif %} {% endif %}
{% if calendar.status == 'canceled' %}
<del>
{% endif %}
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %} {% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
{{ calendar.startDate|format_datetime('short', 'short') }} {{ calendar.startDate|format_datetime('short', 'short') }}
- {{ calendar.endDate|format_datetime('short', 'short') }} - {{ calendar.endDate|format_datetime('short', 'short') }}
@@ -26,13 +40,15 @@
{{ calendar.startDate|format_datetime('short', 'short') }} {{ calendar.startDate|format_datetime('short', 'short') }}
- {{ calendar.endDate|format_datetime('none', 'short') }} - {{ calendar.endDate|format_datetime('none', 'short') }}
{% endif %} {% endif %}
</p> {% if calendar.status == 'canceled' %}
</del>
{% endif %}
<div class="duration short-message"> <div class="duration short-message">
<i class="fa fa-fw fa-hourglass-end"></i> <i class="fa fa-fw fa-hourglass-end"></i>
{{ calendar.duration|date('%H:%I') }} {{ calendar.duration|date('%H:%I') }}
{% if false == calendar.sendSMS or null == calendar.sendSMS %} {% if false == calendar.sendSMS or null == calendar.sendSMS %}
<!-- no sms will be send --> <!-- no sms will be sent -->
{% else %} {% else %}
{% if calendar.smsStatus == 'sms_sent' %} {% if calendar.smsStatus == 'sms_sent' %}
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info"> <span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
@@ -63,8 +79,7 @@
</div> </div>
</div> </div>
{% if calendar.comment.comment is not empty {% if calendar.users|length > 0
or calendar.users|length > 0
or calendar.thirdParties|length > 0 or calendar.thirdParties|length > 0
or calendar.users|length > 0 %} or calendar.users|length > 0 %}
<div class="item-row details separator"> <div class="item-row details separator">
@@ -103,12 +118,13 @@
</div> </div>
{% endif %} {% endif %}
<div class="item-row separator column"> {% if calendar.documents is not empty %}
<div> <div class="item-row separator column">
<div>
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }} {{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
</div>
</div> </div>
</div> {% endif %}
{% if calendar.activity is not null %} {% if calendar.activity is not null %}
<div class="item-row separator"> <div class="item-row separator">
@@ -151,7 +167,7 @@
<div class="item-row separator"> <div class="item-row separator">
<ul class="record_actions"> <ul class="record_actions">
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) %} {% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) and calendar.status is not constant('STATUS_CANCELED', calendar) %}
{% if templates|length == 0 %} {% if templates|length == 0 %}
<li> <li>
<a class="btn btn-create" <a class="btn btn-create"
@@ -191,6 +207,7 @@
or or
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person)) (calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
) )
and calendar.status is not constant('STATUS_CANCELED', calendar)
%} %}
<li> <li>
<a class="btn btn-create" <a class="btn btn-create"
@@ -213,12 +230,18 @@
class="btn btn-show "></a> class="btn btn-show "></a>
</li> </li>
{% endif %} {% endif %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_EDIT', calendar) and calendar.status is not constant('STATUS_CANCELED', calendar) %}
<li> <li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}" <a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}"
class="btn btn-update "></a> class="btn btn-update "></a>
</li> </li>
<li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_cancel', { 'id': calendar.id } ) }}"
class="btn btn-remove">{{ 'Cancel'|trans }}</a>
</li>
{% endif %} {% endif %}
{% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %} {% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %}
<li> <li>
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}" <a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}"

View File

@@ -0,0 +1,29 @@
{% extends "@ChillPerson/AccompanyingCourse/layout.html.twig" %}
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
{% block title 'chill_calendar.cancel_calendar_item'|trans %}
{% block content %}
{{ form_start(form) }}
{{ form_row(form.cancelReason) }}
<ul class="record_actions sticky-form-buttons">
<li class="save">
<a
class="btn btn-cancel"
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'id': accompanyingCourse.id } )}}"
>
{{ 'Cancel'|trans|chill_return_path_label }}
</a>
</li>
<li>
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-save' }, 'label': 'Save' } ) }}
</li>
</ul>
{{ form_end(form) }}
{% endblock %}

View File

@@ -0,0 +1,29 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set activeRouteKey = 'chill_calendar_calendar_list' %}
{% block title 'chill_calendar.cancel_calendar_item'|trans %}
{% block content %}
{{ form_start(form) }}
{{ form_row(form.cancelReason) }}
<ul class="record_actions sticky-form-buttons">
<li class="save">
<a
class="btn btn-cancel"
href="{{ chill_return_path_or('chill_calendar_calendar_list', { 'id': person.id } )}}"
>
{{ 'Cancel'|trans|chill_return_path_label }}
</a>
</li>
<li>
{{ form_widget(form.submit, { 'attr' : { 'class': 'btn btn-save' }, 'label': 'Save' } ) }}
</li>
</ul>
{{ form_end(form) }}
{% endblock %}

View File

@@ -5,7 +5,7 @@
{% block table_entities_thead_tr %} {% block table_entities_thead_tr %}
<th>{{ 'Id'|trans }}</th> <th>{{ 'Id'|trans }}</th>
<th>{{ 'Name'|trans }}</th> <th>{{ 'Name'|trans }}</th>
<th>{{ 'canceledBy'|trans }}</th> <th>{{ 'Canceled by'|trans }}</th>
<th>{{ 'active'|trans }}</th> <th>{{ 'active'|trans }}</th>
<th>&nbsp;</th> <th>&nbsp;</th>
{% endblock %} {% endblock %}
@@ -40,4 +40,4 @@
</li> </li>
{% endblock %} {% endblock %}
{% endembed %} {% endembed %}
{% endblock %} {% endblock %}

View File

@@ -19,6 +19,7 @@ declare(strict_types=1);
namespace Chill\CalendarBundle\Service\ShortMessageNotification; namespace Chill\CalendarBundle\Service\ShortMessageNotification;
use Chill\CalendarBundle\Entity\Calendar; use Chill\CalendarBundle\Entity\Calendar;
use Chill\CalendarBundle\Entity\CancelReason;
use libphonenumber\PhoneNumberFormat; use libphonenumber\PhoneNumberFormat;
use libphonenumber\PhoneNumberUtil; use libphonenumber\PhoneNumberUtil;
use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Component\Notifier\Message\SmsMessage;
@@ -57,7 +58,7 @@ class DefaultShortMessageForCalendarBuilder implements ShortMessageForCalendarBu
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164), $this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]), $this->engine->render('@ChillCalendar/CalendarShortMessage/short_message.txt.twig', ['calendar' => $calendar]),
); );
} elseif (Calendar::SMS_CANCEL_PENDING === $calendar->getSmsStatus()) { } elseif (Calendar::SMS_CANCEL_PENDING === $calendar->getSmsStatus() && (null === $calendar->getCancelReason() || CancelReason::CANCELEDBY_PERSON !== $calendar->getCancelReason()->getCanceledBy())) {
$toUsers[] = new SmsMessage( $toUsers[] = new SmsMessage(
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164), $this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig', ['calendar' => $calendar]), $this->engine->render('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig', ['calendar' => $calendar]),

View File

@@ -31,8 +31,7 @@ Will send SMS: Un SMS de rappel sera envoyé
Will not send SMS: Aucun SMS de rappel ne sera envoyé Will not send SMS: Aucun SMS de rappel ne sera envoyé
SMS already sent: Un SMS a été envoyé SMS already sent: Un SMS a été envoyé
canceledBy: supprimé par Canceled by: Annulé par
Canceled by: supprimé par
Calendar configuration: Gestion des rendez-vous Calendar configuration: Gestion des rendez-vous
crud: crud:
@@ -44,6 +43,14 @@ crud:
title_edit: Modifier le motif d'annulation title_edit: Modifier le motif d'annulation
chill_calendar: chill_calendar:
canceled: Annulé
cancel_reason: Raison d'annulation
cancel_calendar_item: Annuler rendez-vous
calendar_canceled: Le rendez-vous a été annulé
canceled_by:
user: Utilisateur
person: Usager
other: Autre
Document: Document d'un rendez-vous Document: Document d'un rendez-vous
form: form:
The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement. The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement.

View File

@@ -59,7 +59,7 @@ final readonly class StoredObjectVersionApiController
return new JsonResponse( return new JsonResponse(
$this->serializer->serialize( $this->serializer->serialize(
new Collection(array_values($items->toArray()), $paginator), new Collection($items, $paginator),
'json', 'json',
[AbstractNormalizer::GROUPS => ['read', StoredObjectVersionNormalizer::WITH_POINT_IN_TIMES_CONTEXT, StoredObjectVersionNormalizer::WITH_RESTORED_CONTEXT]] [AbstractNormalizer::GROUPS => ['read', StoredObjectVersionNormalizer::WITH_POINT_IN_TIMES_CONTEXT, StoredObjectVersionNormalizer::WITH_RESTORED_CONTEXT]]
), ),

View File

@@ -1,20 +0,0 @@
<?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\DocStoreBundle\Exception;
class ConversionWithSameMimeTypeException extends \RuntimeException
{
public function __construct(string $mimeType, ?\Throwable $previous = null)
{
parent::__construct("Conversion to same MIME type '{$mimeType}' is not allowed: already at the same MIME type", 0, $previous);
}
}

View File

@@ -25,7 +25,7 @@ export interface GenericDoc {
type: "doc_store_generic_doc"; type: "doc_store_generic_doc";
uniqueKey: string; uniqueKey: string;
key: string; key: string;
identifiers: { id: number }; identifiers: object;
context: "person" | "accompanying-period"; context: "person" | "accompanying-period";
doc_date: DateTime; doc_date: DateTime;
metadata: GenericDocMetadata; metadata: GenericDocMetadata;

View File

@@ -3,9 +3,9 @@ import {
StoredObject, StoredObject,
StoredObjectPointInTime, StoredObjectPointInTime,
StoredObjectVersionWithPointInTime, StoredObjectVersionWithPointInTime,
} from "ChillDocStoreAssets/types"; } from "./../../../types";
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue"; import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
import { ISOToDatetime } from "ChillMainAssets/chill/js/date"; import { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date";
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue"; import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
import RestoreVersionButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/RestoreVersionButton.vue"; import RestoreVersionButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/RestoreVersionButton.vue";
import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DownloadButton.vue"; import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DownloadButton.vue";

View File

@@ -46,16 +46,6 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool public function voteOnAttribute(StoredObjectRoleEnum $attribute, StoredObject $subject, TokenInterface $token): bool
{ {
// we first try to get the permission from the workflow, as attachement (this is the less intensive query)
$workflowPermissionAsAttachment = match ($attribute) {
StoredObjectRoleEnum::SEE => $this->workflowDocumentService->isAllowedByWorkflowForReadOperation($subject),
StoredObjectRoleEnum::EDIT => $this->workflowDocumentService->isAllowedByWorkflowForWriteOperation($subject),
};
if (WorkflowRelatedEntityPermissionHelper::FORCE_DENIED === $workflowPermissionAsAttachment) {
return false;
}
// Retrieve the related entity // Retrieve the related entity
$entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject); $entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject);
@@ -76,7 +66,7 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
return match ($workflowPermission) { return match ($workflowPermission) {
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT => true, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT => true,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED => false, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED => false,
WorkflowRelatedEntityPermissionHelper::ABSTAIN => WorkflowRelatedEntityPermissionHelper::FORCE_GRANT === $workflowPermissionAsAttachment || $regularPermission, WorkflowRelatedEntityPermissionHelper::ABSTAIN => $regularPermission,
}; };
} }
} }

View File

@@ -14,12 +14,6 @@ namespace Chill\DocStoreBundle\Security\Authorization;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/**
* Interface for voting on stored object permissions.
*
* Each time a stored object is attached to a document, the voter is responsible for determining
* whether the user has the necessary permissions to access or modify the stored object.
*/
interface StoredObjectVoterInterface interface StoredObjectVoterInterface
{ {
public function supports(StoredObjectRoleEnum $attribute, StoredObject $subject): bool; public function supports(StoredObjectRoleEnum $attribute, StoredObject $subject): bool;

View File

@@ -15,7 +15,6 @@ use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Entity\StoredObjectPointInTime; use Chill\DocStoreBundle\Entity\StoredObjectPointInTime;
use Chill\DocStoreBundle\Entity\StoredObjectPointInTimeReasonEnum; use Chill\DocStoreBundle\Entity\StoredObjectPointInTimeReasonEnum;
use Chill\DocStoreBundle\Entity\StoredObjectVersion; use Chill\DocStoreBundle\Entity\StoredObjectVersion;
use Chill\DocStoreBundle\Exception\ConversionWithSameMimeTypeException;
use Chill\DocStoreBundle\Exception\StoredObjectManagerException; use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
use Chill\WopiBundle\Service\WopiConverter; use Chill\WopiBundle\Service\WopiConverter;
use Symfony\Component\Mime\MimeTypesInterface; use Symfony\Component\Mime\MimeTypesInterface;
@@ -42,10 +41,9 @@ class StoredObjectToPdfConverter
* *
* @return array{0: StoredObjectPointInTime, 1: StoredObjectVersion, 2?: string} contains the point in time before conversion and the new version of the stored object. The converted content is included in the response if $includeConvertedContent is true * @return array{0: StoredObjectPointInTime, 1: StoredObjectVersion, 2?: string} contains the point in time before conversion and the new version of the stored object. The converted content is included in the response if $includeConvertedContent is true
* *
* @throws \UnexpectedValueException if the preferred mime type for the conversion is not found * @throws \UnexpectedValueException if the preferred mime type for the conversion is not found
* @throws \RuntimeException if the conversion or storage of the new version fails * @throws \RuntimeException if the conversion or storage of the new version fails
* @throws StoredObjectManagerException * @throws StoredObjectManagerException
* @throws ConversionWithSameMimeTypeException if the document has already the same mime type79*
*/ */
public function addConvertedVersion(StoredObject $storedObject, string $lang, $convertTo = 'pdf', bool $includeConvertedContent = false): array public function addConvertedVersion(StoredObject $storedObject, string $lang, $convertTo = 'pdf', bool $includeConvertedContent = false): array
{ {
@@ -58,7 +56,7 @@ class StoredObjectToPdfConverter
$currentVersion = $storedObject->getCurrentVersion(); $currentVersion = $storedObject->getCurrentVersion();
if ($currentVersion->getType() === $newMimeType) { if ($currentVersion->getType() === $newMimeType) {
throw new ConversionWithSameMimeTypeException($newMimeType); throw new \UnexpectedValueException('Already at the same mime type');
} }
$content = $this->storedObjectManager->read($currentVersion); $content = $this->storedObjectManager->read($currentVersion);

View File

@@ -40,10 +40,6 @@ class StoredObjectVersionApiControllerTest extends \PHPUnit\Framework\TestCase
$storedObject->registerVersion(); $storedObject->registerVersion();
} }
// remove one version in the history
$v5 = $storedObject->getVersions()->get(5);
$storedObject->removeVersion($v5);
$security = $this->prophesize(Security::class); $security = $this->prophesize(Security::class);
$security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject) $security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)
->willReturn(true) ->willReturn(true)
@@ -57,7 +53,6 @@ class StoredObjectVersionApiControllerTest extends \PHPUnit\Framework\TestCase
self::assertEquals($response->getStatusCode(), 200); self::assertEquals($response->getStatusCode(), 200);
self::assertIsArray($body); self::assertIsArray($body);
self::assertArrayHasKey('results', $body); self::assertArrayHasKey('results', $body);
self::assertIsList($body['results']);
self::assertCount(10, $body['results']); self::assertCount(10, $body['results']);
} }

View File

@@ -86,165 +86,9 @@ class AbstractStoredObjectVoterTest extends TestCase
} }
/** /**
* @dataProvider dataProviderVoteOnAttributeWithStoredObjectPermission * @dataProvider dataProviderVoteOnAttribute
*/ */
public function testVoteOnAttributeWithStoredObjectPermission( public function testVoteOnAttribute(
StoredObjectRoleEnum $attribute,
bool $expected,
bool $isGrantedRegularPermission,
string $isGrantedWorkflowPermission,
string $isGrantedStoredObjectAttachment,
): void {
$storedObject = new StoredObject();
$repository = new DummyRepository($related = new \stdClass());
$token = new UsernamePasswordToken(new User(), 'dummy');
$security = $this->prophesize(Security::class);
$security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission);
$workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelper::class);
$security = $this->prophesize(Security::class);
$security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission);
if (StoredObjectRoleEnum::SEE === $attribute) {
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($storedObject)
->shouldBeCalled()
->willReturn($isGrantedStoredObjectAttachment);
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related)
->willReturn($isGrantedWorkflowPermission);
} elseif (StoredObjectRoleEnum::EDIT === $attribute) {
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($storedObject)
->shouldBeCalled()
->willReturn($isGrantedStoredObjectAttachment);
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($related)
->willReturn($isGrantedWorkflowPermission);
} else {
throw new \LogicException('Invalid attribute for StoredObjectVoter');
}
$storedObjectVoter = new class ($repository, $workflowRelatedEntityPermissionHelper->reveal(), $security->reveal()) extends AbstractStoredObjectVoter {
public function __construct(private $repository, $helper, $security)
{
parent::__construct($security, $helper);
}
protected function getRepository(): AssociatedEntityToStoredObjectInterface
{
return $this->repository;
}
protected function getClass(): string
{
return \stdClass::class;
}
protected function attributeToRole(StoredObjectRoleEnum $attribute): string
{
return 'SOME_ROLE';
}
protected function canBeAssociatedWithWorkflow(): bool
{
return true;
}
};
$actual = $storedObjectVoter->voteOnAttribute($attribute, $storedObject, $token);
self::assertEquals($expected, $actual);
}
public static function dataProviderVoteOnAttributeWithStoredObjectPermission(): iterable
{
foreach (['read' => StoredObjectRoleEnum::SEE, 'write' => StoredObjectRoleEnum::EDIT] as $action => $attribute) {
yield 'Not related to any workflow nor attachment ('.$action.')' => [
$attribute,
true,
true,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
];
yield 'Not related to any workflow nor attachment (refuse) ('.$action.')' => [
$attribute,
false,
false,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
];
yield 'Is granted by a workflow takes precedence (workflow) ('.$action.')' => [
$attribute,
false,
true,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
];
yield 'Is granted by a workflow takes precedence (stored object) ('.$action.')' => [
$attribute,
false,
true,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
];
yield 'Is granted by a workflow takes precedence (workflow) although grant ('.$action.')' => [
$attribute,
false,
true,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
];
yield 'Is granted by a workflow takes precedence (stored object) although grant ('.$action.')' => [
$attribute,
false,
true,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
];
yield 'Is granted by a workflow takes precedence (initially refused) (workflow) although grant ('.$action.')' => [
$attribute,
false,
false,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
];
yield 'Is granted by a workflow takes precedence (initially refused) (stored object) although grant ('.$action.')' => [
$attribute,
false,
false,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED,
];
yield 'Force grant inverse the regular permission (workflow) ('.$action.')' => [
$attribute,
true,
false,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
];
yield 'Force grant inverse the regular permission (so) ('.$action.')' => [
$attribute,
true,
false,
WorkflowRelatedEntityPermissionHelper::ABSTAIN,
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT,
];
}
}
/**
* @dataProvider dataProviderVoteOnAttributeWithoutStoredObjectPermission
*/
public function testVoteOnAttributeWithoutStoredObjectPermission(
StoredObjectRoleEnum $attribute, StoredObjectRoleEnum $attribute,
bool $expected, bool $expected,
bool $canBeAssociatedWithWorkflow, bool $canBeAssociatedWithWorkflow,
@@ -261,10 +105,6 @@ class AbstractStoredObjectVoterTest extends TestCase
$security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission); $security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission);
$workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelper::class); $workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelper::class);
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($storedObject)->willReturn(WorkflowRelatedEntityPermissionHelper::ABSTAIN);
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($storedObject)->willReturn(WorkflowRelatedEntityPermissionHelper::ABSTAIN);
if (null !== $isGrantedWorkflowPermissionRead) { if (null !== $isGrantedWorkflowPermissionRead) {
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related) $workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related)
->willReturn($isGrantedWorkflowPermissionRead)->shouldBeCalled(); ->willReturn($isGrantedWorkflowPermissionRead)->shouldBeCalled();
@@ -283,7 +123,7 @@ class AbstractStoredObjectVoterTest extends TestCase
self::assertEquals($expected, $voter->voteOnAttribute($attribute, $storedObject, $token), $message); self::assertEquals($expected, $voter->voteOnAttribute($attribute, $storedObject, $token), $message);
} }
public static function dataProviderVoteOnAttributeWithoutStoredObjectPermission(): iterable public static function dataProviderVoteOnAttribute(): iterable
{ {
// not associated on a workflow // not associated on a workflow
yield [StoredObjectRoleEnum::SEE, true, false, true, null, null, 'not associated on a workflow, granted by regular access, must not rely on helper']; yield [StoredObjectRoleEnum::SEE, true, false, true, null, null, 'not associated on a workflow, granted by regular access, must not rely on helper'];

View File

@@ -246,7 +246,7 @@ final class EventController extends AbstractController
'class' => Center::class, 'class' => Center::class,
'choices' => $centers, 'choices' => $centers,
'placeholder' => $this->translator->trans('Pick a center'), 'placeholder' => $this->translator->trans('Pick a center'),
'label' => 'To which territory should the event be associated ?', 'label' => 'To which centre should the event be associated ?',
]) ])
->add('submit', SubmitType::class, [ ->add('submit', SubmitType::class, [
'label' => 'Next step', 'label' => 'Next step',

View File

@@ -64,7 +64,7 @@ CHILL_EVENT_PARTICIPATION_SEE_DETAILS: Voir le détail d'une participation
# TODO check place to put this # TODO check place to put this
Next step: Étape suivante Next step: Étape suivante
To which territory should the event be associated ?: À quel territoire doit être associé l'événement ? To which centre should the event be associated ?: À quel centre doit être associé l'événement ?
# timeline # timeline
past: passé past: passé
@@ -151,7 +151,7 @@ event:
filter: filter:
event_types: Par types d'événement event_types: Par types d'événement
event_dates: Par date d'événement event_dates: Par date d'événement
center: Par territoire center: Par centre
by_responsable: Par responsable by_responsable: Par responsable
pick_responsable: Filtrer par responsables pick_responsable: Filtrer par responsables
budget: budget:
@@ -188,7 +188,7 @@ event_id: Identifiant
event_name: Nom event_name: Nom
event_date: Date event_date: Date
event_type: Type d'évenement event_type: Type d'évenement
event_center: Territoire event_center: Centre
event_moderator: Responsable event_moderator: Responsable
event_participants_count: Nombre de participants event_participants_count: Nombre de participants
event_location: Localisation event_location: Localisation

View File

@@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller; namespace Chill\MainBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorFactory;
@@ -28,7 +27,7 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
class WorkflowApiController extends ApiController class WorkflowApiController
{ {
public function __construct(private readonly EntityManagerInterface $entityManager, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly PaginatorFactory $paginatorFactory, private readonly Security $security, private readonly SerializerInterface $serializer) {} public function __construct(private readonly EntityManagerInterface $entityManager, private readonly EntityWorkflowRepository $entityWorkflowRepository, private readonly PaginatorFactory $paginatorFactory, private readonly Security $security, private readonly SerializerInterface $serializer) {}

View File

@@ -44,7 +44,7 @@ final readonly class WorkflowSignatureStateChangeController
$signature, $signature,
$request, $request,
EntityWorkflowStepSignatureVoter::CANCEL, EntityWorkflowStepSignatureVoter::CANCEL,
fn (EntityWorkflowStepSignature $signature): string => $this->signatureStepStateChanger->markSignatureAsCanceled($signature), function (EntityWorkflowStepSignature $signature) {$this->signatureStepStateChanger->markSignatureAsCanceled($signature); },
'@ChillMain/WorkflowSignature/cancel.html.twig', '@ChillMain/WorkflowSignature/cancel.html.twig',
); );
} }
@@ -56,18 +56,11 @@ final readonly class WorkflowSignatureStateChangeController
$signature, $signature,
$request, $request,
EntityWorkflowStepSignatureVoter::REJECT, EntityWorkflowStepSignatureVoter::REJECT,
fn (EntityWorkflowStepSignature $signature): string => $this->signatureStepStateChanger->markSignatureAsRejected($signature), function (EntityWorkflowStepSignature $signature) {$this->signatureStepStateChanger->markSignatureAsRejected($signature); },
'@ChillMain/WorkflowSignature/reject.html.twig', '@ChillMain/WorkflowSignature/reject.html.twig',
); );
} }
/**
* @param callable(EntityWorkflowStepSignature): string $markSignature
*
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
private function markSignatureAction( private function markSignatureAction(
EntityWorkflowStepSignature $signature, EntityWorkflowStepSignature $signature,
Request $request, Request $request,
@@ -86,13 +79,12 @@ final readonly class WorkflowSignatureStateChangeController
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
$expectedStep = $this->entityManager->wrapInTransaction(fn () => $markSignature($signature)); $this->entityManager->wrapInTransaction(function () use ($signature, $markSignature) {
$markSignature($signature);
});
return new RedirectResponse( return new RedirectResponse(
$this->chillUrlGenerator->forwardReturnPath( $this->chillUrlGenerator->returnPathOr('chill_main_workflow_show', ['id' => $signature->getStep()->getEntityWorkflow()->getId()])
'chill_main_workflow_wait',
['id' => $signature->getStep()->getEntityWorkflow()->getId(), 'expectedStep' => $expectedStep]
)
); );
} }

View File

@@ -1,41 +0,0 @@
<?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\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Routing\ChillUrlGeneratorInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Twig\Environment;
final readonly class WorkflowWaitStepChangeController
{
public function __construct(
private ChillUrlGeneratorInterface $chillUrlGenerator,
private Environment $twig,
) {}
#[Route('/{_locale}/main/workflow/{id}/wait/{expectedStep}', name: 'chill_main_workflow_wait', methods: ['GET'])]
public function waitForSignatureChange(EntityWorkflow $entityWorkflow, string $expectedStep): Response
{
if ($entityWorkflow->getStep() === $expectedStep) {
return new RedirectResponse(
$this->chillUrlGenerator->returnPathOr('chill_main_workflow_show', ['id' => $entityWorkflow->getId()])
);
}
return new Response(
$this->twig->render('@ChillMain/Workflow/waiting.html.twig', ['workflow' => $entityWorkflow, 'expectedStep' => $expectedStep])
);
}
}

View File

@@ -30,7 +30,6 @@ use Chill\MainBundle\Controller\UserGroupAdminController;
use Chill\MainBundle\Controller\UserGroupApiController; use Chill\MainBundle\Controller\UserGroupApiController;
use Chill\MainBundle\Controller\UserJobApiController; use Chill\MainBundle\Controller\UserJobApiController;
use Chill\MainBundle\Controller\UserJobController; use Chill\MainBundle\Controller\UserJobController;
use Chill\MainBundle\Controller\WorkflowApiController;
use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Chill\MainBundle\Doctrine\DQL\Age; use Chill\MainBundle\Doctrine\DQL\Age;
use Chill\MainBundle\Doctrine\DQL\Extract; use Chill\MainBundle\Doctrine\DQL\Extract;
@@ -67,7 +66,6 @@ use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserGroup; use Chill\MainBundle\Entity\UserGroup;
use Chill\MainBundle\Entity\UserJob; use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Form\CenterType; use Chill\MainBundle\Form\CenterType;
use Chill\MainBundle\Form\CivilityType; use Chill\MainBundle\Form\CivilityType;
use Chill\MainBundle\Form\CountryType; use Chill\MainBundle\Form\CountryType;
@@ -81,7 +79,6 @@ use Chill\MainBundle\Form\UserGroupType;
use Chill\MainBundle\Form\UserJobType; use Chill\MainBundle\Form\UserJobType;
use Chill\MainBundle\Form\UserType; use Chill\MainBundle\Form\UserType;
use Chill\MainBundle\Security\Authorization\ChillExportVoter; use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType; use Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType;
use Ramsey\Uuid\Doctrine\UuidType; use Ramsey\Uuid\Doctrine\UuidType;
use Symfony\Component\Config\FileLocator; use Symfony\Component\Config\FileLocator;
@@ -943,21 +940,6 @@ class ChillMainExtension extends Extension implements
], ],
], ],
], ],
[
'class' => EntityWorkflow::class,
'name' => 'workflow',
'base_path' => '/api/1.0/main/workflow',
'base_role' => EntityWorkflowVoter::SEE,
'controller' => WorkflowApiController::class,
'actions' => [
'_entity' => [
'methods' => [
Request::METHOD_GET => true,
Request::METHOD_HEAD => true,
],
],
],
],
], ],
]); ]);
} }

View File

@@ -1,13 +0,0 @@
/**
* Extracts the "returnPath" parameter from the current URL's query string and returns it.
* If the parameter is not present, returns the provided fallback path.
*
* @param {string} fallbackPath - The fallback path to use if "returnPath" is not found in the query string.
* @return {string} The "returnPath" from the query string, or the fallback path if "returnPath" is not present.
*/
export function returnPathOr(fallbackPath: string): string {
const urlParams = new URLSearchParams(window.location.search);
const returnPath = urlParams.get("returnPath");
return returnPath ?? fallbackPath;
}

View File

@@ -1,16 +0,0 @@
import { EntityWorkflow } from "ChillMainAssets/types";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
export const fetchWorkflow = async (
workflowId: number,
): Promise<EntityWorkflow> => {
try {
return await makeFetch<null, EntityWorkflow>(
"GET",
`/api/1.0/main/workflow/${workflowId}.json`,
);
} catch (error) {
console.error(`Failed to fetch workflow ${workflowId}:`, error);
throw error;
}
};

View File

@@ -1,6 +1,5 @@
import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc"; import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types"; import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
import { Person } from "../../../ChillPersonBundle/Resources/public/types";
export interface DateTime { export interface DateTime {
datetime: string; datetime: string;
@@ -203,58 +202,6 @@ export interface WorkflowAttachment {
genericDoc: null | GenericDoc; genericDoc: null | GenericDoc;
} }
export interface Workflow {
name: string;
text: string;
}
export interface EntityWorkflowStep {
type: "entity_workflow_step";
id: number;
comment: string;
currentStep: StepDefinition;
isFinal: boolean;
isFreezed: boolean;
isFinalized: boolean;
transitionPrevious: Transition | null;
transitionAfter: Transition | null;
previousId: number | null;
nextId: number | null;
transitionPreviousBy: User | null;
transitionPreviousAt: DateTime | null;
}
export interface Transition {
name: string;
text: string;
isForward: boolean;
}
export interface StepDefinition {
name: string;
text: string;
}
export interface EntityWorkflow {
type: "entity_workflow";
id: number;
relatedEntityClass: string;
relatedEntityId: number;
workflow: Workflow;
currentStep: EntityWorkflowStep;
steps: EntityWorkflowStep[];
datas: WorkflowData;
title: string;
isOnHoldAtCurrentStep: boolean;
_permissions: {
CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT: boolean;
};
}
export interface WorkflowData {
persons: Person[];
}
export interface ExportGeneration { export interface ExportGeneration {
id: string; id: string;
type: "export_generation"; type: "export_generation";
@@ -268,8 +215,3 @@ export interface ExportGeneration {
export interface PrivateCommentEmbeddable { export interface PrivateCommentEmbeddable {
comments: Record<number, string>; comments: Record<number, string>;
} }
/**
* Possible states for the WaitingScreen Component.
*/
export type WaitingScreenState = "pending" | "failure" | "stopped" | "ready";

View File

@@ -10,8 +10,7 @@ import { computed, onMounted, ref } from "vue";
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types"; import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
import { fetchExportGenerationStatus } from "ChillMainAssets/lib/api/export"; import { fetchExportGenerationStatus } from "ChillMainAssets/lib/api/export";
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue"; import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
import WaitingScreen from "../_components/WaitingScreen.vue"; import { ExportGeneration } from "ChillMainAssets/types";
import { ExportGeneration, WaitingScreenState } from "ChillMainAssets/types";
interface AppProps { interface AppProps {
exportGenerationId: string; exportGenerationId: string;
@@ -35,16 +34,13 @@ const storedObject = computed<null | StoredObject>(() => {
}); });
const isPending = computed<boolean>(() => status.value === "pending"); const isPending = computed<boolean>(() => status.value === "pending");
const isFetching = computed<boolean>(
() => tryiesForReady.value < maxTryiesForReady,
);
const isReady = computed<boolean>(() => status.value === "ready");
const isFailure = computed<boolean>(() => status.value === "failure");
const filename = computed<string>(() => `${props.title}-${props.createdDate}`); const filename = computed<string>(() => `${props.title}-${props.createdDate}`);
const state = computed<WaitingScreenState>((): WaitingScreenState => {
if (status.value === "empty") {
return "pending";
}
return status.value;
});
/** /**
* counter for the number of times that we check for a new status * counter for the number of times that we check for a new status
*/ */
@@ -89,36 +85,57 @@ onMounted(() => {
</script> </script>
<template> <template>
<WaitingScreen :state="state"> <div id="waiting-screen">
<template v-slot:pending> <div
<p> v-if="isPending && isFetching"
{{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }} class="alert alert-danger text-center"
</p> >
</template> <div>
<p>
{{ trans(EXPORT_GENERATION_EXPORT_GENERATION_IS_PENDING) }}
</p>
</div>
<template v-slot:stopped> <div>
<p> <i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }} <span class="sr-only">Loading...</span>
</p> </div>
</template> </div>
<div v-if="isPending && !isFetching" class="alert alert-info">
<div>
<p>
{{ trans(EXPORT_GENERATION_TOO_MANY_RETRIES) }}
</p>
</div>
</div>
<div v-if="isFailure" class="alert alert-danger text-center">
<div>
<p>
{{ trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT) }}
</p>
</div>
</div>
<div v-if="isReady" class="alert alert-success text-center">
<div>
<p>
{{ trans(EXPORT_GENERATION_EXPORT_READY) }}
</p>
<template v-slot:failure> <p v-if="storedObject !== null">
<p> <document-action-buttons-group
{{ trans(EXPORT_GENERATION_ERROR_WHILE_GENERATING_EXPORT) }} :stored-object="storedObject"
</p> :filename="filename"
</template> ></document-action-buttons-group>
</p>
<template v-slot:ready> </div>
<p> </div>
{{ trans(EXPORT_GENERATION_EXPORT_READY) }} </div>
</p>
<p v-if="storedObject !== null">
<document-action-buttons-group
:stored-object="storedObject"
:filename="filename"
></document-action-buttons-group>
</p>
</template>
</WaitingScreen>
</template> </template>
<style scoped lang="scss">
#waiting-screen {
> .alert {
min-height: 350px;
}
}
</style>

View File

@@ -1,75 +0,0 @@
<script setup lang="ts">
import { useIntervalFn } from "@vueuse/core";
import { fetchWorkflow } from "ChillMainAssets/lib/workflow/api";
import { returnPathOr } from "ChillMainAssets/lib/return_path/returnPathHelper";
import { ref } from "vue";
import WaitingScreen from "ChillMainAssets/vuejs/_components/WaitingScreen.vue";
import { WaitingScreenState } from "ChillMainAssets/types";
import {
trans,
WORKFLOW_WAIT_TITLE,
WORKFLOW_WAIT_ERROR_WHILE_WAITING,
WORKFLOW_WAIT_SUCCESS,
} from "translator";
interface WaitPostProcessWorkflowComponentProps {
workflowId: number;
expectedStep: string;
}
const props = defineProps<WaitPostProcessWorkflowComponentProps>();
const counter = ref<number>(0);
const MAX_TRYIES = 50;
const state = ref<WaitingScreenState>("pending");
const { pause, resume } = useIntervalFn(
async () => {
try {
const workflow = await fetchWorkflow(props.workflowId);
counter.value++;
if (workflow.currentStep.currentStep.name === props.expectedStep) {
window.location.assign(
returnPathOr("/fr/main/workflow" + workflow.id + "/show"),
);
resume();
state.value = "ready";
}
if (counter.value > MAX_TRYIES) {
pause();
state.value = "failure";
}
} catch (error) {
console.error(error);
pause();
}
},
2000,
{ immediate: true },
);
</script>
<template>
<div class="container">
<WaitingScreen :state="state">
<template v-slot:pending>
<p>
{{ trans(WORKFLOW_WAIT_TITLE) }}
</p>
</template>
<template v-slot:failure>
<p>
{{ trans(WORKFLOW_WAIT_ERROR_WHILE_WAITING) }}
</p>
</template>
<template v-slot:ready>
<p>
{{ trans(WORKFLOW_WAIT_SUCCESS) }}
</p>
</template>
</WaitingScreen>
</div>
</template>
<style scoped lang="scss"></style>

View File

@@ -1,51 +0,0 @@
import { createApp } from "vue";
import App from "./App.vue";
function mountApp(): void {
const el = document.querySelector<HTMLDivElement>(".screen-wait");
if (!el) {
console.error(
"WaitPostProcessWorkflow: mount element .screen-wait not found",
);
return;
}
const workflowIdAttr = el.getAttribute("data-workflow-id");
const expectedStep = el.getAttribute("data-expected-step") || "";
if (!workflowIdAttr) {
console.error(
"WaitPostProcessWorkflow: data-workflow-id attribute missing on mount element",
);
return;
}
if (!expectedStep) {
console.error(
"WaitPostProcessWorkflow: data-expected-step attribute missing on mount element",
);
return;
}
const workflowId = Number(workflowIdAttr);
if (Number.isNaN(workflowId)) {
console.error(
"WaitPostProcessWorkflow: data-workflow-id is not a valid number:",
workflowIdAttr,
);
return;
}
const app = createApp(App, {
workflowId,
expectedStep,
});
app.mount(el);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", mountApp);
} else {
mountApp();
}

View File

@@ -1,11 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref, useTemplateRef } from "vue"; import { computed, useTemplateRef } from "vue";
import type { EntityWorkflow, WorkflowAttachment } from "ChillMainAssets/types"; import type { WorkflowAttachment } from "ChillMainAssets/types";
import PickGenericDocModal from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocModal.vue"; import PickGenericDocModal from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocModal.vue";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc"; import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import AttachmentList from "ChillMainAssets/vuejs/WorkflowAttachment/Component/AttachmentList.vue"; import AttachmentList from "ChillMainAssets/vuejs/WorkflowAttachment/Component/AttachmentList.vue";
import { GenericDoc } from "ChillDocStoreAssets/types"; import { GenericDoc } from "ChillDocStoreAssets/types";
import { fetchWorkflow } from "ChillMainAssets/lib/workflow/api";
interface AppConfig { interface AppConfig {
workflowId: number; workflowId: number;
@@ -35,13 +34,6 @@ const attachedGenericDoc = computed<GenericDocForAccompanyingPeriod[]>(
) as GenericDocForAccompanyingPeriod[], ) as GenericDocForAccompanyingPeriod[],
); );
const workflow = ref<EntityWorkflow | null>(null);
onMounted(async () => {
workflow.value = await fetchWorkflow(Number(props.workflowId));
console.log("workflow", workflow.value);
});
const openModal = function () { const openModal = function () {
pickDocModal.value?.openModal(); pickDocModal.value?.openModal();
}; };
@@ -57,30 +49,20 @@ const onPickGenericDoc = ({
const onRemoveAttachment = (payload: { attachment: WorkflowAttachment }) => { const onRemoveAttachment = (payload: { attachment: WorkflowAttachment }) => {
emit("removeAttachment", payload); emit("removeAttachment", payload);
}; };
const canEditAttachement = computed<boolean>(() => {
if (null === workflow.value) {
return false;
}
return workflow.value._permissions.CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT;
});
</script> </script>
<template> <template>
<pick-generic-doc-modal <pick-generic-doc-modal
:workflow="workflow"
:accompanying-period-id="props.accompanyingPeriodId" :accompanying-period-id="props.accompanyingPeriodId"
:to-remove="attachedGenericDoc" :to-remove="attachedGenericDoc"
ref="pickDocModal" ref="pickDocModal"
@pickGenericDoc="onPickGenericDoc" @pickGenericDoc="onPickGenericDoc"
></pick-generic-doc-modal> ></pick-generic-doc-modal>
<attachment-list <attachment-list
:workflow="workflow"
:attachments="props.attachments" :attachments="props.attachments"
@removeAttachment="onRemoveAttachment" @removeAttachment="onRemoveAttachment"
></attachment-list> ></attachment-list>
<ul v-if="canEditAttachement" class="record_actions"> <ul class="record_actions">
<li> <li>
<button type="button" class="btn btn-create" @click="openModal"> <button type="button" class="btn btn-create" @click="openModal">
Ajouter une pièce jointe Ajouter une pièce jointe

View File

@@ -1,11 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { EntityWorkflow, WorkflowAttachment } from "ChillMainAssets/types"; import { WorkflowAttachment } from "ChillMainAssets/types";
import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Component/GenericDocItemBox.vue"; import GenericDocItemBox from "ChillMainAssets/vuejs/WorkflowAttachment/Component/GenericDocItemBox.vue";
import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue"; import DocumentActionButtonsGroup from "ChillDocStoreAssets/vuejs/DocumentActionButtonsGroup.vue";
interface AttachmentListProps { interface AttachmentListProps {
attachments: WorkflowAttachment[]; attachments: WorkflowAttachment[];
workflow: EntityWorkflow | null;
} }
const emit = defineEmits<{ const emit = defineEmits<{
@@ -37,12 +36,7 @@ const props = defineProps<AttachmentListProps>();
:stored-object="a.genericDoc.storedObject" :stored-object="a.genericDoc.storedObject"
></document-action-buttons-group> ></document-action-buttons-group>
</li> </li>
<li <li>
v-if="
!workflow?._permissions
.CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT
"
>
<button <button
type="button" type="button"
class="btn btn-delete" class="btn btn-delete"

View File

@@ -6,10 +6,8 @@ import {
import PickGenericDocItem from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocItem.vue"; import PickGenericDocItem from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDocItem.vue";
import { fetch_generic_docs_by_accompanying_period } from "ChillDocStoreAssets/js/generic-doc-api"; import { fetch_generic_docs_by_accompanying_period } from "ChillDocStoreAssets/js/generic-doc-api";
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { EntityWorkflow } from "ChillMainAssets/types";
interface PickGenericDocProps { interface PickGenericDocProps {
workflow: EntityWorkflow | null;
accompanyingPeriodId: number; accompanyingPeriodId: number;
pickedList: GenericDocForAccompanyingPeriod[]; pickedList: GenericDocForAccompanyingPeriod[];
toRemove: GenericDocForAccompanyingPeriod[]; toRemove: GenericDocForAccompanyingPeriod[];
@@ -38,21 +36,9 @@ const isPicked = (genericDoc: GenericDocForAccompanyingPeriod): boolean =>
) !== -1; ) !== -1;
onMounted(async () => { onMounted(async () => {
const fetchedGenericDocs = await fetch_generic_docs_by_accompanying_period( genericDocs.value = await fetch_generic_docs_by_accompanying_period(
props.accompanyingPeriodId, props.accompanyingPeriodId,
); );
const documentClasses = [
"Chill\\DocStoreBundle\\Entity\\AccompanyingCourseDocument",
"Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluationDocument",
"Chill\\DocStoreBundle\\Entity\\PersonDocument",
];
genericDocs.value = fetchedGenericDocs.filter(
(doc) =>
!documentClasses.includes(
props.workflow?.relatedEntityClass || "",
) || props.workflow?.relatedEntityId !== doc.identifiers.id,
);
loaded.value = true; loaded.value = true;
}); });

View File

@@ -3,10 +3,8 @@ import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import { computed, ref, useTemplateRef } from "vue"; import { computed, ref, useTemplateRef } from "vue";
import PickGenericDoc from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDoc.vue"; import PickGenericDoc from "ChillMainAssets/vuejs/WorkflowAttachment/Component/PickGenericDoc.vue";
import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc"; import { GenericDocForAccompanyingPeriod } from "ChillDocStoreAssets/types/generic_doc";
import { EntityWorkflow } from "ChillMainAssets/types";
interface PickGenericDocModalProps { interface PickGenericDocModalProps {
workflow: EntityWorkflow | null;
accompanyingPeriodId: number; accompanyingPeriodId: number;
toRemove: GenericDocForAccompanyingPeriod[]; toRemove: GenericDocForAccompanyingPeriod[];
} }
@@ -82,7 +80,6 @@ defineExpose({ openModal, closeModal });
</template> </template>
<template v-slot:body> <template v-slot:body>
<pick-generic-doc <pick-generic-doc
:workflow="props.workflow"
:accompanying-period-id="props.accompanyingPeriodId" :accompanying-period-id="props.accompanyingPeriodId"
:to-remove="props.toRemove" :to-remove="props.toRemove"
:picked-list="pickeds" :picked-list="pickeds"

View File

@@ -1,62 +0,0 @@
<script setup lang="ts">
import { WaitingScreenState } from "ChillMainAssets/types";
interface Props {
state: WaitingScreenState;
}
const props = defineProps<Props>();
</script>
<template>
<div id="waiting-screen">
<div
v-if="props.state === 'pending' && !!$slots.pending"
class="alert alert-danger text-center"
>
<div>
<slot name="pending"></slot>
</div>
<div>
<i class="fa fa-cog fa-spin fa-3x fa-fw"></i>
<span class="sr-only">Loading...</span>
</div>
</div>
<div
v-if="props.state === 'stopped' && !!$slots.stopped"
class="alert alert-info"
>
<div>
<slot name="stopped"></slot>
</div>
</div>
<div
v-if="props.state === 'failure' && !!$slots.failure"
class="alert alert-danger text-center"
>
<div>
<slot name="failure"></slot>
</div>
</div>
<div
v-if="props.state === 'ready' && !!$slots.ready"
class="alert alert-success text-center"
>
<div>
<slot name="ready"></slot>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
#waiting-screen {
> .alert {
min-height: 350px;
}
}
</style>

View File

@@ -21,6 +21,8 @@
{{ form_row(form.title, { 'label': 'notification.subject'|trans }) }} {{ form_row(form.title, { 'label': 'notification.subject'|trans }) }}
{{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }} {{ form_row(form.addressees, { 'label': 'notification.sent_to'|trans }) }}
{{ form_row(form.addressesEmails) }}
{% include handler.template(notification) with handler.templateData(notification) %} {% include handler.template(notification) with handler.templateData(notification) %}
<div class="mb-3 row"> <div class="mb-3 row">

View File

@@ -61,7 +61,7 @@
{% endif %} {% endif %}
</li> </li>
<li> <li>
<span class="dt">service/territoire:</span> <span class="dt">cercle/centre:</span>
{% if entity.mainScope %} {% if entity.mainScope %}
{{ entity.mainScope.name|localize_translatable_string }} {{ entity.mainScope.name|localize_translatable_string }}
{% endif %} {% endif %}

View File

@@ -58,14 +58,12 @@
{% endif %} {% endif %}
</section> </section>
{% if signatures|length > 0 %}
<section class="step my-4">{% include '@ChillMain/Workflow/_signature.html.twig' %}</section>
{% endif %}
<section class="step my-4">{% include '@ChillMain/Workflow/_attachment.html.twig' %}</section> <section class="step my-4">{% include '@ChillMain/Workflow/_attachment.html.twig' %}</section>
<section class="step my-4">{% include '@ChillMain/Workflow/_follow.html.twig' %}</section> <section class="step my-4">{% include '@ChillMain/Workflow/_follow.html.twig' %}</section>
{% if entity_workflow.currentStep.sends|length > 0 %} {% if signatures|length > 0 %}
<section class="step my-4">{% include '@ChillMain/Workflow/_signature.html.twig' %}</section>
{% elseif entity_workflow.currentStep.sends|length > 0 %}
<section class="step my-4"> <section class="step my-4">
<h2>{{ 'workflow.external_views.title'|trans({'numberOfSends': entity_workflow.currentStep.sends|length }) }}</h2> <h2>{{ 'workflow.external_views.title'|trans({'numberOfSends': entity_workflow.currentStep.sends|length }) }}</h2>
{% include '@ChillMain/Workflow/_send_views_list.html.twig' with {'sends': entity_workflow.currentStep.sends} %} {% include '@ChillMain/Workflow/_send_views_list.html.twig' with {'sends': entity_workflow.currentStep.sends} %}

View File

@@ -1,18 +0,0 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title %}{{ 'workflow.signature.waiting_for'|trans }}{% endblock %}
{% block css %}
{{ encore_entry_link_tags('page_workflow_waiting_post_process') }}
{% endblock %}
{% block js %}
{{ encore_entry_script_tags('page_workflow_waiting_post_process') }}
{% endblock %}
{% block content %}
<h1>{{ block('title') }}</h1>
<div class="screen-wait" data-workflow-id="{{ workflow.id|e('html_attr') }}" data-expected-step="{{ expectedStep|e('html_attr') }}"></div>
{% endblock %}

View File

@@ -49,7 +49,7 @@ class AdminUserMenuBuilder implements LocalMenuBuilderInterface
'route' => 'chill_crud_center_index', 'route' => 'chill_crud_center_index',
])->setExtras(['order' => 1010]); ])->setExtras(['order' => 1010]);
$menu->addChild('Regroupements des territoires', [ $menu->addChild('Regroupements des centres', [
'route' => 'chill_crud_regroupment_index', 'route' => 'chill_crud_regroupment_index',
])->setExtras(['order' => 1015]); ])->setExtras(['order' => 1015]);

View File

@@ -1,53 +0,0 @@
<?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\Security\Authorization;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Workflow\Registry;
final class EntityWorkflowAttachmentVoter extends Voter
{
public function __construct(
private readonly Registry $registry,
) {}
public const EDIT = 'CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT';
protected function supports(string $attribute, $subject): bool
{
return $subject instanceof EntityWorkflow && self::EDIT === $attribute;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
if (!$subject instanceof EntityWorkflow) {
throw new \UnexpectedValueException('Subject must be an instance of EntityWorkflow');
}
if ($subject->isFinal()) {
return false;
}
$workflow = $this->registry->get($subject, $subject->getWorkflowName());
$marking = $workflow->getMarking($subject);
foreach ($marking->getPlaces() as $place => $int) {
$placeMetadata = $workflow->getMetadataStore()->getPlaceMetadata($place);
if ($placeMetadata['isSentExternal'] ?? false) {
return false;
}
}
return true;
}
}

View File

@@ -12,25 +12,18 @@ declare(strict_types=1);
namespace Chill\MainBundle\Serializer\Normalizer; namespace Chill\MainBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Security\Authorization\EntityWorkflowAttachmentVoter;
use Chill\MainBundle\Workflow\EntityWorkflowManager; use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\Helper\MetadataExtractor; use Chill\MainBundle\Workflow\Helper\MetadataExtractor;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Registry;
final class EntityWorkflowNormalizer implements NormalizerInterface, NormalizerAwareInterface class EntityWorkflowNormalizer implements NormalizerInterface, NormalizerAwareInterface
{ {
use NormalizerAwareTrait; use NormalizerAwareTrait;
public function __construct( public function __construct(private readonly EntityWorkflowManager $entityWorkflowManager, private readonly MetadataExtractor $metadataExtractor, private readonly Registry $registry) {}
private readonly EntityWorkflowManager $entityWorkflowManager,
private readonly MetadataExtractor $metadataExtractor,
private readonly Registry $registry,
private readonly Security $security,
) {}
/** /**
* @param EntityWorkflow $object * @param EntityWorkflow $object
@@ -53,9 +46,6 @@ final class EntityWorkflowNormalizer implements NormalizerInterface, NormalizerA
'datas' => $this->normalizer->normalize($handler->getEntityData($object), $format, $context), 'datas' => $this->normalizer->normalize($handler->getEntityData($object), $format, $context),
'title' => $handler->getEntityTitle($object), 'title' => $handler->getEntityTitle($object),
'isOnHoldAtCurrentStep' => $object->isOnHoldAtCurrentStep(), 'isOnHoldAtCurrentStep' => $object->isOnHoldAtCurrentStep(),
'_permissions' => [
EntityWorkflowAttachmentVoter::EDIT => $this->security->isGranted(EntityWorkflowAttachmentVoter::EDIT, $object),
],
]; ];
} }

View File

@@ -35,7 +35,7 @@ final class ScopeControllerTest extends WebTestCase
$client->getResponse()->getStatusCode(), $client->getResponse()->getStatusCode(),
'Unexpected HTTP status code for GET /fr/admin/scope/' 'Unexpected HTTP status code for GET /fr/admin/scope/'
); );
$crawler = $client->click($crawler->selectLink('Créer un nouveau service')->link()); $crawler = $client->click($crawler->selectLink('Créer un nouveau cercle')->link());
// Fill in the form and submit it // Fill in the form and submit it
$form = $crawler->selectButton('Créer')->form([ $form = $crawler->selectButton('Créer')->form([
'chill_mainbundle_scope[name][fr]' => 'Test en fr', 'chill_mainbundle_scope[name][fr]' => 'Test en fr',

View File

@@ -1,173 +0,0 @@
<?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\Tests\Security\Authorization;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Security\Authorization\EntityWorkflowAttachmentVoter;
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
use PHPUnit\Framework\TestCase;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Workflow\DefinitionBuilder;
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Workflow\WorkflowInterface;
/**
* @internal
*
* @coversNothing
*/
class EntityWorkflowAttachmentVoterTest extends TestCase
{
use ProphecyTrait;
/**
* @dataProvider dataVoteOnAttribute
*/
public function testVoteOnAttribute(EntityWorkflow $entityWorkflow, int $expected): void
{
$voter = new EntityWorkflowAttachmentVoter($this->buildRegistry());
$actual = $voter->vote(
new UsernamePasswordToken(new User(), 'default'),
$entityWorkflow,
['CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT'],
);
$this->assertEquals($expected, $actual);
}
public static function dataVoteOnAttribute(): iterable
{
$entity = new EntityWorkflow();
$entity->setWorkflowName('dummy');
$workflow = static::buildRegistry()->get($entity, 'dummy');
$dto = new WorkflowTransitionContextDTO($entity);
$dto->futureDestUsers[] = new User();
$workflow->apply(
$entity,
'to_final_positive',
['context' => $dto,
'byUser' => new User(),
'transition' => 'to_final_positive',
'transitionAt' => new \DateTimeImmutable()],
);
// we need to mark manually as final, as the listener is not registered
$entity->getCurrentStep()->setIsFinal(true);
yield 'on final positive' => [
$entity,
VoterInterface::ACCESS_DENIED,
];
$entity = new EntityWorkflow();
$entity->setWorkflowName('dummy');
$workflow = static::buildRegistry()->get($entity, 'dummy');
$dto = new WorkflowTransitionContextDTO($entity);
$dto->futureDestUsers[] = new User();
$workflow->apply(
$entity,
'to_final_negative',
['context' => $dto,
'byUser' => new User(),
'transition' => 'to_final_negative',
'transitionAt' => new \DateTimeImmutable()],
);
// we need to mark manually as final, as the listener is not registered
$entity->getCurrentStep()->setIsFinal(true);
yield 'on final negative' => [
$entity,
VoterInterface::ACCESS_DENIED,
];
$entity = new EntityWorkflow();
$entity->setWorkflowName('dummy');
$workflow = static::buildRegistry()->get($entity, 'dummy');
$dto = new WorkflowTransitionContextDTO($entity);
$dto->futureDestUsers[] = new User();
$workflow->apply(
$entity,
'to_sent_external',
['context' => $dto,
'byUser' => new User(),
'transition' => 'to_sent_external',
'transitionAt' => new \DateTimeImmutable()],
);
yield 'on sent_external' => [
$entity,
VoterInterface::ACCESS_DENIED,
];
$entity = new EntityWorkflow();
$entity->setWorkflowName('dummy');
yield 'on initial' => [
$entity,
VoterInterface::ACCESS_GRANTED,
];
}
private static function buildRegistry(): Registry
{
$builder = new DefinitionBuilder();
$builder
->setInitialPlaces(['initial'])
->addPlaces(['initial', 'sent_external', 'final_positive', 'final_negative'])
->addTransitions([
new Transition('to_final_positive', ['initial'], 'final_positive'),
new Transition('to_sent_external', ['initial'], 'sent_external'),
new Transition('to_final_negative', ['initial'], 'final_negative'),
])
->setMetadataStore(
new InMemoryMetadataStore(
placesMetadata: [
'sent_external' => [
'isSentExternal' => true,
],
'final_positive' => [
'isFinal' => true,
'isFinalPositive' => true,
],
'final_negative' => [
'isFinal' => true,
'isFinalPositive' => false,
],
]
)
);
$workflow = new Workflow($builder->build(), new EntityWorkflowMarkingStore(), name: 'dummy');
$registry = new Registry();
$registry->addWorkflow($workflow, new class () implements WorkflowSupportStrategyInterface {
public function supports(WorkflowInterface $workflow, object $subject): bool
{
return true;
}
});
return $registry;
}
}

View File

@@ -11,9 +11,6 @@ declare(strict_types=1);
namespace Chill\MainBundle\Tests\Workflow\Helper; namespace Chill\MainBundle\Tests\Workflow\Helper;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper; use Chill\MainBundle\Workflow\Helper\WorkflowRelatedEntityPermissionHelper;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
@@ -151,11 +148,8 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase
$dto = new WorkflowTransitionContextDTO($entityWorkflow); $dto = new WorkflowTransitionContextDTO($entityWorkflow);
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User()); $entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
yield [[], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(), yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain because there is no workflow']; 'abstain because the user is not present as a dest user'];
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, new \DateTimeImmutable(),
'force deny because the user is not present as a dest user'];
$entityWorkflow = new EntityWorkflow(); $entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow); $dto = new WorkflowTransitionContextDTO($entityWorkflow);
@@ -177,9 +171,6 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(), yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
'force grant because the user was a previous user']; 'force grant because the user was a previous user'];
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, new \DateTimeImmutable(),
'force denied because the user was not a previous user'];
$entityWorkflow = new EntityWorkflow(); $entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow); $dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User(); $dto->futureDestUsers[] = $user = new User();
@@ -241,13 +232,6 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(), yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain: there is a signature on a canceled workflow']; 'abstain: there is a signature on a canceled workflow'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('sent_external', $dto, 'to_sent_external', new \DateTimeImmutable(), $user);
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, new \DateTimeImmutable(), 'force denied: the workflow is sent to an external user'];
} }
public function testNoWorkflow(): void public function testNoWorkflow(): void
@@ -269,217 +253,7 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase
$entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class); $entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class);
$entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->willReturn($entityWorkflows); $entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->willReturn($entityWorkflows);
$repository = $this->prophesize(EntityWorkflowAttachmentRepository::class); return new WorkflowRelatedEntityPermissionHelper($security->reveal(), $entityWorkflowManager->reveal(), $this->buildRegistry(), new MockClock($atDateTime ?? new \DateTimeImmutable()));
$repository->findByStoredObject(Argument::type(StoredObject::class))->willReturn([]);
return new WorkflowRelatedEntityPermissionHelper($security->reveal(), $entityWorkflowManager->reveal(), $repository->reveal(), $this->buildRegistry(), new MockClock($atDateTime ?? new \DateTimeImmutable()));
}
/**
* @dataProvider provideDataAllowedByWorkflowReadOperationByAttachment
*
* @param list<EntityWorkflow> $entityWorkflows
*/
public function testAllowedByWorkflowReadByAttachment(
array $entityWorkflows,
User $user,
string $expected,
?\DateTimeImmutable $atDate,
string $message,
): void {
// all entities must have this workflow name, so we are ok to set it here
foreach ($entityWorkflows as $entityWorkflow) {
$entityWorkflow->setWorkflowName('dummy');
}
$helper = $this->buildHelperForAttachment($entityWorkflows, $user, $atDate);
self::assertEquals($expected, $helper->isAllowedByWorkflowForReadOperation(new StoredObject()), $message);
}
public static function provideDataAllowedByWorkflowReadOperationByAttachment(): iterable
{
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain because the user is not present as a dest user'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
'force grant because the user is a current user'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
'force grant because the user was a previous user'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futurePersonSignatures[] = new Person();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'Abstain: there is a signature for person, but the attachment is not concerned'];
}
/**
* @dataProvider provideDataAllowedByWorkflowWriteOperationByAttachment
*
* @param list<EntityWorkflow> $entityWorkflows
*/
public function testAllowedByWorkflowWriteByAttachment(
array $entityWorkflows,
User $user,
string $expected,
?\DateTimeImmutable $atDate,
string $message,
): void {
// all entities must have this workflow name, so we are ok to set it here
foreach ($entityWorkflows as $entityWorkflow) {
$entityWorkflow->setWorkflowName('dummy');
}
$helper = $this->buildHelperForAttachment($entityWorkflows, $user, $atDate);
self::assertEquals($expected, $helper->isAllowedByWorkflowForWriteOperation(new StoredObject()), $message);
}
public static function provideDataAllowedByWorkflowWriteOperationByAttachment(): iterable
{
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
yield [[], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain because there is no workflow'];
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain because the user is not present as a dest user (and attachment)'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
'force grant because the user is a current user'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_GRANT, new \DateTimeImmutable(),
'force grant because the user was a previous user'];
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain because the user was not a previous user'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$entityWorkflow->setStep('final_positive', $dto, 'to_final_positive', new \DateTimeImmutable(), new User());
$entityWorkflow->getCurrentStep()->setIsFinal(true);
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, new \DateTimeImmutable(),
'force denied: user was a previous user, but it is finalized positive'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable());
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$entityWorkflow->setStep('final_negative', $dto, 'to_final_negative', new \DateTimeImmutable());
$entityWorkflow->getCurrentStep()->setIsFinal(true);
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain: user was a previous user, it is finalized, but finalized negative'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futurePersonSignatures[] = new Person();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
$signature = $entityWorkflow->getCurrentStep()->getSignatures()->first();
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED)->setStateDate(new \DateTimeImmutable());
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain: there is a signature, but not on the attachment'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futurePersonSignatures[] = new Person();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
yield [[$entityWorkflow], new User(), WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain: there is a signature, but the signature is not on the attachment'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futurePersonSignatures[] = new Person();
$entityWorkflow->setStep('test', $dto, 'to_test', new \DateTimeImmutable(), new User());
$signature = $entityWorkflow->getCurrentStep()->getSignatures()->first();
$signature->setState(EntityWorkflowSignatureStateEnum::SIGNED)->setStateDate(new \DateTimeImmutable());
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$entityWorkflow->setStep('final_negative', $dto, 'to_final_negative', new \DateTimeImmutable(), new User());
$entityWorkflow->getCurrentStep()->setIsFinal(true);
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::ABSTAIN, new \DateTimeImmutable(),
'abstain: there is a signature on a canceled workflow'];
$entityWorkflow = new EntityWorkflow();
$dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestUsers[] = $user = new User();
$entityWorkflow->setStep('sent_external', $dto, 'to_sent_external', new \DateTimeImmutable(), $user);
yield [[$entityWorkflow], $user, WorkflowRelatedEntityPermissionHelper::FORCE_DENIED, new \DateTimeImmutable(),
'force denied: the workflow is sent to an external user'];
}
/**
* @param list<EntityWorkflow> $entityWorkflows
*/
private function buildHelperForAttachment(array $entityWorkflows, User $user, ?\DateTimeImmutable $atDateTime): WorkflowRelatedEntityPermissionHelper
{
$security = $this->prophesize(Security::class);
$security->getUser()->willReturn($user);
$entityWorkflowManager = $this->prophesize(EntityWorkflowManager::class);
$entityWorkflowManager->findByRelatedEntity(Argument::type('object'))->shouldNotBeCalled();
$repository = $this->prophesize(EntityWorkflowAttachmentRepository::class);
$attachments = [];
foreach ($entityWorkflows as $entityWorkflow) {
$attachments[] = new EntityWorkflowAttachment('dummy', ['id' => 1], $entityWorkflow, new StoredObject());
}
$repository->findByStoredObject(Argument::type(StoredObject::class))->willReturn($attachments);
return new WorkflowRelatedEntityPermissionHelper($security->reveal(), $entityWorkflowManager->reveal(), $repository->reveal(), $this->buildRegistry(), new MockClock($atDateTime ?? new \DateTimeImmutable()));
} }
private static function buildRegistry(): Registry private static function buildRegistry(): Registry
@@ -487,13 +261,10 @@ class WorkflowRelatedEntityPermissionHelperTest extends TestCase
$builder = new DefinitionBuilder(); $builder = new DefinitionBuilder();
$builder $builder
->setInitialPlaces(['initial']) ->setInitialPlaces(['initial'])
->addPlaces(['initial', 'test', 'sent_external', 'final_positive', 'final_negative']) ->addPlaces(['initial', 'test', 'final_positive', 'final_negative'])
->setMetadataStore( ->setMetadataStore(
new InMemoryMetadataStore( new InMemoryMetadataStore(
placesMetadata: [ placesMetadata: [
'sent_external' => [
'isSentExternal' => true,
],
'final_positive' => [ 'final_positive' => [
'isFinal' => true, 'isFinal' => true,
'isFinalPositive' => true, 'isFinalPositive' => true,

View File

@@ -11,11 +11,8 @@ declare(strict_types=1);
namespace Chill\MainBundle\Tests\Workflow\Messenger; namespace Chill\MainBundle\Tests\Workflow\Messenger;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Service\StoredObjectToPdfConverter;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface; use Chill\MainBundle\Workflow\EntityWorkflowHandlerInterface;
use Chill\MainBundle\Workflow\EntityWorkflowManager; use Chill\MainBundle\Workflow\EntityWorkflowManager;
@@ -26,7 +23,6 @@ use Chill\ThirdPartyBundle\Entity\ThirdParty;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Prophecy\Argument; use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address; use Symfony\Component\Mime\Address;
@@ -43,54 +39,24 @@ class PostSendExternalMessageHandlerTest extends TestCase
public function testSendMessageHappyScenario(): void public function testSendMessageHappyScenario(): void
{ {
$entityWorkflow = $this->buildEntityWorkflow(); $entityWorkflow = $this->buildEntityWorkflow();
// Prepare attachments (2 attachments)
$attachmentStoredObject1 = new StoredObject();
$attachmentStoredObject2 = new StoredObject();
new EntityWorkflowAttachment('generic_doc', ['id' => 1], $entityWorkflow, $attachmentStoredObject1);
new EntityWorkflowAttachment('generic_doc', ['id' => 2], $entityWorkflow, $attachmentStoredObject2);
// Prepare transition DTO and sends
$dto = new WorkflowTransitionContextDTO($entityWorkflow); $dto = new WorkflowTransitionContextDTO($entityWorkflow);
$dto->futureDestineeEmails = ['external@example.com']; $dto->futureDestineeEmails = ['external@example.com'];
$dto->futureDestineeThirdParties = [(new ThirdParty())->setEmail('3party@example.com')]; $dto->futureDestineeThirdParties = [(new ThirdParty())->setEmail('3party@example.com')];
$entityWorkflow->setStep('send_external', $dto, 'to_send_external', new \DateTimeImmutable(), new User()); $entityWorkflow->setStep('send_external', $dto, 'to_send_external', new \DateTimeImmutable(), new User());
// Repository returns our workflow
$repository = $this->prophesize(EntityWorkflowRepository::class); $repository = $this->prophesize(EntityWorkflowRepository::class);
$repository->find(1)->willReturn($entityWorkflow); $repository->find(1)->willReturn($entityWorkflow);
// Mailer must send to both recipients
$mailer = $this->prophesize(MailerInterface::class); $mailer = $this->prophesize(MailerInterface::class);
$mailer->send(Argument::that($this->buildCheckAddressCallback('3party@example.com')))->shouldBeCalledOnce(); $mailer->send(Argument::that($this->buildCheckAddressCallback('3party@example.com')))->shouldBeCalledOnce();
$mailer->send(Argument::that($this->buildCheckAddressCallback('external@example.com')))->shouldBeCalledOnce(); $mailer->send(Argument::that($this->buildCheckAddressCallback('external@example.com')))->shouldBeCalledOnce();
// Workflow manager and handler
$workflowHandler = $this->prophesize(EntityWorkflowHandlerInterface::class); $workflowHandler = $this->prophesize(EntityWorkflowHandlerInterface::class);
$workflowHandler->getEntityTitle($entityWorkflow, Argument::any())->willReturn('title'); $workflowHandler->getEntityTitle($entityWorkflow, Argument::any())->willReturn('title');
$workflowManager = $this->prophesize(EntityWorkflowManager::class); $workflowManager = $this->prophesize(EntityWorkflowManager::class);
$workflowManager->getHandler($entityWorkflow)->willReturn($workflowHandler->reveal()); $workflowManager->getHandler($entityWorkflow)->willReturn($workflowHandler->reveal());
// Associated stored object for the workflow $handler = new PostSendExternalMessageHandler($repository->reveal(), $mailer->reveal(), $workflowManager->reveal());
$associatedStoredObject = new StoredObject();
$workflowManager->getAssociatedStoredObject($entityWorkflow)->willReturn($associatedStoredObject);
// Converter should be called for each attachment and the associated stored object
$converter = $this->prophesize(StoredObjectToPdfConverter::class);
$converter->addConvertedVersion($attachmentStoredObject1, 'fr')->shouldBeCalledOnce();
$converter->addConvertedVersion($attachmentStoredObject2, 'fr')->shouldBeCalledOnce();
$converter->addConvertedVersion($associatedStoredObject, 'fr')->shouldBeCalledOnce();
// Logger (not used in happy path, but required by handler)
$logger = $this->prophesize(LoggerInterface::class);
$handler = new PostSendExternalMessageHandler(
$repository->reveal(),
$mailer->reveal(),
$workflowManager->reveal(),
$converter->reveal(),
$logger->reveal(),
);
$handler(new PostSendExternalMessage(1, 'fr')); $handler(new PostSendExternalMessage(1, 'fr'));

View File

@@ -11,12 +11,9 @@ declare(strict_types=1);
namespace Chill\MainBundle\Workflow\Helper; namespace Chill\MainBundle\Workflow\Helper;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\MainBundle\Entity\User; use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow; use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowAttachment;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum; use Chill\MainBundle\Entity\Workflow\EntityWorkflowSignatureStateEnum;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowAttachmentRepository;
use Chill\MainBundle\Workflow\EntityWorkflowManager; use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\Security;
@@ -61,39 +58,21 @@ class WorkflowRelatedEntityPermissionHelper
public function __construct( public function __construct(
private readonly Security $security, private readonly Security $security,
private readonly EntityWorkflowManager $entityWorkflowManager, private readonly EntityWorkflowManager $entityWorkflowManager,
private readonly EntityWorkflowAttachmentRepository $entityWorkflowAttachmentRepository,
private readonly Registry $registry, private readonly Registry $registry,
private readonly ClockInterface $clock, private readonly ClockInterface $clock,
) {} ) {}
/** /**
* @param object $entity The entity may be an
*
* @return 'FORCE_GRANT'|'FORCE_DENIED'|'ABSTAIN' * @return 'FORCE_GRANT'|'FORCE_DENIED'|'ABSTAIN'
*/ */
public function isAllowedByWorkflowForReadOperation(object $entity): string public function isAllowedByWorkflowForReadOperation(object $entity): string
{ {
if ($entity instanceof StoredObject) { $entityWorkflows = $this->entityWorkflowManager->findByRelatedEntity($entity);
$attachments = $this->entityWorkflowAttachmentRepository->findByStoredObject($entity);
$entityWorkflows = array_map(static fn (EntityWorkflowAttachment $attachment) => $attachment->getEntityWorkflow(), $attachments);
$isAttached = true;
} else {
$entityWorkflows = $this->entityWorkflowManager->findByRelatedEntity($entity);
$isAttached = false;
}
if ([] === $entityWorkflows) {
return self::ABSTAIN;
}
if ($this->isUserInvolvedInAWorkflow($entityWorkflows)) { if ($this->isUserInvolvedInAWorkflow($entityWorkflows)) {
return self::FORCE_GRANT; return self::FORCE_GRANT;
} }
if ($isAttached) {
return self::ABSTAIN;
}
// give a view permission if there is a Person signature pending, or in the 12 hours following // give a view permission if there is a Person signature pending, or in the 12 hours following
// the signature last state // the signature last state
foreach ($entityWorkflows as $workflow) { foreach ($entityWorkflows as $workflow) {
@@ -121,51 +100,33 @@ class WorkflowRelatedEntityPermissionHelper
*/ */
public function isAllowedByWorkflowForWriteOperation(object $entity): string public function isAllowedByWorkflowForWriteOperation(object $entity): string
{ {
if ($entity instanceof StoredObject) { $entityWorkflows = $this->entityWorkflowManager->findByRelatedEntity($entity);
$attachments = $this->entityWorkflowAttachmentRepository->findByStoredObject($entity); $runningWorkflows = [];
$entityWorkflows = array_map(static fn (EntityWorkflowAttachment $attachment) => $attachment->getEntityWorkflow(), $attachments);
$isAttached = true;
} else {
$entityWorkflows = $this->entityWorkflowManager->findByRelatedEntity($entity);
$isAttached = false;
}
if ([] === $entityWorkflows) { // if a workflow is finalized positive, we are not allowed to edit to document any more
return self::ABSTAIN;
}
// if a workflow is finalized positive, anyone is allowed to edit the document anymore
foreach ($entityWorkflows as $entityWorkflow) { foreach ($entityWorkflows as $entityWorkflow) {
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName()); if ($entityWorkflow->isFinal()) {
$marking = $workflow->getMarkingStore()->getMarking($entityWorkflow); $workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
foreach ($marking->getPlaces() as $place => $int) { $marking = $workflow->getMarkingStore()->getMarking($entityWorkflow);
$placeMetadata = $workflow->getMetadataStore()->getPlaceMetadata($place); foreach ($marking->getPlaces() as $place => $int) {
if ( $placeMetadata = $workflow->getMetadataStore()->getPlaceMetadata($place);
($entityWorkflow->isFinal() && ($placeMetadata['isFinalPositive'] ?? false)) if (true === ($placeMetadata['isFinalPositive'] ?? false)) {
|| ($placeMetadata['isSentExternal'] ?? false) // the workflow is final, and final positive, so we stop here.
) { return self::FORCE_DENIED;
// the workflow is final, and final positive, or is sentExternal, so we stop here. }
return self::FORCE_DENIED;
}
if (
// if not finalized positive
$entityWorkflow->isFinal() && !($placeMetadata['isFinalPositive'] ?? false)
) {
return self::ABSTAIN;
} }
} else {
$runningWorkflows[] = $entityWorkflow;
} }
} }
$runningWorkflows = array_filter($entityWorkflows, fn (EntityWorkflow $ew) => !$ew->isFinal()); // if there is a signature on a **running workflow**, no one can edit the workflow any more
foreach ($runningWorkflows as $entityWorkflow) {
// if there is a signature on a **running workflow**, no one is allowed edit the workflow anymore foreach ($entityWorkflow->getSteps() as $step) {
if (!$isAttached) { foreach ($step->getSignatures() as $signature) {
foreach ($runningWorkflows as $entityWorkflow) { if (EntityWorkflowSignatureStateEnum::SIGNED === $signature->getState()) {
foreach ($entityWorkflow->getSteps() as $step) { return self::FORCE_DENIED;
foreach ($step->getSignatures() as $signature) {
if (EntityWorkflowSignatureStateEnum::SIGNED === $signature->getState()) {
return self::FORCE_DENIED;
}
} }
} }
} }
@@ -176,11 +137,7 @@ class WorkflowRelatedEntityPermissionHelper
return self::FORCE_GRANT; return self::FORCE_GRANT;
} }
if ($isAttached) { return self::ABSTAIN;
return self::ABSTAIN;
}
return self::FORCE_DENIED;
} }
/** /**

View File

@@ -11,14 +11,9 @@ declare(strict_types=1);
namespace Chill\MainBundle\Workflow\Messenger; namespace Chill\MainBundle\Workflow\Messenger;
use Chill\DocStoreBundle\Exception\ConversionWithSameMimeTypeException;
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
use Chill\DocStoreBundle\Service\StoredObjectToPdfConverter;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend; use Chill\MainBundle\Entity\Workflow\EntityWorkflowSend;
use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository; use Chill\MainBundle\Repository\Workflow\EntityWorkflowRepository;
use Chill\MainBundle\Workflow\EntityWorkflowManager; use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Psr\Log\LoggerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException; use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
@@ -30,8 +25,6 @@ final readonly class PostSendExternalMessageHandler implements MessageHandlerInt
private EntityWorkflowRepository $entityWorkflowRepository, private EntityWorkflowRepository $entityWorkflowRepository,
private MailerInterface $mailer, private MailerInterface $mailer,
private EntityWorkflowManager $workflowManager, private EntityWorkflowManager $workflowManager,
private StoredObjectToPdfConverter $storedObjectToPdfConverter,
private LoggerInterface $logger,
) {} ) {}
public function __invoke(PostSendExternalMessage $message): void public function __invoke(PostSendExternalMessage $message): void
@@ -42,38 +35,11 @@ final readonly class PostSendExternalMessageHandler implements MessageHandlerInt
throw new UnrecoverableMessageHandlingException(sprintf('Entity workflow with id %d not found', $message->entityWorkflowId)); throw new UnrecoverableMessageHandlingException(sprintf('Entity workflow with id %d not found', $message->entityWorkflowId));
} }
$this->convertToPdf($entityWorkflow, $message->lang);
foreach ($entityWorkflow->getCurrentStep()->getSends() as $send) { foreach ($entityWorkflow->getCurrentStep()->getSends() as $send) {
$this->sendEmailToDestinee($send, $message); $this->sendEmailToDestinee($send, $message);
} }
} }
private function convertToPdf(EntityWorkflow $entityWorkflow, string $locale): void
{
foreach ($entityWorkflow->getAttachments() as $attachment) {
try {
$this->storedObjectToPdfConverter->addConvertedVersion($attachment->getProxyStoredObject(), $locale);
} catch (StoredObjectManagerException $e) {
$this->logger->error('Error converting attachment to PDF', ['backtrace' => $e->getTraceAsString(), 'attachment_id' => $attachment->getId()]);
} catch (ConversionWithSameMimeTypeException $e) {
$this->logger->error('Error converting attachment to PDF: already at the same MIME type', ['backtrace' => $e->getTraceAsString(), 'attachment_id' => $attachment->getId()]);
}
}
$storedObject = $this->workflowManager->getAssociatedStoredObject($entityWorkflow);
if (null !== $storedObject) {
try {
$this->storedObjectToPdfConverter->addConvertedVersion($storedObject, $locale);
} catch (StoredObjectManagerException $e) {
$this->logger->error('Error converting stored object to PDF', ['backtrace' => $e->getTraceAsString(), 'stored_object_id' => $storedObject->getId(), 'workflow_id' => $entityWorkflow->getId()]);
} catch (ConversionWithSameMimeTypeException $e) {
$this->logger->error('Error converting stored object to PDF: already at the same MIME type', ['backtrace' => $e->getTraceAsString(), 'stored_object_id' => $storedObject->getId(), 'workflow_id' => $entityWorkflow->getId()]);
}
}
}
private function sendEmailToDestinee(EntityWorkflowSend $send, PostSendExternalMessage $message): void private function sendEmailToDestinee(EntityWorkflowSend $send, PostSendExternalMessage $message): void
{ {
$entityWorkflow = $send->getEntityWorkflowStep()->getEntityWorkflow(); $entityWorkflow = $send->getEntityWorkflowStep()->getEntityWorkflow();

View File

@@ -22,7 +22,6 @@ use Psr\Log\LoggerInterface;
use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\Transition;
/** /**
* Handles state changes for signature steps within a workflow. * Handles state changes for signature steps within a workflow.
@@ -51,10 +50,8 @@ class SignatureStepStateChanger
* *
* @param EntityWorkflowStepSignature $signature the signature entity to be marked as signed * @param EntityWorkflowStepSignature $signature the signature entity to be marked as signed
* @param int|null $atIndex optional index position for the signature within the zone * @param int|null $atIndex optional index position for the signature within the zone
*
* @return string The expected new workflow's step, after transition is applyied
*/ */
public function markSignatureAsSigned(EntityWorkflowStepSignature $signature, ?int $atIndex): string public function markSignatureAsSigned(EntityWorkflowStepSignature $signature, ?int $atIndex): void
{ {
$this->entityManager->refresh($signature, LockMode::PESSIMISTIC_WRITE); $this->entityManager->refresh($signature, LockMode::PESSIMISTIC_WRITE);
@@ -63,14 +60,7 @@ class SignatureStepStateChanger
->setZoneSignatureIndex($atIndex) ->setZoneSignatureIndex($atIndex)
->setStateDate($this->clock->now()); ->setStateDate($this->clock->now());
$this->logger->info(self::LOG_PREFIX.'Mark signature entity as signed', ['signatureId' => $signature->getId(), 'index' => (string) $atIndex]); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as signed', ['signatureId' => $signature->getId(), 'index' => (string) $atIndex]);
['transition' => $transition, 'futureUser' => $futureUser] = $this->decideTransition($signature);
$this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId())); $this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId()));
if (null === $transition) {
return $signature->getStep()->getEntityWorkflow()->getStep();
}
return $transition->getTos()[0];
} }
/** /**
@@ -81,10 +71,8 @@ class SignatureStepStateChanger
* *
* This method updates the signature state to 'canceled' and logs the action. * This method updates the signature state to 'canceled' and logs the action.
* It also dispatches a message to notify about the state change. * It also dispatches a message to notify about the state change.
*
* @return string The expected new workflow's step, after transition is applyied
*/ */
public function markSignatureAsCanceled(EntityWorkflowStepSignature $signature): string public function markSignatureAsCanceled(EntityWorkflowStepSignature $signature): void
{ {
$this->entityManager->refresh($signature, LockMode::PESSIMISTIC_WRITE); $this->entityManager->refresh($signature, LockMode::PESSIMISTIC_WRITE);
@@ -92,15 +80,7 @@ class SignatureStepStateChanger
->setState(EntityWorkflowSignatureStateEnum::CANCELED) ->setState(EntityWorkflowSignatureStateEnum::CANCELED)
->setStateDate($this->clock->now()); ->setStateDate($this->clock->now());
$this->logger->info(self::LOG_PREFIX.'Mark signature entity as canceled', ['signatureId' => $signature->getId()]); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as canceled', ['signatureId' => $signature->getId()]);
['transition' => $transition, 'futureUser' => $futureUser] = $this->decideTransition($signature);
$this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId())); $this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId()));
if (null === $transition) {
return $signature->getStep()->getEntityWorkflow()->getStep();
}
return $transition->getTos()[0];
} }
/** /**
@@ -113,10 +93,8 @@ class SignatureStepStateChanger
* a state change has occurred. * a state change has occurred.
* *
* @param EntityWorkflowStepSignature $signature the signature entity to be marked as rejected * @param EntityWorkflowStepSignature $signature the signature entity to be marked as rejected
*
* @return string The expected new workflow's step, after transition is applyied
*/ */
public function markSignatureAsRejected(EntityWorkflowStepSignature $signature): string public function markSignatureAsRejected(EntityWorkflowStepSignature $signature): void
{ {
$this->entityManager->refresh($signature, LockMode::PESSIMISTIC_WRITE); $this->entityManager->refresh($signature, LockMode::PESSIMISTIC_WRITE);
@@ -124,16 +102,7 @@ class SignatureStepStateChanger
->setState(EntityWorkflowSignatureStateEnum::REJECTED) ->setState(EntityWorkflowSignatureStateEnum::REJECTED)
->setStateDate($this->clock->now()); ->setStateDate($this->clock->now());
$this->logger->info(self::LOG_PREFIX.'Mark signature entity as rejected', ['signatureId' => $signature->getId()]); $this->logger->info(self::LOG_PREFIX.'Mark signature entity as rejected', ['signatureId' => $signature->getId()]);
['transition' => $transition, 'futureUser' => $futureUser] = $this->decideTransition($signature);
$this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId())); $this->messageBus->dispatch(new PostSignatureStateChangeMessage((int) $signature->getId()));
if (null === $transition) {
return $signature->getStep()->getEntityWorkflow()->getStep();
}
return $transition->getTos()[0];
} }
/** /**
@@ -148,35 +117,10 @@ class SignatureStepStateChanger
{ {
$this->entityManager->refresh($signature, LockMode::PESSIMISTIC_READ); $this->entityManager->refresh($signature, LockMode::PESSIMISTIC_READ);
['transition' => $transition, 'futureUser' => $futureUser] = $this->decideTransition($signature);
if (null === $transition) {
return;
}
$entityWorkflow = $signature->getStep()->getEntityWorkflow();
$workflow = $this->registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
$transitionDto = new WorkflowTransitionContextDTO($entityWorkflow);
$transitionDto->futureDestUsers[] = $futureUser;
$workflow->apply($entityWorkflow, $transition->getName(), [
'context' => $transitionDto,
'transitionAt' => $this->clock->now(),
'transition' => $transition->getName(),
]);
$this->logger->info(self::LOG_PREFIX.'Transition automatically applied', ['signatureId' => $signature->getId()]);
}
/**
* @return array{transition: Transition|null, futureUser: User|null}
*/
private function decideTransition(EntityWorkflowStepSignature $signature): array
{
if (!EntityWorkflowStepSignature::isAllSignatureNotPendingForStep($signature->getStep())) { if (!EntityWorkflowStepSignature::isAllSignatureNotPendingForStep($signature->getStep())) {
$this->logger->info(self::LOG_PREFIX.'This is not the last signature, skipping transition to another place', ['signatureId' => $signature->getId()]); $this->logger->info(self::LOG_PREFIX.'This is not the last signature, skipping transition to another place', ['signatureId' => $signature->getId()]);
return ['transition' => null, 'futureUser' => null]; return;
} }
$this->logger->debug(self::LOG_PREFIX.'Continuing the process to find a transition', ['signatureId' => $signature->getId()]); $this->logger->debug(self::LOG_PREFIX.'Continuing the process to find a transition', ['signatureId' => $signature->getId()]);
@@ -200,7 +144,7 @@ class SignatureStepStateChanger
if (null === $transition) { if (null === $transition) {
$this->logger->info(self::LOG_PREFIX.'The transition is not configured, will not apply a transition', ['signatureId' => $signature->getId()]); $this->logger->info(self::LOG_PREFIX.'The transition is not configured, will not apply a transition', ['signatureId' => $signature->getId()]);
return ['transition' => null, 'futureUser' => null]; return;
} }
if ('person' === $signature->getSignerKind()) { if ('person' === $signature->getSignerKind()) {
@@ -212,16 +156,19 @@ class SignatureStepStateChanger
if (null === $futureUser) { if (null === $futureUser) {
$this->logger->info(self::LOG_PREFIX.'No previous user, will not apply a transition', ['signatureId' => $signature->getId()]); $this->logger->info(self::LOG_PREFIX.'No previous user, will not apply a transition', ['signatureId' => $signature->getId()]);
return ['transition' => null, 'futureUser' => null]; return;
} }
foreach ($workflow->getDefinition()->getTransitions() as $transitionObj) { $transitionDto = new WorkflowTransitionContextDTO($entityWorkflow);
if ($transitionObj->getName() === $transition) { $transitionDto->futureDestUsers[] = $futureUser;
return ['transition' => $transitionObj, 'futureUser' => $futureUser];
}
}
throw new \RuntimeException('Transition not found'); $workflow->apply($entityWorkflow, $transition, [
'context' => $transitionDto,
'transitionAt' => $this->clock->now(),
'transition' => $transition,
]);
$this->logger->info(self::LOG_PREFIX.'Transition automatically applied', ['signatureId' => $signature->getId()]);
} }
private function getPreviousSender(EntityWorkflowStep $entityWorkflowStep): ?User private function getPreviousSender(EntityWorkflowStep $entityWorkflowStep): ?User

View File

@@ -965,31 +965,6 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/UserJob" $ref: "#/components/schemas/UserJob"
/1.0/main/workflow/{id}.json:
get:
tags:
- workflow
summary: Return a workflow
parameters:
- name: id
in: path
required: true
description: The workflow id
schema:
type: integer
format: integer
minimum: 1
responses:
200:
description: "ok"
content:
application/json:
schema:
$ref: "#/components/schemas/Workflow"
404:
description: "not found"
401:
description: "Unauthorized"
/1.0/main/workflow/my: /1.0/main/workflow/my:
get: get:
tags: tags:

View File

@@ -120,8 +120,5 @@ module.exports = function (encore, entries) {
"vue_onthefly", "vue_onthefly",
__dirname + "/Resources/public/vuejs/OnTheFly/index.js", __dirname + "/Resources/public/vuejs/OnTheFly/index.js",
); );
encore.addEntry(
"page_workflow_waiting_post_process",
__dirname + "/Resources/public/vuejs/WaitPostProcessWorkflow/index.ts"
);
}; };

View File

@@ -54,7 +54,7 @@ user:
title: Mon profil title: Mon profil
Profile successfully updated!: Votre profil a été mis à jour! Profile successfully updated!: Votre profil a été mis à jour!
no job: Pas de métier assigné no job: Pas de métier assigné
no scope: Pas de service assigné no scope: Pas de cercle assigné
notification_preferences: Préférences pour mes notifications notification_preferences: Préférences pour mes notifications
user_group: user_group:
@@ -102,9 +102,9 @@ createdAt: Créé le
createdBy: Créé par createdBy: Créé par
#elements used in software #elements used in software
centers: territoires centers: centres
Centers: Territoires Centers: Centres
center: territoire center: centre
comment: commentaire comment: commentaire
Comment: Commentaire Comment: Commentaire
Comments: Commentaires Comments: Commentaires
@@ -227,12 +227,12 @@ Location Menu: Localisations et types de localisation
Management of location: Gestion des localisations et types de localisation Management of location: Gestion des localisations et types de localisation
#admin section for center's administration #admin section for center's administration
Create a new center: Créer une nouveau territoire Create a new center: Créer un nouveau centre
Center list: Liste des territoires Center list: Liste des centres
Center edit: Édition d'un territoire Center edit: Édition d'un centre
Center creation: Création d'un territoire Center creation: Création d'un centre
New center: Nouveau territoire New center: Nouveau centre
Center: Territoire Center: Centre
#admin section for permissions group #admin section for permissions group
Permissions group list: Groupes de permissions Permissions group list: Groupes de permissions
@@ -246,15 +246,15 @@ New permission group: Nouveau groupe de permissions
PermissionsGroup "%name%" edit: Modification du groupe de permission '%name%' PermissionsGroup "%name%" edit: Modification du groupe de permission '%name%'
Role: Rôle Role: Rôle
Choose amongst roles: Choisir un rôle Choose amongst roles: Choisir un rôle
Choose amongst scopes: Choisir un service Choose amongst scopes: Choisir un cercle
Add permission: Ajouter les permissions Add permission: Ajouter les permissions
This group does not provide any permission: Ce groupe n'attribue aucune permission This group does not provide any permission: Ce groupe n'attribue aucune permission
The role '%role%' has been removed: Le rôle "%role%" a été enlevé de ce groupe de permission The role '%role%' has been removed: Le rôle "%role%" a été enlevé de ce groupe de permission
The role '%role%' on circle '%scope%' has been removed: Le rôle "%role%" sur le service "%scope%" a été enlevé de ce groupe de permission The role '%role%' on circle '%scope%' has been removed: Le rôle "%role%" sur le cercle "%scope%" a été enlevé de ce groupe de permission
Unclassified: Non classifié Unclassified: Non classifié
Help to pick role and scope: Certains rôles ne nécessitent pas de service. Help to pick role and scope: Certains rôles ne nécessitent pas de cercle.
The role need scope: Ce rôle nécessite un service. The role need scope: Ce rôle nécessite un cercle.
The role does not need scope: Ce rôle ne nécessite pas de service ! The role does not need scope: Ce rôle ne nécessite pas de cercle !
#admin section for users #admin section for users
User configuration: Gestion des utilisateurs User configuration: Gestion des utilisateurs
@@ -270,7 +270,7 @@ Grant new permissions: Ajout de permissions
Add a new groupCenter: Ajout de permissions Add a new groupCenter: Ajout de permissions
The permissions have been successfully added to the user: Les permissions ont été accordées à l'utilisateur The permissions have been successfully added to the user: Les permissions ont été accordées à l'utilisateur
The permissions where removed.: Les permissions ont été enlevées. The permissions where removed.: Les permissions ont été enlevées.
Center & groups: Territoire et groupes Center & groups: Centre et groupes
User %username%: Utilisateur %username% User %username%: Utilisateur %username%
Add a new user: Ajouter un nouvel utilisateur Add a new user: Ajouter un nouvel utilisateur
The permissions have been added: Les permissions ont été ajoutées The permissions have been added: Les permissions ont été ajoutées
@@ -280,13 +280,13 @@ Back to the user edition: Retour au formulaire d'édition
Password successfully updated!: Mot de passe mis à jour Password successfully updated!: Mot de passe mis à jour
Flags: Drapeaux Flags: Drapeaux
Main location: Localisation principale Main location: Localisation principale
Main scope: Service Main scope: Cercle
Main center: Territoire Main center: Centre
user job: Métier de l'utilisateur user job: Métier de l'utilisateur
Job: Métier Job: Métier
Jobs: Métiers Jobs: Métiers
Choose a main center: Choisir un territoire Choose a main center: Choisir un centre
Choose a main scope: Choisir un service Choose a main scope: Choisir un cercle
choose a job: Choisir un métier choose a job: Choisir un métier
choose a location: Choisir une localisation choose a location: Choisir une localisation
@@ -302,12 +302,12 @@ Current location successfully updated: Localisation actuelle mise à jour
Pick a location: Choisir un lieu Pick a location: Choisir un lieu
#admin section for circles (old: scopes) #admin section for circles (old: scopes)
List circles: Services List circles: Cercles
New circle: Nouveau service New circle: Nouveau cercle
Circle: Service Circle: Cercle
Circle edit: Modification du service Circle edit: Modification du cercle
Circle creation: Création d'un service Circle creation: Création d'un cercle
Create a new circle: Créer un nouveau service Create a new circle: Créer un nouveau cercle
#admin section for location #admin section for location
Location: Localisation Location: Localisation
@@ -347,9 +347,9 @@ Country list: Liste des pays
Country code: Code du pays Country code: Code du pays
# circles / scopes # circles / scopes
Choose the circle: Choisir le service Choose the circle: Choisir le cercle
Scope: Service Scope: Cercle
Scopes: Services Scopes: Cercles
#export #export
@@ -357,14 +357,14 @@ Scopes: Services
Exports list: Liste des exports Exports list: Liste des exports
Create an export: Créer un export Create an export: Créer un export
#export creation step 'center' : pick a center #export creation step 'center' : pick a center
Pick centers: Choisir les territoires Pick centers: Choisir les centres
Pick a center: Choisir un territoire Pick a center: Choisir un centre
The export will contains only data from the picked centers.: L'export ne contiendra que les données des territoires choisis. The export will contains only data from the picked centers.: L'export ne contiendra que les données des centres choisis.
This will eventually restrict your possibilities in filtering the data.: Les possibilités de filtrages seront adaptées aux droits de consultation pour les territoires choisis. 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 Go to export options: Vers la préparation de l'export
Pick aggregated centers: Regroupement de territoires Pick aggregated centers: Regroupement de centres
uncheck all centers: Désélectionner tous les territoires uncheck all centers: Désélectionner tous les centres
check all centers: Sélectionner tous les territoires check all centers: Sélectionner tous les centres
# export creation step 'export' : choose aggregators, filtering and formatter # export creation step 'export' : choose aggregators, filtering and formatter
Formatter: Mise en forme Formatter: Mise en forme
Choose the formatter: Choisissez le format d'export voulu. Choose the formatter: Choisissez le format d'export voulu.
@@ -510,10 +510,10 @@ crud:
title_edit: Modifier un regroupement title_edit: Modifier un regroupement
center: center:
index: index:
title: Liste des territoires title: Liste des centres
add_new: Ajouter un territoire add_new: Ajouter un centre
title_new: Nouveau territoire title_new: Nouveau centre
title_edit: Modifier un territoire title_edit: Modifier un centre
news_item: news_item:
index: index:
title: Liste des actualités title: Liste des actualités
@@ -666,17 +666,10 @@ workflow:
cancel_are_you_sure: Êtes-vous sûr de vouloir annuler la signature de %signer% cancel_are_you_sure: Êtes-vous sûr de vouloir annuler la signature de %signer%
reject_signature_of: Rejet de la signature de %signer% reject_signature_of: Rejet de la signature de %signer%
reject_are_you_sure: Êtes-vous sûr de vouloir rejeter la signature de %signer% reject_are_you_sure: Êtes-vous sûr de vouloir rejeter la signature de %signer%
waiting_for: En attente de modification de l'état de la signature
attachments: attachments:
title: Pièces jointes title: Pièces jointes
wait:
title: En attente de traitement
error_while_waiting: Le traitement a échoué
success: Traitement terminé. Redirection en cours...
Subscribe final: Recevoir une notification à l'étape finale Subscribe final: Recevoir une notification à l'étape finale
Subscribe all steps: Recevoir une notification à chaque étape Subscribe all steps: Recevoir une notification à chaque étape
CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION: Appliquer les transitions sur tous les workflows CHILL_MAIN_WORKFLOW_APPLY_ALL_TRANSITION: Appliquer les transitions sur tous les workflows
@@ -860,7 +853,7 @@ absence:
admin: admin:
users: users:
export_list_csv: Liste des utilisateurs (format CSV) export_list_csv: Liste des utilisateurs (format CSV)
export_permissions_csv: Association utilisateurs - groupes de permissions - territoire (format CSV) export_permissions_csv: Association utilisateurs - groupes de permissions - centre (format CSV)
export: export:
id: Identifiant id: Identifiant
username: Nom d'utilisateur username: Nom d'utilisateur
@@ -870,8 +863,8 @@ admin:
civility_abbreviation: Abbréviation civilité civility_abbreviation: Abbréviation civilité
civility_name: Civilité civility_name: Civilité
label: Label label: Label
mainCenter_id: Identifiant territoire principal mainCenter_id: Identifiant centre principal
mainCenter_name: Territoire principal mainCenter_name: Centre principal
mainScope_id: Identifiant service principal mainScope_id: Identifiant service principal
mainScope_name: Service principal mainScope_name: Service principal
userJob_id: Identifiant métier userJob_id: Identifiant métier
@@ -881,8 +874,8 @@ admin:
mainLocation_id: Identifiant localisation principale mainLocation_id: Identifiant localisation principale
mainLocation_name: Localisation principale mainLocation_name: Localisation principale
absenceStart: Absent à partir du absenceStart: Absent à partir du
center_id: Identifiant du territoire center_id: Identifiant du centre
center_name: Territoire center_name: Centre
permissionsGroup_id: Identifiant du groupe de permissions permissionsGroup_id: Identifiant du groupe de permissions
permissionsGroup_name: Groupe de permissions permissionsGroup_name: Groupe de permissions
job_scope_histories: job_scope_histories:

View File

@@ -1,15 +1,15 @@
# role_scope constraint # role_scope constraint
# scope presence # scope presence
The role "%role%" require to be associated with a scope.: Le rôle "%role%" doit être associé à un service. The role "%role%" require to be associated with a scope.: Le rôle "%role%" doit être associé à un cercle.
The role "%role%" should not be associated with a scope.: Le rôle "%role%" ne doit pas être associé à un service. The role "%role%" should not be associated with a scope.: Le rôle "%role%" ne doit pas être associé à un cercle.
"The password must contains one letter, one capitalized letter, one number and one special character as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed.": "Le mot de passe doit contenir une majuscule, une minuscule, et au moins un caractère spécial parmi *[@#$%!,;:+\"'-/{}~=µ()£]). Les autres caractères sont autorisés." "The password must contains one letter, one capitalized letter, one number and one special character as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed.": "Le mot de passe doit contenir une majuscule, une minuscule, et au moins un caractère spécial parmi *[@#$%!,;:+\"'-/{}~=µ()£]). Les autres caractères sont autorisés."
The password fields must match: Les mots de passe doivent correspondre The password fields must match: Les mots de passe doivent correspondre
The password must be greater than {{ limit }} characters: "[1,Inf] Le mot de passe doit contenir au moins {{ limit }} caractères" The password must be greater than {{ limit }} characters: "[1,Inf] Le mot de passe doit contenir au moins {{ limit }} caractères"
A permission is already present for the same role and scope: Une permission est déjà présente pour le même rôle et service. A permission is already present for the same role and scope: Une permission est déjà présente pour le même rôle et cercle.
#UserCircleConsistency #UserCircleConsistency
"{{ username }} is not allowed to see entities published in this circle": "{{ username }} n'est pas autorisé à voir l'élément publié dans ce service." "{{ username }} is not allowed to see entities published in this circle": "{{ username }} n'est pas autorisé à voir l'élément publié dans ce cercle."
The user in cc cannot be a dest user in the same workflow step: Un utilisateur en Cc ne peut pas être un utilisateur qui valide. The user in cc cannot be a dest user in the same workflow step: Un utilisateur en Cc ne peut pas être un utilisateur qui valide.

View File

@@ -80,7 +80,7 @@ const appMessages = {
firstName: "Prénom", firstName: "Prénom",
lastName: "Nom", lastName: "Nom",
birthdate: "Date de naissance", birthdate: "Date de naissance",
center: "Territoire", center: "Centre",
phonenumber: "Téléphone", phonenumber: "Téléphone",
mobilenumber: "Mobile", mobilenumber: "Mobile",
altNames: "Autres noms", altNames: "Autres noms",

View File

@@ -50,8 +50,8 @@ const visMessages = {
return "Né·e le"; return "Né·e le";
} }
}, },
center_id: "Identifiant du territoire", center_id: "Identifiant du centre",
center_type: "Type de territoire", center_type: "Type de centre",
center_name: "Territoire", // vendée center_name: "Territoire", // vendée
phonenumber: "Téléphone", phonenumber: "Téléphone",
mobilenumber: "Mobile", mobilenumber: "Mobile",

View File

@@ -464,7 +464,7 @@ export default {
this.errors.push("Le genre doit être renseigné"); this.errors.push("Le genre doit être renseigné");
} }
if (this.showCenters && this.person.center === null) { if (this.showCenters && this.person.center === null) {
this.errors.push("Le territoire doit être renseigné"); this.errors.push("Le centre doit être renseigné");
} }
}, },
loadData() { loadData() {

View File

@@ -25,8 +25,8 @@ const personMessages = {
return "Né·e le"; return "Né·e le";
} }
}, },
center_id: "Identifiant du territoire", center_id: "Identifiant du centre",
center_type: "Type de territoire", center_type: "Type de centre",
center_name: "Territoire", // vendée center_name: "Territoire", // vendée
phonenumber: "Téléphone", phonenumber: "Téléphone",
mobilenumber: "Mobile", mobilenumber: "Mobile",
@@ -53,8 +53,8 @@ const personMessages = {
"Un nouveau ménage va être créé. L'usager sera membre de ce ménage.", "Un nouveau ménage va être créé. L'usager sera membre de ce ménage.",
}, },
center: { center: {
placeholder: "Choisissez un territoire", placeholder: "Choisissez un centre",
title: "territoire", title: "Centre",
}, },
}, },
error_only_one_person: "Une seule personne peut être sélectionnée !", error_only_one_person: "Une seule personne peut être sélectionnée !",

View File

@@ -376,7 +376,7 @@ Create a list of people according to various filters.: Crée une liste d'usagers
Fields to include in export: Champs à inclure dans l'export Fields to include in export: Champs à inclure dans l'export
Address valid at this date: Addresse valide à cette date Address valid at this date: Addresse valide à cette date
Data valid at this date: Données valides à cette date Data valid at this date: Données valides à cette date
Data regarding center, addresses, and so on will be computed at this date: Les données concernant le territoire, l'adresse, le ménage, sera calculé à cette date. Data regarding center, addresses, and so on will be computed at this date: Les données concernant le centre, l'adresse, le ménage, sera calculé à cette date.
List duplicates: Liste des doublons List duplicates: Liste des doublons
Create a list of duplicate people: Créer la liste des usagers détectés comme doublons. Create a list of duplicate people: Créer la liste des usagers détectés comme doublons.
Count people participating in an accompanying course: Nombre d'usagers concernés par un parcours Count people participating in an accompanying course: Nombre d'usagers concernés par un parcours
@@ -1110,9 +1110,9 @@ export:
Group course by household composition: Grouper les usagers par composition familiale Group course by household composition: Grouper les usagers par composition familiale
Calc date: Date de calcul de la composition du ménage Calc date: Date de calcul de la composition du ménage
by_center: by_center:
title: Grouper les usagers par territoire title: Grouper les usagers par centre
at_date: Date de calcul du territoire at_date: Date de calcul du centre
center: Territoire de l'usager center: Centre de l'usager
by_postal_code: by_postal_code:
title: Grouper les usagers par code postal de l'adresse title: Grouper les usagers par code postal de l'adresse
at_date: Date de calcul de l'adresse at_date: Date de calcul de l'adresse
@@ -1437,7 +1437,7 @@ export:
acpParticipantPersons: Usagers concernés acpParticipantPersons: Usagers concernés
acpParticipantPersonsIds: Usagers concernés (identifiants) acpParticipantPersonsIds: Usagers concernés (identifiants)
duration: Durée du parcours (en jours) duration: Durée du parcours (en jours)
centers: Territoires des usagers centers: Centres des usagers
eval: eval:
List of evaluations: Liste des évaluations List of evaluations: Liste des évaluations

View File

@@ -23,7 +23,7 @@ The gender must be set: Le genre doit être renseigné
You are not allowed to perform this action: Vous n'avez pas le droit de changer cette valeur. You are not allowed to perform this action: Vous n'avez pas le droit de changer cette valeur.
Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again: Désolé, mais quelqu'un d'autre a déjà modifié cette entité. Veuillez actualiser la page et appliquer à nouveau les modifications Sorry, but someone else has already changed this entity. Please refresh the page and apply the changes again: Désolé, mais quelqu'un d'autre a déjà modifié cette entité. Veuillez actualiser la page et appliquer à nouveau les modifications
A center is required: Un territoire est requis A center is required: Un centre est requis
#export list #export list
You must select at least one element: Vous devez sélectionner au moins un élément You must select at least one element: Vous devez sélectionner au moins un élément

View File

@@ -9,7 +9,7 @@
'Report list': 'Liste des rapports' 'Report list': 'Liste des rapports'
Details: Détails Details: Détails
Person: Usager Person: Usager
Scope: Service Scope: Cercle
Date: Date Date: Date
User: Utilisateur User: Utilisateur
'Report type': 'Type de rapport' 'Report type': 'Type de rapport'

View File

@@ -4,7 +4,7 @@ Tasks: "Tâches"
Title: Titre Title: Titre
Description: Description Description: Description
Assignee: "Personne assignée" Assignee: "Personne assignée"
Scope: Service Scope: Cercle
"Start date": "Date de début" "Start date": "Date de début"
"End date": "Date d'échéance" "End date": "Date d'échéance"
"Warning date": "Date d'avertissement" "Warning date": "Date d'avertissement"
@@ -106,7 +106,7 @@ My tasks over deadline: Mes tâches à échéance dépassée
#transition page #transition page
Apply transition on task <em>%title%</em>: Appliquer la transition sur la tâche <em>%title%</em> Apply transition on task <em>%title%</em>: Appliquer la transition sur la tâche <em>%title%</em>
All centers: Tous les territoires All centers: Tous les centres
# ROLES # ROLES
CHILL_TASK_TASK_CREATE: Ajouter une tâche CHILL_TASK_TASK_CREATE: Ajouter une tâche

View File

@@ -73,8 +73,8 @@ No acronym given: Aucun sigle renseigné
No phone given: Aucun téléphone renseigné No phone given: Aucun téléphone renseigné
No email given: Aucune adresse courriel renseignée No email given: Aucune adresse courriel renseignée
The party is visible in those centers: Le tiers est visible dans ces territoires The party is visible in those centers: Le tiers est visible dans ces centres
The party is not visible in any center: Le tiers n'est associé à aucun territoire The party is not visible in any center: Le tiers n'est associé à aucun centre
No third parties: Aucun tiers No third parties: Aucun tiers
Any third party selected: Aucun tiers sélectionné Any third party selected: Aucun tiers sélectionné