mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-11-28 22:54:33 +00:00
Compare commits
123 Commits
v4.4.2
...
dutch-tran
| Author | SHA1 | Date | |
|---|---|---|---|
| 75780252eb | |||
| 45d7d1614a | |||
| 009846853b | |||
| 7f71fae295 | |||
| cf0cdfb3a0 | |||
| 0380bc59de | |||
| 1cc59e33cc | |||
| 30e87c3369 | |||
| eec65a7943 | |||
| a56ef214a3 | |||
| d328b8bcaf | |||
| 2e3ef233e5 | |||
| 7a95c444dd | |||
| 6e2215964d | |||
| e63fe793f8 | |||
| 0e1f9dcb93 | |||
| e30847dfcd | |||
| 65025dc7da | |||
| e3d80ac468 | |||
| 55195c7bde | |||
| eb88063cee | |||
| 3eea8abbeb | |||
| 5335b62679 | |||
| 31fb428703 | |||
| 604b2361d8 | |||
| 93e76952dd | |||
| 0d32810d0d | |||
| b221ad1621 | |||
| a96e9d5377 | |||
| 54b73128c3 | |||
| 5c0cb01fdc | |||
| 26d9b55c6d | |||
| add9249502 | |||
| 380d48c43a | |||
| c7d7c3ac6f | |||
| 7eb895c0e1 | |||
|
e1b91ebbfd
|
|||
| 2139b53fb0 | |||
| a43181d60d | |||
| 04bc1c5de8 | |||
| 0a07d68b6d | |||
| fccd29e3c7 | |||
| 274ee94196 | |||
| 799d04142e | |||
| dfe8d8b0bf | |||
| 82f347b93a | |||
| 635efd6f1d | |||
| 869880d8f3 | |||
| f7ea7e4dbf | |||
| 0a58e05230 | |||
| 68c83223dd | |||
| c28bd22560 | |||
| a5ef2475fb | |||
| 86dd9bfb80 | |||
| c28670f0fd | |||
| 9e2c030224 | |||
| a706c6f337 | |||
| bc63b489ee | |||
| a4cfc6a178 | |||
| f75d1da3b1 | |||
|
b8b68e5e5a
|
|||
|
ae5ba67064
|
|||
|
bfe4dd3aec
|
|||
| 3a4c20b53d | |||
| b0c86e238d | |||
| d7614aeab2 | |||
| 671ed21d59 | |||
| 4b9db6ceb6 | |||
| c79c39b562 | |||
| bf768b8e99 | |||
| 2df01833ad | |||
| ffb8183d4d | |||
| 5d45339bf7 | |||
| e87e5cbbaf | |||
| fa8e92ebf5 | |||
| b7a92bf656 | |||
| 3dbbda7b64 | |||
| 769d76a0cc | |||
| 722b37fbcc | |||
| bf38ec22c9 | |||
| 3d99c0f561 | |||
| 2221d17930 | |||
| 9c2abb2dfa | |||
| 94744b9542 | |||
| f42bb498e4 | |||
| 01889ac671 | |||
| 62e5842311 | |||
|
8ad6f397a8
|
|||
| d713704633 | |||
| b1fa9242a0 | |||
| 6ac554f93a | |||
| 372d8e5825 | |||
| 10f05e5559 | |||
| ddb2a65419 | |||
| 8d40a8089f | |||
| e1bf4a24d2 | |||
| 208a378185 | |||
| 9089c8959b | |||
|
1b9b581c31
|
|||
| aa1abe4c88 | |||
| d82c9cc9a7 | |||
| a7e3b1c5d2 | |||
| 84cf11933d | |||
| bc2fbee5c6 | |||
| ebd10ca522 | |||
|
d3a31be412
|
|||
|
d159a82f88
|
|||
|
c2d9c73fd4
|
|||
| 0d6d15fcf7 | |||
|
f9ad96c78b
|
|||
|
fcc9529a20
|
|||
|
955cb817c4
|
|||
| 823f9546b9 | |||
| be39fa16e7 | |||
| 74c9eb5585 | |||
| f93c7e014f | |||
| e6a799abc4 | |||
| 68a0ef7115 | |||
| 1675c56f3d | |||
| 675e8450fc | |||
| 4ffd7034d0 | |||
| c8bb7575e7 | |||
|
|
80a3734171 |
7
.changes/unreleased/Fixed-20251118-140559.yaml
Normal file
7
.changes/unreleased/Fixed-20251118-140559.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
kind: Fixed
|
||||
body: |
|
||||
Associate activity's creator as a participant by default, and retro-actively append the creator to each activity
|
||||
time: 2025-11-18T14:05:59.904993123+01:00
|
||||
custom:
|
||||
Issue: "466"
|
||||
SchemaChange: Add columns or tables
|
||||
6
.changes/unreleased/UX-20251119-153706.yaml
Normal file
6
.changes/unreleased/UX-20251119-153706.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: UX
|
||||
body: Alphabetically order userJobs and mainLocations within user creation form
|
||||
time: 2025-11-19T15:37:06.393470745+01:00
|
||||
custom:
|
||||
Issue: "470"
|
||||
SchemaChange: No schema change
|
||||
6
.changes/unreleased/UX-20251124-151115.yaml
Normal file
6
.changes/unreleased/UX-20251124-151115.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
kind: UX
|
||||
body: Change position and color of confirm parcours button
|
||||
time: 2025-11-24T15:11:15.960279853+01:00
|
||||
custom:
|
||||
Issue: "437"
|
||||
SchemaChange: No schema change
|
||||
13
.changes/v4.5.0.md
Normal file
13
.changes/v4.5.0.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## 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
|
||||
4
.changes/v4.5.1.md
Normal file
4
.changes/v4.5.1.md
Normal file
@@ -0,0 +1,4 @@
|
||||
## 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
|
||||
14
.changes/v4.6.0.md
Normal file
14
.changes/v4.6.0.md
Normal file
@@ -0,0 +1,14 @@
|
||||
## v4.6.0 - 2025-10-15
|
||||
### Feature
|
||||
* ([#423](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/423)) Create environment banner that can be activated and configured depending on the image deployed
|
||||
* ([#394](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/394)) Only show active workflow on the page "my tracked workflow"
|
||||
### Fixed
|
||||
* Fix loading of classLists in SocialIssuesAcc.vue, ensure elements are present
|
||||
* Fix the rendering of list of StoredObjectVersions, where there are kept version (before converting to pdf) and intermediate versions deleted
|
||||
* ([#434](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/434)) Notification: fix editing of sent notification by removing form.addressesEmails, a field that no longer exists
|
||||
* Fix loading of social issues and social actions within vue component
|
||||
* ([#446](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/446)) Add unique condition on stored object filename, with cleaning step on existing duplicate filenames
|
||||
|
||||
**Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
|
||||
* [workflow] take permissions into account to delete the workflow attachment
|
||||
* ([#448](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/448)) Fix the execution of daily cronjob notification, when the previous last execution storage was invalid
|
||||
3
.changes/v4.6.1.md
Normal file
3
.changes/v4.6.1.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## v4.6.1 - 2025-10-27
|
||||
### Fixed
|
||||
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php
|
||||
21
.changes/v4.7.0.md
Normal file
21
.changes/v4.7.0.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## v4.7.0 - 2025-11-10
|
||||
### Feature
|
||||
* ([#385](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/385)) Create invitation list in user menu
|
||||
* ([#404](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/404)) Add columns for comments linked to an activity in the activity list export
|
||||
### Fixed
|
||||
* ([#451](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/451)) Fix: display also social actions linked to parents of the selected social issue
|
||||
* ([#453](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/453)) Fix: export actions and their results in csv even when action does not have any goals attached to it.
|
||||
* Fix the possibility to delete a workflow
|
||||
|
||||
**Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
|
||||
* ([#457](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/457)) Fix the fusion of thirdparty properties that are located in another schema than public for TO_ONE relations + add extra loop for MANY_TO_MANY relations where thirdparty is the source instead of the target
|
||||
* ([#428](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/428)) Fix suggestion of referrer when creating notification for accompanyingPeriodWorkDocument
|
||||
### DX
|
||||
* Send notifications log to dedicated channel, if it exists
|
||||
|
||||
### UX
|
||||
* ([#425](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/425)) Change the terms 'cercle' and 'centre' to 'service', and 'territoire' respectively.
|
||||
* ([#542](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/542)) Improve the ux for selecting whether user wants to be notified of the final step of a workflow or all steps
|
||||
* Expand timeSpent choices for evaluation document and translate them to user locale or fallback 'fr'
|
||||
* ([#455](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/455)) Change the order of display for results and objectives in the social work/action form
|
||||
* Wrap text when it is too long within badges
|
||||
9
.changes/v4.8.0.md
Normal file
9
.changes/v4.8.0.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## v4.8.0 - 2025-11-17
|
||||
### Feature
|
||||
* ([#461](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/461)) Make a calendar item on the 'mes rendez-vous' page clickable. Clicking will navigate to the edit page of the calendar item.
|
||||
### Fixed
|
||||
* ([#463](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/463)) Display calendar items for which an invite was accepted on the mes rendez-vous page
|
||||
* Improve accessibility on login page
|
||||
|
||||
### UX
|
||||
* ([#449](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/449)) Remove the label if there is only one scope and no scope picking field is displayed.
|
||||
6
.changes/v4.8.1.md
Normal file
6
.changes/v4.8.1.md
Normal file
@@ -0,0 +1,6 @@
|
||||
## v4.8.1 - 2025-11-20
|
||||
### Fixed
|
||||
* Insert name of file as the document title when uploading
|
||||
* Add missing path paramater 'id' for editing multiple participations
|
||||
* ([#471](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/471)) Hide the display of inactive user groups in the api
|
||||
|
||||
4
.env
4
.env
@@ -92,3 +92,7 @@ REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}
|
||||
###> symfony/ovh-cloud-notifier ###
|
||||
# OVHCLOUD_DSN=ovhcloud://APPLICATION_KEY:APPLICATION_SECRET@default?consumer_key=CONSUMER_KEY&service_name=SERVICE_NAME
|
||||
###< symfony/ovh-cloud-notifier ###
|
||||
|
||||
###> symfony/loco-translation-provider ###
|
||||
#LOCO_DSN=loco://API_KEY@default
|
||||
###< symfony/loco-translation-provider ###
|
||||
|
||||
@@ -240,9 +240,6 @@ The tests are run from the project's root (not from the bundle's root).
|
||||
# Run all tests
|
||||
vendor/bin/phpunit
|
||||
|
||||
# Run tests for a specific bundle
|
||||
vendor/bin/phpunit --testsuite NameBundle
|
||||
|
||||
# Run a specific test file
|
||||
vendor/bin/phpunit path/to/TestFile.php
|
||||
|
||||
@@ -250,6 +247,9 @@ vendor/bin/phpunit path/to/TestFile.php
|
||||
vendor/bin/phpunit --filter methodName path/to/TestFile.php
|
||||
```
|
||||
|
||||
When writing tests, only test specific files. Do not run all tests or the full
|
||||
test suite.
|
||||
|
||||
#### Test Structure
|
||||
|
||||
Tests are organized by bundle and follow the same structure as the bundle itself:
|
||||
|
||||
77
CHANGELOG.md
77
CHANGELOG.md
@@ -6,6 +6,83 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||
|
||||
|
||||
## v4.8.1 - 2025-11-20
|
||||
### Fixed
|
||||
* Insert name of file as the document title when uploading
|
||||
* Add missing path paramater 'id' for editing multiple participations
|
||||
* ([#471](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/471)) Hide the display of inactive user groups in the api
|
||||
|
||||
|
||||
## v4.8.0 - 2025-11-17
|
||||
### Feature
|
||||
* ([#461](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/461)) Make a calendar item on the 'mes rendez-vous' page clickable. Clicking will navigate to the edit page of the calendar item.
|
||||
### Fixed
|
||||
* ([#463](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/463)) Display calendar items for which an invite was accepted on the mes rendez-vous page
|
||||
* Improve accessibility on login page
|
||||
|
||||
### UX
|
||||
* ([#449](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/449)) Remove the label if there is only one scope and no scope picking field is displayed.
|
||||
|
||||
## v4.7.0 - 2025-11-10
|
||||
### Feature
|
||||
* ([#385](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/385)) Create invitation list in user menu
|
||||
* ([#404](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/404)) Add columns for comments linked to an activity in the activity list export
|
||||
### Fixed
|
||||
* ([#451](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/451)) Fix: display also social actions linked to parents of the selected social issue
|
||||
* ([#453](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/453)) Fix: export actions and their results in csv even when action does not have any goals attached to it.
|
||||
* Fix the possibility to delete a workflow
|
||||
|
||||
**Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
|
||||
* ([#457](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/457)) Fix the fusion of thirdparty properties that are located in another schema than public for TO_ONE relations + add extra loop for MANY_TO_MANY relations where thirdparty is the source instead of the target
|
||||
* ([#428](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/428)) Fix suggestion of referrer when creating notification for accompanyingPeriodWorkDocument
|
||||
### DX
|
||||
* Send notifications log to dedicated channel, if it exists
|
||||
|
||||
### UX
|
||||
* ([#425](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/425)) Change the terms 'cercle' and 'centre' to 'service', and 'territoire' respectively.
|
||||
* ([#542](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/542)) Improve the ux for selecting whether user wants to be notified of the final step of a workflow or all steps
|
||||
* Expand timeSpent choices for evaluation document and translate them to user locale or fallback 'fr'
|
||||
* ([#455](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/455)) Change the order of display for results and objectives in the social work/action form
|
||||
* Wrap text when it is too long within badges
|
||||
|
||||
## v4.6.1 - 2025-10-27
|
||||
### Fixed
|
||||
* Fix export case where no 'reason' is picked within the PersonHavingActivityBetweenDateFilter.php
|
||||
|
||||
## v4.6.0 - 2025-10-15
|
||||
### Feature
|
||||
* ([#423](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/423)) Create environment banner that can be activated and configured depending on the image deployed
|
||||
* ([#394](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/394)) Only show active workflow on the page "my tracked workflow"
|
||||
### Fixed
|
||||
* Fix loading of classLists in SocialIssuesAcc.vue, ensure elements are present
|
||||
* Fix the rendering of list of StoredObjectVersions, where there are kept version (before converting to pdf) and intermediate versions deleted
|
||||
* ([#434](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/434)) Notification: fix editing of sent notification by removing form.addressesEmails, a field that no longer exists
|
||||
* Fix loading of social issues and social actions within vue component
|
||||
* ([#446](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/446)) Add unique condition on stored object filename, with cleaning step on existing duplicate filenames
|
||||
|
||||
**Schema Change**: Drop or rename table or columns, or enforce new constraint that must be manually fixed
|
||||
* [workflow] take permissions into account to delete the workflow attachment
|
||||
* ([#448](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/448)) Fix the execution of daily cronjob notification, when the previous last execution storage was invalid
|
||||
|
||||
## 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
|
||||
### Fixed
|
||||
* Fix document generation and workflow generation do not work on accompanying period work documents
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"ext-openssl": "*",
|
||||
"ext-redis": "*",
|
||||
"ext-zlib": "*",
|
||||
"champs-libres/wopi-bundle": "dev-master@dev",
|
||||
"champs-libres/wopi-bundle": "dev-symfony-v5@dev",
|
||||
"champs-libres/wopi-lib": "dev-master@dev",
|
||||
"doctrine/data-fixtures": "^1.8",
|
||||
"doctrine/doctrine-bundle": "^2.1",
|
||||
@@ -54,6 +54,7 @@
|
||||
"symfony/http-client": "^5.4",
|
||||
"symfony/http-foundation": "^5.4",
|
||||
"symfony/intl": "^5.4",
|
||||
"symfony/loco-translation-provider": "^6.0",
|
||||
"symfony/mailer": "^5.4",
|
||||
"symfony/messenger": "^5.4",
|
||||
"symfony/mime": "^5.4",
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
|
||||
ChampsLibres\WopiBundle\WopiBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
|
||||
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
|
||||
@@ -37,4 +36,5 @@ return [
|
||||
Chill\WopiBundle\ChillWopiBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\UX\Translator\UxTranslatorBundle::class => ['all' => true],
|
||||
loophp\PsrHttpMessageBridgeBundle\PsrHttpMessageBridgeBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
chill_main:
|
||||
available_languages: [ '%env(resolve:LOCALE)%', 'en' ]
|
||||
available_languages: [ '%env(resolve:LOCALE)%', 'en', 'nl' ]
|
||||
available_countries: ['BE', 'FR']
|
||||
top_banner:
|
||||
visible: false
|
||||
text:
|
||||
fr: 'Vous travaillez actuellement avec la version de PRÉ-PRODUCTION.'
|
||||
nl: 'Je werkt momenteel in de PRE-PRODUCTIE versie'
|
||||
color: '#353535'
|
||||
background_color: '#d8bb48'
|
||||
notifications:
|
||||
from_email: '%env(resolve:NOTIFICATION_FROM_EMAIL)%'
|
||||
from_name: '%env(resolve:NOTIFICATION_FROM_NAME)%'
|
||||
|
||||
2
config/packages/chill_aside_activity.yaml
Normal file
2
config/packages/chill_aside_activity.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
chill_aside_activity:
|
||||
show_concerned_persons_count: hidden
|
||||
@@ -1,7 +1,12 @@
|
||||
# config/packages/translation.yaml
|
||||
framework:
|
||||
default_locale: en
|
||||
default_locale: '%env(resolve:LOCALE)%' # LOCALE=fr in .env
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
fallbacks:
|
||||
- en
|
||||
- '%env(resolve:LOCALE)%' # fr
|
||||
providers:
|
||||
loco:
|
||||
dsn: '%env(LOCO_DSN)%'
|
||||
domains: [ 'messages' ]
|
||||
locales: [ 'fr', 'nl' ]
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
framework:
|
||||
default_locale: '%env(resolve:LOCALE)%'
|
||||
translator:
|
||||
fallbacks: [ '%env(resolve:LOCALE)%' ]
|
||||
@@ -23,8 +23,8 @@ class "Document" {
|
||||
- text description
|
||||
- ArrayCollection_DocumentCategory categories
|
||||
- varchar_150 content #link to openstack
|
||||
- Center center
|
||||
- Cercle cercle
|
||||
- Territoire territoire
|
||||
- Service service
|
||||
- User user
|
||||
- DateTime date # Creation date
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ Certaines données sont historisées:
|
||||
|
||||
- les référents d'un parcours;
|
||||
- les statuts d'un parcours;
|
||||
- la liaison entre les centres et les usagers;
|
||||
- la liaison entre les territoires et les usagers;
|
||||
- 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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
order,table_schema,table_name,commentaire
|
||||
1,chill_3party,party_category,Catégorie de tiers
|
||||
2,chill_3party,party_center,Association entre les tiers et les centres (déprécié)
|
||||
2,chill_3party,party_center,Association entre les tiers et les territoires (déprécié)
|
||||
3,chill_3party,party_profession,Profession du tiers (déprécié)
|
||||
4,chill_3party,third_party,Tiers
|
||||
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
|
||||
54,public,activitytype,Types d'échanges
|
||||
55,public,activitytypecategory,Catégories de types d'échanges
|
||||
56,public,centers,"Centres (territoires, agences, etc.)"
|
||||
56,public,centers,"Territoires (territoires, agences, etc.)"
|
||||
57,public,chill_activity_activity_chill_person_socialaction,
|
||||
58,public,chill_activity_activity_chill_person_socialissue
|
||||
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
|
||||
111,public,chill_person_not_duplicate,
|
||||
112,public,chill_person_person,Usagers
|
||||
113,public,chill_person_person_center_history,Historique des centres d'un usagers
|
||||
113,public,chill_person_person_center_history,Historique des territoires d'un usagers
|
||||
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
|
||||
116,public,chill_person_relations,Types de relations de filiation
|
||||
@@ -142,7 +142,7 @@ order,table_schema,table_name,commentaire
|
||||
141,public,permission_groups
|
||||
142,public,permissionsgroup_rolescope
|
||||
143,public,persons_spoken_languages
|
||||
144,public,regroupment,Regroupement de centres
|
||||
144,public,regroupment,Regroupement de territoires
|
||||
145,public,regroupment_center,
|
||||
146,public,role_scopes,
|
||||
147,public,scopes,Services
|
||||
|
||||
|
419
docs/source/development/translation_directives.rst
Normal file
419
docs/source/development/translation_directives.rst
Normal file
@@ -0,0 +1,419 @@
|
||||
============================================
|
||||
Directives for creating new translation keys
|
||||
============================================
|
||||
|
||||
These directives are meant to ensure better consistency across bundles, avoid duplication, and make keys more predictable.
|
||||
|
||||
|
||||
General Principles
|
||||
==================
|
||||
|
||||
1. **Use lowercase snake_case for all keys**
|
||||
|
||||
2. **Use dot-separated namespaces**
|
||||
The dot is used to reflect:
|
||||
- bundle
|
||||
- feature
|
||||
- sub-feature
|
||||
- key type
|
||||
|
||||
3. **Do not use spaces in keys**
|
||||
|
||||
4. **Avoid duplicating the same text in multiple places**
|
||||
When a translation is needed, try a search for the translation value first and see if it exists elsewhere
|
||||
|
||||
5. **If a key is used across multiple bundles, it must live in ChillMainBundle.**
|
||||
|
||||
6. **If a key is used across multiple bundles and is a generic term, it must be placed in the ``common`` namespace.**
|
||||
|
||||
|
||||
Key Structure
|
||||
=============
|
||||
|
||||
We use the following structure:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
<scope>.<feature>.<sub-feature>.<key-type>
|
||||
|
||||
Where:
|
||||
|
||||
* ``<>`` identifies the bundle or shared context
|
||||
* ``<feature>`` identifies the part of the module using the translation
|
||||
* ``<element>`` describes the text purpose
|
||||
* ``<subelement>`` for a multi-level element ( eg. activity.export.person.count.description)
|
||||
|
||||
Examples of scopes
|
||||
------------------
|
||||
|
||||
* ``activity`` — ChillActivityBundle
|
||||
* ``person`` — ChillPersonBundle
|
||||
* ``common`` — neutral shared translation values
|
||||
|
||||
|
||||
Naming Scopes
|
||||
=============
|
||||
|
||||
1. **Bundle-specific keys**
|
||||
|
||||
For most things inside a bundle:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.<feature>.<element>
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.form.save
|
||||
activity.list.title
|
||||
activity.entity.type
|
||||
activity.menu.activities
|
||||
activity.controller.success_created
|
||||
|
||||
2. **Shared UI elements (buttons, labels, generic text)**
|
||||
|
||||
These belong in the ``common`` namespace in ChillMainBundle:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
common.save
|
||||
common.delete
|
||||
common.edit
|
||||
common.filter
|
||||
common.duration_time
|
||||
|
||||
Translation workflow
|
||||
====================
|
||||
|
||||
Use the following workflow when deciding where a key belongs:
|
||||
|
||||
1. **Is this text used in more than one bundle?**
|
||||
→ Place in ``main`` or ``common``
|
||||
|
||||
2. **Is this text generic UI (button, label, pagination, yes/no)?**
|
||||
→ Place in ``common``
|
||||
|
||||
3. **Is this text specific to one bundle and one feature?**
|
||||
→ Place in ``<bundle>.feature.<element>``
|
||||
|
||||
4. **Is this text related to an entity or value object?**
|
||||
→ Place in ``<bundle>.entity.<entityname>.<field>``
|
||||
|
||||
5. **Is this text used in forms?**
|
||||
→ ``<bundle>.form.<field>`` or ``<bundle>.form.<action>``
|
||||
|
||||
6. **Is this text related to exports?**
|
||||
→ ``<bundle>.export.<feature>.<column>``
|
||||
|
||||
7. **Is it related to filtering, searching or parameters?**
|
||||
→ ``<bundle>.filter.<name>`` or
|
||||
→ ``<bundle>.filter.<feature>.<field>`` for nested filters
|
||||
|
||||
|
||||
Examples based on translations within ChillActivityBundle
|
||||
=========================================================
|
||||
|
||||
Below are concrete examples from ``ChillActivityBundle``,
|
||||
refactored according to the guidelines.
|
||||
|
||||
|
||||
General activity keys
|
||||
---------------------
|
||||
|
||||
Instead of scattered keys like::
|
||||
|
||||
Show the activity
|
||||
Edit the activity
|
||||
Activity
|
||||
Duration time
|
||||
...
|
||||
|
||||
We use:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.general.show
|
||||
activity.general.edit
|
||||
activity.general.title
|
||||
activity.general.duration
|
||||
activity.general.travel_time
|
||||
activity.general.attendee
|
||||
activity.general.remark
|
||||
activity.general.no_comments
|
||||
|
||||
|
||||
Forms
|
||||
-----
|
||||
|
||||
Instead of keys like::
|
||||
|
||||
Activity creation
|
||||
Save activity
|
||||
Reset form
|
||||
Choose a type
|
||||
|
||||
Use:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.form.title_create
|
||||
activity.form.save
|
||||
activity.form.reset
|
||||
activity.form.choose_type
|
||||
activity.form.choose_duration
|
||||
|
||||
Long lists (like durations) should be grouped:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.form.duration.5min
|
||||
activity.form.duration.10min
|
||||
activity.form.duration.15min
|
||||
activity.form.duration.1h
|
||||
activity.form.duration.1h30
|
||||
activity.form.duration.2h
|
||||
...
|
||||
|
||||
Entities
|
||||
--------
|
||||
|
||||
Entity fields should follow:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.entity.activity.date
|
||||
activity.entity.activity.comment
|
||||
activity.entity.activity.deleted
|
||||
activity.entity.location.name
|
||||
activity.entity.location.type
|
||||
|
||||
|
||||
Controller messages
|
||||
-------------------
|
||||
|
||||
Instead of strings as keys::
|
||||
|
||||
'Success : activity created!'
|
||||
'The form is not valid. The activity has not been created !'
|
||||
|
||||
Use:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.controller.success_created
|
||||
activity.controller.error_invalid_create
|
||||
activity.controller.success_updated
|
||||
activity.controller.error_invalid_update
|
||||
|
||||
|
||||
Roles
|
||||
-----
|
||||
|
||||
Access control keys should be:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.role.create
|
||||
activity.role.update
|
||||
activity.role.see
|
||||
activity.role.see_details
|
||||
activity.role.delete
|
||||
activity.role.stats
|
||||
activity.role.list
|
||||
|
||||
|
||||
Admin
|
||||
-----
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.admin.configuration
|
||||
activity.admin.types
|
||||
activity.admin.reasons
|
||||
activity.admin.reason_category
|
||||
activity.admin.presence
|
||||
|
||||
|
||||
CRUD
|
||||
----
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.crud.type.title_new
|
||||
activity.crud.type.title_edit
|
||||
activity.crud.presence.title_new
|
||||
|
||||
|
||||
Activity Reason
|
||||
---------------
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.reason.list
|
||||
activity.reason.create
|
||||
activity.reason.active
|
||||
activity.reason.category
|
||||
activity.reason.entity_title
|
||||
|
||||
|
||||
Exports
|
||||
-------
|
||||
|
||||
Group them logically:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.export.person.count.title
|
||||
activity.export.person.count.description
|
||||
activity.export.person.count.header
|
||||
|
||||
activity.export.period.sum_duration.title
|
||||
activity.export.period.sum_duration.description
|
||||
activity.export.period.sum_duration.header
|
||||
|
||||
|
||||
Filters
|
||||
-------
|
||||
|
||||
Use hierarchical filters:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.filter.by_reason
|
||||
activity.filter.by_type
|
||||
activity.filter.by_date
|
||||
activity.filter.by_location
|
||||
activity.filter.by_sent_received
|
||||
activity.filter.by_user
|
||||
|
||||
|
||||
Aggregators
|
||||
-----------
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.aggregator.reason.by_category
|
||||
activity.aggregator.reason.level
|
||||
activity.aggregator.user.by_scope
|
||||
activity.aggregator.user.by_job
|
||||
|
||||
|
||||
Global/Shared Keys
|
||||
==================
|
||||
|
||||
Keys like the following **must not be redeclared** in each bundle:
|
||||
|
||||
- First name
|
||||
- Last name
|
||||
- Username
|
||||
- ID
|
||||
- Type
|
||||
- Duration
|
||||
- Comment
|
||||
- Date
|
||||
- Location
|
||||
- Present / Not present
|
||||
- Add / Edit / Delete / Save / Update
|
||||
|
||||
These belong in ``common`` or ``main``:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
common.firstname
|
||||
common.lastname
|
||||
common.username
|
||||
common.id
|
||||
common.type
|
||||
common.comment
|
||||
common.date
|
||||
common.location
|
||||
common.present
|
||||
common.absent
|
||||
common.add
|
||||
common.edit
|
||||
common.delete
|
||||
common.save
|
||||
common.update
|
||||
|
||||
|
||||
Naming directives summary
|
||||
==========================
|
||||
|
||||
* **snake_case**
|
||||
* **namespaced with dots**
|
||||
* **bundle prefix for bundle-specific concepts**
|
||||
* **common or main for shared concepts**
|
||||
* **avoid free-floating keys (without namespace)**
|
||||
* **reuse common keys wherever possible**
|
||||
|
||||
|
||||
Migration Strategy (Optional)
|
||||
=============================
|
||||
|
||||
To apply this structure progressively:
|
||||
|
||||
1. New keys must follow these guidelines.
|
||||
2. Existing keys may remain as-is until refactored.
|
||||
3. When refactoring:
|
||||
- Move cross-bundle keys to ChillMainBundle and possible `common` namespace.
|
||||
- Replace duplicated keys with shared ones.
|
||||
|
||||
===========================================
|
||||
Avoiding duplicate translations
|
||||
===========================================
|
||||
|
||||
1. Use Shared Namespaces
|
||||
========================
|
||||
|
||||
Two namespaces must be used for shared translations:
|
||||
|
||||
* ``common.*`` — generic UI concepts (save, delete, date, name, etc.)
|
||||
|
||||
If a translation may be reused in multiple bundles, it must be placed
|
||||
in the ``common`` namespace or in ChillMainBundle.
|
||||
|
||||
2. Bundle-Specific Keys
|
||||
=======================
|
||||
|
||||
Keys belonging only to one bundle or one feature are namespaced inside that
|
||||
bundle:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
activity.<feature>.<element>
|
||||
person.<feature>.<element>
|
||||
|
||||
3. Search Before Creating
|
||||
=========================
|
||||
|
||||
Before adding a new translation key, developers must:
|
||||
|
||||
1. For common translations like: "enregistrer/opslaan" look in the `common` namespace.
|
||||
3. Search in Loco or translations for existing values.
|
||||
|
||||
If a suitable key exists, reuse it.
|
||||
|
||||
4. Only Create a New Key When Necessary
|
||||
=======================================
|
||||
|
||||
Create a new key only when the text is:
|
||||
|
||||
* specific to the bundle
|
||||
* specific to the feature
|
||||
* not reusable elsewhere
|
||||
|
||||
6. Progressive Cleanup
|
||||
======================
|
||||
|
||||
Old duplicates may remain temporarily. When updating code in an area, clean
|
||||
duplicate values by moving them into ``common`` or ``main``.
|
||||
|
||||
General workflow
|
||||
================
|
||||
|
||||
* **Reuse shared keys** within ``common`` namespace.
|
||||
* **Search before creating** new keys.
|
||||
* **Namespace bundle-specific keys** under their bundle.
|
||||
* **Refactor progressively** when touching old code.
|
||||
148
docs/source/development/translation_provider.rst
Normal file
148
docs/source/development/translation_provider.rst
Normal file
@@ -0,0 +1,148 @@
|
||||
=======================================================================
|
||||
Managing translations within CHILL using Loco as a translation provider
|
||||
=======================================================================
|
||||
|
||||
Within CHILL we make use of Symfony's translation component together with *Loco* as an external
|
||||
translation provider. Using this setup centralise translations in a single online
|
||||
location (Loco), while still allowing developers to create and update
|
||||
translation keys locally in the project (YAML files).
|
||||
|
||||
Workflow
|
||||
========
|
||||
|
||||
We use the following workflow:
|
||||
|
||||
* Developers create translation keys in YAML files inside each bundle.
|
||||
* Keys are written in **English**.
|
||||
* Application UI defaults to **French**, with **Dutch** as an additional locale (other languages can be added in the future).
|
||||
* Loco acts as the central translation memory and synchronisation source.
|
||||
* Loco Symfony package was installed so that built-in translation commands can be used to push/pull content
|
||||
between Loco and the local project.
|
||||
|
||||
|
||||
Translation directory structure
|
||||
===============================
|
||||
|
||||
Each bundle contains its own ``translations`` directory, for example::
|
||||
|
||||
chill-bundles/
|
||||
ChillCoreBundle/
|
||||
translations/
|
||||
messages.fr.yml
|
||||
messages.nl.yml
|
||||
ChillPersonBundle/
|
||||
translations/
|
||||
messages.fr.yml
|
||||
messages.nl.yml
|
||||
...
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
The translation configuration is defined in
|
||||
``config/packages/translation.yaml``::
|
||||
|
||||
framework:
|
||||
default_locale: '%env(resolve:LOCALE)%'
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
fallbacks:
|
||||
- '%env(resolve:LOCALE)%'
|
||||
- 'en'
|
||||
providers:
|
||||
loco:
|
||||
dsn: '%env(LOCO_DSN)%'
|
||||
domains: [ 'messages' ]
|
||||
locales: [ 'fr', 'nl' ]
|
||||
|
||||
Note:
|
||||
|
||||
* ``en`` is the **source locale** in Loco.
|
||||
* ``fr`` and ``nl`` are the **application locales**.
|
||||
* ``domains: [messages]`` means only ``messages.*.yml`` files are pushed.
|
||||
|
||||
|
||||
Environment variables
|
||||
---------------------
|
||||
|
||||
In ``.env``::
|
||||
|
||||
LOCALE=fr
|
||||
|
||||
In ``.env.local``::
|
||||
|
||||
LOCO_DSN="loco://API_KEY@default"
|
||||
|
||||
Replace ``API_KEY`` with the key provided by Loco.
|
||||
|
||||
|
||||
Working with Loco
|
||||
=================
|
||||
|
||||
Loco shows all translation keys under three languages:
|
||||
|
||||
* **English (source)** — keys are listed but remain “untranslated”
|
||||
* **French** — translated strings for French users
|
||||
* **Dutch** — translated strings for Dutch users
|
||||
|
||||
Note: Don't add translations directly in the English column.
|
||||
This column simply represents the *key*.
|
||||
|
||||
|
||||
Pushing translations to Loco
|
||||
============================
|
||||
|
||||
You can push local translations to Loco using:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
symfony console translation:push loco --locales=fr --locales=nl --force
|
||||
|
||||
This will:
|
||||
|
||||
* Upload all French and Dutch translation values from ``*.fr.yml`` and
|
||||
``*.nl.yml`` files
|
||||
* Ensures Loco stays in sync with local YAML files
|
||||
* Creates any missing keys in Loco
|
||||
|
||||
|
||||
Pulling translations from Loco
|
||||
==============================
|
||||
|
||||
When translators update strings in Loco, developers can fetch updates with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
symfony console translation:pull loco --locales=fr --locales=nl --force
|
||||
|
||||
This will:
|
||||
|
||||
* Download the latest French and Dutch translations
|
||||
* Overwrite the local YAML files with Loco’s content
|
||||
* Keep everything consistent across the team
|
||||
|
||||
|
||||
Adding new translation keys (Developer workflow)
|
||||
================================================
|
||||
|
||||
1. Add a new key directly in the appropriate YAML file, for example::
|
||||
|
||||
chill-bundles/ChillPersonBundle/translations/messages.fr.yml
|
||||
|
||||
Example key::
|
||||
|
||||
person.form.submit: "Envoyer"
|
||||
|
||||
2. Add Dutch translation as well if you can (otherwise leave empty to be translated within Loco later)::
|
||||
|
||||
person.form.submit: "Verzenden"
|
||||
|
||||
3. Run a push to send the new key to Loco:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
symfony console translation:push loco --locales=fr --locales=nl --force
|
||||
|
||||
4. The key will now appear in Loco for translation management.
|
||||
|
||||
Note: English appears as “untranslated”, because it is merely the source language
|
||||
@@ -55,6 +55,7 @@
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/leaflet": "^1.9.3",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"dropzone": "^5.7.6",
|
||||
"es6-promise": "^4.2.8",
|
||||
|
||||
@@ -382,6 +382,7 @@ final class ActivityController extends AbstractController
|
||||
|
||||
$entity = new Activity();
|
||||
$entity->setUser($this->security->getUser());
|
||||
$entity->addUser($this->security->getUser());
|
||||
|
||||
if ($person instanceof Person) {
|
||||
$entity->setPerson($person);
|
||||
|
||||
@@ -66,6 +66,9 @@ class ListActivityHelper
|
||||
->leftJoin('activity.location', 'location')
|
||||
->addSelect('location.name AS locationName')
|
||||
->addSelect('activity.sentReceived')
|
||||
->addSelect('activity.comment.comment AS commentText')
|
||||
->addSelect('activity.comment.date AS commentDate')
|
||||
->addSelect('JSON_BUILD_OBJECT(\'uid\', activity.comment.userId, \'d\', activity.comment.date) AS commentUser')
|
||||
->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.createdBy), \'d\', activity.createdAt) AS createdBy')
|
||||
->addSelect('activity.createdAt')
|
||||
->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.updatedBy), \'d\', activity.updatedAt) AS updatedBy')
|
||||
@@ -87,6 +90,8 @@ class ListActivityHelper
|
||||
'createdAt', 'updatedAt' => $this->dateTimeHelper->getLabel($key),
|
||||
'createdBy', 'updatedBy' => $this->userHelper->getLabel($key, $values, $key),
|
||||
'date' => $this->dateTimeHelper->getLabel(self::MSG_KEY.$key),
|
||||
'commentDate' => $this->dateTimeHelper->getLabel(self::MSG_KEY.'comment_date'),
|
||||
'commentUser' => $this->userHelper->getLabel($key, $values, self::MSG_KEY.'comment_user'),
|
||||
'attendeeName' => function ($value) {
|
||||
if ('_header' === $value) {
|
||||
return 'Attendee';
|
||||
@@ -176,6 +181,9 @@ class ListActivityHelper
|
||||
'usersNames',
|
||||
'thirdPartiesIds',
|
||||
'thirdPartiesNames',
|
||||
'commentText',
|
||||
'commentDate',
|
||||
'commentUser',
|
||||
'createdBy',
|
||||
'createdAt',
|
||||
'updatedBy',
|
||||
|
||||
@@ -90,7 +90,9 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
return [
|
||||
'reasons' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function describeAction($data, ExportGenerationContext $context): string|\Symfony\Contracts\Translation\TranslatableInterface|array
|
||||
|
||||
@@ -42,6 +42,8 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data, ExportGenerationContext $exportGenerationContext): void
|
||||
{
|
||||
error_log('alterQuery called with data: '.json_encode(array_keys($data)));
|
||||
|
||||
// create a subquery for activity
|
||||
$sqb = $qb->getEntityManager()->createQueryBuilder();
|
||||
$sqb->select('1')
|
||||
@@ -59,7 +61,6 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
|
||||
if (\in_array('activity', $qb->getAllAliases(), true)) {
|
||||
$sqb->andWhere('activity_person_having_activity.id = activity.id');
|
||||
}
|
||||
|
||||
if (isset($data['reasons']) && [] !== $data['reasons']) {
|
||||
// add clause activity reason
|
||||
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
|
||||
@@ -124,12 +125,38 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return ['date_from_rolling' => $formData['date_from_rolling']->normalize(), 'date_to_rolling' => $formData['date_to_rolling']->normalize()];
|
||||
$normalized = [
|
||||
'date_from_rolling' => $formData['date_from_rolling']->normalize(),
|
||||
'date_to_rolling' => $formData['date_to_rolling']->normalize(),
|
||||
'reasons' => [],
|
||||
];
|
||||
|
||||
if (isset($formData['reasons']) && [] !== $formData['reasons']) {
|
||||
$normalized['reasons'] = array_map(
|
||||
fn (ActivityReason $reason) => $reason->getId(),
|
||||
$formData['reasons']
|
||||
);
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return ['date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']), 'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling'])];
|
||||
$denormalized = [
|
||||
'date_from_rolling' => RollingDate::fromNormalized($formData['date_from_rolling']),
|
||||
'date_to_rolling' => RollingDate::fromNormalized($formData['date_to_rolling']),
|
||||
'reasons' => [],
|
||||
];
|
||||
|
||||
if (isset($formData['reasons']) && [] !== $formData['reasons']) {
|
||||
$denormalized['reasons'] = array_map(
|
||||
fn ($id) => $this->activityReasonRepository->find($id),
|
||||
$formData['reasons']
|
||||
);
|
||||
}
|
||||
|
||||
return $denormalized;
|
||||
}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
@@ -143,10 +170,12 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
|
||||
|
||||
public function describeAction($data, ExportGenerationContext $context): array
|
||||
{
|
||||
$reasons = $data['reasons'] ?? [];
|
||||
|
||||
return [
|
||||
[] === $data['reasons'] ?
|
||||
'export.filter.person_between_dates.describe_action_with_no_subject'
|
||||
: 'export.filter.person_between_dates.describe_action_with_subject',
|
||||
[] === $reasons ?
|
||||
'export.filter.activity.describe_action_with_no_subject'
|
||||
: 'export.filter.activity.describe_action_with_subject',
|
||||
[
|
||||
'date_from' => $this->rollingDateConverter->convert($data['date_from_rolling']),
|
||||
'date_to' => $this->rollingDateConverter->convert($data['date_to_rolling']),
|
||||
@@ -154,7 +183,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
|
||||
', ',
|
||||
array_map(
|
||||
fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"',
|
||||
$data['reasons']
|
||||
$reasons
|
||||
)
|
||||
),
|
||||
],
|
||||
@@ -168,6 +197,7 @@ final readonly class PersonHavingActivityBetweenDateFilter implements ExportElem
|
||||
|
||||
public function validateForm($data, ExecutionContextInterface $context): void
|
||||
{
|
||||
error_log('validateForm called with data: '.json_encode(array_keys($data)));
|
||||
if ($this->rollingDateConverter->convert($data['date_from_rolling'])
|
||||
>= $this->rollingDateConverter->convert($data['date_to_rolling'])) {
|
||||
$context->buildViolation('export.filter.activity.person_between_dates.date mismatch')
|
||||
|
||||
@@ -88,8 +88,8 @@ class ActivityType extends AbstractType
|
||||
|
||||
if (null !== $options['data']->getPerson()) {
|
||||
$builder->add('scope', ScopePickerType::class, [
|
||||
'center' => $options['center'],
|
||||
'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'],
|
||||
'center' => $options['center'],
|
||||
'required' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -136,8 +136,14 @@ export default {
|
||||
issueIsLoading: false,
|
||||
actionIsLoading: false,
|
||||
actionAreLoaded: false,
|
||||
socialIssuesClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialIssues").getAttribute("required") ? "required" : ""}`,
|
||||
socialActionsClassList: `col-form-label ${document.querySelector("input#chill_activitybundle_activity_socialActions").getAttribute("required") ? "required" : ""}`,
|
||||
socialIssuesClassList: {
|
||||
"col-form-label": true,
|
||||
required: false,
|
||||
},
|
||||
socialActionsClassList: {
|
||||
"col-form-label": true,
|
||||
required: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -158,6 +164,21 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
/* Load classNames after element is present */
|
||||
const socialActionsEl = document.querySelector(
|
||||
"input#chill_activitybundle_activity_socialActions",
|
||||
);
|
||||
if (socialActionsEl && socialActionsEl.hasAttribute("required")) {
|
||||
this.socialActionsClassList.required = true;
|
||||
}
|
||||
|
||||
const socialIssuesEl = document.querySelector(
|
||||
"input#chill_activitybundle_activity_socialIssues",
|
||||
);
|
||||
if (socialIssuesEl && socialIssuesEl.hasAttribute("required")) {
|
||||
this.socialIssuesClassList.required = true;
|
||||
}
|
||||
|
||||
/* Load other issues in multiselect */
|
||||
this.issueIsLoading = true;
|
||||
this.actionAreLoaded = false;
|
||||
|
||||
@@ -43,11 +43,23 @@ export default {
|
||||
span.badge {
|
||||
@include badge_social($social-action-color);
|
||||
font-size: 95%;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
max-width: 100%; /* Adjust as needed */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
line-height: 1.2em;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: 0;
|
||||
margin: 0 0.3em 0 -0.75em;
|
||||
}
|
||||
position: relative;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -43,7 +43,22 @@ export default {
|
||||
span.badge {
|
||||
@include badge_social($social-issue-color);
|
||||
font-size: 95%;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 1em;
|
||||
text-align: left;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: 0;
|
||||
margin: 0 0.3em 0 -0.75em;
|
||||
}
|
||||
position: relative;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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\Migrations\Activity;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Migration fixing the automatic association of users to activities (exchanges).
|
||||
*
|
||||
* Originally, the user who created an exchange was not automatically associated
|
||||
* to it (the "TMS" column), which led to incomplete data and biased statistics.
|
||||
*
|
||||
* This migration:
|
||||
* - retroactively associates the creator of each exchange to the corresponding
|
||||
* activity;
|
||||
* - flags these backfilled associations with a temporary column so it is clear
|
||||
* they were added by this data correction and can be safely cleaned up later.
|
||||
*/
|
||||
final class Version20251118124241 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Insert the creator of activity into the activity_user table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE activity_user ADD COLUMN by_migration BOOL DEFAULT FALSE');
|
||||
$this->addSql("COMMENT ON COLUMN activity_user.by_migration IS 'For backup purpose - can be safely deleted after a while. See migration \\Chill\\Migrations\\Activity\\Version20251118124241'");
|
||||
|
||||
$this->addSql('INSERT INTO activity_user (activity_id, user_id, by_migration)
|
||||
SELECT id, user_id, true FROM activity
|
||||
ON CONFLICT DO NOTHING');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE activity_user DROP COLUMN by_migration');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export:
|
||||
filter:
|
||||
activity:
|
||||
course_having_activity_between_date:
|
||||
Only course having an activity between from and to: Alleen trajecten met een activiteit tussen {from, date, short} en {to, date, short}
|
||||
|
||||
acp_by_activity_type:
|
||||
'acp_containing_at_least_one_activitytypes': >-
|
||||
Gefilterde trajecten: alleen die welke ten minste één activiteit bevatten van een van de volgende types: {activitytypes}
|
||||
{has_date_after, select, 1 {, na {date_after, date}} other {}}
|
||||
{has_date_before, select, 1 {, voor {date_before, date}} other {}}
|
||||
describe_action_with_no_subject: >-
|
||||
Gefilterd op persoon die een activiteit had tussen {date_from, date} en {date_to, date}
|
||||
describe_action_with_subject: >-
|
||||
Gefilterd op persoon die een activiteit had tussen {date_from, date} en {date_to, date}, en een van deze gekozen onderwerpen: {reasons}
|
||||
|
||||
activity:
|
||||
title: Activiteit van {date, date, long} - {type}
|
||||
@@ -10,7 +10,7 @@ Attendee: Présence de l'usager
|
||||
attendee: présence de l'usager
|
||||
list_reasons: liste des sujets
|
||||
user_username: nom de l'utilisateur
|
||||
circle_name: nom du cercle
|
||||
circle_name: nom du service
|
||||
Remark: Commentaire
|
||||
No comments: Aucun commentaire
|
||||
Add a new activity: Ajouter une nouvel échange
|
||||
@@ -20,7 +20,7 @@ not present: absent
|
||||
Delete: Supprimer
|
||||
Update: Mettre à jour
|
||||
Update activity: Modifier l'échange
|
||||
Scope: Cercle
|
||||
Scope: Service
|
||||
Activity data: Données de l'échange
|
||||
Activity location: Localisation de l'échange
|
||||
No reason associated: Aucun sujet
|
||||
@@ -398,13 +398,15 @@ export:
|
||||
sent received: Envoyé ou reçu
|
||||
emergency: Urgence
|
||||
accompanying course id: Identifiant du parcours
|
||||
course circles: Cercles du parcours
|
||||
course circles: Services du parcours
|
||||
travelTime: Durée de déplacement
|
||||
durationTime: Durée
|
||||
id: Identifiant
|
||||
List activities linked to an accompanying course: Liste les échanges liés à un parcours en fonction de différents filtres.
|
||||
List activity linked to a course: Liste des échanges liés à un parcours
|
||||
|
||||
commentText: Commentaire
|
||||
comment_date: Date de la dernière édition du commentaire
|
||||
comment_user: Dernière édition par
|
||||
|
||||
filter:
|
||||
activity:
|
||||
|
||||
@@ -1,234 +1,500 @@
|
||||
#general
|
||||
Show the activity: Toon activiteit
|
||||
Edit the activity: Wijzig activiteit
|
||||
Activity: Activiteit
|
||||
Show the activity: Uitwisseling bekijken
|
||||
Edit the activity: Uitwisseling bewerken
|
||||
Activity: Uitwisseling
|
||||
Duration time: Duur
|
||||
Duration Time: Duur
|
||||
durationTime: duur
|
||||
Travel time: Duur van verplaatsing
|
||||
Attendee: Aanwezigheden
|
||||
attendee: aanwezigheden
|
||||
list_reasons: Onderwerpen
|
||||
user_username: gebruikersnaam
|
||||
circle_name: naam kring
|
||||
durationTime: duur
|
||||
Travel time: Reisduur
|
||||
Attendee: Aanwezigheid van de gebruiker
|
||||
attendee: aanwezigheid van de gebruiker
|
||||
list_reasons: lijst van onderwerpen
|
||||
user_username: naam van de gebruiker
|
||||
circle_name: naam van de dienst
|
||||
Remark: Opmerking
|
||||
No comments: Geen opmerkingen
|
||||
Add a new activity: Voeg een nieuwe activiteit toe
|
||||
Activity list: Lijst van activiteiten
|
||||
No comments: Geen opmerking
|
||||
Add a new activity: Nieuwe uitwisseling toevoegen
|
||||
Activity list: Lijst van uitwisselingen
|
||||
present: aanwezig
|
||||
not present: afwezig
|
||||
Delete: Verwijderen
|
||||
Update: Bijwerken
|
||||
Update activity: Activieit bijwerken
|
||||
Scope: Werkingsgebied
|
||||
Activity data: Gegevens activiteit
|
||||
Activity location: Locatie activiteit
|
||||
Update activity: Uitwisseling bewerken
|
||||
Scope: Dienst
|
||||
Activity data: Gegevens van de uitwisseling
|
||||
Activity location: Locatie van de uitwisseling
|
||||
No reason associated: Geen onderwerp
|
||||
No social issues associated: Geen sociaal vraagstuk
|
||||
No social actions associated: Geen maatschappelijke actie
|
||||
There isn't any activities.: Er zijn geen activiteiten
|
||||
type_name: Soort activiteit
|
||||
No social issues associated: Geen sociale problematiek
|
||||
No social actions associated: Geen begeleidingsactie
|
||||
There isn't any activities.: Geen uitwisseling geregistreerd.
|
||||
type_name: type van de uitwisseling
|
||||
person_firstname: voornaam
|
||||
person_lastname: familienaam
|
||||
person_id: Identificatienummer persoon
|
||||
Type: Soort
|
||||
person_lastname: achternaam
|
||||
person_id: identificatie van de gebruiker
|
||||
Type: Type
|
||||
Invisible: Onzichtbaar
|
||||
Optional: Optioneel
|
||||
Required: Verplicht
|
||||
Persons: Personen
|
||||
Persons: Gebruikers
|
||||
Users: Gebruikers
|
||||
Emergency: Dringend
|
||||
Emergency: Urgent
|
||||
Sent received: Inkomend / Uitgaand
|
||||
Sent: Verzenden
|
||||
Received: Ontvangen
|
||||
by: 'Door '
|
||||
location: Plaats
|
||||
Reasons: Onderwerpen
|
||||
Private comment: Privé opmerking
|
||||
sent: Verzonden
|
||||
received: Ontvangen
|
||||
|
||||
|
||||
#forms
|
||||
Activity creation: Nouvel échange
|
||||
Create: Créer
|
||||
Back to the list: Retour à la liste
|
||||
Save activity: Sauver l'échange
|
||||
Reset form: Remise à zéro du formulaire
|
||||
Choose the duration: Choisir la durée
|
||||
Choose a type: Choisir un type
|
||||
5 minutes: 5 minutes
|
||||
10 minutes: 10 minutes
|
||||
15 minutes: 15 minutes
|
||||
20 minutes: 20 minutes
|
||||
25 minutes: 25 minutes
|
||||
30 minutes: 30 minutes
|
||||
45 minutes: 45 minutes
|
||||
1 hour: 1 heure
|
||||
1 hour 15: 1 heure 15
|
||||
1 hour 30: 1 heure 30
|
||||
1 hour 45: 1 heure 45
|
||||
2 hours: 2 heures
|
||||
Concerned groups: Parties concernées
|
||||
Persons in accompanying course: Usagers du parcours
|
||||
Third persons: Tiers non-pro.
|
||||
Others persons: Usagers
|
||||
Third parties: Tiers professionnels
|
||||
Activity creation: Nieuwe uitwisseling
|
||||
Create: Aanmaken
|
||||
Back to the list: Terug naar de lijst
|
||||
Save activity: Uitwisseling opslaan
|
||||
Reset form: Formulier resetten
|
||||
Choose the duration: Duur kiezen
|
||||
Choose a type: Type kiezen
|
||||
5 minutes: 5 minuten
|
||||
10 minutes: 10 minuten
|
||||
15 minutes: 15 minuten
|
||||
20 minutes: 20 minuten
|
||||
25 minutes: 25 minuten
|
||||
30 minutes: 30 minuten
|
||||
45 minutes: 45 minuten
|
||||
1 hour: 1 uur
|
||||
1 hour 15: 1 uur 15
|
||||
1 hour 30: 1 uur 30
|
||||
1 hour 45: 1 uur 45
|
||||
2 hours: 2 uur
|
||||
2 hours 15: 2 uur 15
|
||||
2 hours 30: 2 uur 30
|
||||
2 hours 45: 2 uur 45
|
||||
3 hours: 3 uur
|
||||
3 hours 30: 3 uur 30
|
||||
4 hours: 4 uur
|
||||
4 hours 30: 4 uur 30
|
||||
5 hours: 5 uur
|
||||
5 hours 30: 5 uur 30
|
||||
6 hours: 6 uur
|
||||
6 hours 30: 6 uur 30
|
||||
7 hours: 7 uur
|
||||
7 hours 30: 7 uur 30
|
||||
8 hours: 8 uur
|
||||
8 hours 30: 8 uur 30
|
||||
9 hours: 9 uur
|
||||
9 hours 30: 9 uur 30
|
||||
10 hours: 10 uur
|
||||
11 hours: 11 uur
|
||||
12 hours: 12 uur
|
||||
Concerned groups: Betrokken partijen bij de uitwisseling
|
||||
Persons in accompanying course: Gebruikers van het traject
|
||||
Third persons: Niet-prof. derden
|
||||
Others persons: Gebruikers
|
||||
Third parties: Professionele derden
|
||||
Users concerned: T(M)S
|
||||
|
||||
activity:
|
||||
Insert a document: Insérer un document
|
||||
Remove a document: Supprimer le document
|
||||
comment: Commentaire
|
||||
No documents: Aucun document
|
||||
date: Datum van de uitwisseling
|
||||
Insert a document: Document invoegen
|
||||
Remove a document: Document verwijderen
|
||||
comment: Opmerking
|
||||
deleted: Uitwisseling verwijderd
|
||||
|
||||
errors: Het formulier bevat fouten
|
||||
social_issues: Sociale problematieken
|
||||
choose_other_social_issue: Andere sociale problematiek toevoegen...
|
||||
social_actions: Begeleidingsacties
|
||||
select_first_a_social_issue: Selecteer eerst een sociale problematiek
|
||||
social_action_list_empty: Geen sociale actie beschikbaar
|
||||
add_persons: Betrokken personen toevoegen
|
||||
bloc_persons: Gebruikers
|
||||
bloc_persons_associated: Gebruikers van het traject
|
||||
bloc_persons_not_associated: Niet-prof. derden
|
||||
bloc_thirdparty: Professionele derden
|
||||
bloc_users: T(M)S
|
||||
location: Locatie
|
||||
choose_location: Kies een locatie
|
||||
choose_location_type: Kies een type locatie
|
||||
create_new_location: Nieuwe locatie aanmaken
|
||||
location_fields:
|
||||
name: Naam
|
||||
type: Type
|
||||
phonenumber1: Telefoon
|
||||
phonenumber2: Andere telefoon
|
||||
email: E-mailadres
|
||||
create_address: Adres aanmaken
|
||||
edit_address: Adres bewerken
|
||||
|
||||
No documents: Geen document
|
||||
|
||||
# activity filter in list page
|
||||
activity_filter:
|
||||
My activities: Mijn uitwisselingen (waar ik aan deelneem)
|
||||
Types: Op type uitwisseling
|
||||
Jobs: Op betrokken beroep
|
||||
|
||||
#timeline
|
||||
'%user% has done an %activity_type%': '%user% a effectué un échange de type "%activity_type%"'
|
||||
'%user% has done an %activity_type%': '%user% heeft een uitwisseling van type "%activity_type%" uitgevoerd'
|
||||
|
||||
#controller
|
||||
'Success : activity created!': L'échange a été créé.
|
||||
'The form is not valid. The activity has not been created !': Le formulaire est invalide. L'échange n'a pas été créé.
|
||||
'Success : activity updated!': L'échange a été mis à jour.
|
||||
'The form is not valid. The activity has not been updated !': Le formulaire est invalide. L'échange n'a pas été mis à jour.
|
||||
'Success : activity created!': De uitwisseling is aangemaakt.
|
||||
'The form is not valid. The activity has not been created !': Het formulier is ongeldig. De uitwisseling is niet aangemaakt.
|
||||
'Success : activity updated!': De uitwisseling is bijgewerkt.
|
||||
'The form is not valid. The activity has not been updated !': Het formulier is ongeldig. De uitwisseling is niet bijgewerkt.
|
||||
|
||||
# ROLES
|
||||
CHILL_ACTIVITY_CREATE: Créer un échange
|
||||
CHILL_ACTIVITY_UPDATE: Modifier un échange
|
||||
CHILL_ACTIVITY_SEE: Voir un échange
|
||||
CHILL_ACTIVITY_SEE_DETAILS: Voir le détail des échanges
|
||||
CHILL_ACTIVITY_DELETE: Supprimer un échange
|
||||
CHILL_ACTIVITY_STATS: Statistique des échanges
|
||||
CHILL_ACTIVITY_LIST: Liste des échanges
|
||||
CHILL_ACTIVITY_CREATE: Uitwisseling aanmaken
|
||||
CHILL_ACTIVITY_UPDATE: Uitwisseling bewerken
|
||||
CHILL_ACTIVITY_SEE: Uitwisseling bekijken
|
||||
CHILL_ACTIVITY_SEE_DETAILS: Detail van uitwisselingen bekijken
|
||||
CHILL_ACTIVITY_DELETE: Uitwisseling verwijderen
|
||||
CHILL_ACTIVITY_STATS: Statistieken van uitwisselingen
|
||||
CHILL_ACTIVITY_LIST: Lijst van uitwisselingen
|
||||
CHILL_ACTIVITY_CREATE_PERSON: Uitwisseling aanmaken gekoppeld aan een gebruiker
|
||||
CHILL_ACTIVITY_CREATE_ACCOMPANYING_COURSE: Uitwisseling aanmaken gekoppeld aan een traject
|
||||
CHILL_ACTIVITY_FULL: Details bekijken, aanmaken, verwijderen en bijwerken van een uitwisseling
|
||||
|
||||
# admin
|
||||
Activities: Échanges
|
||||
Activity configuration: Configuration des échanges
|
||||
Activity configuration menu: Configuration des échanges
|
||||
Activity types: Types d'échange
|
||||
Activity type configuration: Configuration des categories d'échanges
|
||||
Activity Reasons: Sujets d'un échange
|
||||
Activity Reasons Category: Catégories de sujet d'échanges
|
||||
Activity Types Categories: Catégories des types d'échanges
|
||||
Activity Presences: Presences des échanges
|
||||
Activities: Uitwisselingen
|
||||
Activity configuration: Configuratie van uitwisselingen
|
||||
Activity configuration menu: Configuratie van uitwisselingen
|
||||
Activity types: Types uitwisseling
|
||||
Activity type configuration: Configuratie van categorieën van uitwisselingen
|
||||
Activity Reasons: Onderwerpen van een uitwisseling
|
||||
Activity Reasons Category: Categorieën van onderwerpen van uitwisselingen
|
||||
Activity Types Categories: Categorieën van types uitwisseling
|
||||
Activity Presences: Aanwezigheden bij uitwisselingen
|
||||
Associated activity reason category is inactive: De gekoppelde onderwerpscategorie is inactief
|
||||
|
||||
|
||||
# Crud
|
||||
crud:
|
||||
activity_type:
|
||||
title_new: Nouveau type d'échange
|
||||
title_edit: Edition d'un type d'échange
|
||||
activity_type_category:
|
||||
title_new: Nouvelle catégorie de type d'échange
|
||||
title_edit: Edition d'une catégorie de type d'échange
|
||||
activity_type:
|
||||
title_new: Nieuw type uitwisseling
|
||||
title_edit: Type uitwisseling bewerken
|
||||
activity_type_category:
|
||||
title_new: Nieuwe categorie van type uitwisseling
|
||||
title_edit: Categorie van type uitwisseling bewerken
|
||||
activity_presence:
|
||||
title_new: Nieuwe aanwezigheid bij uitwisselingen
|
||||
title_edit: Aanwezigheid bij uitwisselingen bewerken
|
||||
|
||||
# activity reason admin
|
||||
ActivityReason list: Liste des sujets
|
||||
Create a new activity reason: Créer un nouveau sujet
|
||||
Active: Actif
|
||||
Category: Catégorie
|
||||
ActivityReason creation: Nouveau sujet
|
||||
ActivityReason edit: Modification d'un sujet
|
||||
ActivityReason: Sujet d'échange
|
||||
The entity is inactive and won't be proposed: Le sujet est inactif et ne sera pas proposé
|
||||
The entity is active and will be proposed: Le sujet est actif et sera proposé
|
||||
ActivityReason list: Lijst van onderwerpen
|
||||
Create a new activity reason: Nieuw onderwerp aanmaken
|
||||
Active: Actief
|
||||
Category: Categorie
|
||||
ActivityReason creation: Nieuw onderwerp
|
||||
ActivityReason edit: Onderwerp bewerken
|
||||
ActivityReason: Onderwerp van uitwisseling
|
||||
The entity is inactive and won't be proposed: Het onderwerp is inactief en zal niet worden voorgesteld
|
||||
The entity is active and will be proposed: Het onderwerp is actief en zal worden voorgesteld
|
||||
|
||||
#activity reason category admin
|
||||
ActivityReasonCategory list: Catégories de sujets
|
||||
Create a new activity category reason: Créer une nouvelle catégorie
|
||||
ActivityReasonCategory creation: Nouvelle catégorie de sujet
|
||||
ActivityReasonCategory edit: Modification d'une catégorie de sujet
|
||||
ActivityReasonCategory: Catégorie de sujet d'échange
|
||||
ActivityReasonCategory is active and will be proposed: La catégorie est active et sera proposée
|
||||
ActivityReasonCategory is inactive and won't be proposed: La catégorie est inactive et ne sera pas proposée
|
||||
ActivityReasonCategory list: Categorieën van onderwerpen
|
||||
Create a new activity category reason: Nieuwe categorie aanmaken
|
||||
ActivityReasonCategory creation: Nieuwe categorie van onderwerp
|
||||
ActivityReasonCategory edit: Categorie van onderwerp bewerken
|
||||
ActivityReasonCategory: Categorie van onderwerp van uitwisseling
|
||||
ActivityReasonCategory is active and will be proposed: De categorie is actief en zal worden voorgesteld
|
||||
ActivityReasonCategory is inactive and won't be proposed: De categorie is inactief en zal niet worden voorgesteld
|
||||
|
||||
#activity presence admin
|
||||
ActivityPresence list: Lijst van aanwezigheden bij uitwisselingen
|
||||
Create a new activity presence: Nieuwe "Aanwezigheid bij uitwisselingen" aanmaken
|
||||
|
||||
# activity type type admin
|
||||
ActivityType list: Types d'échanges
|
||||
Create a new activity type: Créer un nouveau type d'échange
|
||||
Persons visible: Visibilité du champ Personnes
|
||||
Persons label: Libellé du champ Personnes
|
||||
User visible: Visibilité du champ Utilisateur
|
||||
User label: Libellé du champ Utilisateur
|
||||
Date visible: Visibilité du champ Date
|
||||
Date label: Libellé du champ Date
|
||||
Location visible: Visibilité du champ Lieu
|
||||
Location label: Libellé du champ Lieu
|
||||
Third parties visible: Visibilité du champ Tiers
|
||||
Third parties label: Libellé du champ Tiers
|
||||
Duration time visible: Visibilité du champ Durée
|
||||
Duration time label: Libellé du champ Durée
|
||||
Travel time visible: Visibilité du champ Durée de déplacement
|
||||
Travel time label: Libellé du champ Durée de déplacement
|
||||
Attendee visible: Visibilité du champ Présence de l'usager
|
||||
Attendee label: Libellé du champ Présence de l'usager
|
||||
Reasons visible: Visibilité du champ Sujet
|
||||
Reasons label: Libellé du champ Sujet
|
||||
Comment visible: Visibilité du champ Commentaire
|
||||
Comment label: Libellé du champ Commentaire
|
||||
Emergency visible: Visibilité du champ Urgent
|
||||
Emergency label: Libellé du champ Urgent
|
||||
Accompanying period visible: Visibilité du champ Période d'accompagnement
|
||||
Accompanying period label: Libellé du champ Période d'accompagnement
|
||||
Social issues visible: Visibilité du champ Problématiques sociales
|
||||
Social issues label: Libellé du champ Problématiques sociales
|
||||
Social actions visible: Visibilité du champ Action sociale
|
||||
Social actions label: Libellé du champ Action sociale
|
||||
Users visible: Visibilité du champ Utilisateurs
|
||||
Users label: Libellé du champ Utilisateurs
|
||||
Sent received visible: Visibilité du champ Entrant / Sortant
|
||||
Sent received label: Libellé du champ Entrant / Sortant
|
||||
Documents visible: Visibilité du champ Documents
|
||||
Documents label: Libellé du champ Documents
|
||||
ActivityType list: Types uitwisselingen
|
||||
Create a new activity type: Nieuw type uitwisseling aanmaken
|
||||
Persons visible: Zichtbaarheid van het veld Gebruikers
|
||||
Persons label: Label van het veld Gebruikers
|
||||
User visible: Zichtbaarheid van het veld Gebruiker
|
||||
User label: Label van het veld Gebruiker
|
||||
Date visible: Zichtbaarheid van het veld Datum
|
||||
Date label: Label van het veld Datum
|
||||
Location visible: Zichtbaarheid van het veld Plaats
|
||||
Location label: Label van het veld Plaats
|
||||
Third parties visible: Zichtbaarheid van het veld Derden
|
||||
Third parties label: Label van het veld Derden
|
||||
Duration time visible: Zichtbaarheid van het veld Duur
|
||||
Duration time label: Label van het veld Duur
|
||||
Travel time visible: Zichtbaarheid van het veld Reisduur
|
||||
Travel time label: Label van het veld Reisduur
|
||||
Attendee visible: Zichtbaarheid van het veld Aanwezigheid van de gebruiker
|
||||
Attendee label: Label van het veld Aanwezigheid van de gebruiker
|
||||
Reasons visible: Zichtbaarheid van het veld Onderwerp
|
||||
Reasons label: Label van het veld Onderwerp
|
||||
Comment visible: Zichtbaarheid van het veld Opmerking
|
||||
Comment label: Label van het veld Opmerking
|
||||
Private comment visible: Zichtbaarheid van het veld Privé Opmerking
|
||||
Private comment label: Label van het veld Privé Opmerking
|
||||
Emergency visible: Zichtbaarheid van het veld Urgent
|
||||
Emergency label: Label van het veld Urgent
|
||||
Accompanying period visible: Zichtbaarheid van het veld begeleidingstraject
|
||||
Accompanying period label: Label van het veld begeleidingstraject
|
||||
Social issues visible: Zichtbaarheid van het veld Sociale problematieken
|
||||
Social issues label: Label van het veld Sociale problematieken
|
||||
Social actions visible: Zichtbaarheid van het veld Sociale actie
|
||||
Social actions label: Label van het veld Sociale actie
|
||||
Users visible: Zichtbaarheid van het veld Gebruikers
|
||||
Users label: Label van het veld Gebruikers
|
||||
Sent received visible: Zichtbaarheid van het veld Inkomend / Uitgaand
|
||||
Sent received label: Label van het veld Inkomend / Uitgaand
|
||||
Documents visible: Zichtbaarheid van het veld Documenten
|
||||
Documents label: Label van het veld Documenten
|
||||
|
||||
# activity type category admin
|
||||
ActivityTypeCategory list: Liste des catégories des types d'activité
|
||||
Create a new activity type category: Créer une nouvelle catégorie de type d'échange
|
||||
ActivityTypeCategory list: Lijst van categorieën van types uitwisseling
|
||||
Create a new activity type category: Nieuwe categorie van type uitwisseling aanmaken
|
||||
Create a new activity in accompanying course: Uitwisseling aanmaken in het traject
|
||||
|
||||
# activity delete
|
||||
Remove activity: Supprimer un échange
|
||||
Are you sure you want to remove the activity about "%name%" ?: Êtes-vous sûr de vouloir supprimer un échange qui concerne "%name%" ?
|
||||
The activity has been successfully removed.: L'échange a été supprimée.
|
||||
Remove activity: Uitwisseling verwijderen
|
||||
Are you sure you want to remove the activity about "%name%" ?: Weet u zeker dat u een uitwisseling wilt verwijderen die betrekking heeft op "%name%"?
|
||||
The activity has been successfully removed.: De uitwisseling is verwijderd.
|
||||
|
||||
# exports
|
||||
Count activities: Nombre d'échanges
|
||||
Count activities by various parameters.: Compte le nombre d'échanges enregistrées en fonction de différents paramètres.
|
||||
Sum activity duration: Total de la durée des échanges
|
||||
Sum activities duration by various parameters.: Additionne la durée des échanges en fonction de différents paramètres.
|
||||
List activities: Liste les échanges
|
||||
Number of activities: Nombre d'échanges
|
||||
Exports of activities linked to a person: Exports van uitwisselingen gekoppeld aan een gebruiker
|
||||
Number of activities linked to a person: Aantal uitwisselingen gekoppeld aan een gebruiker
|
||||
Count activities linked to a person: Aantal uitwisselingen
|
||||
Count activities linked to a person by various parameters.: Telt het aantal geregistreerde uitwisselingen gekoppeld aan een gebruiker op basis van verschillende parameters.
|
||||
Sum activity linked to a person duration: Duur van uitwisselingen
|
||||
Sum activities linked to a person duration: Duur van uitwisselingen gekoppeld aan een gebruiker
|
||||
Sum activities linked to a person duration by various parameters.: Telt de duur van uitwisselingen op basis van verschillende parameters.
|
||||
List activity linked to a person: Uitwisselingen opsommen
|
||||
List activities linked to a person: Lijst van uitwisselingen gekoppeld aan een gebruiker
|
||||
List activities linked to a person description: Maakt de lijst van uitwisselingen op basis van verschillende parameters.
|
||||
|
||||
Exports of activities linked to an accompanying period: Exports van uitwisselingen gekoppeld aan een traject
|
||||
Number of activities linked to an accompanying period: Aantal uitwisselingen gekoppeld aan een traject
|
||||
Count activities linked to an accompanying period: Aantal uitwisselingen
|
||||
Count activities linked to an accompanying period by various parameters.: Telt het aantal geregistreerde uitwisselingen gekoppeld aan een traject op basis van verschillende parameters.
|
||||
Sum activity linked to an accompanying period duration: Som van de duur van uitwisselingen
|
||||
Sum activities linked to an accompanying period duration: Som van de duur van uitwisselingen gekoppeld aan een traject
|
||||
Sum activities linked to an accompanying period duration by various parameters.: Telt de duur van uitwisselingen op basis van verschillende parameters.
|
||||
Sum activity linked to an accompanying period visit duration: Som van de reisduur van uitwisselingen
|
||||
Sum activities linked to an accompanying period visit duration: Som van de reisduur van uitwisselingen gekoppeld aan een traject
|
||||
Sum activities linked to an accompanying period visit duration by various parameters.: Telt de reisduur van uitwisselingen op basis van verschillende parameters.
|
||||
Average activity linked to an accompanying period duration: Gemiddelde van de duur van uitwisselingen
|
||||
Average activities linked to an accompanying period duration: Gemiddelde van de duur van uitwisselingen gekoppeld aan een traject
|
||||
Average activities linked to an accompanying period duration by various parameters.: Gemiddelde van de duur van uitwisselingen op basis van verschillende parameters.
|
||||
Average activity linked to an accompanying period visit duration: Gemiddelde van de reisduur van uitwisselingen
|
||||
Average activities linked to an accompanying period visit duration: Gemiddelde van de reisduur van uitwisselingen gekoppeld aan een traject
|
||||
Average activities linked to an accompanying period visit duration by various parameters.: Gemiddelde van de reisduur van uitwisselingen op basis van verschillende parameters.
|
||||
|
||||
#filters
|
||||
Filter by reason: Filtrer par sujet d'activité
|
||||
'Filtered by reasons: only %list%': 'Filtré par sujet: seulement %list%'
|
||||
'Filtered by activity type: only %list%': "Filtré par type d'activity: uniquement %list%"
|
||||
Filtered by date activity: Filtrer par date d'activité
|
||||
Activities after this date: Activités après cette date
|
||||
Activities before this date: Activités avant cette date
|
||||
"Filtered by date of activity: only between %date_from% and %date_to%": "Filtré par date de l'activité: uniquement entre %date_from% et %date_to%"
|
||||
This date should be after the date given in "Implied in an activity after this date" field: Cette date devrait être postérieure à la date donnée dans le champ "activités après cette date"
|
||||
Filter by reason: Uitwisselingen filteren op onderwerp
|
||||
'Filtered by reasons: only %list%': 'Gefilterd op onderwerp: alleen %list%'
|
||||
'Filtered by activity type: only %list%': "Gefilterd op type uitwisseling: alleen %list%"
|
||||
Filtered by date activity: Uitwisselingen filteren op datum
|
||||
Activities after this date: Uitwisselingen na deze datum
|
||||
Activities before this date: Uitwisselingen vóór deze datum
|
||||
"Filtered by date of activity: only between %date_from% and %date_to%": "Gefilterd op datum van de uitwisseling: alleen tussen %date_from% en %date_to%"
|
||||
This date should be after the date given in "Implied in an activity after this date" field: Deze datum moet later zijn dan de datum in het veld "uitwisselingen na deze datum"
|
||||
|
||||
Filtered by person having an activity in a period: Uniquement les personnes ayant eu une activité dans la période donnée
|
||||
Implied in an activity after this date: Impliqué dans une activité après cette date
|
||||
Implied in an activity before this date: Impliqué dans une activité avant cette date
|
||||
Filtered by person having an activity between %date_from% and %date_to% with reasons %reasons_name%: Filtré par personnes associées à une activité entre %date_from% et %date_to% avec les sujets %reasons_name%
|
||||
Activity reasons for those activities: Sujets de ces activités
|
||||
|
||||
Filter by activity type: Filtrer par type d'activité
|
||||
Filter by activity type: Uitwisselingen filteren op type
|
||||
|
||||
Filter activity by location: Uitwisselingen filteren op locatie
|
||||
'Filtered activity by location: only %locations%': "Gefilterd op locatie: alleen %locations%"
|
||||
Filter activity by locationtype: Uitwisselingen filteren op type locatie
|
||||
'Filtered activity by locationtype: only %types%': "Gefilterd op type locatie: alleen %types%"
|
||||
Accepted locationtype: Types locatie
|
||||
Accepted users: TMS(en)
|
||||
Filter activity by emergency: Uitwisselingen filteren op urgentie
|
||||
'Filtered activity by emergency: only %emergency%': "Gefilterd op urgentie: alleen als %emergency%"
|
||||
activity is emergency: de uitwisseling is urgent
|
||||
activity is not emergency: de uitwisseling is niet urgent
|
||||
Filter activity by sentreceived: Uitwisselingen filteren op verzonden/ontvangen
|
||||
'Filtered activity by sentreceived: only %sentreceived%': "Gefilterd op verzonden/ontvangen: alleen %sentreceived%"
|
||||
Accepted sentreceived: ''
|
||||
Filter activity by linked socialaction: Uitwisselingen filteren op gekoppelde actie
|
||||
'Filtered activity by linked socialaction: only %actions%': "Gefilterd op gekoppelde actie: alleen %actions%"
|
||||
Filter activity by linked socialissue: Uitwisselingen filteren op gekoppelde problematiek
|
||||
'Filtered activity by linked socialissue: only %issues%': "Gefilterd op gekoppelde problematiek: alleen %issues%"
|
||||
Filter activity by user: Uitwisselingen filteren op hoofdgebruiker
|
||||
Filter activity by users: Uitwisselingen filteren op deelnemende gebruiker
|
||||
Filter activity by creator: Uitwisselingen filteren op aanmaker van de uitwisseling
|
||||
'Filtered activity by user: only %users%': "Gefilterd op referent: alleen %users%"
|
||||
'Filtered activity by users: only %users%': "Gefilterd op deelnemende gebruikers: alleen %users%"
|
||||
'Filtered activity by creator: only %users%': "Gefilterd op aanmaker: alleen %users%"
|
||||
Creators: Aanmakers
|
||||
Accepted userscope: Diensten
|
||||
|
||||
Filter acp which has no activity: Trajecten filteren die geen uitwisseling hebben
|
||||
Filtered acp which has no activities: Trajecten zonder gekoppelde uitwisseling filteren
|
||||
Group acp by activity number: Trajecten groeperen op aantal uitwisselingen
|
||||
|
||||
#aggregators
|
||||
Activity type: Type d'activité
|
||||
Activity user: Utilisateur lié à l'activity
|
||||
By reason: Par sujet
|
||||
By category of reason: Par catégorie de sujet
|
||||
Reason's level: Niveau du sujet
|
||||
Group by reasons: Sujet d'activité
|
||||
Aggregate by activity user: Grouper par utilisateur lié à l'activité
|
||||
Aggregate by activity type: Grouper par type d'activité
|
||||
Aggregate by activity reason: Grouper par sujet de l'activité
|
||||
Activity type: Type uitwisseling
|
||||
Activity user: Gebruiker gekoppeld aan de uitwisseling
|
||||
By reason: Op onderwerp
|
||||
By category of reason: Op categorie van onderwerp
|
||||
Reason's level: Niveau van het onderwerp
|
||||
Group by reasons: Onderwerp van uitwisseling
|
||||
Aggregate by activity user: Uitwisselingen groeperen op referent
|
||||
Aggregate by activity users: Uitwisselingen groeperen op deelnemende gebruikers
|
||||
Aggregate by activity type: Uitwisselingen groeperen op type
|
||||
Aggregate by activity reason: Uitwisselingen groeperen op onderwerp
|
||||
|
||||
Last activities: Les dernières activités
|
||||
Group activity by locationtype: Uitwisselingen groeperen op type locatie
|
||||
Group activity by date: Uitwisselingen groeperen op datum
|
||||
Frequency: Frequentie
|
||||
by month: Per maand
|
||||
by week: Per week
|
||||
for week: Week
|
||||
by year: Per jaar
|
||||
in year: In
|
||||
Group activity by creator: Uitwisselingen groeperen op aanmaker van de uitwisseling
|
||||
Group activity by linked thirdparties: Uitwisselingen groeperen op betrokken derde
|
||||
Accepted thirdparty: Betrokken derde
|
||||
Group activity by linked socialaction: Uitwisselingen groeperen op gekoppelde actie
|
||||
Group activity by linked socialissue: Uitwisselingen groeperen op gekoppelde problematiek
|
||||
Group activity by userscope: Uitwisselingen groeperen op dienst van de aanmaker
|
||||
|
||||
See activity in accompanying course context: Voir l'activité dans le contexte du parcours d'accompagnement
|
||||
Last activities: De laatste uitwisselingen
|
||||
|
||||
You get notified of an activity which does not exists any more: Cette notification ne correspond pas à une activité valide.
|
||||
you are not allowed to see it details: La notification fait référence à une activité à laquelle vous n'avez pas accès.
|
||||
This is the minimal activity data: Activité n°
|
||||
See activity in accompanying course context: Uitwisseling bekijken in de context van het begeleidingstraject
|
||||
|
||||
You get notified of an activity which does not exists any more: Deze melding komt niet overeen met een geldige uitwisseling.
|
||||
you are not allowed to see it details: De melding verwijst naar een uitwisseling waartoe u geen toegang hebt.
|
||||
This is the minimal activity data: Uitwisseling nr.
|
||||
|
||||
docgen:
|
||||
Activity basic: Echange
|
||||
A basic context for activity: Contexte pour les activités
|
||||
Activity basic: Uitwisseling
|
||||
A basic context for activity: Context voor uitwisselingen
|
||||
Accompanying period with a list of activities: Begeleidingstraject met lijst van uitwisselingen
|
||||
Accompanying period with a list of activities description: Deze context neemt de informatie van het traject over, en alle uitwisselingen voor een traject. De uitwisselingen worden niet gefilterd.
|
||||
myActivitiesOnly: Alleen rekening houden met uitwisselingen waarin ik heb deelgenomen
|
||||
myWorksOnly: Alleen rekening houden met begeleidingsacties waarvan ik referent ben
|
||||
|
||||
export:
|
||||
export:
|
||||
count_person_on_activity:
|
||||
title: Aantal betrokken gebruikers bij uitwisselingen
|
||||
description: Telt het aantal betrokken gebruikers bij uitwisselingen. Als een gebruiker aanwezig is in meerdere uitwisselingen, wordt hij slechts één keer geteld.
|
||||
header: Aantal betrokken gebruikers bij uitwisselingen
|
||||
count_household_on_activity:
|
||||
title: Aantal betrokken huishoudens bij uitwisselingen
|
||||
description: Telt het aantal betrokken huishoudens bij uitwisselingen. Als een huishouden aanwezig is in meerdere uitwisselingen, wordt het slechts één keer geteld. Gebruikers zonder huishouden worden niet geteld.
|
||||
header: Aantal betrokken huishoudens bij uitwisselingen
|
||||
count_household_on_activity_person:
|
||||
title: Aantal betrokken huishoudens bij uitwisselingen
|
||||
description: Telt het aantal betrokken huishoudens bij uitwisselingen. Als een huishouden aanwezig is in meerdere uitwisselingen, wordt het slechts één keer geteld. Gebruikers zonder huishouden worden niet geteld. Wanneer een gebruiker van huishouden verandert, wordt elk huishouden één keer geteld.
|
||||
header: Aantal betrokken huishoudens bij uitwisselingen
|
||||
list:
|
||||
activity:
|
||||
users name: Naam van de gebruikers
|
||||
users ids: Identificatie van de gebruikers
|
||||
third parties ids: Identificatie van de derden
|
||||
persons ids: Identificatie van de gebruikers
|
||||
persons name: Naam van de gebruikers
|
||||
thirds parties: Derden
|
||||
date: Datum van de uitwisseling
|
||||
locationName: Locatie
|
||||
sent received: Verzonden of ontvangen
|
||||
emergency: Urgentie
|
||||
accompanying course id: Identificatie van het traject
|
||||
course circles: Diensten van het traject
|
||||
travelTime: Reisduur
|
||||
durationTime: Duur
|
||||
id: Identificatie
|
||||
List activities linked to an accompanying course: Somt uitwisselingen op gekoppeld aan een traject op basis van verschillende filters.
|
||||
List activity linked to a course: Lijst van uitwisselingen gekoppeld aan een traject
|
||||
commentText: Opmerking
|
||||
comment_date: Datum van de laatste bewerking van de opmerking
|
||||
comment_user: Laatste bewerking door
|
||||
|
||||
filter:
|
||||
activity:
|
||||
by_users_job:
|
||||
Filter by users job: Uitwisselingen filteren op beroep van ten minste één deelnemende gebruiker
|
||||
'Filtered activity by users job: only %jobs%': 'Gefilterd op beroep van ten minste één deelnemende gebruiker: alleen %jobs%'
|
||||
by_users_scope:
|
||||
Filter by users scope: Uitwisselingen filteren op dienst van ten minste één deelnemende gebruiker
|
||||
'Filtered activity by users scope: only %scopes%': 'Gefilterd op dienst van ten minste één deelnemende gebruiker: alleen %scopes%'
|
||||
course_having_activity_between_date:
|
||||
Title: Trajecten filteren die een uitwisseling hebben ontvangen tussen twee data
|
||||
Receiving an activity after: Die een uitwisseling hebben ontvangen na
|
||||
Receiving an activity before: Die een uitwisseling hebben ontvangen vóór
|
||||
acp_by_activity_type:
|
||||
'activity after': Uitwisselingen na
|
||||
activity after help: Indien leeg gelaten, wordt er geen rekening mee gehouden
|
||||
activity before: Uitwisselingen vóór
|
||||
activity before help: Indien leeg gelaten, wordt er geen rekening mee gehouden
|
||||
person_between_dates:
|
||||
Implied in an activity after this date: Betrokken bij een uitwisseling na deze datum
|
||||
Implied in an activity before this date: Betrokken bij een uitwisseling vóór deze datum
|
||||
Activity reasons for those activities: Onderwerpen van deze uitwisselingen
|
||||
if no reasons: Als geen enkel onderwerp is aangevinkt, worden alle onderwerpen in aanmerking genomen
|
||||
title: Gebruikers filteren die gekoppeld zijn geweest aan een uitwisseling tijdens de periode
|
||||
date mismatch: De einddatum van de periode moet later zijn dan de startdatum
|
||||
by_creator_scope:
|
||||
Filter activity by user scope: Uitwisselingen filteren op dienst van de aanmaker van de uitwisseling
|
||||
'Filtered activity by user scope: only %scopes%': "Gefilterd op dienst van de aanmaker van de uitwisseling: alleen %scopes%"
|
||||
by_creator_job:
|
||||
job_form_label: Beroepen
|
||||
Filter activity by user job: Uitwisselingen filteren op beroep van de aanmaker van de uitwisseling
|
||||
'Filtered activity by user job: only %jobs%': "Gefilterd op beroep van de aanmaker van de uitwisseling: alleen %jobs%"
|
||||
by_persons:
|
||||
Filter activity by persons: Uitwisselingen filteren op deelnemende gebruiker
|
||||
'Filtered activity by persons: only %persons%': 'Uitwisselingen gefilterd op deelnemende gebruikers: alleen %persons%'
|
||||
persons taking part on the activity: Gebruikers deelnemend aan de uitwisseling
|
||||
by_sent_received:
|
||||
Sent or received: Verzonden of ontvangen
|
||||
is sent: verzonden
|
||||
is received: ontvangen
|
||||
by_presence:
|
||||
Filter activity by activity presence: Uitwisselingen filteren op aanwezigheid van de gebruiker
|
||||
presences: Aanwezigheden
|
||||
'Filtered by activity presence: only %presences%': 'Gefilterd op aanwezigheid van de gebruiker: alleen %presences%'
|
||||
|
||||
aggregator:
|
||||
person:
|
||||
by_person:
|
||||
title: Uitwisselingen groeperen op gebruiker (gebruikersdossier waarin de uitwisseling is geregistreerd)
|
||||
person: Gebruiker
|
||||
by_household:
|
||||
title: Uitwisselingen groeperen op huishouden
|
||||
household: Identificatie huishouden
|
||||
acp:
|
||||
by_activity_type:
|
||||
title: Trajecten groeperen op type uitwisseling
|
||||
after_date: Alleen uitwisselingen na deze datum
|
||||
before_date: Alleen uitwisselingen vóór deze datum
|
||||
activity_type: Types uitwisseling
|
||||
activity:
|
||||
by_sent_received:
|
||||
Sent or received: Verzonden of ontvangen
|
||||
is sent: verzonden
|
||||
is received: ontvangen
|
||||
Group activity by sentreceived: Uitwisselingen groeperen op verzonden / ontvangen
|
||||
by_location:
|
||||
Activity Location: Locatie van de uitwisseling
|
||||
Title: Uitwisselingen groeperen op locatie van de uitwisseling
|
||||
by_user_job:
|
||||
Users 's job: Beroep van de gebruikers deelnemend aan de uitwisseling
|
||||
Aggregate by users job: Uitwisselingen groeperen op beroep van de deelnemende gebruikers
|
||||
by_user_scope:
|
||||
Users 's scope: Hoofddienst van de gebruikers deelnemend aan de uitwisseling
|
||||
Aggregate by users scope: Uitwisselingen groeperen op hoofddienst van de gebruiker
|
||||
by_creator_scope:
|
||||
Group activity by creator scope: Uitwisselingen groeperen op dienst van de aanmaker van de uitwisseling
|
||||
Calc date: Berekeningsdatum van de dienst van de aanmaker van de uitwisseling
|
||||
by_creator_job:
|
||||
Group activity by creator job: Uitwisselingen groeperen op beroep van de aanmaker van de uitwisseling
|
||||
Calc date: Berekeningsdatum van het beroep van de aanmaker van de uitwisseling
|
||||
by_persons:
|
||||
Group activity by persons: Uitwisselingen groeperen op deelnemende gebruiker
|
||||
Persons: Deelnemende gebruikers
|
||||
by_activity_presence:
|
||||
Group activity by presence: Uitwisselingen groeperen op aanwezigheid van de gebruiker
|
||||
header: Aanwezigheid van gebruiker(s)
|
||||
|
||||
generic_doc:
|
||||
filter:
|
||||
keys:
|
||||
accompanying_period_activity_document: Document van uitwisselingen van trajecten
|
||||
|
||||
@@ -25,6 +25,7 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
|
||||
$config = $this->processConfiguration($configuration, $configs);
|
||||
|
||||
$container->setParameter('chill_aside_activity.form.time_duration', $config['form']['time_duration']);
|
||||
$container->setParameter('chill_aside_activity.show_concerned_persons_count', 'visible' === $config['show_concerned_persons_count']);
|
||||
|
||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
||||
$loader->load('services.yaml');
|
||||
@@ -38,6 +39,24 @@ final class ChillAsideActivityExtension extends Extension implements PrependExte
|
||||
{
|
||||
$this->prependRoute($container);
|
||||
$this->prependCruds($container);
|
||||
$this->prependTwigConfig($container);
|
||||
}
|
||||
|
||||
protected function prependTwigConfig(ContainerBuilder $container)
|
||||
{
|
||||
// Get the configuration for this bundle
|
||||
$chillAsideActivityConfig = $container->getExtensionConfig($this->getAlias());
|
||||
$config = $this->processConfiguration($this->getConfiguration($chillAsideActivityConfig, $container), $chillAsideActivityConfig);
|
||||
|
||||
// Add configuration to twig globals
|
||||
$twigConfig = [
|
||||
'globals' => [
|
||||
'chill_aside_activity_config' => [
|
||||
'show_concerned_persons_count' => 'visible' === $config['show_concerned_persons_count'],
|
||||
],
|
||||
],
|
||||
];
|
||||
$container->prependExtensionConfig('twig', $twigConfig);
|
||||
}
|
||||
|
||||
protected function prependCruds(ContainerBuilder $container)
|
||||
|
||||
@@ -141,6 +141,12 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->enumNode('show_concerned_persons_count')
|
||||
->values(['hidden', 'visible'])
|
||||
->defaultValue('hidden')
|
||||
->info('Show the concerned persons count field in aside activity forms and views')
|
||||
->end()
|
||||
->end();
|
||||
|
||||
return $treeBuilder;
|
||||
|
||||
@@ -62,6 +62,10 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
private User $updatedBy;
|
||||
|
||||
#[Assert\GreaterThanOrEqual(0)]
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::INTEGER, nullable: true)]
|
||||
private ?int $concernedPersonsCount = 0;
|
||||
|
||||
public function getAgent(): ?User
|
||||
{
|
||||
return $this->agent;
|
||||
@@ -186,4 +190,16 @@ class AsideActivity implements TrackCreationInterface, TrackUpdateInterface
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConcernedPersonsCount(): ?int
|
||||
{
|
||||
return $this->concernedPersonsCount;
|
||||
}
|
||||
|
||||
public function setConcernedPersonsCount(?int $concernedPersonsCount): self
|
||||
{
|
||||
$this->concernedPersonsCount = $concernedPersonsCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?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\AsideActivityBundle\Export\Aggregator;
|
||||
|
||||
use Chill\AsideActivityBundle\Export\Declarations;
|
||||
use Chill\MainBundle\Export\AggregatorInterface;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class ByConcernedPersonsCountAggregator implements AggregatorInterface
|
||||
{
|
||||
public function addRole(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function alterQuery(QueryBuilder $qb, $data, \Chill\MainBundle\Export\ExportGenerationContext $exportGenerationContext): void
|
||||
{
|
||||
$qb->addSelect('aside.concernedPersonsCount AS by_concerned_persons_count_aggregator')
|
||||
->addGroupBy('by_concerned_persons_count_aggregator');
|
||||
}
|
||||
|
||||
public function applyOn(): string
|
||||
{
|
||||
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder): void
|
||||
{
|
||||
// No form needed
|
||||
}
|
||||
|
||||
public function getNormalizationVersion(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data): callable
|
||||
{
|
||||
return function ($value): string {
|
||||
if ('_header' === $value) {
|
||||
return 'export.aggregator.Concerned persons count';
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
return 'export.aggregator.No concerned persons count specified';
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
};
|
||||
}
|
||||
|
||||
public function getQueryKeys($data): array
|
||||
{
|
||||
return ['by_concerned_persons_count_aggregator'];
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'export.aggregator.Group by concerned persons count';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?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\AsideActivityBundle\Export\Export;
|
||||
|
||||
use Chill\AsideActivityBundle\Export\Declarations;
|
||||
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
|
||||
use Chill\AsideActivityBundle\Security\AsideActivityVoter;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||
use Doctrine\ORM\Query;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
class SumConcernedPersonsCountAsideActivity implements ExportInterface, GroupedExportInterface
|
||||
{
|
||||
public function __construct(private readonly AsideActivityRepository $repository) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder) {}
|
||||
|
||||
public function getNormalizationVersion(): int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, int $fromVersion): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getFormDefaultData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
return [FormatterInterface::TYPE_TABULAR];
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'export.Sum concerned persons count for aside activities';
|
||||
}
|
||||
|
||||
public function getGroup(): string
|
||||
{
|
||||
return 'export.Exports of aside activities';
|
||||
}
|
||||
|
||||
public function getLabels($key, array $values, $data)
|
||||
{
|
||||
if ('export_sum_concerned_persons_count' !== $key) {
|
||||
throw new \LogicException("the key {$key} is not used by this export");
|
||||
}
|
||||
|
||||
$labels = array_combine($values, $values);
|
||||
$labels['_header'] = $this->getTitle();
|
||||
|
||||
return static fn ($value) => $labels[$value];
|
||||
}
|
||||
|
||||
public function getQueryKeys($data): array
|
||||
{
|
||||
return ['export_sum_concerned_persons_count'];
|
||||
}
|
||||
|
||||
public function getResult($query, $data, \Chill\MainBundle\Export\ExportGenerationContext $context): array
|
||||
{
|
||||
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return 'export.Sum concerned persons count for aside activities';
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return Declarations::ASIDE_ACTIVITY_TYPE;
|
||||
}
|
||||
|
||||
public function initiateQuery(array $requiredModifiers, array $acl, array $data, \Chill\MainBundle\Export\ExportGenerationContext $context): \Doctrine\ORM\QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('aside');
|
||||
|
||||
$qb->select('SUM(COALESCE(aside.concernedPersonsCount, 0)) as export_sum_concerned_persons_count');
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
public function requiredRole(): string
|
||||
{
|
||||
return AsideActivityVoter::STATS;
|
||||
}
|
||||
|
||||
public function supportsModifiers(): array
|
||||
{
|
||||
return [
|
||||
Declarations::ASIDE_ACTIVITY_TYPE,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvent;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
@@ -29,11 +30,13 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
final class AsideActivityFormType extends AbstractType
|
||||
{
|
||||
private readonly array $timeChoices;
|
||||
private readonly bool $showConcernedPersonsCount;
|
||||
|
||||
public function __construct(
|
||||
ParameterBagInterface $parameterBag,
|
||||
) {
|
||||
$this->timeChoices = $parameterBag->get('chill_aside_activity.form.time_duration');
|
||||
$this->showConcernedPersonsCount = $parameterBag->get('chill_aside_activity.show_concerned_persons_count');
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
@@ -76,6 +79,16 @@ final class AsideActivityFormType extends AbstractType
|
||||
->add('location', PickUserLocationType::class)
|
||||
;
|
||||
|
||||
if ($this->showConcernedPersonsCount) {
|
||||
$builder->add('concernedPersonsCount', IntegerType::class, [
|
||||
'label' => 'Concerned persons count',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'min' => 0,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
foreach (['duration'] as $fieldName) {
|
||||
$builder->get($fieldName)
|
||||
->addModelTransformer($durationTimeTransformer);
|
||||
|
||||
@@ -42,6 +42,11 @@
|
||||
{%- if entity.location.name is defined -%}
|
||||
<div><i class="fa fa-fw fa-map-marker"></i>{{ entity.location.name }}</div>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if entity.concernedPersonsCount > 0 -%}
|
||||
<div><i class="fa fa-fw fa-user"></i>{{ entity.concernedPersonsCount }}</div>
|
||||
{%- endif -%}
|
||||
|
||||
</div>
|
||||
<div class="item-col" style="justify-content: flex-end;">
|
||||
<div class="box">
|
||||
|
||||
@@ -38,6 +38,11 @@
|
||||
<dt class="inline">{{ 'Duration'|trans }}</dt>
|
||||
<dd>{{ entity.duration|date('H:i') }}</dd>
|
||||
|
||||
{% if chill_aside_activity_config.show_concerned_persons_count == 'visible' %}
|
||||
<dt class="inline">{{ 'Concerned persons count'|trans }}</dt>
|
||||
<dd>{{ entity.concernedPersonsCount }}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="inline">{{ 'Remark'|trans }}</dt>
|
||||
{%- if entity.note is empty -%}
|
||||
<dd>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?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\AsideActivityBundle\Tests\Export\Aggregator;
|
||||
|
||||
use Chill\AsideActivityBundle\Entity\AsideActivity;
|
||||
use Chill\AsideActivityBundle\Export\Aggregator\ByConcernedPersonsCountAggregator;
|
||||
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class ByConcernedPersonsCountAggregatorTest extends AbstractAggregatorTest
|
||||
{
|
||||
public function getAggregator()
|
||||
{
|
||||
return new ByConcernedPersonsCountAggregator();
|
||||
}
|
||||
|
||||
public static function getFormData(): array
|
||||
{
|
||||
return [
|
||||
[],
|
||||
];
|
||||
}
|
||||
|
||||
public static function getQueryBuilders(): iterable
|
||||
{
|
||||
self::bootKernel();
|
||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
||||
|
||||
return [
|
||||
$em->createQueryBuilder()
|
||||
->select('count(aside.id)')
|
||||
->from(AsideActivity::class, 'aside'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?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\AsideActivityBundle\Tests\Export\Export;
|
||||
|
||||
use Chill\AsideActivityBundle\Export\Export\SumConcernedPersonsCountAsideActivity;
|
||||
use Chill\AsideActivityBundle\Repository\AsideActivityRepository;
|
||||
use Chill\MainBundle\Test\Export\AbstractExportTest;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
final class SumConcernedPersonsCountAsideActivityTest extends AbstractExportTest
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
}
|
||||
|
||||
public function getExport()
|
||||
{
|
||||
$repository = self::getContainer()->get(AsideActivityRepository::class);
|
||||
|
||||
yield new SumConcernedPersonsCountAsideActivity($repository);
|
||||
}
|
||||
|
||||
public static function getFormData(): array
|
||||
{
|
||||
return [
|
||||
[],
|
||||
];
|
||||
}
|
||||
|
||||
public static function getModifiersCombination(): array
|
||||
{
|
||||
return [
|
||||
['aside_activity'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,10 @@ services:
|
||||
tags:
|
||||
- { name: chill.export, alias: 'avg_aside_activity_duration' }
|
||||
|
||||
Chill\AsideActivityBundle\Export\Export\SumConcernedPersonsCountAsideActivity:
|
||||
tags:
|
||||
- { name: chill.export, alias: 'sum_aside_activity_concerned_persons_count' }
|
||||
|
||||
## Filters
|
||||
chill.aside_activity.export.date_filter:
|
||||
class: Chill\AsideActivityBundle\Export\Filter\ByDateFilter
|
||||
@@ -70,3 +74,7 @@ services:
|
||||
Chill\AsideActivityBundle\Export\Aggregator\ByLocationAggregator:
|
||||
tags:
|
||||
- { name: chill.export_aggregator, alias: 'aside_activity_location_aggregator' }
|
||||
|
||||
Chill\AsideActivityBundle\Export\Aggregator\ByConcernedPersonsCountAggregator:
|
||||
tags:
|
||||
- { name: chill.export_aggregator, alias: 'aside_activity_concerned_persons_count_aggregator' }
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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\Migrations\AsideActivity;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20251006113048 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add concernedPersonsCount property to AsideActivity entity';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.asideactivity ADD concernedPersonsCount INT DEFAULT 0');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE chill_asideactivity.AsideActivity DROP concernedPersonsCount');
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ Emergency: Urgent
|
||||
by: "Par "
|
||||
location: Lieu
|
||||
Asideactivity location: Localisation de l'activité
|
||||
Concerned persons count: Nombre d'usager concernés
|
||||
|
||||
# Crud
|
||||
crud:
|
||||
@@ -177,7 +178,7 @@ export:
|
||||
agent_id: Utilisateur
|
||||
creator_id: Créateur
|
||||
main_scope: Service principal de l'utilisateur
|
||||
main_center: Centre principal de l'utilisateur
|
||||
main_center: Territoire principal de l'utilisateur
|
||||
aside_activity_type: Catégorie d'activité annexe
|
||||
date: Date
|
||||
duration: Durée
|
||||
@@ -190,6 +191,7 @@ export:
|
||||
Count aside activities by various parameters.: Compte le nombre d'activités annexes selon divers critères
|
||||
Average aside activities duration: Durée moyenne des activités annexes
|
||||
Sum aside activities duration: Durée des activités annexes
|
||||
Sum concerned persons count for aside activities: Nombre d'usager concernés par les activités annexes
|
||||
filter:
|
||||
Filter by aside activity date: Filtrer les activités annexes par date
|
||||
Filter by aside activity type: Filtrer les activités annexes par type d'activité
|
||||
@@ -210,6 +212,8 @@ export:
|
||||
'Filtered by aside activity location: only %location%': "Filtré par localisation: uniquement %location%"
|
||||
aggregator:
|
||||
Group by aside activity type: Grouper les activités annexes par type d'activité
|
||||
Group by concerned persons count: Grouper les activités annexes par nombre d'usagers conernés
|
||||
Concerned persons count: Nombre d'usagers concernés
|
||||
Aside activity type: Type d'activité annexe
|
||||
by_user_job:
|
||||
Aggregate by user job: Grouper les activités annexes par métier des utilisateurs
|
||||
|
||||
@@ -165,3 +165,60 @@ Phonecall: "Telefoon oproep"
|
||||
Aside activities: Nevenactiviteiten
|
||||
Aside activity types: Types nevenactiviteiten
|
||||
Aside activity type configuration: Configuratie categorieën nevenactiviteiten
|
||||
|
||||
# exports
|
||||
export:
|
||||
aside_activity:
|
||||
List of aside activities: Lijst van nevenactiviteiten
|
||||
createdAt: Aanmaak
|
||||
updatedAt: Laatste update
|
||||
agent_id: Gebruiker
|
||||
creator_id: Aanmaker
|
||||
main_scope: Hoofddienst van de gebruiker
|
||||
main_center: Hoofdterritorium van de gebruiker
|
||||
aside_activity_type: Categorie nevenactiviteit
|
||||
date: Datum
|
||||
duration: Duur
|
||||
note: Notitie
|
||||
id: Identificatie
|
||||
location: Locatie
|
||||
|
||||
Exports of aside activities: Exports van nevenactiviteiten
|
||||
Count aside activities: Aantal nevenactiviteiten
|
||||
Count aside activities by various parameters.: Telt het aantal nevenactiviteiten volgens diverse criteria
|
||||
Average aside activities duration: Gemiddelde duur van nevenactiviteiten
|
||||
Sum aside activities duration: Duur van nevenactiviteiten
|
||||
Sum concerned persons count for aside activities: Aantal betrokken gebruikers bij nevenactiviteiten
|
||||
filter:
|
||||
Filter by aside activity date: Nevenactiviteiten filteren op datum
|
||||
Filter by aside activity type: Nevenactiviteiten filteren op type activiteit
|
||||
'Filtered by aside activity type: only %type%': "Gefilterd op type nevenactiviteit: alleen %type%"
|
||||
Filtered by aside activities between %dateFrom% and %dateTo%: Gefilterd op datum van nevenactiviteit, tussen %dateFrom% en %dateTo%
|
||||
This date should be after the date given in "Implied in an aside activity after this date" field: Deze datum moet later zijn dan de datum in het veld "nevenactiviteiten na deze datum"
|
||||
Aside activities after this date: Nevenactiviteiten na deze datum
|
||||
Aside activities before this date: Nevenactiviteiten vóór deze datum
|
||||
'Filtered aside activity by user: only %users%': "Gefilterd op gebruiker: alleen %users%"
|
||||
Filter aside activity by user: Filteren op gebruiker
|
||||
by_user_job:
|
||||
'Filtered aside activities by user jobs: only %jobs%': "Gefilterd op beroep van gebruikers: alleen %jobs%"
|
||||
Filter by user jobs: Nevenactiviteiten filteren op beroep van gebruikers
|
||||
by_user_scope:
|
||||
'Filtered aside activities by user scope: only %scopes%': "Gefilterd op dienst van gebruikers: alleen %scopes%"
|
||||
Filter by user scope: Nevenactiviteiten filteren op dienst van gebruiker
|
||||
Filter by aside activity location: Nevenactiviteiten filteren op locatie
|
||||
'Filtered by aside activity location: only %location%': "Gefilterd op locatie: alleen %location%"
|
||||
aggregator:
|
||||
Group by aside activity type: Nevenactiviteiten groeperen op type activiteit
|
||||
Group by concerned persons count: Nevenactiviteiten groeperen op aantal betrokken gebruikers
|
||||
Concerned persons count: Aantal betrokken gebruikers
|
||||
Aside activity type: Type nevenactiviteit
|
||||
by_user_job:
|
||||
Aggregate by user job: Nevenactiviteiten groeperen op beroep van gebruikers
|
||||
by_user_scope:
|
||||
Aggregate by user scope: Nevenactiviteiten groeperen op dienst van gebruikers
|
||||
Aside activity location: Locatie van nevenactiviteiten
|
||||
Group by aside activity location: Nevenactiviteiten groeperen op locatie
|
||||
Aside activity localisation: Locatie
|
||||
|
||||
# ROLES
|
||||
CHILL_ASIDE_ACTIVITY_STATS: Statistieken voor nevenactiviteiten
|
||||
|
||||
@@ -74,3 +74,42 @@ The balance: Verschil tussen inkomsten en onkosten
|
||||
|
||||
Valid since %startDate% until %endDate%: Geldig sinds %startDate% tot %endDate%
|
||||
Valid since %startDate%: Geldig sinds %startDate%
|
||||
|
||||
budget:
|
||||
admin:
|
||||
form:
|
||||
Charge_kind_key: Identificatiesleutel
|
||||
Resource_kind_key: Identificatiesleutel
|
||||
This kind must contains only alphabeticals characters, and dashes. This string is in use during document generation. Changes may have side effect on document: Deze sleutel dient om het type last of inkomen te identificeren bij het genereren van documenten. Alleen alfanumerieke tekens zijn toegestaan. Het wijzigen van deze sleutel kan een effect hebben bij het genereren van nieuwe documenten.
|
||||
|
||||
# ROLES
|
||||
Budget elements: Budget
|
||||
CHILL_BUDGET_ELEMENT_CREATE: Inkomsten/last aanmaken
|
||||
CHILL_BUDGET_ELEMENT_DELETE: Inkomsten/last verwijderen
|
||||
CHILL_BUDGET_ELEMENT_SEE: Inkomstenen/lasten bekijken
|
||||
CHILL_BUDGET_ELEMENT_UPDATE: Inkomsten/last bewerken
|
||||
|
||||
## admin
|
||||
|
||||
crud:
|
||||
resource_kind:
|
||||
title_new: Nieuw type inkomsten
|
||||
title_edit: Type inkomsten bewerken
|
||||
charge_kind:
|
||||
title_new: Nieuw type last
|
||||
title_edit: Type last bewerken
|
||||
|
||||
admin:
|
||||
menu:
|
||||
Resource types: Types inkomsten
|
||||
Charge types: Types last
|
||||
title:
|
||||
Charge Type List: Lijst van types last
|
||||
Resource Type List: Lijst van types inkomsten
|
||||
Budget configuration: Configuratie van budgetelementen
|
||||
new:
|
||||
Create a new charge type: Nieuw type last aanmaken
|
||||
Create a new resource type: Nieuw type inkomsten aanmaken
|
||||
form:
|
||||
Choose the type of resource: Kies een type inkomsten
|
||||
Choose the type of charge: Kies een type last
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
The amount cannot be empty: Le montant ne peut pas être vide ou égal à zéro
|
||||
The budget element's end date must be after the start date: La date de fin doit être après la date de début
|
||||
The amount cannot be empty: Het bedrag mag niet nul of leeg zijn
|
||||
The budget element's end date must be after the start date: De einddatum moet later vallen dan de begindatum
|
||||
|
||||
budget:
|
||||
admin:
|
||||
form:
|
||||
kind:
|
||||
enkel_alphanumeriek
|
||||
|
||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\CalendarBundle\Controller;
|
||||
|
||||
use Chill\CalendarBundle\Repository\CalendarRepository;
|
||||
use Chill\CalendarBundle\Repository\InviteRepository;
|
||||
use Chill\MainBundle\CRUD\Controller\ApiController;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Serializer\Model\Collection;
|
||||
@@ -23,7 +24,10 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class CalendarAPIController extends ApiController
|
||||
{
|
||||
public function __construct(private readonly CalendarRepository $calendarRepository) {}
|
||||
public function __construct(
|
||||
private readonly CalendarRepository $calendarRepository,
|
||||
private readonly InviteRepository $inviteRepository,
|
||||
) {}
|
||||
|
||||
#[Route(path: '/api/1.0/calendar/calendar/by-user/{id}.{_format}', name: 'chill_api_single_calendar_list_by-user', requirements: ['_format' => 'json'])]
|
||||
public function listByUser(User $user, Request $request, string $_format): JsonResponse
|
||||
@@ -52,16 +56,37 @@ class CalendarAPIController extends ApiController
|
||||
throw new BadRequestHttpException('dateTo not parsable');
|
||||
}
|
||||
|
||||
$total = $this->calendarRepository->countByUser($user, $dateFrom, $dateTo);
|
||||
$paginator = $this->getPaginatorFactory()->create($total);
|
||||
$ranges = $this->calendarRepository->findByUser(
|
||||
// Get calendar items where user is the main user
|
||||
$ownCalendars = $this->calendarRepository->findByUser(
|
||||
$user,
|
||||
$dateFrom,
|
||||
$dateTo,
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
$dateTo
|
||||
);
|
||||
|
||||
// Get calendar items from accepted invites
|
||||
$acceptedInvites = $this->inviteRepository->findAcceptedInvitesByUserAndDateRange($user, $dateFrom, $dateTo);
|
||||
$inviteCalendars = array_map(fn ($invite) => $invite->getCalendar(), $acceptedInvites);
|
||||
|
||||
// Merge
|
||||
$allCalendars = array_merge($ownCalendars, $inviteCalendars);
|
||||
$uniqueCalendars = [];
|
||||
$seenIds = [];
|
||||
|
||||
foreach ($allCalendars as $calendar) {
|
||||
$id = $calendar->getId();
|
||||
if (!in_array($id, $seenIds, true)) {
|
||||
$seenIds[] = $id;
|
||||
$uniqueCalendars[] = $calendar;
|
||||
}
|
||||
}
|
||||
|
||||
$total = count($uniqueCalendars);
|
||||
$paginator = $this->getPaginatorFactory()->create($total);
|
||||
|
||||
$offset = $paginator->getCurrentPageFirstItemNumber();
|
||||
$limit = $paginator->getItemsPerPage();
|
||||
$ranges = array_slice($uniqueCalendars, $offset, $limit);
|
||||
|
||||
$collection = new Collection($ranges, $paginator);
|
||||
|
||||
return $this->json($collection, Response::HTTP_OK, [], ['groups' => ['calendar:light']]);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Chill\CalendarBundle\Controller;
|
||||
|
||||
use Chill\CalendarBundle\Entity\Calendar;
|
||||
use Chill\CalendarBundle\Form\CalendarType;
|
||||
use Chill\CalendarBundle\Form\CancelType;
|
||||
use Chill\CalendarBundle\RemoteCalendar\Connector\RemoteCalendarConnectorInterface;
|
||||
use Chill\CalendarBundle\Repository\CalendarACLAwareRepositoryInterface;
|
||||
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\PersonVoter;
|
||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use http\Exception\UnexpectedValueException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
@@ -60,6 +62,7 @@ class CalendarController extends AbstractController
|
||||
private readonly UserRepositoryInterface $userRepository,
|
||||
private readonly TranslatorInterface $translator,
|
||||
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.
|
||||
*/
|
||||
@@ -266,7 +318,7 @@ class CalendarController extends AbstractController
|
||||
}
|
||||
|
||||
if (!$this->getUser() instanceof User) {
|
||||
throw new UnauthorizedHttpException('you are not an user');
|
||||
throw new UnauthorizedHttpException('you are not a user');
|
||||
}
|
||||
|
||||
$view = '@ChillCalendar/Calendar/listByUser.html.twig';
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?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\Controller;
|
||||
|
||||
use Chill\CalendarBundle\Entity\Calendar;
|
||||
use Chill\CalendarBundle\Repository\InviteRepository;
|
||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class MyInvitationsController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly InviteRepository $inviteRepository, private readonly PaginatorFactory $paginator, private readonly DocGeneratorTemplateRepositoryInterface $docGeneratorTemplateRepository) {}
|
||||
|
||||
#[Route(path: '/{_locale}/calendar/invitations/my', name: 'chill_calendar_invitations_list_my')]
|
||||
public function myInvitations(Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('ROLE_USER');
|
||||
|
||||
$user = $this->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new UnauthorizedHttpException('you are not a user');
|
||||
}
|
||||
|
||||
$total = count($this->inviteRepository->findBy(['user' => $user]));
|
||||
$paginator = $this->paginator->create($total);
|
||||
|
||||
$invitations = $this->inviteRepository->findBy(
|
||||
['user' => $user],
|
||||
['createdAt' => 'DESC'],
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPageFirstItemNumber()
|
||||
);
|
||||
|
||||
$view = '@ChillCalendar/Invitations/listByUser.html.twig';
|
||||
|
||||
return $this->render($view, [
|
||||
'invitations' => $invitations,
|
||||
'paginator' => $paginator,
|
||||
'templates' => $this->docGeneratorTemplateRepository->findByEntity(Calendar::class),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class LoadCancelReason extends Fixture implements FixtureGroupInterface
|
||||
$arr = [
|
||||
['name' => CancelReason::CANCELEDBY_USER],
|
||||
['name' => CancelReason::CANCELEDBY_PERSON],
|
||||
['name' => CancelReason::CANCELEDBY_DONOTCOUNT],
|
||||
['name' => CancelReason::CANCELEDBY_OTHER],
|
||||
];
|
||||
|
||||
foreach ($arr as $a) {
|
||||
|
||||
@@ -269,6 +269,11 @@ class Calendar implements TrackCreationInterface, TrackUpdateInterface, HasCente
|
||||
return $this->cancelReason;
|
||||
}
|
||||
|
||||
public function isCanceled(): bool
|
||||
{
|
||||
return null !== $this->cancelReason;
|
||||
}
|
||||
|
||||
public function getCenters(): ?iterable
|
||||
{
|
||||
return match ($this->getContext()) {
|
||||
|
||||
@@ -18,14 +18,14 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
#[ORM\Table(name: 'chill_calendar.cancel_reason')]
|
||||
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_USER = 'CANCELEDBY_USER';
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN)]
|
||||
private ?bool $active = null;
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::BOOLEAN, options: ['default' => true])]
|
||||
private bool $active = true;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::STRING, length: 255)]
|
||||
private ?string $canceledBy = null;
|
||||
|
||||
@@ -15,7 +15,7 @@ use Chill\CalendarBundle\Entity\CancelReason;
|
||||
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
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\OptionsResolver\OptionsResolver;
|
||||
|
||||
@@ -28,7 +28,14 @@ class CancelReasonType extends AbstractType
|
||||
->add('active', CheckboxType::class, [
|
||||
'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)
|
||||
|
||||
42
src/Bundle/ChillCalendarBundle/Form/CancelType.php
Normal file
42
src/Bundle/ChillCalendarBundle/Form/CancelType.php
Normal 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,
|
||||
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,13 @@ class UserMenuBuilder implements LocalMenuBuilderInterface
|
||||
if ($this->security->isGranted('ROLE_USER')) {
|
||||
$menu->addChild('My calendar list', [
|
||||
'route' => 'chill_calendar_calendar_list_my',
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 8,
|
||||
'icon' => 'tasks',
|
||||
]);
|
||||
$menu->addChild('invite.list.title', [
|
||||
'route' => 'chill_calendar_invitations_list_my',
|
||||
])
|
||||
->setExtras([
|
||||
'order' => 9,
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Chill\CalendarBundle\Messenger\Doctrine;
|
||||
use Chill\CalendarBundle\Entity\Calendar;
|
||||
use Chill\CalendarBundle\Messenger\Message\CalendarMessage;
|
||||
use Chill\CalendarBundle\Messenger\Message\CalendarRemovedMessage;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\Event\PostPersistEventArgs;
|
||||
use Doctrine\ORM\Event\PostRemoveEventArgs;
|
||||
use Doctrine\ORM\Event\PostUpdateEventArgs;
|
||||
@@ -31,6 +32,17 @@ class CalendarEntityListener
|
||||
{
|
||||
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
|
||||
{
|
||||
if (!$calendar->preventEnqueueChanges) {
|
||||
@@ -38,7 +50,7 @@ class CalendarEntityListener
|
||||
new CalendarMessage(
|
||||
$calendar,
|
||||
CalendarMessage::CALENDAR_PERSIST,
|
||||
$this->security->getUser()
|
||||
$this->getAuthenticatedUser()
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -50,7 +62,7 @@ class CalendarEntityListener
|
||||
$this->messageBus->dispatch(
|
||||
new CalendarRemovedMessage(
|
||||
$calendar,
|
||||
$this->security->getUser()
|
||||
$this->getAuthenticatedUser()
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -58,12 +70,19 @@ class CalendarEntityListener
|
||||
|
||||
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(
|
||||
new CalendarMessage(
|
||||
$calendar,
|
||||
CalendarMessage::CALENDAR_UPDATE,
|
||||
$this->security->getUser()
|
||||
$this->getAuthenticatedUser()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,8 @@ class CalendarRemovedMessage
|
||||
|
||||
public function getRemoteId(): string
|
||||
{
|
||||
dump($this->remoteId);
|
||||
|
||||
return $this->remoteId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ class CalendarRepository implements ObjectRepository
|
||||
$qb->expr()->eq('c.mainUser', ':user'),
|
||||
$qb->expr()->gte('c.startDate', ':startDate'),
|
||||
$qb->expr()->lte('c.endDate', ':endDate'),
|
||||
$qb->expr()->isNull('c.cancelReason'),
|
||||
)
|
||||
)
|
||||
->setParameters([
|
||||
|
||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\CalendarBundle\Repository;
|
||||
|
||||
use Chill\CalendarBundle\Entity\Invite;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
@@ -41,7 +42,7 @@ class InviteRepository implements ObjectRepository
|
||||
/**
|
||||
* @return array|Invite[]
|
||||
*/
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null)
|
||||
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
|
||||
{
|
||||
return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset);
|
||||
}
|
||||
@@ -51,6 +52,52 @@ class InviteRepository implements ObjectRepository
|
||||
return $this->entityRepository->findOneBy($criteria);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find accepted invites for a user within a date range.
|
||||
*
|
||||
* @return array|Invite[]
|
||||
*/
|
||||
public function findAcceptedInvitesByUserAndDateRange(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): array
|
||||
{
|
||||
return $this->buildAcceptedInviteByUserAndDateRangeQuery($user, $from, $to)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count accepted invites for a user within a date range.
|
||||
*/
|
||||
public function countAcceptedInvitesByUserAndDateRange(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to): int
|
||||
{
|
||||
return $this->buildAcceptedInviteByUserAndDateRangeQuery($user, $from, $to)
|
||||
->select('COUNT(c)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function buildAcceptedInviteByUserAndDateRangeQuery(User $user, \DateTimeImmutable $from, \DateTimeImmutable $to)
|
||||
{
|
||||
$qb = $this->entityRepository->createQueryBuilder('i');
|
||||
|
||||
return $qb
|
||||
->join('i.calendar', 'c')
|
||||
->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('i.user', ':user'),
|
||||
$qb->expr()->eq('i.status', ':status'),
|
||||
$qb->expr()->gte('c.startDate', ':startDate'),
|
||||
$qb->expr()->lte('c.endDate', ':endDate'),
|
||||
$qb->expr()->isNull('c.cancelReason')
|
||||
)
|
||||
)
|
||||
->setParameters([
|
||||
'user' => $user,
|
||||
'status' => Invite::ACCEPTED,
|
||||
'startDate' => $from,
|
||||
'endDate' => $to,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getClassName(): string
|
||||
{
|
||||
return Invite::class;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
services:
|
||||
Chill\CalendarBundle\Controller\:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
resource: '../../../Controller'
|
||||
tags: ['controller.service_arguments']
|
||||
|
||||
@@ -108,9 +108,12 @@
|
||||
{{ formatDate(event.endStr, "time") }}:
|
||||
{{ event.extendedProps.locationName }}</b
|
||||
>
|
||||
<b v-else-if="event.extendedProps.is === 'local'">{{
|
||||
event.title
|
||||
}}</b>
|
||||
<a
|
||||
:href="calendarLink(event.id)"
|
||||
v-else-if="event.extendedProps.is === 'local'"
|
||||
>
|
||||
<b>{{ event.title }}</b>
|
||||
</a>
|
||||
<b v-else>no 'is'</b>
|
||||
<a
|
||||
v-if="event.extendedProps.is === 'range'"
|
||||
@@ -486,6 +489,12 @@ function copyWeek() {
|
||||
});
|
||||
}
|
||||
|
||||
const calendarLink = (calendarId: string) => {
|
||||
const idStr = calendarId.match(/_(\d+)$/)?.[1];
|
||||
|
||||
return `/fr/calendar/calendar/${idStr}/edit`;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
copyFromWeek.value = dateToISO(getMonday(0));
|
||||
copyToWeek.value = dateToISO(getMonday(1));
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
{# list used in context of person or accompanyingPeriod #}
|
||||
{# list used in context of person, accompanyingPeriod or user #}
|
||||
|
||||
{% if calendarItems|length > 0 %}
|
||||
<div class="flex-table list-records context-accompanyingCourse">
|
||||
|
||||
{% for calendar in calendarItems %}
|
||||
|
||||
<div class="item-bloc">
|
||||
<div class="item-row main">
|
||||
<div class="item-col">
|
||||
<div class="wrap-header">
|
||||
<div class="item-bloc">
|
||||
<div class="item-row main">
|
||||
<div class="item-col">
|
||||
<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-col title">
|
||||
<p class="date-label">
|
||||
{% if calendar.status == 'canceled' %}
|
||||
<del>
|
||||
{% endif %}
|
||||
{% 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;">
|
||||
<span class="badge bg-primary">
|
||||
@@ -19,6 +25,9 @@
|
||||
</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if calendar.status == 'canceled' %}
|
||||
<del>
|
||||
{% endif %}
|
||||
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
||||
{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||
- {{ calendar.endDate|format_datetime('short', 'short') }}
|
||||
@@ -26,44 +35,46 @@
|
||||
{{ calendar.startDate|format_datetime('short', 'short') }}
|
||||
- {{ calendar.endDate|format_datetime('none', 'short') }}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<div class="duration short-message">
|
||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||
{{ calendar.duration|date('%H:%I') }}
|
||||
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
|
||||
<!-- no sms will be send -->
|
||||
{% else %}
|
||||
{% if calendar.smsStatus == 'sms_sent' %}
|
||||
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
|
||||
<i class="fa fa-check "></i>
|
||||
<i class="fa fa-envelope "></i>
|
||||
</span>
|
||||
{% else %}
|
||||
<span title="{{ 'Will send SMS'|trans }}" class="badge bg-info">
|
||||
<i class="fa fa-envelope "></i>
|
||||
<i class="fa fa-hourglass-end "></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if calendar.status == 'canceled' %}
|
||||
</del>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
{% if calendar.mainUser is not empty %}
|
||||
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
|
||||
<div class="duration short-message">
|
||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||
{{ calendar.duration|date('%H:%I') }}
|
||||
{% if false == calendar.sendSMS or null == calendar.sendSMS %}
|
||||
<!-- no sms will be sent -->
|
||||
{% else %}
|
||||
{% if calendar.smsStatus == 'sms_sent' %}
|
||||
<span title="{{ 'SMS already sent'|trans }}" class="badge bg-info">
|
||||
<i class="fa fa-check "></i>
|
||||
<i class="fa fa-envelope "></i>
|
||||
</span>
|
||||
{% else %}
|
||||
<span title="{{ 'Will send SMS'|trans }}" class="badge bg-info">
|
||||
<i class="fa fa-envelope "></i>
|
||||
<i class="fa fa-hourglass-end "></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if calendar.comment.comment is not empty
|
||||
<div class="item-col">
|
||||
<ul class="list-content">
|
||||
{% if calendar.mainUser is not empty %}
|
||||
<span class="badge-user">{{ calendar.mainUser|chill_entity_render_box({'at_date': calendar.startDate}) }}</span>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if calendar.comment.comment is not empty
|
||||
or calendar.users|length > 0
|
||||
or calendar.thirdParties|length > 0
|
||||
or calendar.users|length > 0 %}
|
||||
@@ -76,131 +87,133 @@
|
||||
} %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if calendar.comment.comment is not empty %}
|
||||
<div class="item-row details separator">
|
||||
<div class="item-col comment">
|
||||
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if calendar.location is not empty %}
|
||||
<div class="item-row separator">
|
||||
<div>
|
||||
{% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
|
||||
<i class="fa fa-map-marker"></i>{% endif %}
|
||||
{% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %}
|
||||
{% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %}
|
||||
<i class="fa fa-map-marker"></i>{% endif %}
|
||||
{% if calendar.location.phonenumber1 is not empty %}<i
|
||||
class="fa fa-phone"></i> {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
|
||||
{% if calendar.location.phonenumber2 is not empty %}<i
|
||||
class="fa fa-phone"></i> {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if calendar.documents is not empty %}
|
||||
<div class="item-row separator column">
|
||||
<div>
|
||||
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if calendar.activity is not null %}
|
||||
<div class="item-row separator">
|
||||
<div class="item-col">
|
||||
<div class="wrap-list">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title"><h3>{{ 'Activity'|trans }}</h3></div>
|
||||
<div class="wl-col list activity-linked">
|
||||
<h2 class="badge-title">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ calendar.activity.type.name | localize_translatable_string }}
|
||||
|
||||
{% if calendar.activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<span class="createdBy">
|
||||
{{ 'Created by'|trans }}
|
||||
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
|
||||
</span>
|
||||
</li>
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': calendar.activity.id}) }}" class="btn btn-sm btn-show" ></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
{% if calendar.comment.comment is not empty %}
|
||||
<div class="item-row details separator">
|
||||
<div class="item-col comment">
|
||||
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if calendar.location is not empty %}
|
||||
<div class="item-row separator">
|
||||
<div>
|
||||
{% if calendar.location.address is not same as(null) and calendar.location.name is not empty %}
|
||||
<i class="fa fa-map-marker"></i>{% endif %}
|
||||
{% if calendar.location.name is not empty %}{{ calendar.location.name }}{% endif %}
|
||||
{% if calendar.location.address is not same as(null) %}{{ calendar.location.address|chill_entity_render_box({'multiline': false, 'with_picto': (calendar.location.name is empty)}) }}{% else %}
|
||||
<i class="fa fa-map-marker"></i>{% endif %}
|
||||
{% if calendar.location.phonenumber1 is not empty %}<i
|
||||
class="fa fa-phone"></i> {{ calendar.location.phonenumber1|chill_format_phonenumber }}{% endif %}
|
||||
{% if calendar.location.phonenumber2 is not empty %}<i
|
||||
class="fa fa-phone"></i> {{ calendar.location.phonenumber2|chill_format_phonenumber }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="item-row separator column">
|
||||
<div>
|
||||
|
||||
{{ include('@ChillCalendar/Calendar/_documents.twig.html') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if calendar.activity is not null %}
|
||||
<div class="item-row separator">
|
||||
<div class="item-col">
|
||||
<div class="wrap-list">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title"><h3>{{ 'Activity'|trans }}</h3></div>
|
||||
<div class="wl-col list activity-linked">
|
||||
<h2 class="badge-title">
|
||||
<span class="title_label"></span>
|
||||
<span class="title_action">
|
||||
{{ calendar.activity.type.name | localize_translatable_string }}
|
||||
|
||||
{% if calendar.activity.emergency %}
|
||||
<span class="badge bg-danger rounded-pill fs-6 float-end">{{ 'Emergency'|trans|upper }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<span class="createdBy">
|
||||
{{ 'Created by'|trans }}
|
||||
<b>{{ calendar.activity.createdBy|chill_entity_render_string({'at_date': calendar.activity.createdAt}) }}</b>, {{ 'on'|trans }} {{ calendar.activity.createdAt|format_datetime('short', 'short') }}
|
||||
</span>
|
||||
</li>
|
||||
{% if is_granted('CHILL_ACTIVITY_SEE', calendar.activity) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_activity_activity_show', {'id': calendar.activity.id}) }}" class="btn btn-sm btn-show" ></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-row separator">
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) and calendar.status is not constant('STATUS_CANCELED', calendar) %}
|
||||
{% if templates|length == 0 %}
|
||||
<li>
|
||||
<a class="btn btn-create"
|
||||
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
||||
{{ 'chill_calendar.Add a document'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-create dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{ 'chill_calendar.Add a document'|trans }}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
||||
{{ 'chill_calendar.Upload a document'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% for template in templates %}
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ chill_path_add_return_path('chill_docgenerator_generate_from_template', {'template': template.id, 'entityClassName': 'Chill\\CalendarBundle\\Entity\\Calendar', 'entityId': calendar.id}) }}"
|
||||
>
|
||||
{{ template.name|localize_translatable_string }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if calendar.activity is null and (
|
||||
(calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod))
|
||||
or
|
||||
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
|
||||
)
|
||||
and calendar.status is not constant('STATUS_CANCELED', calendar)
|
||||
%}
|
||||
<li>
|
||||
<a class="btn btn-create"
|
||||
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
|
||||
{{ 'Transform to activity'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<div class="item-row separator">
|
||||
<ul class="record_actions">
|
||||
{% if is_granted('CHILL_CALENDAR_DOC_EDIT', calendar) %}
|
||||
{% if templates|length == 0 %}
|
||||
<li>
|
||||
<a class="btn btn-create"
|
||||
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
||||
{{ 'chill_calendar.Add a document'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-create dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{ 'chill_calendar.Add a document'|trans }}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ chill_path_add_return_path('chill_calendar_calendardoc_new', {'id': calendar.id }) }}">
|
||||
{{ 'chill_calendar.Upload a document'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% for template in templates %}
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ chill_path_add_return_path('chill_docgenerator_generate_from_template', {'template': template.id, 'entityClassName': 'Chill\\CalendarBundle\\Entity\\Calendar', 'entityId': calendar.id}) }}"
|
||||
>
|
||||
{{ template.name|localize_translatable_string }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if calendar.activity is null and (
|
||||
(calendar.context == 'accompanying_period' and is_granted('CHILL_ACTIVITY_CREATE', calendar.accompanyingPeriod))
|
||||
or
|
||||
(calendar.context == 'person' and is_granted('CHILL_ACTIVITY_CREATE', calendar.person))
|
||||
)
|
||||
%}
|
||||
<li>
|
||||
<a class="btn btn-create"
|
||||
href="{{ chill_path_add_return_path('chill_calendar_calendar_to_activity', { 'id': calendar.id }) }}">
|
||||
{{ 'Transform to activity'|trans }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if (calendar.isInvited(app.user)) %}
|
||||
{% if calendar.isInvited(app.user) and not calendar.isCanceled %}
|
||||
{% set invite = calendar.inviteForUser(app.user) %}
|
||||
<li>
|
||||
<div invite-answer data-status="{{ invite.status|e('html_attr') }}"
|
||||
@@ -213,12 +226,18 @@
|
||||
class="btn btn-show "></a>
|
||||
</li>
|
||||
{% 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>
|
||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_edit', { 'id': calendar.id }) }}"
|
||||
class="btn btn-update "></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_cancel', { 'id': calendar.id } ) }}"
|
||||
class="btn btn-action"><i class="bi bi-x-circle"></i> {{ 'Cancel'|trans }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('CHILL_CALENDAR_CALENDAR_DELETE', calendar) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_calendar_calendar_delete', { 'id': calendar.id } ) }}"
|
||||
@@ -227,14 +246,8 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if calendarItems|length < paginator.getTotalItems %}
|
||||
{{ chill_pagination(paginator) }}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -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="cancel">
|
||||
<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 %}
|
||||
@@ -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="cancel">
|
||||
<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 %}
|
||||
@@ -34,7 +34,18 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
{% else %}
|
||||
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
|
||||
{% if calendarItems|length > 0 %}
|
||||
<div class="flex-table list-records context-accompanyingCourse">
|
||||
{% for calendar in calendarItems %}
|
||||
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'accompanying_course'}) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if calendarItems|length < paginator.getTotalItems %}
|
||||
{{ chill_pagination(paginator) }}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
|
||||
@@ -33,7 +33,17 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
{% else %}
|
||||
{{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }}
|
||||
{% if calendarItems|length > 0 %}
|
||||
<div class="flex-table list-records context-person">
|
||||
{% for calendar in calendarItems %}
|
||||
{{ include ('@ChillCalendar/Calendar/_list.html.twig', {context: 'person'}) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if calendarItems|length < paginator.getTotalItems %}
|
||||
{{ chill_pagination(paginator) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{% block table_entities_thead_tr %}
|
||||
<th>{{ 'Id'|trans }}</th>
|
||||
<th>{{ 'Name'|trans }}</th>
|
||||
<th>{{ 'canceledBy'|trans }}</th>
|
||||
<th>{{ 'Canceled by'|trans }}</th>
|
||||
<th>{{ 'active'|trans }}</th>
|
||||
<th> </th>
|
||||
{% endblock %}
|
||||
@@ -40,4 +40,4 @@
|
||||
</li>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% set activeRouteKey = 'chill_calendar_invitations_list' %}
|
||||
|
||||
{% block title %}{{ 'invite.list.title'|trans }}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ 'invite.list.title'|trans }}</h1>
|
||||
|
||||
{% if invitations|length == 0 %}
|
||||
<p class="chill-no-data-statement">
|
||||
{{ "invite.list.none"|trans }}
|
||||
</p>
|
||||
{% else %}
|
||||
<div class="flex-table list-records">
|
||||
{% for invitation in invitations %}
|
||||
{% set calendar = invitation.getCalendar %}
|
||||
{{ include('@ChillCalendar/Calendar/_list.html.twig', {context: 'user'}) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if invitations|length < paginator.getTotalItems %}
|
||||
{{ chill_pagination(paginator) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_answer') }}
|
||||
{{ encore_entry_script_tags('mod_document_action_buttons_group') }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('mod_answer') }}
|
||||
{{ encore_entry_link_tags('mod_document_action_buttons_group') }}
|
||||
{% endblock %}
|
||||
@@ -19,6 +19,7 @@ declare(strict_types=1);
|
||||
namespace Chill\CalendarBundle\Service\ShortMessageNotification;
|
||||
|
||||
use Chill\CalendarBundle\Entity\Calendar;
|
||||
use Chill\CalendarBundle\Entity\CancelReason;
|
||||
use libphonenumber\PhoneNumberFormat;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Symfony\Component\Notifier\Message\SmsMessage;
|
||||
@@ -57,7 +58,7 @@ class DefaultShortMessageForCalendarBuilder implements ShortMessageForCalendarBu
|
||||
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
|
||||
$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(
|
||||
$this->phoneUtil->format($person->getMobilenumber(), PhoneNumberFormat::E164),
|
||||
$this->engine->render('@ChillCalendar/CalendarShortMessage/short_message_canceled.txt.twig', ['calendar' => $calendar]),
|
||||
|
||||
@@ -0,0 +1,292 @@
|
||||
<?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\Tests\Controller;
|
||||
|
||||
use Chill\CalendarBundle\Controller\MyInvitationsController;
|
||||
use Chill\CalendarBundle\Entity\Calendar;
|
||||
use Chill\CalendarBundle\Entity\Invite;
|
||||
use Chill\CalendarBundle\Repository\InviteRepository;
|
||||
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepositoryInterface;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Pagination\PaginatorInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
final class MyInvitationsControllerTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
private MyInvitationsController $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
// Create prophecies for dependencies
|
||||
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||
|
||||
// Create controller instance
|
||||
$this->controller = new MyInvitationsController(
|
||||
$inviteRepository->reveal(),
|
||||
$paginatorFactory->reveal(),
|
||||
$docGeneratorTemplateRepository->reveal()
|
||||
);
|
||||
|
||||
// Set up necessary services for AbstractController
|
||||
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
|
||||
$twig = $this->prophesize(Environment::class);
|
||||
|
||||
// Use reflection to set the container
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||
$containerProperty->setAccessible(true);
|
||||
|
||||
// Create a mock container
|
||||
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||
$container->has('security.authorization_checker')->willReturn(true);
|
||||
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||
$container->has('security.token_storage')->willReturn(true);
|
||||
$container->get('security.token_storage')->willReturn($tokenStorage->reveal());
|
||||
$container->has('twig')->willReturn(true);
|
||||
$container->get('twig')->willReturn($twig->reveal());
|
||||
|
||||
$containerProperty->setValue($this->controller, $container->reveal());
|
||||
}
|
||||
|
||||
public function testMyInvitationsReturnsCorrectAmountOfInvitations(): void
|
||||
{
|
||||
// Create test user
|
||||
$user = new User();
|
||||
$user->setUsername('testuser');
|
||||
|
||||
// Create test invitations
|
||||
$invite1 = new Invite();
|
||||
$invite1->setUser($user);
|
||||
$invite1->setStatus(Invite::PENDING);
|
||||
|
||||
$invite2 = new Invite();
|
||||
$invite2->setUser($user);
|
||||
$invite2->setStatus(Invite::ACCEPTED);
|
||||
|
||||
$invite3 = new Invite();
|
||||
$invite3->setUser($user);
|
||||
$invite3->setStatus(Invite::DECLINED);
|
||||
|
||||
$allInvitations = [$invite1, $invite2, $invite3];
|
||||
$paginatedInvitations = [$invite1, $invite2]; // First page with 2 items per page
|
||||
|
||||
// Set up repository prophecies
|
||||
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||
$inviteRepository->findBy(['user' => $user])->willReturn($allInvitations);
|
||||
$inviteRepository->findBy(
|
||||
['user' => $user],
|
||||
['createdAt' => 'DESC'],
|
||||
2, // items per page
|
||||
0 // offset
|
||||
)->willReturn($paginatedInvitations);
|
||||
|
||||
// Set up paginator prophecies
|
||||
$paginator = $this->prophesize(PaginatorInterface::class);
|
||||
$paginator->getItemsPerPage()->willReturn(2);
|
||||
$paginator->getCurrentPageFirstItemNumber()->willReturn(0);
|
||||
|
||||
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||
$paginatorFactory->create(3)->willReturn($paginator->reveal());
|
||||
|
||||
// Set up doc generator repository
|
||||
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||
$docGeneratorTemplateRepository->findByEntity(Calendar::class)->willReturn([]);
|
||||
|
||||
// Create controller with mocked dependencies
|
||||
$controller = new MyInvitationsController(
|
||||
$inviteRepository->reveal(),
|
||||
$paginatorFactory->reveal(),
|
||||
$docGeneratorTemplateRepository->reveal()
|
||||
);
|
||||
|
||||
// Set up authorization checker to return true for ROLE_USER
|
||||
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||
$authorizationChecker->isGranted('ROLE_USER', null)->willReturn(true);
|
||||
|
||||
// Set up token storage to return user
|
||||
$token = $this->prophesize(TokenInterface::class);
|
||||
$token->getUser()->willReturn($user);
|
||||
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
|
||||
$tokenStorage->getToken()->willReturn($token->reveal());
|
||||
|
||||
// Set up twig to return a response
|
||||
$twig = $this->prophesize(Environment::class);
|
||||
$twig->render('@ChillCalendar/Invitations/listByUser.html.twig', [
|
||||
'invitations' => $paginatedInvitations,
|
||||
'paginator' => $paginator->reveal(),
|
||||
'templates' => [],
|
||||
])->willReturn('rendered content');
|
||||
|
||||
// Set up container
|
||||
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||
$container->has('security.authorization_checker')->willReturn(true);
|
||||
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||
$container->has('security.token_storage')->willReturn(true);
|
||||
$container->get('security.token_storage')->willReturn($tokenStorage->reveal());
|
||||
$container->has('twig')->willReturn(true);
|
||||
$container->get('twig')->willReturn($twig->reveal());
|
||||
|
||||
// Use reflection to set the container
|
||||
$reflection = new \ReflectionClass($controller);
|
||||
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||
$containerProperty->setAccessible(true);
|
||||
$containerProperty->setValue($controller, $container->reveal());
|
||||
|
||||
// Create request
|
||||
$request = new Request();
|
||||
|
||||
// Execute the action
|
||||
$response = $controller->myInvitations($request);
|
||||
|
||||
// Assert that response is successful
|
||||
self::assertInstanceOf(Response::class, $response);
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertSame('rendered content', $response->getContent());
|
||||
}
|
||||
|
||||
public function testMyInvitationsPageLoads(): void
|
||||
{
|
||||
// Create test user
|
||||
$user = new User();
|
||||
$user->setUsername('testuser');
|
||||
|
||||
// Set up repository prophecies - no invitations
|
||||
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||
$inviteRepository->findBy(['user' => $user])->willReturn([]);
|
||||
$inviteRepository->findBy(
|
||||
['user' => $user],
|
||||
['createdAt' => 'DESC'],
|
||||
20, // default items per page
|
||||
0 // offset
|
||||
)->willReturn([]);
|
||||
|
||||
// Set up paginator prophecies
|
||||
$paginator = $this->prophesize(PaginatorInterface::class);
|
||||
$paginator->getItemsPerPage()->willReturn(20);
|
||||
$paginator->getCurrentPageFirstItemNumber()->willReturn(0);
|
||||
|
||||
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||
$paginatorFactory->create(0)->willReturn($paginator->reveal());
|
||||
|
||||
// Set up doc generator repository
|
||||
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||
$docGeneratorTemplateRepository->findByEntity(Calendar::class)->willReturn([]);
|
||||
|
||||
// Create controller with mocked dependencies
|
||||
$controller = new MyInvitationsController(
|
||||
$inviteRepository->reveal(),
|
||||
$paginatorFactory->reveal(),
|
||||
$docGeneratorTemplateRepository->reveal()
|
||||
);
|
||||
|
||||
// Set up authorization checker to return true for ROLE_USER
|
||||
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||
$authorizationChecker->isGranted('ROLE_USER', null)->willReturn(true);
|
||||
|
||||
// Set up token storage to return user
|
||||
$token = $this->prophesize(TokenInterface::class);
|
||||
$token->getUser()->willReturn($user);
|
||||
$tokenStorage = $this->prophesize(TokenStorageInterface::class);
|
||||
$tokenStorage->getToken()->willReturn($token->reveal());
|
||||
|
||||
// Set up twig to return a response
|
||||
$twig = $this->prophesize(Environment::class);
|
||||
$twig->render('@ChillCalendar/Invitations/listByUser.html.twig', [
|
||||
'invitations' => [],
|
||||
'paginator' => $paginator->reveal(),
|
||||
'templates' => [],
|
||||
])->willReturn('empty page content');
|
||||
|
||||
// Set up container
|
||||
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||
$container->has('security.authorization_checker')->willReturn(true);
|
||||
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||
$container->has('security.token_storage')->willReturn(true);
|
||||
$container->get('security.token_storage')->willReturn($tokenStorage->reveal());
|
||||
$container->has('twig')->willReturn(true);
|
||||
$container->get('twig')->willReturn($twig->reveal());
|
||||
|
||||
// Use reflection to set the container
|
||||
$reflection = new \ReflectionClass($controller);
|
||||
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||
$containerProperty->setAccessible(true);
|
||||
$containerProperty->setValue($controller, $container->reveal());
|
||||
|
||||
// Create request
|
||||
$request = new Request();
|
||||
|
||||
// Execute the action
|
||||
$response = $controller->myInvitations($request);
|
||||
|
||||
// Assert that page loads successfully
|
||||
self::assertInstanceOf(Response::class, $response);
|
||||
self::assertSame(200, $response->getStatusCode());
|
||||
self::assertSame('empty page content', $response->getContent());
|
||||
}
|
||||
|
||||
public function testMyInvitationsRequiresAuthentication(): void
|
||||
{
|
||||
// Create controller with minimal dependencies
|
||||
$inviteRepository = $this->prophesize(InviteRepository::class);
|
||||
$paginatorFactory = $this->prophesize(PaginatorFactory::class);
|
||||
$docGeneratorTemplateRepository = $this->prophesize(DocGeneratorTemplateRepositoryInterface::class);
|
||||
|
||||
$controller = new MyInvitationsController(
|
||||
$inviteRepository->reveal(),
|
||||
$paginatorFactory->reveal(),
|
||||
$docGeneratorTemplateRepository->reveal()
|
||||
);
|
||||
|
||||
// Set up authorization checker to return false for ROLE_USER
|
||||
$authorizationChecker = $this->prophesize(AuthorizationCheckerInterface::class);
|
||||
$authorizationChecker->isGranted('ROLE_USER')->willReturn(false);
|
||||
$authorizationChecker->isGranted('ROLE_USER', null)->willReturn(false);
|
||||
|
||||
// Set up container
|
||||
$container = $this->prophesize(\Psr\Container\ContainerInterface::class);
|
||||
$container->has('security.authorization_checker')->willReturn(true);
|
||||
$container->get('security.authorization_checker')->willReturn($authorizationChecker->reveal());
|
||||
|
||||
// Use reflection to set the container
|
||||
$reflection = new \ReflectionClass($controller);
|
||||
$containerProperty = $reflection->getParentClass()->getProperty('container');
|
||||
$containerProperty->setAccessible(true);
|
||||
$containerProperty->setValue($controller, $container->reveal());
|
||||
|
||||
// Create request
|
||||
$request = new Request();
|
||||
|
||||
// Expect AccessDeniedException
|
||||
$this->expectException(\Symfony\Component\Security\Core\Exception\AccessDeniedException::class);
|
||||
|
||||
// Execute the action
|
||||
$controller->myInvitations($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
chill_calendar:
|
||||
There are count ignored calendars by date filter: >-
|
||||
{nbIgnored, plural,
|
||||
=0 {Er zijn geen afspraken genegeerd door de datumfilter.}
|
||||
one {Er is een afspraak genegeerd door de datumfilter. Wijzig de datumfilter om deze te laten verschijnen.}
|
||||
few {# afspraken zijn genegeerd door de datumfilter. Wijzig de datumfilter om deze te laten verschijnen.}
|
||||
other {# afspraken zijn genegeerd door de datumfilter. Wijzig de datumfilter om deze te laten verschijnen.}
|
||||
}
|
||||
@@ -31,8 +31,7 @@ Will send SMS: Un SMS de rappel sera envoyé
|
||||
Will not send SMS: Aucun SMS de rappel ne sera envoyé
|
||||
SMS already sent: Un SMS a été envoyé
|
||||
|
||||
canceledBy: supprimé par
|
||||
Canceled by: supprimé par
|
||||
Canceled by: Annulé par
|
||||
Calendar configuration: Gestion des rendez-vous
|
||||
|
||||
crud:
|
||||
@@ -44,6 +43,14 @@ crud:
|
||||
title_edit: Modifier le motif d'annulation
|
||||
|
||||
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
|
||||
form:
|
||||
The main user is mandatory. He will organize the appointment.: L'utilisateur principal est obligatoire. Il est l'organisateur de l'événement.
|
||||
@@ -86,6 +93,9 @@ invite:
|
||||
declined: Refusé
|
||||
pending: En attente
|
||||
tentative: Accepté provisoirement
|
||||
list:
|
||||
none: Il n'y aucun invitation
|
||||
title: Mes invitations
|
||||
|
||||
# exports
|
||||
Exports of calendar: Exports des rendez-vous
|
||||
|
||||
@@ -26,3 +26,149 @@ The calendar item has been successfully removed.: De afspraak is verwijdert
|
||||
From the day: Vanaf
|
||||
to the day: tot
|
||||
Transform to activity: In activiteit omzetten
|
||||
Create a new calendar in accompanying course: Afspraak aanmaken in het traject
|
||||
Will send SMS: Er zal een herinnerings-sms worden verzonden
|
||||
Will not send SMS: Er wordt geen herinnerings-sms verzonden
|
||||
SMS already sent: Er is een sms verzonden
|
||||
|
||||
Canceled by: Geannuleerd door
|
||||
Calendar configuration: Beheer van afspraken
|
||||
|
||||
crud:
|
||||
calendar_cancel-reason:
|
||||
index:
|
||||
title: Lijst van annuleringsredenen
|
||||
add_new: Nieuwe toevoegen
|
||||
title_new: Nieuwe annuleringsreden
|
||||
title_edit: Annuleringsreden bewerken
|
||||
|
||||
chill_calendar:
|
||||
canceled: Geannuleerd
|
||||
cancel_reason: Reden van annulering
|
||||
cancel_calendar_item: Afspraak annuleren
|
||||
calendar_canceled: De afspraak is geannuleerd
|
||||
canceled_by:
|
||||
user: Gebruiker
|
||||
person: Gebruiker
|
||||
other: Andere
|
||||
Document: Document van een afspraak
|
||||
form:
|
||||
The main user is mandatory. He will organize the appointment.: De hoofdgebruiker is verplicht. Hij is de organisator van de gebeurtenis.
|
||||
Create for referrer: Aanmaken voor de referent
|
||||
start date filter: Begin van de afspraak
|
||||
From: Van
|
||||
To: Tot
|
||||
Next calendars: Volgende afspraken
|
||||
Add a document: Document toevoegen
|
||||
Documents: Documenten
|
||||
Create and add a document: Aanmaken en document toevoegen
|
||||
Save and add a document: Opslaan en document toevoegen
|
||||
Create for me: Afspraak aanmaken voor mezelf
|
||||
Edit a document: Document bewerken
|
||||
Document title: Titel
|
||||
Document object: Document
|
||||
Add a document from template: Document toevoegen vanaf sjabloon
|
||||
Upload a document: Document uploaden
|
||||
Remove a calendar document: Document van een afspraak verwijderen
|
||||
Are you sure you want to remove the doc?: Weet u zeker dat u het gekoppelde document wilt verwijderen?
|
||||
Document outdated: De datum en tijd van de afspraak zijn gewijzigd na het aanmaken van het document
|
||||
|
||||
|
||||
|
||||
remote_ms_graph:
|
||||
freebusy_statuses:
|
||||
busy: Bezet
|
||||
free: Vrij
|
||||
tentative: In afwachting van bevestiging
|
||||
oof: Buiten kantoor
|
||||
workingElsewhere: Werkt elders
|
||||
unknown: Onbekend
|
||||
cancel_event_because_main_user_is_%label%: De gebeurtenis is overgedragen aan gebruiker %label%
|
||||
|
||||
remote_calendar:
|
||||
calendar_range_title: Chill-beschikbaarheidsperiode
|
||||
|
||||
invite:
|
||||
accepted: Geaccepteerd
|
||||
declined: Geweigerd
|
||||
pending: In afwachting
|
||||
tentative: Voorlopig geaccepteerd
|
||||
list:
|
||||
none: Er is geen uitnodiging
|
||||
title: Mijn uitnodigingen
|
||||
|
||||
# exports
|
||||
Exports of calendar: Exports van afspraken
|
||||
Count calendars: Aantal afspraken
|
||||
Count calendars by various parameters.: Telt het aantal afspraken op basis van verschillende parameters.
|
||||
|
||||
Average appointment duration: Gemiddelde duur van afspraken
|
||||
Get the average of appointment duration according to various filters: Berekent het gemiddelde van de duur van afspraken op basis van verschillende parameters.
|
||||
|
||||
Sum of appointment durations: Som van de duur van afspraken
|
||||
Get the sum of appointment durations according to various filters: Berekent de som van de duur van afspraken op basis van verschillende parameters.
|
||||
|
||||
'Filtered by agent: only %agents%': "Gefilterd op agenten: alleen %agents%"
|
||||
Filter calendars by agent: Afspraken filteren op agenten
|
||||
Filter calendars between certain dates: Afspraken filteren op datum van de afspraak
|
||||
'Filtered by calendars between %dateFrom% and %dateTo%': 'Gefilterd op afspraken tussen %dateFrom% en %dateTo%'
|
||||
'Filtered by calendar range: only %calendarRange%': 'Gefilterd op afspraken per beschikbaarheidsperiode: alleen de %calendarRange%'
|
||||
Filter by calendar range: Filteren op afspraken binnen een beschikbaarheidsperiode of niet
|
||||
|
||||
Group calendars by agent: Afspraken groeperen op agent
|
||||
Group calendars by location type: Afspraken groeperen op type locatie
|
||||
Group calendars by location: Afspraken groeperen op afspraaklocatie
|
||||
Group calendars by cancel reason: Afspraken groeperen op annuleringsreden
|
||||
Group calendars by month and year: Afspraken groeperen op maand en jaar
|
||||
Group calendars by urgency: Afspraken groeperen op urgent of niet
|
||||
|
||||
export:
|
||||
aggregator.calendar:
|
||||
agent_job:
|
||||
Group calendars by agent job: Afspraken groeperen op beroep van de agent
|
||||
agent_scope:
|
||||
Group calendars by agent scope: Afspraken groeperen op dienst van de agent
|
||||
filter.calendar:
|
||||
agent_job:
|
||||
Filter calendars by agent job: Afspraken filteren op beroepen van de agenten (hoofdgebruikers)
|
||||
'Filtered by agent job: only %jobs%': 'Gefilterd op beroepen van de agenten (hoofdgebruikers): alleen de %jobs%'
|
||||
agent_scope:
|
||||
Filter calendars by agent scope: Afspraken filteren op diensten van de agenten (hoofdgebruikers)
|
||||
'Filtered by agent scope: only %scopes%': 'Gefilterd op diensten van de agenten (hoofdgebruikers): alleen de diensten %scopes%'
|
||||
|
||||
Scope: Dienst
|
||||
Job: Beroep
|
||||
Location type: Type locatie
|
||||
Location: Afspraaklocatie
|
||||
by month and year: Per maand en jaar
|
||||
|
||||
is urgent: Urgent
|
||||
is not urgent: Niet urgent
|
||||
has calendar range: Binnen een beschikbaarheidsperiode?
|
||||
Not made within a calendar range: Afspraak binnen een beschikbaarheidsperiode
|
||||
Made within a calendar range: Afspraak buiten een beschikbaarheidsperiode
|
||||
|
||||
docgen:
|
||||
calendar:
|
||||
Base context for calendar: 'Afspraak: basiscontext'
|
||||
A base context for generating document on calendar: Context voor het genereren van documenten op basis van afspraken
|
||||
Track changes on datetime and warn user if date time is updated after the doc generation: Wijzigingen in het document volgen en gebruikers waarschuwen dat de datum en tijd zijn gewijzigd na het genereren van het document
|
||||
Ask main person: Vragen om een gebruiker te kiezen uit de deelnemers aan de afspraken
|
||||
Main person label: Label om de gebruiker te kiezen
|
||||
Ask third party: Vragen om een derde te kiezen uit de deelnemers aan de afspraken
|
||||
Third party label: Label om de derde te kiezen
|
||||
Destinee: Geadresseerde
|
||||
None: Geen keuze
|
||||
title of the generated document: Titel van het gegenereerde document
|
||||
|
||||
CHILL_CALENDAR_CALENDAR_CREATE: Afspraken aanmaken
|
||||
CHILL_CALENDAR_CALENDAR_EDIT: Afspraken bewerken
|
||||
CHILL_CALENDAR_CALENDAR_DELETE: Afspraken verwijderen
|
||||
CHILL_CALENDAR_CALENDAR_SEE: Afspraken bekijken
|
||||
|
||||
|
||||
generic_doc:
|
||||
filter:
|
||||
keys:
|
||||
accompanying_period_calendar_document: Document van afspraken van trajecten
|
||||
person_calendar_document: Document van afspraken van de gebruiker
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
calendar:
|
||||
At least {{ limit }} person is required.: Minimaal {{ limit }} persoon is vereist voor deze afspraak
|
||||
An end date is required: Geef een einddatum en -tijd op
|
||||
A start date is required: Geef een startdatum en -tijd op
|
||||
A location is required: Geef een locatie op
|
||||
A main user is mandator: Geef een hoofdgebruiker op
|
||||
@@ -1,3 +1,103 @@
|
||||
'Not available in your language': 'Vertaling niet mogelijk in het Nederlands'
|
||||
'Other value': 'Andere mogelijkheid'
|
||||
'None': 'Niet gespecifieerd'
|
||||
'None': 'Niet gespecifieerd'
|
||||
|
||||
#customfieldsgroup rendering
|
||||
Empty data: Lege gegevens
|
||||
No data to show: Geen waarden om te tonen
|
||||
|
||||
#customfieldsgroup administration
|
||||
CustomFieldsGroup list: Groepen van aangepaste velden
|
||||
CustomFieldsGroup creation: Nieuwe groep van aangepaste velden
|
||||
Entity: Entiteit
|
||||
"Is default ?": "Standaard?"
|
||||
"Some module select default groups for some usage. Example: the default person group is shown under person page.": "Sommige modules selecteren standaardgroepen voor bepaald gebruik. Voorbeeld: de standaard persoonsgroep wordt getoond op de persoonspagina"
|
||||
Make default: Groep standaard maken
|
||||
Create a new group: Nieuwe groep aanmaken
|
||||
CustomFieldsGroup details: Details van de groep aangepaste velden
|
||||
Fields associated with this group: Velden geassocieerd met deze groep
|
||||
Any field is currently associated with this group: Geen veld is momenteel geassocieerd met deze groep
|
||||
Create a new field: Nieuw aangepast veld aanmaken
|
||||
Add a new field: Aangepast veld toevoegen
|
||||
ordering: volgorde
|
||||
label_field: label van het veld
|
||||
active: actief
|
||||
No value defined for this option: Geen waarde gedefinieerd voor deze optie
|
||||
CustomFieldsGroup edit: Bewerken van een groep aangepaste velden
|
||||
type: type
|
||||
The custom fields group has been created: De groep aangepaste velden is aangemaakt
|
||||
The custom fields group has been updated: De groep aangepaste velden is bijgewerkt
|
||||
The custom fields group form contains errors: Het formulier bevat fouten
|
||||
The default custom fields group has been changed: De standaardgroep is gewijzigd
|
||||
|
||||
|
||||
#menu entries
|
||||
Custom fields configuration: Aangepaste velden
|
||||
CustomFields List: Lijst van aangepaste velden
|
||||
CustomFields Groups: Groep van aangepaste velden
|
||||
|
||||
#customfield administration
|
||||
Custom fields: Aangepaste velden
|
||||
CustomFields configuration: Beheer van aangepaste velden
|
||||
CustomField edit: Wijziging van een aangepast veld
|
||||
CustomField creation: Nieuw aangepast veld
|
||||
General informations: Algemene informatie
|
||||
Options: Opties
|
||||
Custom fields group: Groep van aangepaste velden
|
||||
Ordering: Volgorde van verschijning
|
||||
Required field: Verplicht veld
|
||||
An answer is required: Een antwoord is vereist
|
||||
Any answer is required: Geen antwoord is vereist
|
||||
Back to the group: Terug naar de groep aangepaste velden
|
||||
Slug: Tekstuele identificatie
|
||||
The custom field has been created: Het aangepaste veld is aangemaakt
|
||||
The custom field form contains errors: Het formulier bevat fouten
|
||||
The custom field has been updated: Het aangepaste veld is bijgewerkt
|
||||
|
||||
#custom field name
|
||||
choice: keuze
|
||||
Title: Titel
|
||||
text: tekst
|
||||
Text field: Tekstveld
|
||||
Date field: Datumveld
|
||||
|
||||
#custom field choice
|
||||
Multiplicity: Multipliciteit
|
||||
Multiple: Meerdere
|
||||
Unique: Slechts één keuze mogelijk
|
||||
Choice display: Weergave van keuzes
|
||||
Expanded: Uitgebreide keuzes (radioknoppen)
|
||||
Non expanded: Gegroepeerde keuzes
|
||||
Allow other: Andere waarde toestaan
|
||||
No: Nee
|
||||
Yes: Ja
|
||||
Other value label (empty if use by default): Label van het veld "andere waarde"
|
||||
Choices: Keuzes
|
||||
Add an element: Element toevoegen
|
||||
|
||||
#custom field text
|
||||
Max length: Maximale lengte
|
||||
Box appearance: Verschijning van het veld
|
||||
Multiple boxes on the line: Meerdere velden op de lijn
|
||||
One box on the line: Slechts één veld op de lijn
|
||||
|
||||
#custom field title
|
||||
Title level: Titelniveau
|
||||
Main title: Hoofdtitel
|
||||
Subtitle: Ondertitel
|
||||
|
||||
#custom field number
|
||||
Greater or equal than: Groter dan of gelijk aan
|
||||
Lesser or equal than: Kleiner dan of gelijk aan
|
||||
Precision: Precisie
|
||||
Text after the field: Tekst na het veld
|
||||
Number field: Nummerveld
|
||||
|
||||
#custom field long choice
|
||||
Options key: Sleutel van opties
|
||||
Choose a value: Kies een waarde
|
||||
Long choice field: Veld met vooraf geregistreerde keuzes
|
||||
|
||||
#Custom field date
|
||||
Greater or equal than (expression like 1 day ago, 2 years ago, +1 month, today, tomorrow, or date with format YYYY-mm-dd): Datum na (geef een PHP-expressie op zoals '1 day ago', '2 years ago', '+1 month', 'today', 'tomorrow', ...)
|
||||
Lesser or equal than (expression like 1 day ago, 2 years ago, +1 month, today, tomorrow, or date with format YYYY-mm-dd): Datum voor (geef een PHP-expressie op zoals '1 day ago', '2 years ago', '+1 month', 'today', 'tomorrow', ...)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Characters not allowed. Only lowercase letters, numbers and "-" are allowed.: Tekens niet toegestaan. Alleen kleine letters, cijfers en "-" zijn toegestaan.
|
||||
'This date must be after or equal to %date%': Deze datum moet gelijk zijn aan of na %date%
|
||||
'This date must be before or equal to %date%': Deze datum moet voor of gelijk zijn aan %date%
|
||||
@@ -20,4 +20,9 @@ use Doctrine\Persistence\ObjectRepository;
|
||||
interface DocGeneratorTemplateRepositoryInterface extends ObjectRepository
|
||||
{
|
||||
public function countByEntity(string $entity): int;
|
||||
|
||||
/**
|
||||
* @return array|DocGeneratorTemplate[]
|
||||
*/
|
||||
public function findByEntity(string $entity, ?int $start = 0, ?int $limit = 50): array;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ use Symfony\Component\Mailer\MailerInterface;
|
||||
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
// use Symfony\Component\Translation\LocaleSwitcher;
|
||||
|
||||
/**
|
||||
* @see OnGenerationFailsTest for test suite
|
||||
*/
|
||||
@@ -40,6 +42,7 @@ final readonly class OnGenerationFails implements EventSubscriberInterface
|
||||
private StoredObjectRepositoryInterface $storedObjectRepository,
|
||||
private TranslatorInterface $translator,
|
||||
private UserRepositoryInterface $userRepository,
|
||||
// private LocaleSwitcher $localeSwitcher,
|
||||
) {}
|
||||
|
||||
public static function getSubscribedEvents()
|
||||
@@ -118,6 +121,25 @@ final readonly class OnGenerationFails implements EventSubscriberInterface
|
||||
return;
|
||||
}
|
||||
|
||||
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
|
||||
/*
|
||||
$this->localeSwitcher->runWithLocale($creator->getLocale(), function () use ($message, $errors, $template, $creator) {
|
||||
$email = (new TemplatedEmail())
|
||||
->to($message->getSendResultToEmail())
|
||||
->subject($this->translator->trans('docgen.failure_email.The generation of a document failed'))
|
||||
->textTemplate('@ChillDocGenerator/Email/on_generation_failed_email.txt.twig')
|
||||
->context([
|
||||
'errors' => $errors,
|
||||
'template' => $template,
|
||||
'creator' => $creator,
|
||||
'stored_object_id' => $message->getDestinationStoredObjectId(),
|
||||
]);
|
||||
|
||||
$this->mailer->send($email);
|
||||
});
|
||||
*/
|
||||
|
||||
// Current implementation:
|
||||
$email = (new TemplatedEmail())
|
||||
->to($message->getSendResultToEmail())
|
||||
->subject($this->translator->trans('docgen.failure_email.The generation of a document failed'))
|
||||
|
||||
@@ -27,6 +27,8 @@ use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
// use Symfony\Component\Translation\LocaleSwitcher;
|
||||
|
||||
/**
|
||||
* Handle the request of document generation.
|
||||
*/
|
||||
@@ -46,6 +48,7 @@ class RequestGenerationHandler implements MessageHandlerInterface
|
||||
private readonly MailerInterface $mailer,
|
||||
private readonly TranslatorInterface $translator,
|
||||
private readonly StoredObjectManagerInterface $storedObjectManager,
|
||||
// private readonly LocaleSwitcher $localeSwitcher,
|
||||
) {}
|
||||
|
||||
public function __invoke(RequestGenerationMessage $message)
|
||||
@@ -122,6 +125,30 @@ class RequestGenerationHandler implements MessageHandlerInterface
|
||||
|
||||
private function sendDataDump(StoredObject $destinationStoredObject, RequestGenerationMessage $message): void
|
||||
{
|
||||
// Implementation with LocaleSwitcher (commented out - to be activated after migration to sf7.2):
|
||||
// Note: This method sends emails to admin addresses, not user addresses, so locale switching may not be needed
|
||||
/*
|
||||
$this->localeSwitcher->runWithLocale('fr', function () use ($destinationStoredObject, $message) {
|
||||
// Get the content of the document
|
||||
$content = $this->storedObjectManager->read($destinationStoredObject);
|
||||
$filename = $destinationStoredObject->getFilename();
|
||||
$contentType = $destinationStoredObject->getType();
|
||||
|
||||
// Create the email with the document as an attachment
|
||||
$email = (new TemplatedEmail())
|
||||
->to($message->getSendResultToEmail())
|
||||
->textTemplate('@ChillDocGenerator/Email/send_data_dump_to_admin.txt.twig')
|
||||
->context([
|
||||
'filename' => $filename,
|
||||
])
|
||||
->subject($this->translator->trans('docgen.data_dump_email.subject'))
|
||||
->attach($content, $filename, $contentType);
|
||||
|
||||
$this->mailer->send($email);
|
||||
});
|
||||
*/
|
||||
|
||||
// Current implementation:
|
||||
// Get the content of the document
|
||||
$content = $this->storedObjectManager->read($destinationStoredObject);
|
||||
$filename = $destinationStoredObject->getFilename();
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
docgen:
|
||||
# Geen ICU berichten nodig voor data_dump_email meer
|
||||
@@ -0,0 +1,51 @@
|
||||
docgen:
|
||||
Generate a document: Document genereren
|
||||
Generate: Genereren
|
||||
Document generation: Documentgeneratie
|
||||
Manage templates and document generation: Beheer van gegenereerde documenten en hun sjablonen
|
||||
Pick template context: Context kiezen
|
||||
Context: Context
|
||||
New template: Nieuw sjabloon
|
||||
Edit template: Sjabloon bewerken
|
||||
test generate: Generatie testen
|
||||
With context %name%: 'Met context "%name%"'
|
||||
|
||||
Doc generation failed: Het genereren van dit document is mislukt
|
||||
Doc generation is pending: Het genereren van dit document is bezig
|
||||
Come back later: Kom later terug
|
||||
|
||||
Send report to: Rapport verzenden naar
|
||||
Send report errors to this email address: Foutrapporten worden verzonden naar het opgegeven e-mailadres
|
||||
Generate as creator: Genereren als
|
||||
The document will be generated as the given creator: Het document wordt gegenereerd namens de opgegeven gebruiker
|
||||
Show data instead of generating: Gegevens tonen in plaats van document genereren
|
||||
|
||||
Any template configured: Geen documentsjabloon geconfigureerd
|
||||
|
||||
entity_id_placeholder: Identificatie van de entiteit
|
||||
|
||||
failure_email:
|
||||
The generation of a document failed: Het genereren van een document is mislukt
|
||||
The generation of the document %template_name% failed: Het genereren van een document op basis van sjabloon {{ template_name }} is mislukt.
|
||||
The following errors were encoutered: De volgende fouten zijn opgetreden
|
||||
Forward this email to your administrator for solving: Stuur dit bericht door naar uw beheerder voor probleemoplossing.
|
||||
References: Referenties
|
||||
|
||||
data_dump_email:
|
||||
subject: Inhoud van documentgeneratiegegevens beschikbaar
|
||||
Dear: Beste
|
||||
data_dump_ready_and_attached: >-
|
||||
De inhoud van de gegevens is beschikbaar. U vindt deze als bijlage bij deze e-mail.
|
||||
filename: >-
|
||||
Bestandsnaam: %filename%
|
||||
|
||||
|
||||
|
||||
crud:
|
||||
docgen_template:
|
||||
index:
|
||||
title: Documentgeneratie
|
||||
add_new: Aanmaken
|
||||
|
||||
|
||||
Template file: Sjabloonbestand
|
||||
@@ -59,7 +59,7 @@ final readonly class StoredObjectVersionApiController
|
||||
|
||||
return new JsonResponse(
|
||||
$this->serializer->serialize(
|
||||
new Collection($items, $paginator),
|
||||
new Collection(array_values($items->toArray()), $paginator),
|
||||
'json',
|
||||
[AbstractNormalizer::GROUPS => ['read', StoredObjectVersionNormalizer::WITH_POINT_IN_TIMES_CONTEXT, StoredObjectVersionNormalizer::WITH_RESTORED_CONTEXT]]
|
||||
),
|
||||
|
||||
@@ -23,10 +23,14 @@ use Random\RandomException;
|
||||
* Store each version of StoredObject's.
|
||||
*
|
||||
* A version should not be created manually: use the method @see{StoredObject::registerVersion} instead.
|
||||
*
|
||||
* Each filename must be unique within the same StoredObject. We add a condition on id to apply this condition only for
|
||||
* newly created versions when this new index is applied.
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table('chill_doc.stored_object_version')]
|
||||
#[ORM\UniqueConstraint(name: 'chill_doc_stored_object_version_unique_by_object', columns: ['stored_object_id', 'version'])]
|
||||
#[ORM\UniqueConstraint(name: 'chill_doc_stored_object_version_unique_by_filename', columns: ['filename'], options: ['where' => '(id > 0)'])]
|
||||
class StoredObjectVersion implements TrackCreationInterface
|
||||
{
|
||||
use TrackCreationTrait;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||
use Chill\MainBundle\Form\Type\ChillDateType;
|
||||
use Chill\MainBundle\Form\Type\ChillTextareaType;
|
||||
use Chill\MainBundle\Form\Type\ScopePickerType;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
|
||||
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
@@ -30,7 +29,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class PersonDocumentType extends AbstractType
|
||||
{
|
||||
public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly ScopeResolverDispatcher $scopeResolverDispatcher, private readonly ParameterBagInterface $parameterBag, private readonly CenterResolverDispatcher $centerResolverDispatcher) {}
|
||||
public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly ScopeResolverDispatcher $scopeResolverDispatcher, private readonly ParameterBagInterface $parameterBag) {}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||
{
|
||||
@@ -57,8 +56,8 @@ class PersonDocumentType extends AbstractType
|
||||
|
||||
if ($isScopeConcerned && $this->parameterBag->get('chill_main')['acl']['form_show_scopes']) {
|
||||
$builder->add('scope', ScopePickerType::class, [
|
||||
'center' => $this->centerResolverDispatcher->resolveCenter($document),
|
||||
'role' => $options['role'],
|
||||
'subject' => $document,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface GenericDoc {
|
||||
type: "doc_store_generic_doc";
|
||||
uniqueKey: string;
|
||||
key: string;
|
||||
identifiers: object;
|
||||
identifiers: { id: number };
|
||||
context: "person" | "accompanying-period";
|
||||
doc_date: DateTime;
|
||||
metadata: GenericDocMetadata;
|
||||
@@ -36,6 +36,18 @@ export interface GenericDocForAccompanyingPeriod extends GenericDoc {
|
||||
context: "accompanying-period";
|
||||
}
|
||||
|
||||
export function isGenericDocForAccompanyingPeriod(
|
||||
doc: GenericDoc,
|
||||
): doc is GenericDocForAccompanyingPeriod {
|
||||
return doc.context === "accompanying-period";
|
||||
}
|
||||
|
||||
export function isGenericDocWithStoredObject(
|
||||
doc: GenericDoc,
|
||||
): doc is GenericDoc & { storedObject: StoredObject } {
|
||||
return doc.storedObject !== null;
|
||||
}
|
||||
|
||||
interface BaseMetadataWithHtml extends BaseMetadata {
|
||||
html: string;
|
||||
}
|
||||
@@ -44,28 +56,33 @@ export interface GenericDocForAccompanyingCourseDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseActivityDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_activity_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseCalendarDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_course_calendar_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCoursePersonDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "person_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
export interface GenericDocForAccompanyingCourseWorkEvaluationDocument
|
||||
extends GenericDocForAccompanyingPeriod {
|
||||
key: "accompanying_period_work_evaluation_document";
|
||||
metadata: BaseMetadataWithHtml;
|
||||
storedObject: StoredObject;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import {
|
||||
StoredObject,
|
||||
StoredObjectPointInTime,
|
||||
StoredObjectVersionWithPointInTime,
|
||||
} from "./../../../types";
|
||||
} from "ChillDocStoreAssets/types";
|
||||
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
|
||||
import { ISOToDatetime } from "./../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
import { ISOToDatetime } from "ChillMainAssets/chill/js/date";
|
||||
import FileIcon from "ChillDocStoreAssets/vuejs/FileIcon.vue";
|
||||
import RestoreVersionButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/HistoryButton/RestoreVersionButton.vue";
|
||||
import DownloadButton from "ChillDocStoreAssets/vuejs/StoredObjectButton/DownloadButton.vue";
|
||||
|
||||
@@ -46,6 +46,16 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
|
||||
|
||||
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
|
||||
$entity = $this->getRepository()->findAssociatedEntityToStoredObject($subject);
|
||||
|
||||
@@ -66,7 +76,7 @@ abstract class AbstractStoredObjectVoter implements StoredObjectVoterInterface
|
||||
return match ($workflowPermission) {
|
||||
WorkflowRelatedEntityPermissionHelper::FORCE_GRANT => true,
|
||||
WorkflowRelatedEntityPermissionHelper::FORCE_DENIED => false,
|
||||
WorkflowRelatedEntityPermissionHelper::ABSTAIN => $regularPermission,
|
||||
WorkflowRelatedEntityPermissionHelper::ABSTAIN => WorkflowRelatedEntityPermissionHelper::FORCE_GRANT === $workflowPermissionAsAttachment || $regularPermission,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,12 @@ namespace Chill\DocStoreBundle\Security\Authorization;
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
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
|
||||
{
|
||||
public function supports(StoredObjectRoleEnum $attribute, StoredObject $subject): bool;
|
||||
|
||||
@@ -15,6 +15,7 @@ use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Entity\StoredObjectPointInTime;
|
||||
use Chill\DocStoreBundle\Entity\StoredObjectPointInTimeReasonEnum;
|
||||
use Chill\DocStoreBundle\Entity\StoredObjectVersion;
|
||||
use Chill\DocStoreBundle\Exception\ConversionWithSameMimeTypeException;
|
||||
use Chill\DocStoreBundle\Exception\StoredObjectManagerException;
|
||||
use Chill\WopiBundle\Service\WopiConverter;
|
||||
use Symfony\Component\Mime\MimeTypesInterface;
|
||||
@@ -41,9 +42,10 @@ 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
|
||||
*
|
||||
* @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 \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 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
|
||||
{
|
||||
@@ -56,7 +58,7 @@ class StoredObjectToPdfConverter
|
||||
$currentVersion = $storedObject->getCurrentVersion();
|
||||
|
||||
if ($currentVersion->getType() === $newMimeType) {
|
||||
throw new \UnexpectedValueException('Already at the same mime type');
|
||||
throw new ConversionWithSameMimeTypeException($newMimeType);
|
||||
}
|
||||
|
||||
$content = $this->storedObjectManager->read($currentVersion);
|
||||
|
||||
@@ -40,6 +40,10 @@ class StoredObjectVersionApiControllerTest extends \PHPUnit\Framework\TestCase
|
||||
$storedObject->registerVersion();
|
||||
}
|
||||
|
||||
// remove one version in the history
|
||||
$v5 = $storedObject->getVersions()->get(5);
|
||||
$storedObject->removeVersion($v5);
|
||||
|
||||
$security = $this->prophesize(Security::class);
|
||||
$security->isGranted(StoredObjectRoleEnum::SEE->value, $storedObject)
|
||||
->willReturn(true)
|
||||
@@ -53,6 +57,7 @@ class StoredObjectVersionApiControllerTest extends \PHPUnit\Framework\TestCase
|
||||
self::assertEquals($response->getStatusCode(), 200);
|
||||
self::assertIsArray($body);
|
||||
self::assertArrayHasKey('results', $body);
|
||||
self::assertIsList($body['results']);
|
||||
self::assertCount(10, $body['results']);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,9 +86,165 @@ class AbstractStoredObjectVoterTest extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataProviderVoteOnAttribute
|
||||
* @dataProvider dataProviderVoteOnAttributeWithStoredObjectPermission
|
||||
*/
|
||||
public function testVoteOnAttribute(
|
||||
public function testVoteOnAttributeWithStoredObjectPermission(
|
||||
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,
|
||||
bool $expected,
|
||||
bool $canBeAssociatedWithWorkflow,
|
||||
@@ -105,6 +261,10 @@ class AbstractStoredObjectVoterTest extends TestCase
|
||||
$security->isGranted('SOME_ROLE', $related)->willReturn($isGrantedRegularPermission);
|
||||
|
||||
$workflowRelatedEntityPermissionHelper = $this->prophesize(WorkflowRelatedEntityPermissionHelper::class);
|
||||
|
||||
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($storedObject)->willReturn(WorkflowRelatedEntityPermissionHelper::ABSTAIN);
|
||||
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForWriteOperation($storedObject)->willReturn(WorkflowRelatedEntityPermissionHelper::ABSTAIN);
|
||||
|
||||
if (null !== $isGrantedWorkflowPermissionRead) {
|
||||
$workflowRelatedEntityPermissionHelper->isAllowedByWorkflowForReadOperation($related)
|
||||
->willReturn($isGrantedWorkflowPermissionRead)->shouldBeCalled();
|
||||
@@ -123,7 +283,7 @@ class AbstractStoredObjectVoterTest extends TestCase
|
||||
self::assertEquals($expected, $voter->voteOnAttribute($attribute, $storedObject, $token), $message);
|
||||
}
|
||||
|
||||
public static function dataProviderVoteOnAttribute(): iterable
|
||||
public static function dataProviderVoteOnAttributeWithoutStoredObjectPermission(): iterable
|
||||
{
|
||||
// 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'];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user